Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

hackathon - search/notebook: basic end-to-end notebook result rendering #33161

Merged
merged 3 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions client/search-ui/src/components/SearchResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
getRepoMatchLabel,
getRepoMatchUrl,
getRepositoryUrl,
NotebookMatch,
RepositoryMatch,
} from '@sourcegraph/shared/src/search/stream'
import { formatRepositoryStarCount } from '@sourcegraph/shared/src/util/stars'
Expand All @@ -28,7 +29,7 @@ import { CommitSearchResultMatch } from './CommitSearchResultMatch'
import styles from './SearchResult.module.scss'

interface Props extends PlatformContextProps<'requestGraphQL'> {
result: CommitMatch | RepositoryMatch
result: CommitMatch | RepositoryMatch | NotebookMatch
repoName: string
icon: React.ComponentType<{ className?: string }>
onSelect: () => void
Expand All @@ -44,7 +45,7 @@ export const SearchResult: React.FunctionComponent<Props> = ({
openInNewTab,
}) => {
const renderTitle = (): JSX.Element => {
const formattedRepositoryStarCount = formatRepositoryStarCount(result.repoStars)
const formattedRepositoryStarCount = formatRepositoryStarCount(result.type === 'notebook' ? result.stars : result.repoStars)
return (
<div className={styles.title}>
<RepoIcon repoName={repoName} className="text-muted flex-shrink-0" />
Expand All @@ -61,6 +62,9 @@ export const SearchResult: React.FunctionComponent<Props> = ({
{result.type === 'repo' && (
<Link to={getRepoMatchUrl(result)}>{displayRepoName(getRepoMatchLabel(result))}</Link>
)}
{result.type === 'notebook' && (
<Link to={result.url}>{repoName}</Link>
)}
</span>
<span className={styles.spacer} />
{result.type === 'commit' && (
Expand Down Expand Up @@ -147,6 +151,33 @@ export const SearchResult: React.FunctionComponent<Props> = ({
</div>
)
}
if (result.type === 'notebook') {
return (
<div data-testid="search-repo-result">
<div className={classNames(styles.searchResultMatch, 'p-2 flex-column')}>
<div className="d-flex align-items-center flex-row">
<div className={styles.matchType}>
<small>Notebook match</small>
</div>
{result.private && (
<>
<div className={styles.divider} />
<div>
<Icon
className={classNames('flex-shrink-0 text-muted', styles.icon)}
as={LockIcon}
/>
</div>
<div>
<small>Private</small>
</div>
</>
)}
</div>
</div>
</div>
)
}

return (
<CommitSearchResultMatch
Expand Down
11 changes: 11 additions & 0 deletions client/search-ui/src/results/StreamingSearchResultsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as H from 'history'
import AlphaSBoxIcon from 'mdi-react/AlphaSBoxIcon'
import FileDocumentIcon from 'mdi-react/FileDocumentIcon'
import FileIcon from 'mdi-react/FileIcon'
import NotebookIcon from 'mdi-react/NotebookIcon'
import SourceCommitIcon from 'mdi-react/SourceCommitIcon'
import SourceRepositoryIcon from 'mdi-react/SourceRepositoryIcon'
import { Observable } from 'rxjs'
Expand Down Expand Up @@ -141,6 +142,16 @@ export const StreamingSearchResultsList: React.FunctionComponent<StreamingSearch
onSelect={() => logSearchResultClicked(index, 'repo')}
/>
)
case 'notebook':
return (
<SearchResult
icon={NotebookIcon}
result={result}
repoName={`${result.namespace} / ${result.title}`}
platformContext={platformContext}
onSelect={() => logSearchResultClicked(index, 'notebook')}
/>
)
}
},
[
Expand Down
17 changes: 15 additions & 2 deletions client/shared/src/search/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SearchPatternType } from '../graphql-operations'
import { SymbolKind } from '../schema'

/** All values that are valid for the `type:` filter. `null` represents default code search. */
export type SearchType = 'file' | 'repo' | 'path' | 'symbol' | 'diff' | 'commit' | null
export type SearchType = 'file' | 'repo' | 'path' | 'symbol' | 'diff' | 'commit' | 'notebook' | null

export type SearchEvent =
| { type: 'matches'; data: SearchMatch[] }
Expand All @@ -19,7 +19,7 @@ export type SearchEvent =
| { type: 'error'; data: ErrorLike }
| { type: 'done'; data: {} }

export type SearchMatch = ContentMatch | RepositoryMatch | CommitMatch | SymbolMatch | PathMatch
export type SearchMatch = ContentMatch | RepositoryMatch | CommitMatch | SymbolMatch | PathMatch | NotebookMatch

export interface PathMatch {
type: 'path'
Expand Down Expand Up @@ -126,6 +126,17 @@ export interface RepositoryMatch {
branches?: string[]
}

export interface NotebookMatch {
type: 'notebook'
id: string
title: string
namespace: string
url: string

stars?: number
private: boolean
}

/**
* An aggregate type representing a progress update.
* Should be replaced when a new ones come in.
Expand Down Expand Up @@ -528,5 +539,7 @@ export function getMatchUrl(match: SearchMatch): string {
return getCommitMatchUrl(match)
case 'repo':
return getRepoMatchUrl(match)
case 'notebook':
return match.url
}
}
23 changes: 21 additions & 2 deletions cmd/frontend/internal/search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,13 @@ func (h *streamHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Don't send matches which we cannot map to a repo the actor has access to. This
// check is expected to always pass. Missing metadata is a sign that we have
// searched repos that user shouldn't have access to.
if md, ok := repoMetadata[repo.ID]; !ok || md.Name != repo.Name {
continue
//
// This check doesn't apply if the result is not associated with a repository,
// i.e. RepoName is a 0-value.
if repo.ID != 0 && repo.Name != "" {
if md, ok := repoMetadata[repo.ID]; !ok || md.Name != repo.Name {
continue
}
}

eventMatch := fromMatch(match, repoMetadata)
Expand Down Expand Up @@ -419,6 +424,8 @@ func fromMatch(match result.Match, repoCache map[api.RepoID]*types.SearchedRepo)
return fromRepository(v, repoCache)
case *result.CommitMatch:
return fromCommit(v, repoCache)
case *result.NotebookMatch:
return fromNotebook(v)
default:
panic(fmt.Sprintf("unknown match type %T", v))
}
Expand Down Expand Up @@ -577,6 +584,18 @@ func fromCommit(commit *result.CommitMatch, repoCache map[api.RepoID]*types.Sear
return commitEvent
}

func fromNotebook(notebook *result.NotebookMatch) *streamhttp.EventNotebookMatch {
return &streamhttp.EventNotebookMatch{
Type: streamhttp.NotebookMatchType,
ID: notebook.Key().ID,
Title: notebook.Title,
Namespace: notebook.Namespace,
URL: notebook.URL().String(),
Stars: notebook.Stars,
Private: notebook.Private,
}
}

// eventStreamOTHook returns a StatHook which logs to log.
func eventStreamOTHook(log func(...otlog.Field)) func(streamhttp.WriterStat) {
return func(stat streamhttp.WriterStat) {
Expand Down
5 changes: 4 additions & 1 deletion internal/search/job/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,11 @@ func ToSearchJob(jargs *Args, q query.Q, db database.DB) (Job, error) {
}

if resultTypes.Has(result.TypeNotebook) {
// TODO
log15.Info("Notebook search yay")
addJob(true, &notebook.SearchJob{})
addJob(true, &notebook.SearchJob{
// Query: b.PatternString(),
})
}

addJob(true, &searchrepos.ComputeExcludedRepos{
Expand Down
19 changes: 14 additions & 5 deletions internal/search/notebook/notebook.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package notebook
import (
"context"

"github.com/sourcegraph/sourcegraph/internal/api"
"github.com/sourcegraph/sourcegraph/internal/database"
"github.com/sourcegraph/sourcegraph/internal/search"
"github.com/sourcegraph/sourcegraph/internal/search/result"
Expand All @@ -15,14 +14,24 @@ type SearchJob struct {

func (s *SearchJob) Run(ctx context.Context, db database.DB, stream streaming.Sender) (alert *search.Alert, err error) {
// TODO:
// 1. New result.NotebookMatch
// ~1. New result.NotebookMatch~
// 2. Search database for input pattern
// 3. Return NotebookMatches to frontend.
stream.Send(streaming.SearchEvent{
Results: result.Matches{
&result.RepoMatch{
Name: api.RepoName("FOOBAR"),
ID: 1,
&result.NotebookMatch{
Title: "FOOBAR",
Namespace: "sourcegraph",
ID: 1,
Stars: 64,
Private: false,
},
&result.NotebookMatch{
Title: "BAZ",
Namespace: "robert",
ID: 2,
Stars: 0,
Private: true,
},
},
})
Expand Down
5 changes: 5 additions & 0 deletions internal/search/result/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
_ Match = (*FileMatch)(nil)
_ Match = (*RepoMatch)(nil)
_ Match = (*CommitMatch)(nil)
_ Match = (*NotebookMatch)(nil)
)

// Match ranks are used for sorting the different match types.
Expand All @@ -57,6 +58,10 @@ type Key struct {
// Rev is the revision associated with the repo if it exists
Rev string

// ID is an arbitrary identifier that can be used to distinguish this result,
// e.g. if the result type is not associated with a repository.
ID string

// AuthorDate is the date a commit was authored if this key is for
// a commit match.
//
Expand Down
66 changes: 66 additions & 0 deletions internal/search/result/notebook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package result

import (
"net/url"

"github.com/graph-gophers/graphql-go/relay"

"github.com/sourcegraph/sourcegraph/internal/search/filter"
"github.com/sourcegraph/sourcegraph/internal/types"
)

type NotebookMatch struct {
ID int64

Title string
Namespace string
Private bool
Stars int
}

func (n NotebookMatch) RepoName() types.MinimalRepo {
// This result type is not associated with any repository.
return types.MinimalRepo{}
}

func (n NotebookMatch) Limit(limit int) int {
// Always represents one result and limit > 0 so we just return limit - 1.
return limit - 1
}

func (n *NotebookMatch) ResultCount() int {
return 1
}

func (n *NotebookMatch) Select(path filter.SelectPath) Match {
return nil
}

func (n *NotebookMatch) URL() *url.URL {
return &url.URL{Path: "/notebooks/" + n.marshalNotebookID()}
}

func (n *NotebookMatch) Key() Key {
return Key{
ID: n.marshalNotebookID(),

// TODO: Could represent date created, or maybe date updated, to improve relevance
// AuthorDate: n.AuthorDate,

// Use same rank as repos
TypeRank: rankRepoMatch,

// TODO: Key appears to be used for ranking results, maybe we should extend it to
// include star count, or a more generic rank weight. This might be duplicative of
// repo.Stars, but that appears only to be used in
// (*searchIndexerServer).serveConfiguration so maybe that is okay?
// WeightRank: n.Stars,
}
}

func (n *NotebookMatch) searchResultMarker() {}

// from enterprise/cmd/frontend/internal/notebooks/resolvers/resolvers.go
func (n *NotebookMatch) marshalNotebookID() string {
return string(relay.MarshalID("Notebook", 1))
}
18 changes: 18 additions & 0 deletions internal/search/streaming/http/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,19 @@ type EventCommitMatch struct {

func (e *EventCommitMatch) eventMatch() {}

type EventNotebookMatch struct {
Type MatchType `json:"type"`

ID string `json:"id"`
Title string `json:"title"`
Namespace string `json:"namespace"`
URL string `json:"url"`
Private bool `json:"private"`
Stars int `json:"stars,omitempty"`
}

func (e *EventNotebookMatch) eventMatch() {}

// EventFilter is a suggestion for a search filter. Currently has a 1-1
// correspondance with the SearchFilter graphql type.
type EventFilter struct {
Expand Down Expand Up @@ -188,6 +201,7 @@ const (
SymbolMatchType
CommitMatchType
PathMatchType
NotebookMatchType
)

func (t MatchType) MarshalJSON() ([]byte, error) {
Expand All @@ -202,6 +216,8 @@ func (t MatchType) MarshalJSON() ([]byte, error) {
return []byte(`"commit"`), nil
case PathMatchType:
return []byte(`"path"`), nil
case NotebookMatchType:
return []byte(`"notebook"`), nil
default:
return nil, errors.Errorf("unknown MatchType: %d", t)
}
Expand All @@ -218,6 +234,8 @@ func (t *MatchType) UnmarshalJSON(b []byte) error {
*t = CommitMatchType
} else if bytes.Equal(b, []byte(`"path"`)) {
*t = PathMatchType
} else if bytes.Equal(b, []byte(`"notebook"`)) {
*t = NotebookMatchType
} else {
return errors.Errorf("unknown MatchType: %s", b)
}
Expand Down