mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:51:43 +00:00
notebooks: store default pattern type per notebook (#63472)
This PR refactors notebooks to support the upcoming Keyword Search GA. The main goal is to make it easier to switch to a new default pattern type without breaking existing notebooks. **Before** - pattern type and version were hardcoded in several places **After** - Each notebook has a read-only pattern type as determined by the new column `notebooks.pattern_type` (defaults to "standard"). **Notes** - Notebooks call the Stream API via various helper functions. `patternType` and `version` are both required parameters, which is redundant, because version acts as a default pattern type already. I left a TODO in the code for that. I don't want to change this as part of this PR because the change would get very big and affect too much code outside of Notebooks. - We support rendering notebooks with `.snb.md` extension. Unlike notebooks stored in our db, we cannot migrate those files. **Q&A** Q: How does this help for Keyword Search GA? A: Once we default to keyword search, we can change the default of `notebooks.pattern_type` from "standard" to "keyword". Existing notebooks will still work with "standard". New Notebooks will use "keyword". Q: How can customers migrate existing notebooks to a new version? A: Use the existing "Copy to My Notebooks" function of Notebooks. The copied notebook will have the current default pattern type. Test plan: - existing tests pass - manual testing - I created a couple of notebooks with all the different block types and verified via the network tab that all requests to the Stream API have the proper pattern type. I played around with different values in `notebooks.pattern_type` to make sure that the request parameters change.
This commit is contained in:
parent
40ea9132ab
commit
4f79980e0e
@ -1,12 +1,12 @@
|
||||
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
||||
import {
|
||||
Observable,
|
||||
fromEvent,
|
||||
Subscription,
|
||||
type Notification,
|
||||
Observable,
|
||||
type OperatorFunction,
|
||||
pipe,
|
||||
type Subscriber,
|
||||
type Notification,
|
||||
Subscription,
|
||||
} from 'rxjs'
|
||||
import { defaultIfEmpty, map, materialize, scan, switchMap } from 'rxjs/operators'
|
||||
|
||||
@ -501,6 +501,12 @@ export const messageHandlers: MessageHandlers = {
|
||||
|
||||
export interface StreamSearchOptions {
|
||||
version: string
|
||||
/**
|
||||
* TODO(stefan): "patternType" should be an optional parameter. Both Stream API and the GQL API don't require it.
|
||||
* In the UI, we sometimes prefer to remove the "patternType:" filter from the query for better readability.
|
||||
* "patternType" should be used to set the patternType of a query for those cases. Use "version" to
|
||||
* define the default patternType instead.
|
||||
*/
|
||||
patternType: SearchPatternType
|
||||
caseSensitive: boolean
|
||||
trace: string | undefined
|
||||
|
||||
@ -45,9 +45,17 @@ function firstMatchStreamingSearch(
|
||||
}
|
||||
|
||||
export function fetchStreamSuggestions(query: string, sourcegraphURL?: string): Observable<SearchMatch[]> {
|
||||
return fetchStreamSuggestionsPatternType(query, SearchPatternType.standard, sourcegraphURL)
|
||||
}
|
||||
|
||||
export function fetchStreamSuggestionsPatternType(
|
||||
query: string,
|
||||
patternType: SearchPatternType,
|
||||
sourcegraphURL?: string
|
||||
): Observable<SearchMatch[]> {
|
||||
return firstMatchStreamingSearch(of(query), {
|
||||
version: LATEST_VERSION,
|
||||
patternType: SearchPatternType.standard,
|
||||
patternType,
|
||||
caseSensitive: false,
|
||||
trace: undefined,
|
||||
sourcegraphURL,
|
||||
|
||||
@ -3,7 +3,7 @@ import expect from 'expect'
|
||||
import { after, afterEach, before, beforeEach, describe, test } from 'mocha'
|
||||
|
||||
import { encodeURIPathComponent } from '@sourcegraph/common'
|
||||
import type { SharedGraphQlOperations } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import { SearchPatternType, type SharedGraphQlOperations } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import { mixedSearchStreamEvents } from '@sourcegraph/shared/src/search/integration/streaming-search-mocks'
|
||||
import { createDriverForTest, type Driver } from '@sourcegraph/shared/src/testing/driver'
|
||||
import { afterEachSaveScreenshotIfFailed } from '@sourcegraph/shared/src/testing/screenshotReporter'
|
||||
@ -66,6 +66,7 @@ const notebookFixture = (id: string, title: string, blocks: NotebookFields['bloc
|
||||
creator: { __typename: 'User', username: 'user1' },
|
||||
updater: { __typename: 'User', username: 'user1' },
|
||||
blocks,
|
||||
patternType: SearchPatternType.standard,
|
||||
})
|
||||
|
||||
describe('GlobalNavbar', () => {
|
||||
|
||||
@ -6,6 +6,7 @@ import expect from 'expect'
|
||||
import { afterEach, beforeEach, describe, it } from 'mocha'
|
||||
|
||||
import type { SharedGraphQlOperations } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import {
|
||||
highlightFileResult,
|
||||
mixedSearchStreamEvents,
|
||||
@ -88,6 +89,7 @@ const notebookFixture = (id: string, title: string, blocks: NotebookFields['bloc
|
||||
creator: { __typename: 'User', username: 'user1' },
|
||||
updater: { __typename: 'User', username: 'user1' },
|
||||
blocks,
|
||||
patternType: SearchPatternType.standard,
|
||||
})
|
||||
|
||||
const GQLBlockInputToResponse = (block: CreateNotebookBlockInput): NotebookFields['blocks'][number] => {
|
||||
|
||||
@ -49,6 +49,7 @@ const notebooksFragment = gql`
|
||||
stars {
|
||||
totalCount
|
||||
}
|
||||
patternType
|
||||
blocks {
|
||||
... on MarkdownBlock {
|
||||
__typename
|
||||
|
||||
@ -2,6 +2,7 @@ import type { Decorator, Meta, StoryFn } from '@storybook/react'
|
||||
import { noop } from 'lodash'
|
||||
import { of } from 'rxjs'
|
||||
|
||||
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import { HIGHLIGHTED_FILE_LINES_LONG } from '@sourcegraph/shared/src/testing/searchTestHelpers'
|
||||
|
||||
import type { FileBlockInput } from '../..'
|
||||
@ -49,6 +50,7 @@ export const Default: StoryFn = () => (
|
||||
isReadOnly={false}
|
||||
showMenu={false}
|
||||
isSourcegraphDotCom={false}
|
||||
patternType={SearchPatternType.standard}
|
||||
/>
|
||||
)}
|
||||
</WebStory>
|
||||
@ -67,6 +69,7 @@ export const EditMode: StoryFn = () => (
|
||||
isReadOnly={false}
|
||||
showMenu={false}
|
||||
isSourcegraphDotCom={false}
|
||||
patternType={SearchPatternType.standard}
|
||||
/>
|
||||
)}
|
||||
</WebStory>
|
||||
@ -87,6 +90,7 @@ export const ErrorFetchingFile: StoryFn = () => (
|
||||
isReadOnly={false}
|
||||
showMenu={false}
|
||||
isSourcegraphDotCom={false}
|
||||
patternType={SearchPatternType.standard}
|
||||
/>
|
||||
)}
|
||||
</WebStory>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import type { Meta, StoryFn, Decorator } from '@storybook/react'
|
||||
import { noop } from 'lodash'
|
||||
|
||||
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
|
||||
import { WebStory } from '../../../components/WebStory'
|
||||
|
||||
import { NotebookFileBlockInputs } from './NotebookFileBlockInputs'
|
||||
@ -30,6 +32,7 @@ const defaultProps = {
|
||||
editor: undefined,
|
||||
onEditorCreated: noop,
|
||||
isSourcegraphDotCom: false,
|
||||
patternType: SearchPatternType.standard,
|
||||
}
|
||||
|
||||
export const Default: StoryFn = () => (
|
||||
|
||||
@ -12,6 +12,7 @@ import { Icon, Button, Input, InputStatus } from '@sourcegraph/wildcard'
|
||||
|
||||
import type { BlockProps, FileBlockInput } from '../..'
|
||||
import type { HighlightLineRange } from '../../../graphql-operations'
|
||||
import { SearchPatternType } from '../../../graphql-operations'
|
||||
import { parseLineRange, serializeLineRange } from '../../serialize'
|
||||
import { SearchTypeSuggestionsInput } from '../suggestions/SearchTypeSuggestionsInput'
|
||||
import { fetchSuggestions } from '../suggestions/suggestions'
|
||||
@ -21,6 +22,7 @@ import styles from './NotebookFileBlockInputs.module.scss'
|
||||
interface NotebookFileBlockInputsProps extends Pick<BlockProps, 'onRunBlock'> {
|
||||
id: string
|
||||
queryInput: string
|
||||
patternType: SearchPatternType
|
||||
lineRange: HighlightLineRange | null
|
||||
onEditorCreated: (editor: EditorView) => void
|
||||
setQueryInput: (value: string) => void
|
||||
@ -44,7 +46,7 @@ const editorAttributes = [
|
||||
|
||||
export const NotebookFileBlockInputs: React.FunctionComponent<
|
||||
React.PropsWithChildren<NotebookFileBlockInputsProps>
|
||||
> = ({ id, lineRange, onFileSelected, onLineRangeChange, isSourcegraphDotCom, ...inputProps }) => {
|
||||
> = ({ id, lineRange, onFileSelected, onLineRangeChange, isSourcegraphDotCom, patternType, ...inputProps }) => {
|
||||
const [lineRangeInput, setLineRangeInput] = useState(serializeLineRange(lineRange))
|
||||
const debouncedOnLineRangeChange = useMemo(() => debounce(onLineRangeChange, 300), [onLineRangeChange])
|
||||
|
||||
@ -65,10 +67,11 @@ export const NotebookFileBlockInputs: React.FunctionComponent<
|
||||
(query: string) =>
|
||||
fetchSuggestions(
|
||||
getFileSuggestionsQuery(query),
|
||||
patternType,
|
||||
(suggestion): suggestion is PathMatch => suggestion.type === 'path',
|
||||
file => file
|
||||
),
|
||||
[]
|
||||
[patternType]
|
||||
)
|
||||
|
||||
const countSuggestions = useCallback((suggestions: PathMatch[]) => suggestions.length, [])
|
||||
|
||||
@ -80,7 +80,7 @@ const staticExtensions: Extension[] = [
|
||||
editorHeight({ maxHeight: '60rem' }),
|
||||
]
|
||||
|
||||
interface NotebookMarkdownBlockProps extends BlockProps<MarkdownBlock> {
|
||||
interface NotebookMarkdownBlockProps extends Omit<BlockProps<MarkdownBlock>, 'patternType'> {
|
||||
isEmbedded?: boolean
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import type { Decorator, StoryFn, Meta } from '@storybook/react'
|
||||
import { noop } from 'lodash'
|
||||
import { of } from 'rxjs'
|
||||
|
||||
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import type { AggregateStreamingSearchResults } from '@sourcegraph/shared/src/search/stream'
|
||||
import { EMPTY_SETTINGS_CASCADE } from '@sourcegraph/shared/src/settings/settings'
|
||||
import { noOpTelemetryRecorder } from '@sourcegraph/shared/src/telemetry'
|
||||
@ -68,6 +69,7 @@ export const Default: StoryFn = () => (
|
||||
fetchHighlightedFileLineRanges={() => of([HIGHLIGHTED_FILE_LINES_LONG])}
|
||||
settingsCascade={EMPTY_SETTINGS_CASCADE}
|
||||
platformContext={NOOP_PLATFORM_CONTEXT}
|
||||
patternType={SearchPatternType.standard}
|
||||
/>
|
||||
)}
|
||||
</WebStory>
|
||||
@ -94,6 +96,7 @@ export const Selected: StoryFn = () => (
|
||||
settingsCascade={EMPTY_SETTINGS_CASCADE}
|
||||
authenticatedUser={null}
|
||||
platformContext={NOOP_PLATFORM_CONTEXT}
|
||||
patternType={SearchPatternType.standard}
|
||||
/>
|
||||
)}
|
||||
</WebStory>
|
||||
@ -120,6 +123,7 @@ export const ReadOnlySelected: StoryFn = () => (
|
||||
settingsCascade={EMPTY_SETTINGS_CASCADE}
|
||||
authenticatedUser={null}
|
||||
platformContext={NOOP_PLATFORM_CONTEXT}
|
||||
patternType={SearchPatternType.standard}
|
||||
/>
|
||||
)}
|
||||
</WebStory>
|
||||
|
||||
@ -40,6 +40,7 @@ interface NotebookQueryBlockProps
|
||||
isSourcegraphDotCom: boolean
|
||||
fetchHighlightedFileLineRanges: (parameters: FetchFileParameters, force?: boolean) => Observable<string[][]>
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
patternType: SearchPatternType
|
||||
}
|
||||
|
||||
// Defines the max height for the CodeMirror editor
|
||||
@ -68,6 +69,7 @@ export const NotebookQueryBlock: React.FunctionComponent<React.PropsWithChildren
|
||||
isSourcegraphDotCom,
|
||||
searchContextsEnabled,
|
||||
ownEnabled,
|
||||
patternType,
|
||||
...props
|
||||
}) => {
|
||||
const [editor, setEditor] = useState<EditorView | null>(null)
|
||||
@ -107,10 +109,10 @@ export const NotebookQueryBlock: React.FunctionComponent<React.PropsWithChildren
|
||||
type: 'link',
|
||||
label: 'Open in new tab',
|
||||
icon: <Icon aria-hidden={true} svgPath={mdiOpenInNew} />,
|
||||
url: `/search?${buildSearchURLQuery(input.query, SearchPatternType.standard, false)}`,
|
||||
url: `/search?${buildSearchURLQuery(input.query, patternType, false)}`,
|
||||
},
|
||||
],
|
||||
[input]
|
||||
[input, patternType]
|
||||
)
|
||||
|
||||
const commonMenuActions = linkMenuActions.concat(useCommonBlockMenuActions({ id, ...props }))
|
||||
@ -161,7 +163,7 @@ export const NotebookQueryBlock: React.FunctionComponent<React.PropsWithChildren
|
||||
<CodeMirrorQueryInput
|
||||
ref={setEditor}
|
||||
value={input.query}
|
||||
patternType={SearchPatternType.standard}
|
||||
patternType={patternType}
|
||||
interpretComments={true}
|
||||
onChange={onInputChange}
|
||||
multiLine={true}
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
import type { Observable } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
|
||||
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import type { PathMatch, RepositoryMatch, SearchMatch, SymbolMatch } from '@sourcegraph/shared/src/search/stream'
|
||||
import { fetchStreamSuggestions } from '@sourcegraph/shared/src/search/suggestions'
|
||||
import { fetchStreamSuggestionsPatternType } from '@sourcegraph/shared/src/search/suggestions'
|
||||
|
||||
export function fetchSuggestions<T extends RepositoryMatch | PathMatch | SymbolMatch, O>(
|
||||
query: string,
|
||||
patternType: SearchPatternType,
|
||||
filterSuggestionFunc: (match: SearchMatch) => match is T,
|
||||
mapSuggestionFunc: (match: T) => O
|
||||
): Observable<O[]> {
|
||||
return fetchStreamSuggestions(query).pipe(
|
||||
return fetchStreamSuggestionsPatternType(query, patternType).pipe(
|
||||
map(suggestions => suggestions.filter(filterSuggestionFunc).map(mapSuggestionFunc))
|
||||
)
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import React, { useCallback, useMemo } from 'react'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
|
||||
import { createDefaultSuggestions, RepoFileLink } from '@sourcegraph/branded'
|
||||
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import { getFileMatchUrl, getRepositoryUrl, type SymbolMatch } from '@sourcegraph/shared/src/search/stream'
|
||||
import { fetchStreamSuggestions } from '@sourcegraph/shared/src/search/suggestions'
|
||||
import { useExperimentalFeatures } from '@sourcegraph/shared/src/settings/settings'
|
||||
@ -18,6 +19,7 @@ import styles from './NotebookSymbolBlockInput.module.scss'
|
||||
interface NotebookSymbolBlockInputProps extends Pick<BlockProps, 'onRunBlock'> {
|
||||
id: string
|
||||
queryInput: string
|
||||
patternType: SearchPatternType
|
||||
onEditorCreated: (editor: EditorView) => void
|
||||
setQueryInput: (value: string) => void
|
||||
onSymbolSelected: (symbol: SymbolBlockInput) => void
|
||||
@ -39,15 +41,16 @@ const editorAttributes = [
|
||||
|
||||
export const NotebookSymbolBlockInput: React.FunctionComponent<
|
||||
React.PropsWithChildren<NotebookSymbolBlockInputProps>
|
||||
> = ({ onSymbolSelected, isSourcegraphDotCom, ...inputProps }) => {
|
||||
> = ({ onSymbolSelected, isSourcegraphDotCom, patternType, ...inputProps }) => {
|
||||
const fetchSymbolSuggestions = useCallback(
|
||||
(query: string) =>
|
||||
fetchSuggestions(
|
||||
getSymbolSuggestionsQuery(query),
|
||||
patternType,
|
||||
(suggestion): suggestion is SymbolMatch => suggestion.type === 'symbol',
|
||||
symbol => symbol
|
||||
),
|
||||
[]
|
||||
[patternType]
|
||||
)
|
||||
|
||||
const countSuggestions = useCallback(
|
||||
|
||||
@ -6,6 +6,7 @@ import type { AggregateStreamingSearchResults } from '@sourcegraph/shared/src/se
|
||||
import type { UIRangeSpec } from '@sourcegraph/shared/src/util/url'
|
||||
|
||||
import type { HighlightLineRange, SymbolKind } from '../graphql-operations'
|
||||
import { SearchPatternType } from '../graphql-operations'
|
||||
|
||||
// When adding a new block type, make sure to track its usage in internal/usagestats/notebooks.go.
|
||||
export type BlockType = 'md' | 'query' | 'file' | 'compute' | 'symbol'
|
||||
@ -108,6 +109,7 @@ export interface BlockProps<T extends Block = Block> {
|
||||
id: T['id']
|
||||
input: T['input']
|
||||
output: T['output']
|
||||
patternType: SearchPatternType
|
||||
onRunBlock(id: string): void
|
||||
onDeleteBlock(id: string): void
|
||||
onBlockInputChange(id: string, blockInput: BlockInput): void
|
||||
|
||||
@ -7,6 +7,7 @@ import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/teleme
|
||||
|
||||
import { WebStory } from '../../components/WebStory'
|
||||
import type { ListNotebooksResult } from '../../graphql-operations'
|
||||
import { SearchPatternType } from '../../graphql-operations'
|
||||
|
||||
import { NotebooksListPage } from './NotebooksListPage'
|
||||
|
||||
@ -45,6 +46,7 @@ const fetchNotebooks = (): Observable<ListNotebooksResult['notebooks']> =>
|
||||
{ __typename: 'MarkdownBlock', id: '1', markdownInput: '# Title' },
|
||||
{ __typename: 'QueryBlock', id: '2', queryInput: 'query' },
|
||||
],
|
||||
patternType: SearchPatternType.standard,
|
||||
},
|
||||
{
|
||||
__typename: 'Notebook',
|
||||
@ -60,6 +62,7 @@ const fetchNotebooks = (): Observable<ListNotebooksResult['notebooks']> =>
|
||||
updater: { __typename: 'User', username: 'user2' },
|
||||
namespace: { __typename: 'User', namespaceName: 'user2', id: '2' },
|
||||
blocks: [{ __typename: 'MarkdownBlock', id: '1', markdownInput: '# Title' }],
|
||||
patternType: SearchPatternType.standard,
|
||||
},
|
||||
],
|
||||
pageInfo: { hasNextPage: false, endCursor: null },
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { Decorator, Meta, StoryFn } from '@storybook/react'
|
||||
import { NEVER, of } from 'rxjs'
|
||||
|
||||
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import { EMPTY_SETTINGS_CASCADE } from '@sourcegraph/shared/src/settings/settings'
|
||||
import { noOpTelemetryRecorder } from '@sourcegraph/shared/src/telemetry'
|
||||
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
@ -58,6 +59,7 @@ export const Default: StoryFn = () => (
|
||||
platformContext={NOOP_PLATFORM_CONTEXT}
|
||||
exportedFileName="notebook.snb.md"
|
||||
onCopyNotebook={() => NEVER}
|
||||
patternType={SearchPatternType.standard}
|
||||
/>
|
||||
)}
|
||||
</WebStory>
|
||||
@ -83,6 +85,7 @@ export const DefaultReadOnly: StoryFn = () => (
|
||||
platformContext={NOOP_PLATFORM_CONTEXT}
|
||||
exportedFileName="notebook.snb.md"
|
||||
onCopyNotebook={() => NEVER}
|
||||
patternType={SearchPatternType.standard}
|
||||
/>
|
||||
)}
|
||||
</WebStory>
|
||||
|
||||
@ -17,6 +17,7 @@ import { Button, useEventObservable, Icon } from '@sourcegraph/wildcard'
|
||||
import { V2BlockTypes, type Block, type BlockDirection, type BlockInit, type BlockInput, type BlockType } from '..'
|
||||
import type { AuthenticatedUser } from '../../auth'
|
||||
import type { NotebookFields } from '../../graphql-operations'
|
||||
import { SearchPatternType } from '../../graphql-operations'
|
||||
import type { OwnConfigProps } from '../../own/OwnConfigProps'
|
||||
import { PageRoutes } from '../../routes.constants'
|
||||
import type { SearchStreamingProps } from '../../search'
|
||||
@ -47,6 +48,7 @@ export interface NotebookComponentProps
|
||||
outlineContainerElement?: HTMLElement | null
|
||||
onSerializeBlocks: (blocks: Block[]) => void
|
||||
onCopyNotebook: (props: Omit<CopyNotebookProps, 'title'>) => Observable<NotebookFields>
|
||||
patternType: SearchPatternType
|
||||
}
|
||||
|
||||
const LOADING = 'LOADING' as const
|
||||
@ -94,13 +96,14 @@ export const NotebookComponent: React.FunctionComponent<React.PropsWithChildren<
|
||||
ownEnabled,
|
||||
settingsCascade,
|
||||
outlineContainerElement,
|
||||
patternType,
|
||||
}) => {
|
||||
const notebook = useMemo(
|
||||
() =>
|
||||
new Notebook(initialBlocks, {
|
||||
new Notebook(initialBlocks, patternType, {
|
||||
fetchHighlightedFileLineRanges,
|
||||
}),
|
||||
[initialBlocks, fetchHighlightedFileLineRanges]
|
||||
[initialBlocks, fetchHighlightedFileLineRanges, patternType]
|
||||
)
|
||||
|
||||
const notebookElement = useRef<HTMLDivElement | null>(null)
|
||||
@ -421,6 +424,7 @@ export const NotebookComponent: React.FunctionComponent<React.PropsWithChildren<
|
||||
telemetryService={telemetryService}
|
||||
telemetryRecorder={telemetryRecorder}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
patternType={patternType}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -438,6 +442,7 @@ export const NotebookComponent: React.FunctionComponent<React.PropsWithChildren<
|
||||
telemetryRecorder={telemetryRecorder}
|
||||
platformContext={platformContext}
|
||||
authenticatedUser={authenticatedUser}
|
||||
patternType={patternType}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -450,6 +455,7 @@ export const NotebookComponent: React.FunctionComponent<React.PropsWithChildren<
|
||||
telemetryService={telemetryService}
|
||||
telemetryRecorder={telemetryRecorder}
|
||||
platformContext={platformContext}
|
||||
patternType={patternType}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -476,6 +482,7 @@ export const NotebookComponent: React.FunctionComponent<React.PropsWithChildren<
|
||||
settingsCascade,
|
||||
platformContext,
|
||||
authenticatedUser,
|
||||
patternType,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@ -1,22 +1,21 @@
|
||||
import { escapeRegExp } from 'lodash'
|
||||
// We're using marked import here to access the `marked` package type definitions.
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { type marked, Renderer } from 'marked'
|
||||
import { type Observable, forkJoin, of } from 'rxjs'
|
||||
import { startWith, catchError, map, switchMap } from 'rxjs/operators'
|
||||
import { forkJoin, type Observable, of } from 'rxjs'
|
||||
import { catchError, map, startWith, switchMap } from 'rxjs/operators'
|
||||
import * as uuid from 'uuid'
|
||||
|
||||
import { renderMarkdown, asError, isErrorLike } from '@sourcegraph/common'
|
||||
import { asError, isErrorLike, renderMarkdown } from '@sourcegraph/common'
|
||||
import {
|
||||
aggregateStreamingSearch,
|
||||
emptyAggregateResults,
|
||||
LATEST_VERSION,
|
||||
type SymbolMatch,
|
||||
LATEST_VERSION,
|
||||
} from '@sourcegraph/shared/src/search/stream'
|
||||
import type { UIRangeSpec } from '@sourcegraph/shared/src/util/url'
|
||||
|
||||
import type { Block, BlockInit, BlockDependencies, BlockInput, BlockDirection, SymbolBlockInput } from '..'
|
||||
import type { Block, BlockDependencies, BlockDirection, BlockInit, BlockInput, SymbolBlockInput } from '..'
|
||||
import { type NotebookFields, SearchPatternType } from '../../graphql-operations'
|
||||
import { parseBrowserRepoURL } from '../../util/url'
|
||||
import { createNotebook } from '../backend'
|
||||
@ -46,6 +45,7 @@ export function copyNotebook({ title, blocks, namespace }: CopyNotebookProps): O
|
||||
|
||||
function findSymbolAtRevision(
|
||||
input: Omit<SymbolBlockInput, 'revision'>,
|
||||
patternType: SearchPatternType,
|
||||
revision: string
|
||||
): Observable<{ range: UIRangeSpec['range']; revision: string } | Error> {
|
||||
const { repositoryName, filePath, symbolName, symbolContainerName, symbolKind } = input
|
||||
@ -53,6 +53,7 @@ function findSymbolAtRevision(
|
||||
`repo:${escapeRegExp(repositoryName)} file:${escapeRegExp(
|
||||
filePath
|
||||
)} rev:${revision} ${symbolName} type:symbol count:50`,
|
||||
patternType,
|
||||
(suggestion): suggestion is SymbolMatch => suggestion.type === 'symbol',
|
||||
symbol => symbol
|
||||
).pipe(
|
||||
@ -98,11 +99,17 @@ export class NotebookHeadingMarkdownRenderer extends Renderer {
|
||||
export class Notebook {
|
||||
private blocks: Map<string, Block>
|
||||
private blockOrder: string[]
|
||||
private patternType: SearchPatternType
|
||||
|
||||
constructor(initializerBlocks: BlockInit[], private dependencies: BlockDependencies) {
|
||||
constructor(
|
||||
initializerBlocks: BlockInit[],
|
||||
patternType: SearchPatternType,
|
||||
private dependencies: BlockDependencies
|
||||
) {
|
||||
const blocks = initializerBlocks.map(block => ({ ...block, output: null }))
|
||||
|
||||
this.blocks = new Map(blocks.map(block => [block.id, block]))
|
||||
this.patternType = patternType
|
||||
this.blockOrder = blocks.map(block => block.id)
|
||||
|
||||
// Pre-run certain blocks, for a better user experience.
|
||||
@ -162,7 +169,7 @@ export class Notebook {
|
||||
...block,
|
||||
output: aggregateStreamingSearch(of(query), {
|
||||
version: LATEST_VERSION,
|
||||
patternType: SearchPatternType.standard,
|
||||
patternType: this.patternType,
|
||||
caseSensitive: false,
|
||||
trace: undefined,
|
||||
chunkMatches: true,
|
||||
@ -192,13 +199,13 @@ export class Notebook {
|
||||
}
|
||||
case 'symbol': {
|
||||
// Start by searching for the symbol at the latest HEAD (main) revision.
|
||||
const output = findSymbolAtRevision(block.input, 'HEAD').pipe(
|
||||
const output = findSymbolAtRevision(block.input, this.patternType, 'HEAD').pipe(
|
||||
switchMap(symbolSearchResult => {
|
||||
if (!isErrorLike(symbolSearchResult)) {
|
||||
return of({ ...symbolSearchResult, symbolFoundAtLatestRevision: true })
|
||||
}
|
||||
// If not found, look at the revision stored in the block input (should always be found).
|
||||
return findSymbolAtRevision(block.input, block.input.revision).pipe(
|
||||
return findSymbolAtRevision(block.input, this.patternType, block.input.revision).pipe(
|
||||
map(symbolSearchResult =>
|
||||
!isErrorLike(symbolSearchResult)
|
||||
? { ...symbolSearchResult, symbolFoundAtLatestRevision: false }
|
||||
|
||||
@ -85,6 +85,7 @@ export const EmbeddedNotebookPage: FC<EmbeddedNotebookPageProps> = ({ platformCo
|
||||
// Copying is not supported in embedded notebooks
|
||||
onCopyNotebook={() => NEVER}
|
||||
isEmbedded={true}
|
||||
patternType={notebookOrError.patternType}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -11,6 +11,7 @@ import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetry
|
||||
|
||||
import type { Block, BlockInit } from '..'
|
||||
import type { NotebookFields } from '../../graphql-operations'
|
||||
import { SearchPatternType } from '../../graphql-operations'
|
||||
import type { OwnConfigProps } from '../../own/OwnConfigProps'
|
||||
import type { SearchStreamingProps } from '../../search'
|
||||
import type { CopyNotebookProps } from '../notebook'
|
||||
@ -31,6 +32,7 @@ export interface NotebookContentProps
|
||||
outlineContainerElement?: HTMLElement | null
|
||||
onUpdateBlocks: (blocks: Block[]) => void
|
||||
onCopyNotebook: (props: Omit<CopyNotebookProps, 'title'>) => Observable<NotebookFields>
|
||||
patternType: SearchPatternType
|
||||
}
|
||||
|
||||
export const NotebookContent: React.FunctionComponent<React.PropsWithChildren<NotebookContentProps>> = React.memo(
|
||||
@ -52,6 +54,7 @@ export const NotebookContent: React.FunctionComponent<React.PropsWithChildren<No
|
||||
platformContext,
|
||||
outlineContainerElement,
|
||||
isEmbedded,
|
||||
patternType,
|
||||
}) => {
|
||||
const initializerBlocks: BlockInit[] = useMemo(
|
||||
() =>
|
||||
@ -101,6 +104,7 @@ export const NotebookContent: React.FunctionComponent<React.PropsWithChildren<No
|
||||
onCopyNotebook={onCopyNotebook}
|
||||
outlineContainerElement={outlineContainerElement}
|
||||
isEmbedded={isEmbedded}
|
||||
patternType={patternType}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -299,6 +299,7 @@ export const NotebookPage: React.FunctionComponent<React.PropsWithChildren<Noteb
|
||||
settingsCascade={settingsCascade}
|
||||
platformContext={platformContext}
|
||||
outlineContainerElement={outlineContainerElement.current}
|
||||
patternType={notebookOrError.patternType}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -22,7 +22,7 @@ import {
|
||||
useCurrentSpan,
|
||||
} from '@sourcegraph/observability-client'
|
||||
import type { FetchFileParameters } from '@sourcegraph/shared/src/backend/file'
|
||||
import { HighlightResponseFormat } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import { HighlightResponseFormat, SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import type { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
import type { SearchContextProps } from '@sourcegraph/shared/src/search'
|
||||
import { useExperimentalFeatures, type SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
|
||||
@ -582,6 +582,7 @@ export const BlobPage: React.FunctionComponent<BlobPageProps> = ({ className, co
|
||||
onCopyNotebook={onCopyNotebook}
|
||||
exportedFileName={basename(blobInfoOrError.filePath)}
|
||||
className={styles.border}
|
||||
patternType={SearchPatternType.standard}
|
||||
/>
|
||||
</React.Suspense>
|
||||
)}
|
||||
|
||||
@ -60,6 +60,7 @@ type NotebookResolver interface {
|
||||
ViewerCanManage(ctx context.Context) (bool, error)
|
||||
ViewerHasStarred(ctx context.Context) (bool, error)
|
||||
Stars(ctx context.Context, args ListNotebookStarsArgs) (NotebookStarConnectionResolver, error)
|
||||
PatternType(ctx context.Context) string
|
||||
}
|
||||
|
||||
type NotebookBlockResolver interface {
|
||||
|
||||
@ -298,6 +298,10 @@ type Notebook implements Node {
|
||||
"""
|
||||
after: String
|
||||
): NotebookStarConnection!
|
||||
"""
|
||||
The default pattern type that is used to interpret queries that do not contain a patternType: filter.
|
||||
"""
|
||||
patternType: SearchPatternType!
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
@ -513,6 +513,10 @@ func (r *notebookBlockResolver) ToSymbolBlock() (graphqlbackend.SymbolBlockResol
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (r *notebookResolver) PatternType(_ context.Context) string {
|
||||
return r.notebook.PatternType
|
||||
}
|
||||
|
||||
type markdownBlockResolver struct {
|
||||
// block.type == NotebookMarkdownBlockType
|
||||
block notebooks.NotebookBlock
|
||||
|
||||
@ -11234,6 +11234,19 @@
|
||||
],
|
||||
"IsCreateIndexConcurrently": false,
|
||||
"IndexMetadata": null
|
||||
},
|
||||
{
|
||||
"ID": 1719214941,
|
||||
"Name": "notebooks_add_field_pattern_type",
|
||||
"UpQuery": "DO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'pattern_type') THEN\n CREATE TYPE pattern_type AS ENUM ('keyword', 'literal', 'regexp', 'standard', 'structural');\n END IF;\nEND\n$$;\n\nALTER TABLE notebooks ADD COLUMN IF NOT EXISTS pattern_type pattern_type NOT NULL DEFAULT 'standard';",
|
||||
"DownQuery": "ALTER TABLE notebooks DROP COLUMN IF EXISTS pattern_type;\nDROP TYPE IF EXISTS pattern_type;",
|
||||
"Privileged": false,
|
||||
"NonIdempotent": false,
|
||||
"Parents": [
|
||||
1717699555
|
||||
],
|
||||
"IsCreateIndexConcurrently": false,
|
||||
"IndexMetadata": null
|
||||
}
|
||||
],
|
||||
"BoundsByRev": {
|
||||
@ -11494,7 +11507,7 @@
|
||||
"v5.4.0": {
|
||||
"RootID": 1648051770,
|
||||
"LeafIDs": [
|
||||
1717699555
|
||||
1719214941
|
||||
],
|
||||
"PreCreation": false
|
||||
}
|
||||
|
||||
@ -45,6 +45,16 @@
|
||||
"rollout"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "pattern_type",
|
||||
"Labels": [
|
||||
"keyword",
|
||||
"literal",
|
||||
"regexp",
|
||||
"standard",
|
||||
"structural"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "persistmode",
|
||||
"Labels": [
|
||||
@ -19077,6 +19087,19 @@
|
||||
"GenerationExpression": "",
|
||||
"Comment": ""
|
||||
},
|
||||
{
|
||||
"Name": "pattern_type",
|
||||
"Index": 12,
|
||||
"TypeName": "pattern_type",
|
||||
"IsNullable": false,
|
||||
"Default": "'standard'::pattern_type",
|
||||
"CharacterMaximumLength": 0,
|
||||
"IsIdentity": false,
|
||||
"IdentityGeneration": "",
|
||||
"IsGenerated": "NEVER",
|
||||
"GenerationExpression": "",
|
||||
"Comment": ""
|
||||
},
|
||||
{
|
||||
"Name": "public",
|
||||
"Index": 4,
|
||||
|
||||
@ -2798,6 +2798,7 @@ Foreign-key constraints:
|
||||
namespace_user_id | integer | | |
|
||||
namespace_org_id | integer | | |
|
||||
updater_user_id | integer | | |
|
||||
pattern_type | pattern_type | | not null | 'standard'::pattern_type
|
||||
Indexes:
|
||||
"notebooks_pkey" PRIMARY KEY, btree (id)
|
||||
"notebooks_blocks_tsvector_idx" gin (blocks_tsvector)
|
||||
@ -5096,6 +5097,14 @@ Foreign-key constraints:
|
||||
- bool
|
||||
- rollout
|
||||
|
||||
# Type pattern_type
|
||||
|
||||
- keyword
|
||||
- literal
|
||||
- regexp
|
||||
- standard
|
||||
- structural
|
||||
|
||||
# Type persistmode
|
||||
|
||||
- record
|
||||
|
||||
@ -117,6 +117,7 @@ var notebookColumns = []*sqlf.Query{
|
||||
sqlf.Sprintf("notebooks.namespace_org_id"),
|
||||
sqlf.Sprintf("notebooks.created_at"),
|
||||
sqlf.Sprintf("notebooks.updated_at"),
|
||||
sqlf.Sprintf("pattern_type"),
|
||||
}
|
||||
|
||||
func notebooksPermissionsCondition(ctx context.Context) *sqlf.Query {
|
||||
@ -196,6 +197,7 @@ func scanNotebook(scanner dbutil.Scanner) (*Notebook, error) {
|
||||
&dbutil.NullInt32{N: &n.NamespaceOrgID},
|
||||
&n.CreatedAt,
|
||||
&n.UpdatedAt,
|
||||
&n.PatternType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -68,6 +68,7 @@ type Notebook struct {
|
||||
NamespaceOrgID int32 // if non-zero, the owner is this organization. NamespaceUserID/NamespaceOrgID are mutually exclusive.
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
PatternType string
|
||||
}
|
||||
|
||||
type NotebookStar struct {
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
ALTER TABLE notebooks DROP COLUMN IF EXISTS pattern_type;
|
||||
DROP TYPE IF EXISTS pattern_type;
|
||||
@ -0,0 +1,2 @@
|
||||
name: notebooks_add_field_pattern_type
|
||||
parents: [1717699555]
|
||||
@ -0,0 +1,9 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'pattern_type') THEN
|
||||
CREATE TYPE pattern_type AS ENUM ('keyword', 'literal', 'regexp', 'standard', 'structural');
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
ALTER TABLE notebooks ADD COLUMN IF NOT EXISTS pattern_type pattern_type NOT NULL DEFAULT 'standard';
|
||||
@ -76,6 +76,14 @@ CREATE TYPE lsif_uploads_transition_columns AS (
|
||||
|
||||
COMMENT ON TYPE lsif_uploads_transition_columns IS 'A type containing the columns that make-up the set of tracked transition columns. Primarily used to create a nulled record due to `OLD` being unset in INSERT queries, and creating a nulled record with a subquery is not allowed.';
|
||||
|
||||
CREATE TYPE pattern_type AS ENUM (
|
||||
'keyword',
|
||||
'literal',
|
||||
'regexp',
|
||||
'standard',
|
||||
'structural'
|
||||
);
|
||||
|
||||
CREATE TYPE persistmode AS ENUM (
|
||||
'record',
|
||||
'snapshot'
|
||||
@ -3553,6 +3561,7 @@ CREATE TABLE notebooks (
|
||||
namespace_user_id integer,
|
||||
namespace_org_id integer,
|
||||
updater_user_id integer,
|
||||
pattern_type pattern_type DEFAULT 'standard'::pattern_type NOT NULL,
|
||||
CONSTRAINT blocks_is_array CHECK ((jsonb_typeof(blocks) = 'array'::text)),
|
||||
CONSTRAINT notebooks_has_max_1_namespace CHECK ((((namespace_user_id IS NULL) AND (namespace_org_id IS NULL)) OR ((namespace_user_id IS NULL) <> (namespace_org_id IS NULL))))
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user