Create search and search-ui packages (#29773)

Co-authored-by: Juliana Peña <juliana@sourcegraph.com>
This commit is contained in:
TJ Kandala 2022-01-20 12:56:23 -05:00 committed by GitHub
parent c7f9aeaf38
commit 29ef1da2a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
385 changed files with 2260 additions and 1392 deletions

View File

@ -21,6 +21,8 @@ jobs:
- client/browser
- client/build-config
- client/shared
- client/search
- client/search-ui
- client/web
- client/wildcard
- client/common

2
.gitignore vendored
View File

@ -104,7 +104,7 @@ package-lock.json
coverage/
out/
client/shared/src/schema.ts
client/web/src/schema/*
client/shared/src/schema/*
puppeteer/
package-lock.json
/dist

View File

@ -16,7 +16,7 @@ vendor/
out/
dist/
client/shared/src/graphqlschema.ts
client/web/src/schema/
client/shared/src/schema/
ts-node-*
testdata
.github/*

View File

@ -61,6 +61,8 @@
"./client/browser",
"./client/build-config",
"./client/shared",
"./client/search",
"./client/search-ui",
"./client/http-client",
"./client/branded",
"./client/wildcard",

View File

@ -0,0 +1 @@
out/

View File

@ -0,0 +1,12 @@
// @ts-check
const baseConfig = require('../../.eslintrc.js')
module.exports = {
extends: '../../.eslintrc.js',
parserOptions: {
...baseConfig.parserOptions,
project: [__dirname + '/tsconfig.json'],
},
overrides: baseConfig.overrides,
}

View File

@ -0,0 +1,3 @@
# Search UI package
This package contains search UI components with branded styling that are shared between clients. For example, the `<SearchBox>` component that is used in both the web application and VS Code extension.

View File

@ -0,0 +1,5 @@
// @ts-check
module.exports = {
extends: '../../babel.config.js',
}

View File

@ -0,0 +1,13 @@
// @ts-check
const config = require('../../jest.config.base')
const exportedConfig = {
...config,
displayName: 'common',
rootDir: __dirname,
roots: ['<rootDir>'],
verbose: true,
}
module.exports = exportedConfig

View File

@ -0,0 +1,14 @@
{
"private": true,
"name": "@sourcegraph/search-ui",
"version": "0.0.1",
"description": "Branded Sourcegraph search UI components",
"main": "./src/index.ts",
"sideEffects": false,
"license": "Apache-2.0",
"scripts": {
"storybook": "STORIES_GLOB=client/search-ui/src/**/*.story.tsx yarn workspace @sourcegraph/storybook run start",
"eslint": "eslint --cache 'src/**/*.[jt]s?(x)'",
"test": "jest"
}
}

View File

@ -6,18 +6,18 @@ import { combineLatest, of, Subject, Subscription } from 'rxjs'
import { catchError, distinctUntilChanged, filter, switchMap } from 'rxjs/operators'
import sanitizeHtml from 'sanitize-html'
import { highlightCode } from '@sourcegraph/search'
import { LastSyncedIcon } from '@sourcegraph/shared/src/components/LastSyncedIcon'
import { Markdown } from '@sourcegraph/shared/src/components/Markdown'
import { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
import { CommitMatch } from '@sourcegraph/shared/src/search/stream'
import { highlightNode } from '@sourcegraph/shared/src/util/dom'
import { LoadingSpinner, Link } from '@sourcegraph/wildcard'
import { highlightCode } from '../search/backend'
import styles from './CommitSearchResultMatch.module.scss'
import searchResultStyles from './SearchResult.module.scss'
interface CommitSearchResultMatchProps {
interface CommitSearchResultMatchProps extends PlatformContextProps<'requestGraphQL'> {
item: CommitMatch
}
@ -66,6 +66,7 @@ export class CommitSearchResultMatch extends React.Component<
code: codeContent,
fuzzyLanguage: lang,
disableTimeout: false,
platformContext: props.platformContext,
}).pipe(
// Return the rendered markdown if highlighting fails.
catchError(error => {

View File

@ -9,21 +9,21 @@ import { Markdown } from '@sourcegraph/shared/src/components/Markdown'
import { RepoIcon } from '@sourcegraph/shared/src/components/RepoIcon'
import { ResultContainer } from '@sourcegraph/shared/src/components/ResultContainer'
import { SearchResultStar } from '@sourcegraph/shared/src/components/SearchResultStar'
import { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
import { CommitMatch, getMatchTitle, RepositoryMatch } from '@sourcegraph/shared/src/search/stream'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { renderMarkdown } from '@sourcegraph/shared/src/util/markdown'
import { formatRepositoryStarCount } from '@sourcegraph/shared/src/util/stars'
import { CommitSearchResultMatch } from './CommitSearchResultMatch'
import styles from './SearchResult.module.scss'
interface Props extends TelemetryProps {
interface Props extends PlatformContextProps<'requestGraphQL'> {
result: CommitMatch | RepositoryMatch
repoName: string
icon: React.ComponentType<{ className?: string }>
}
export const SearchResult: React.FunctionComponent<Props> = ({ result, icon, repoName, telemetryService }) => {
export const SearchResult: React.FunctionComponent<Props> = ({ result, icon, repoName, platformContext }) => {
const renderTitle = (): JSX.Element => {
const formattedRepositoryStarCount = formatRepositoryStarCount(result.repoStars)
return (
@ -117,7 +117,7 @@ export const SearchResult: React.FunctionComponent<Props> = ({ result, icon, rep
)
}
return <CommitSearchResultMatch key={result.url} item={result} />
return <CommitSearchResultMatch key={result.url} item={result} platformContext={platformContext} />
}
return (

View File

@ -1,15 +1,16 @@
import { storiesOf } from '@storybook/react'
import React from 'react'
import { SyntaxHighlightedSearchQuery } from './SyntaxHighlightedSearchQuery'
import { WebStory } from './WebStory'
import { BrandedStory } from '@sourcegraph/branded/src/components/BrandedStory'
const { add } = storiesOf('web/SyntaxHighlightedSearchQuery', module).addParameters({
import { SyntaxHighlightedSearchQuery } from './SyntaxHighlightedSearchQuery'
const { add } = storiesOf('search-ui/SyntaxHighlightedSearchQuery', module).addParameters({
chromatic: { viewports: [480] },
})
add('Examples', () => (
<WebStory>
<BrandedStory>
{() => (
<p>
<SyntaxHighlightedSearchQuery query="test AND spec" />
@ -19,5 +20,5 @@ add('Examples', () => (
<SyntaxHighlightedSearchQuery query="test -lang:ts" />
</p>
)}
</WebStory>
</BrandedStory>
))

View File

@ -0,0 +1,2 @@
export * from './SyntaxHighlightedSearchQuery'
export * from './SearchResult'

View File

@ -14,6 +14,7 @@ interface ModalVideoProps {
onToggle?: (isOpen: boolean) => void
showCaption?: boolean
className?: string
assetsRoot?: string
}
export const ModalVideo: React.FunctionComponent<ModalVideoProps> = ({
@ -24,8 +25,8 @@ export const ModalVideo: React.FunctionComponent<ModalVideoProps> = ({
onToggle,
showCaption = false,
className,
assetsRoot = '',
}) => {
const assetsRoot = window.context?.assetsRoot || ''
const [isOpen, setIsOpen] = useState(false)
const toggleDialog = useCallback(
isOpen => {

View File

@ -0,0 +1,9 @@
export * from './components'
export * from './input/toggles'
export * from './input/SearchBox'
export * from './documentation/ModalVideo'
export * from './results/StreamingSearchResultsList'
export * from './results/progress/StreamingProgress'
export * from './results/sidebar/revisions'
export * from './results/sidebar/FilterLink'
export * from './results/sidebar/SearchSidebar'

View File

@ -1,7 +1,7 @@
import classNames from 'classnames'
import React, { Suspense } from 'react'
import { lazyComponent } from '../../util/lazyComponent'
import { lazyComponent } from '@sourcegraph/shared/src/util/lazyComponent'
import styles from './LazyMonacoQueryInput.module.scss'
import { MonacoQueryInputProps } from './MonacoQueryInput'

View File

@ -1,12 +1,12 @@
import { storiesOf } from '@storybook/react'
import React from 'react'
import { WebStory } from '../../components/WebStory'
import { SearchPatternType } from '../../graphql-operations'
import { BrandedStory } from '@sourcegraph/branded/src/components/BrandedStory'
import { SearchPatternType } from '@sourcegraph/shared/src/schema'
import { MonacoQueryInput, MonacoQueryInputProps } from './MonacoQueryInput'
const { add } = storiesOf('web/search/input/MonacoQueryInput', module)
const { add } = storiesOf('search-ui/search/input/MonacoQueryInput', module)
.addParameters({ chromatic: { viewports: [700] } })
.addDecorator(story => (
<div className="p-3" style={{ height: 'calc(34px + 1rem + 1rem)', display: 'flex' }}>
@ -29,6 +29,8 @@ const defaultProps: MonacoQueryInputProps = {
add(
'default',
() => <WebStory>{props => <MonacoQueryInput {...defaultProps} isLightTheme={props.isLightTheme} />}</WebStory>,
() => (
<BrandedStory>{props => <MonacoQueryInput {...defaultProps} isLightTheme={props.isLightTheme} />}</BrandedStory>
),
{}
)

View File

@ -3,20 +3,25 @@ import { isPlainObject, noop } from 'lodash'
import * as Monaco from 'monaco-editor'
import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react'
import {
QueryChangeSource,
QueryState,
CaseSensitivityProps,
SearchPatternTypeProps,
SearchContextProps,
useQueryIntelligence,
useQueryDiagnostics,
} from '@sourcegraph/search'
import { MonacoEditor } from '@sourcegraph/shared/src/components/MonacoEditor'
import { KeyboardShortcut } from '@sourcegraph/shared/src/keyboardShortcuts'
import { KEYBOARD_SHORTCUT_FOCUS_SEARCHBAR } from '@sourcegraph/shared/src/keyboardShortcuts/keyboardShortcuts'
import { toMonacoRange } from '@sourcegraph/shared/src/search/query/monaco'
import { appendContextFilter } from '@sourcegraph/shared/src/search/query/transformer'
import { fetchStreamSuggestions } from '@sourcegraph/shared/src/search/suggestions'
import { ThemeProps } from '@sourcegraph/shared/src/theme'
import { observeResize } from '@sourcegraph/shared/src/util/dom'
import { hasProperty } from '@sourcegraph/shared/src/util/types'
import { CaseSensitivityProps, SearchPatternTypeProps, SearchContextProps } from '..'
import { MonacoEditor } from '../../components/MonacoEditor'
import { KEYBOARD_SHORTCUT_FOCUS_SEARCHBAR } from '../../keyboardShortcuts/keyboardShortcuts'
import { observeResize } from '../../util/dom'
import { QueryChangeSource, QueryState } from '../helpers'
import { useQueryIntelligence, useQueryDiagnostics } from '../useQueryIntelligence'
import styles from './MonacoQueryInput.module.scss'
export const DEFAULT_MONACO_OPTIONS: Monaco.editor.IStandaloneEditorConstructionOptions = {

View File

@ -1,19 +1,19 @@
import { storiesOf } from '@storybook/react'
import React from 'react'
import { BrandedStory } from '@sourcegraph/branded/src/components/BrandedStory'
import { SearchPatternType } from '@sourcegraph/shared/src/schema'
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
import {
mockFetchAutoDefinedSearchContexts,
mockFetchSearchContexts,
mockGetUserSearchContextNamespaces,
} from '@sourcegraph/shared/src/testing/searchContexts/testHelpers'
import { WebStory } from '../../components/WebStory'
import { SearchPatternType } from '../../graphql-operations'
import { NOOP_PLATFORM_CONTEXT } from '@sourcegraph/shared/src/testing/searchTestHelpers'
import { SearchBox, SearchBoxProps } from './SearchBox'
const { add } = storiesOf('web/search/input/SearchBox', module)
const { add } = storiesOf('search-ui/search/input/SearchBox', module)
.addParameters({ chromatic: { viewports: [575, 700] } })
.addDecorator(story => <div className="w-100 d-flex">{story()}</div>)
@ -45,22 +45,23 @@ const defaultProps: SearchBoxProps = {
authenticatedUser: null,
hasUserAddedExternalServices: false,
getUserSearchContextNamespaces: mockGetUserSearchContextNamespaces,
platformContext: NOOP_PLATFORM_CONTEXT,
}
add(
'default',
() => <WebStory>{props => <SearchBox {...defaultProps} isLightTheme={props.isLightTheme} />}</WebStory>,
() => <BrandedStory>{props => <SearchBox {...defaultProps} isLightTheme={props.isLightTheme} />}</BrandedStory>,
{}
)
add(
'regexp enabled',
() => (
<WebStory>
<BrandedStory>
{props => (
<SearchBox {...defaultProps} patternType={SearchPatternType.regexp} isLightTheme={props.isLightTheme} />
)}
</WebStory>
</BrandedStory>
),
{}
)
@ -68,7 +69,7 @@ add(
add(
'structural enabled',
() => (
<WebStory>
<BrandedStory>
{props => (
<SearchBox
{...defaultProps}
@ -76,7 +77,7 @@ add(
isLightTheme={props.isLightTheme}
/>
)}
</WebStory>
</BrandedStory>
),
{}
)
@ -84,9 +85,9 @@ add(
add(
'case sensitivity enabled',
() => (
<WebStory>
<BrandedStory>
{props => <SearchBox {...defaultProps} caseSensitive={true} isLightTheme={props.isLightTheme} />}
</WebStory>
</BrandedStory>
),
{}
)
@ -94,7 +95,7 @@ add(
add(
'with search contexts',
() => (
<WebStory>
<BrandedStory>
{props => (
<SearchBox
{...defaultProps}
@ -103,7 +104,7 @@ add(
selectedSearchContextSpec="global"
/>
)}
</WebStory>
</BrandedStory>
),
{}
)
@ -111,7 +112,7 @@ add(
add(
'with search contexts, user context selected',
() => (
<WebStory>
<BrandedStory>
{props => (
<SearchBox
{...defaultProps}
@ -120,7 +121,7 @@ add(
selectedSearchContextSpec="@username/test-version-1.5"
/>
)}
</WebStory>
</BrandedStory>
),
{}
)
@ -128,7 +129,7 @@ add(
add(
'with search contexts, disabled based on query',
() => (
<WebStory>
<BrandedStory>
{props => (
<SearchBox
{...defaultProps}
@ -138,7 +139,7 @@ add(
selectedSearchContextSpec="@username"
/>
)}
</WebStory>
</BrandedStory>
),
{}
)

View File

@ -2,25 +2,25 @@ import classNames from 'classnames'
import * as Monaco from 'monaco-editor'
import React, { useCallback, useState } from 'react'
import { SearchContextInputProps, QueryState, SubmitSearchProps } from '@sourcegraph/search'
import { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
import { KeyboardShortcut } from '@sourcegraph/shared/src/keyboardShortcuts'
import { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { ThemeProps } from '@sourcegraph/shared/src/theme'
import { SearchContextInputProps } from '..'
import { AuthenticatedUser } from '../../auth'
import { QueryState, SubmitSearchProps } from '../helpers'
import { LazyMonacoQueryInput } from './LazyMonacoQueryInput'
import styles from './SearchBox.module.scss'
import { SearchButton } from './SearchButton'
import { SearchContextDropdown } from './SearchContextDropdown'
import { Toggles, TogglesProps } from './toggles/Toggles'
import { Toggles, TogglesProps } from './toggles'
export interface SearchBoxProps
extends Omit<TogglesProps, 'navbarSearchQuery' | 'submitSearch'>,
ThemeProps,
SearchContextInputProps,
TelemetryProps {
TelemetryProps,
PlatformContextProps<'requestGraphQL'> {
authenticatedUser: AuthenticatedUser | null
isSourcegraphDotCom: boolean // significant for query suggestions
showSearchContext: boolean
@ -46,6 +46,9 @@ export interface SearchBoxProps
hideHelpButton?: boolean
onHandleFuzzyFinder?: React.Dispatch<React.SetStateAction<boolean>>
/** Set in JSContext only available to the web app. */
isExternalServicesUserModeAll?: boolean
}
export const SearchBox: React.FunctionComponent<SearchBoxProps> = props => {
@ -84,7 +87,12 @@ export const SearchBox: React.FunctionComponent<SearchBoxProps> = props => {
/>
</div>
</div>
<SearchButton hideHelpButton={props.hideHelpButton} className={styles.searchBoxButton} />
<SearchButton
hideHelpButton={props.hideHelpButton}
className={styles.searchBoxButton}
telemetryService={props.telemetryService}
isSourcegraphDotCom={props.isSourcegraphDotCom}
/>
</div>
)
}

View File

@ -2,22 +2,29 @@ import classNames from 'classnames'
import SearchIcon from 'mdi-react/SearchIcon'
import React from 'react'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { Button } from '@sourcegraph/wildcard'
import styles from './SearchButton.module.scss'
import { SearchHelpDropdownButton } from './SearchHelpDropdownButton'
interface Props {
interface Props extends TelemetryProps {
/** Hide the "help" icon and dropdown. */
hideHelpButton?: boolean
className?: string
isSourcegraphDotCom?: boolean
}
/**
* A search button with a dropdown with related links. It must be wrapped in a form whose onSubmit
* handler performs the search.
*/
export const SearchButton: React.FunctionComponent<Props> = ({ hideHelpButton, className }) => (
export const SearchButton: React.FunctionComponent<Props> = ({
hideHelpButton,
className,
isSourcegraphDotCom,
telemetryService,
}) => (
<div className={className}>
<Button
data-search-button={true}
@ -28,6 +35,8 @@ export const SearchButton: React.FunctionComponent<Props> = ({ hideHelpButton, c
>
<SearchIcon className="icon-inline" aria-hidden="true" />
</Button>
{!hideHelpButton && <SearchHelpDropdownButton />}
{!hideHelpButton && (
<SearchHelpDropdownButton isSourcegraphDotCom={isSourcegraphDotCom} telemetryService={telemetryService} />
)}
</div>
)

View File

@ -1,14 +1,13 @@
import { storiesOf } from '@storybook/react'
import React from 'react'
import { BrandedStory } from '@sourcegraph/branded/src/components/BrandedStory'
import { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { AuthenticatedUser } from '../../auth'
import { WebStory } from '../../components/WebStory'
import { SearchContextCtaPrompt } from './SearchContextCtaPrompt'
const { add } = storiesOf('web/searchContexts/SearchContextCtaPrompt', module)
const { add } = storiesOf('search-ui/searchContexts/SearchContextCtaPrompt', module)
.addParameters({
chromatic: { viewports: [500] },
})
@ -41,7 +40,7 @@ const authUser: AuthenticatedUser = {
add(
'not authenticated',
() => (
<WebStory>
<BrandedStory>
{() => (
<SearchContextCtaPrompt
telemetryService={NOOP_TELEMETRY_SERVICE}
@ -50,7 +49,7 @@ add(
onDismiss={() => {}}
/>
)}
</WebStory>
</BrandedStory>
),
{}
)
@ -58,7 +57,7 @@ add(
add(
'authenticated without added external services',
() => (
<WebStory>
<BrandedStory>
{() => (
<SearchContextCtaPrompt
telemetryService={NOOP_TELEMETRY_SERVICE}
@ -67,7 +66,7 @@ add(
onDismiss={() => {}}
/>
)}
</WebStory>
</BrandedStory>
),
{}
)
@ -75,7 +74,7 @@ add(
add(
'authenticated with added external services',
() => (
<WebStory>
<BrandedStory>
{() => (
<SearchContextCtaPrompt
telemetryService={NOOP_TELEMETRY_SERVICE}
@ -84,7 +83,7 @@ add(
onDismiss={() => {}}
/>
)}
</WebStory>
</BrandedStory>
),
{}
)
@ -92,7 +91,7 @@ add(
add(
'authenticated with private code',
() => (
<WebStory>
<BrandedStory>
{() => (
<SearchContextCtaPrompt
telemetryService={NOOP_TELEMETRY_SERVICE}
@ -101,7 +100,7 @@ add(
onDismiss={() => {}}
/>
)}
</WebStory>
</BrandedStory>
),
{}
)

View File

@ -1,28 +1,29 @@
import classNames from 'classnames'
import React from 'react'
import { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { Button, ProductStatusBadge, Link } from '@sourcegraph/wildcard'
import { AuthenticatedUser } from '../../auth'
import styles from './SearchContextCtaPrompt.module.scss'
export interface SearchContextCtaPromptProps extends TelemetryProps {
authenticatedUser: AuthenticatedUser | null
hasUserAddedExternalServices: boolean
onDismiss: () => void
/** Set in JSContext so only available to the web app. */
isExternalServicesUserModeAll?: boolean
}
export const SearchContextCtaPrompt: React.FunctionComponent<SearchContextCtaPromptProps> = ({
authenticatedUser,
hasUserAddedExternalServices,
telemetryService,
isExternalServicesUserModeAll,
onDismiss,
}) => {
const repositoriesVisibility =
window.context.externalServicesUserMode === 'all' ||
authenticatedUser?.tags.includes('AllowUserExternalServicePrivate')
isExternalServicesUserModeAll || authenticatedUser?.tags.includes('AllowUserExternalServicePrivate')
? 'repositories'
: 'public repositories'

View File

@ -4,6 +4,8 @@ import React from 'react'
import { act } from 'react-dom/test-utils'
import sinon from 'sinon'
import { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
import { MockTemporarySettings } from '@sourcegraph/shared/src/settings/temporary/testUtils'
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { MockIntersectionObserver } from '@sourcegraph/shared/src/testing/MockIntersectionObserver'
import { renderWithRouter } from '@sourcegraph/shared/src/testing/render-with-router'
@ -12,10 +14,7 @@ import {
mockFetchSearchContexts,
mockGetUserSearchContextNamespaces,
} from '@sourcegraph/shared/src/testing/searchContexts/testHelpers'
import { AuthenticatedUser } from '../../auth'
import { SourcegraphContext } from '../../jscontext'
import { MockTemporarySettings } from '../../settings/temporary/testUtils'
import { NOOP_PLATFORM_CONTEXT } from '@sourcegraph/shared/src/testing/searchTestHelpers'
import { SearchContextDropdown, SearchContextDropdownProps } from './SearchContextDropdown'
@ -35,6 +34,7 @@ describe('SearchContextDropdown', () => {
isSourcegraphDotCom: false,
authenticatedUser: null,
searchContextsEnabled: true,
platformContext: NOOP_PLATFORM_CONTEXT,
}
const RealIntersectionObserver = window.IntersectionObserver
let clock: sinon.SinonFakeTimers
@ -110,24 +110,12 @@ describe('SearchContextDropdown', () => {
})
describe('with CTA', () => {
let oldContext: SourcegraphContext & Mocha.SuiteFunction
beforeEach(() => {
oldContext = window.context
window.context = { externalServicesUserMode: 'all' } as SourcegraphContext & Mocha.SuiteFunction
})
afterEach(() => {
window.context = oldContext
})
const props = { ...defaultProps, isExternalServicesUserModeAll: true }
it('should not display CTA if not on Sourcegraph.com', () => {
render(
<MockTemporarySettings settings={{ 'search.contexts.ctaDismissed': false }}>
<SearchContextDropdown
{...defaultProps}
isSourcegraphDotCom={false}
hasUserAddedRepositories={false}
/>
<SearchContextDropdown {...props} isSourcegraphDotCom={false} hasUserAddedRepositories={false} />
</MockTemporarySettings>
)
@ -139,11 +127,7 @@ describe('SearchContextDropdown', () => {
it('should display CTA on Sourcegraph.com if no repos have been added and not permanently dismissed', () => {
renderWithRouter(
<MockTemporarySettings settings={{ 'search.contexts.ctaDismissed': false }}>
<SearchContextDropdown
{...defaultProps}
isSourcegraphDotCom={true}
hasUserAddedRepositories={false}
/>
<SearchContextDropdown {...props} isSourcegraphDotCom={true} hasUserAddedRepositories={false} />
</MockTemporarySettings>
)
@ -162,7 +146,7 @@ describe('SearchContextDropdown', () => {
render(
<MockTemporarySettings settings={{ 'search.contexts.ctaDismissed': false }}>
<SearchContextDropdown
{...defaultProps}
{...props}
isSourcegraphDotCom={true}
hasUserAddedRepositories={false}
authenticatedUser={mockUserWithOrg}
@ -178,11 +162,7 @@ describe('SearchContextDropdown', () => {
it('should not display CTA on Sourcegraph.com if repos have been added', () => {
render(
<MockTemporarySettings settings={{ 'search.contexts.ctaDismissed': false }}>
<SearchContextDropdown
{...defaultProps}
isSourcegraphDotCom={true}
hasUserAddedRepositories={true}
/>
<SearchContextDropdown {...props} isSourcegraphDotCom={true} hasUserAddedRepositories={true} />
</MockTemporarySettings>
)
@ -194,11 +174,7 @@ describe('SearchContextDropdown', () => {
it('should not display CTA on Sourcegraph.com if dimissed', () => {
renderWithRouter(
<MockTemporarySettings settings={{ 'search.contexts.ctaDismissed': true }}>
<SearchContextDropdown
{...defaultProps}
isSourcegraphDotCom={true}
hasUserAddedRepositories={false}
/>
<SearchContextDropdown {...props} isSourcegraphDotCom={true} hasUserAddedRepositories={false} />
</MockTemporarySettings>
)
@ -215,11 +191,7 @@ describe('SearchContextDropdown', () => {
settings={{ 'search.contexts.ctaDismissed': false }}
onSettingsChanged={onSettingsChanged}
>
<SearchContextDropdown
{...defaultProps}
isSourcegraphDotCom={true}
hasUserAddedRepositories={false}
/>
<SearchContextDropdown {...props} isSourcegraphDotCom={true} hasUserAddedRepositories={false} />
</MockTemporarySettings>
)

View File

@ -2,15 +2,14 @@ import classNames from 'classnames'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { Dropdown, DropdownMenu, DropdownToggle } from 'reactstrap'
import { SearchContextInputProps, SubmitSearchProps } from '@sourcegraph/search'
import { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
import { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
import { FilterType } from '@sourcegraph/shared/src/search/query/filters'
import { filterExists } from '@sourcegraph/shared/src/search/query/validate'
import { useTemporarySetting } from '@sourcegraph/shared/src/settings/temporary/useTemporarySetting'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { SearchContextInputProps } from '..'
import { AuthenticatedUser } from '../../auth'
import { useTemporarySetting } from '../../settings/temporary/useTemporarySetting'
import { SubmitSearchProps } from '../helpers'
import { SearchContextCtaPrompt } from './SearchContextCtaPrompt'
import styles from './SearchContextDropdown.module.scss'
import { SearchContextMenu } from './SearchContextMenu'
@ -18,13 +17,15 @@ import { SearchContextMenu } from './SearchContextMenu'
export interface SearchContextDropdownProps
extends SearchContextInputProps,
TelemetryProps,
Partial<Pick<SubmitSearchProps, 'submitSearch'>> {
Partial<Pick<SubmitSearchProps, 'submitSearch'>>,
PlatformContextProps<'requestGraphQL'> {
isSourcegraphDotCom: boolean
showSearchContextManagement: boolean
authenticatedUser: AuthenticatedUser | null
query: string
className?: string
onEscapeMenuClose?: () => void
isExternalServicesUserModeAll?: boolean
}
export const SearchContextDropdown: React.FunctionComponent<SearchContextDropdownProps> = props => {
@ -43,6 +44,7 @@ export const SearchContextDropdown: React.FunctionComponent<SearchContextDropdow
telemetryService,
onEscapeMenuClose,
showSearchContextManagement,
isExternalServicesUserModeAll,
} = props
const [contextCtaDismissed, setContextCtaDismissed] = useTemporarySetting('search.contexts.ctaDismissed', false)
@ -147,6 +149,7 @@ export const SearchContextDropdown: React.FunctionComponent<SearchContextDropdow
authenticatedUser={authenticatedUser}
hasUserAddedExternalServices={hasUserAddedExternalServices}
onDismiss={onCtaDismissed}
isExternalServicesUserModeAll={isExternalServicesUserModeAll}
/>
)}
</DropdownMenu>

View File

@ -2,18 +2,19 @@ import { storiesOf } from '@storybook/react'
import React from 'react'
import { Observable, of } from 'rxjs'
import { BrandedStory } from '@sourcegraph/branded/src/components/BrandedStory'
import { ListSearchContextsResult } from '@sourcegraph/search'
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
import {
mockFetchAutoDefinedSearchContexts,
mockFetchSearchContexts,
mockGetUserSearchContextNamespaces,
} from '@sourcegraph/shared/src/testing/searchContexts/testHelpers'
import { WebStory } from '../../components/WebStory'
import { ListSearchContextsResult } from '../../graphql-operations'
import { NOOP_PLATFORM_CONTEXT } from '@sourcegraph/shared/src/testing/searchTestHelpers'
import { SearchContextMenu, SearchContextMenuProps } from './SearchContextMenu'
const { add } = storiesOf('web/search/input/SearchContextMenu', module)
const { add } = storiesOf('search-ui/search/input/SearchContextMenu', module)
.addParameters({
chromatic: { viewports: [500] },
design: {
@ -73,6 +74,8 @@ const defaultProps: SearchContextMenuProps = {
closeMenu: () => {},
getUserSearchContextNamespaces: mockGetUserSearchContextNamespaces,
searchContextsEnabled: true,
platformContext: NOOP_PLATFORM_CONTEXT,
telemetryService: NOOP_TELEMETRY_SERVICE,
}
const emptySearchContexts = {
@ -80,12 +83,18 @@ const emptySearchContexts = {
fetchSearchContexts: mockFetchSearchContexts,
}
add('default', () => <WebStory>{() => <SearchContextMenu {...defaultProps} />}</WebStory>, {})
add('default', () => <BrandedStory>{() => <SearchContextMenu {...defaultProps} />}</BrandedStory>, {})
add('empty', () => <WebStory>{() => <SearchContextMenu {...defaultProps} {...emptySearchContexts} />}</WebStory>, {})
add(
'empty',
() => <BrandedStory>{() => <SearchContextMenu {...defaultProps} {...emptySearchContexts} />}</BrandedStory>,
{}
)
add(
'with manage link',
() => <WebStory>{() => <SearchContextMenu {...defaultProps} showSearchContextManagement={true} />}</WebStory>,
() => (
<BrandedStory>{() => <SearchContextMenu {...defaultProps} showSearchContextManagement={true} />}</BrandedStory>
),
{}
)

View File

@ -6,11 +6,12 @@ import { DropdownMenu, UncontrolledDropdown } from 'reactstrap'
import { Observable, of, throwError } from 'rxjs'
import sinon from 'sinon'
import { ListSearchContextsResult, SearchContextFields } from '@sourcegraph/search'
import { ISearchContext } from '@sourcegraph/shared/src/schema'
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { MockIntersectionObserver } from '@sourcegraph/shared/src/testing/MockIntersectionObserver'
import { mockGetUserSearchContextNamespaces } from '@sourcegraph/shared/src/testing/searchContexts/testHelpers'
import { ListSearchContextsResult, SearchContextFields } from '../../graphql-operations'
import { NOOP_PLATFORM_CONTEXT } from '@sourcegraph/shared/src/testing/searchTestHelpers'
import { SearchContextMenu, SearchContextMenuProps } from './SearchContextMenu'
@ -112,6 +113,8 @@ describe('SearchContextMenu', () => {
closeMenu: () => {},
getUserSearchContextNamespaces: mockGetUserSearchContextNamespaces,
searchContextsEnabled: true,
platformContext: NOOP_PLATFORM_CONTEXT,
telemetryService: NOOP_TELEMETRY_SERVICE,
}
const RealIntersectionObserver = window.IntersectionObserver

View File

@ -14,31 +14,42 @@ import { BehaviorSubject, combineLatest, of, timer } from 'rxjs'
import { catchError, debounce, switchMap, tap } from 'rxjs/operators'
import { asError, isErrorLike } from '@sourcegraph/common'
import { SearchContextInputProps, SearchContextFields } from '@sourcegraph/search'
import { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
import { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
import { ISearchContext } from '@sourcegraph/shared/src/schema'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { Badge, Button, useObservable, Link } from '@sourcegraph/wildcard'
import { SearchContextInputProps } from '..'
import { AuthenticatedUser } from '../../auth'
import { SearchContextFields } from '../../graphql-operations'
import { eventLogger } from '../../tracking/eventLogger'
import { HighlightedSearchContextSpec } from './HighlightedSearchContextSpec'
import styles from './SearchContextMenu.module.scss'
export const SearchContextMenuItem: React.FunctionComponent<{
spec: string
description: string
query: string
selected: boolean
isDefault: boolean
selectSearchContextSpec: (spec: string) => void
searchFilter: string
onKeyDown: (key: string) => void
}> = ({ spec, description, query, selected, isDefault, selectSearchContextSpec, searchFilter, onKeyDown }) => {
export const SearchContextMenuItem: React.FunctionComponent<
{
spec: string
description: string
query: string
selected: boolean
isDefault: boolean
selectSearchContextSpec: (spec: string) => void
searchFilter: string
onKeyDown: (key: string) => void
} & TelemetryProps
> = ({
spec,
description,
query,
selected,
isDefault,
selectSearchContextSpec,
searchFilter,
onKeyDown,
telemetryService,
}) => {
const setContext = useCallback(() => {
eventLogger.log('SearchContextSelected')
telemetryService.log('SearchContextSelected')
selectSearchContextSpec(spec)
}, [selectSearchContextSpec, spec])
}, [selectSearchContextSpec, spec, telemetryService])
const descriptionOrQuery = description.length > 0 ? description : query
@ -72,9 +83,11 @@ export const SearchContextMenuItem: React.FunctionComponent<{
export interface SearchContextMenuProps
extends Omit<
SearchContextInputProps,
'setSelectedSearchContextSpec' | 'hasUserAddedRepositories' | 'hasUserAddedExternalServices'
> {
SearchContextInputProps,
'setSelectedSearchContextSpec' | 'hasUserAddedRepositories' | 'hasUserAddedExternalServices'
>,
PlatformContextProps<'requestGraphQL'>,
TelemetryProps {
showSearchContextManagement: boolean
authenticatedUser: AuthenticatedUser | null
closeMenu: (isEscapeKey?: boolean) => void
@ -108,6 +121,8 @@ export const SearchContextMenu: React.FunctionComponent<SearchContextMenuProps>
fetchSearchContexts,
closeMenu,
showSearchContextManagement,
platformContext,
telemetryService,
}) => {
const inputElement = useRef<HTMLInputElement | null>(null)
@ -182,6 +197,7 @@ export const SearchContextMenu: React.FunctionComponent<SearchContextMenuProps>
query,
after: cursor,
namespaces: getUserSearchContextNamespaces(authenticatedUser),
platformContext,
}),
])
),
@ -211,11 +227,13 @@ export const SearchContextMenu: React.FunctionComponent<SearchContextMenuProps>
setLastPageInfo,
getUserSearchContextNamespaces,
fetchSearchContexts,
platformContext,
])
const autoDefinedSearchContexts = useObservable(
useMemo(() => fetchAutoDefinedSearchContexts().pipe(catchError(error => [asError(error)])), [
useMemo(() => fetchAutoDefinedSearchContexts(platformContext).pipe(catchError(error => [asError(error)])), [
fetchAutoDefinedSearchContexts,
platformContext,
])
)
const filteredAutoDefinedSearchContexts = useMemo(
@ -298,6 +316,7 @@ export const SearchContextMenu: React.FunctionComponent<SearchContextMenuProps>
selectSearchContextSpec={selectSearchContextSpec}
searchFilter={searchFilter}
onKeyDown={key => index === 0 && key === 'ArrowUp' && focusInputElement()}
telemetryService={telemetryService}
/>
))}
{(loadingState === 'LOADING' || loadingState === 'LOADING_NEXT_PAGE') && (

View File

@ -2,11 +2,12 @@ import { storiesOf } from '@storybook/react'
import { noop } from 'lodash'
import React from 'react'
import { WebStory } from '../../components/WebStory'
import { BrandedStory } from '@sourcegraph/branded/src/components/BrandedStory'
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { SearchContextMenuItem } from './SearchContextMenu'
const { add } = storiesOf('web/searchContexts/SearchContextMenuItem', module)
const { add } = storiesOf('search-ui/searchContexts/SearchContextMenuItem', module)
.addParameters({
chromatic: { viewports: [1200] },
})
@ -19,7 +20,7 @@ const { add } = storiesOf('web/searchContexts/SearchContextMenuItem', module)
add(
'selected default item',
() => (
<WebStory>
<BrandedStory>
{() => (
<SearchContextMenuItem
spec="@user/test"
@ -30,9 +31,10 @@ add(
isDefault={true}
selectSearchContextSpec={noop}
onKeyDown={noop}
telemetryService={NOOP_TELEMETRY_SERVICE}
/>
)}
</WebStory>
</BrandedStory>
),
{}
)
@ -40,7 +42,7 @@ add(
add(
'highlighted item',
() => (
<WebStory>
<BrandedStory>
{() => (
<SearchContextMenuItem
spec="@user/test"
@ -51,9 +53,10 @@ add(
isDefault={false}
selectSearchContextSpec={noop}
onKeyDown={noop}
telemetryService={NOOP_TELEMETRY_SERVICE}
/>
)}
</WebStory>
</BrandedStory>
),
{}
)

View File

@ -3,20 +3,27 @@ import HelpCircleOutlineIcon from 'mdi-react/HelpCircleOutlineIcon'
import React, { useCallback, useState } from 'react'
import { DropdownItem, DropdownMenu, DropdownToggle, ButtonDropdown } from 'reactstrap'
import { eventLogger } from '../../tracking/eventLogger'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
interface SearchHelpDropdownButtonProps extends TelemetryProps {
isSourcegraphDotCom?: boolean
}
/**
* A dropdown button that shows a menu with reference documentation for Sourcegraph search query
* syntax.
*/
export const SearchHelpDropdownButton: React.FunctionComponent = () => {
export const SearchHelpDropdownButton: React.FunctionComponent<SearchHelpDropdownButtonProps> = ({
isSourcegraphDotCom,
telemetryService,
}) => {
const [isOpen, setIsOpen] = useState(false)
const toggleIsOpen = useCallback(() => setIsOpen(!isOpen), [isOpen])
const onQueryDocumentationLinkClicked = useCallback(() => {
eventLogger.log('SearchHelpDropdownQueryDocsLinkClicked')
telemetryService.log('SearchHelpDropdownQueryDocsLinkClicked')
toggleIsOpen()
}, [toggleIsOpen])
const documentationUrlPrefix = window.context?.sourcegraphDotComMode ? 'https://docs.sourcegraph.com' : '/help'
}, [toggleIsOpen, telemetryService])
const documentationUrlPrefix = isSourcegraphDotCom ? 'https://docs.sourcegraph.com' : '/help'
return (
<ButtonDropdown isOpen={isOpen} toggle={toggleIsOpen} className="search-help-dropdown-button d-flex">
@ -65,7 +72,7 @@ export const SearchHelpDropdownButton: React.FunctionComponent = () => {
repo:<strong>my/repo</strong>
</code>
</li>
{window.context?.sourcegraphDotComMode && (
{isSourcegraphDotCom && (
<li>
<code>
repo:<strong>github.com/myorg/</strong>
@ -116,7 +123,7 @@ export const SearchHelpDropdownButton: React.FunctionComponent = () => {
>
<ExternalLinkIcon className="icon-inline small" /> All search keywords
</a>
{window.context?.sourcegraphDotComMode && (
{isSourcegraphDotCom && (
<div className="alert alert-info small rounded-0 mb-0 mt-1">
On Sourcegraph.com, use a <code>repo:</code> filter to narrow your search to &le;500
repositories.

View File

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import { SearchPatternType } from '../../../graphql-operations'
import { SearchPatternType } from '@sourcegraph/shared/src/schema'
import { getFullQuery, Toggles } from './Toggles'

View File

@ -5,15 +5,19 @@ import RegexIcon from 'mdi-react/RegexIcon'
import React, { useCallback } from 'react'
import { isErrorLike } from '@sourcegraph/common'
import {
SearchPatternTypeProps,
CaseSensitivityProps,
SearchContextProps,
SearchPatternTypeMutationProps,
SubmitSearchProps,
} from '@sourcegraph/search'
import { KEYBOARD_SHORTCUT_COPY_FULL_QUERY } from '@sourcegraph/shared/src/keyboardShortcuts/keyboardShortcuts'
import { SearchPatternType } from '@sourcegraph/shared/src/schema'
import { findFilter, FilterKind } from '@sourcegraph/shared/src/search/query/query'
import { appendContextFilter } from '@sourcegraph/shared/src/search/query/transformer'
import { SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
import { SearchPatternTypeProps, CaseSensitivityProps, SearchContextProps, SearchPatternTypeMutationProps } from '../..'
import { SearchPatternType } from '../../../graphql-operations'
import { KEYBOARD_SHORTCUT_COPY_FULL_QUERY } from '../../../keyboardShortcuts/keyboardShortcuts'
import { isMacPlatform } from '../../../util'
import { SubmitSearchProps } from '../../helpers'
import { isMacPlatform } from '@sourcegraph/shared/src/util/browserDetection'
import { CopyQueryButton } from './CopyQueryButton'
import { QueryInputToggle } from './QueryInputToggle'
@ -35,6 +39,8 @@ export interface TogglesProps
* being disabled, because the buttons still render normally.
*/
interactive?: boolean
/** Comes from JSContext only set in the web app. */
structuralSearchDisabled?: boolean
}
export const getFullQuery = (
@ -64,10 +70,9 @@ export const Toggles: React.FunctionComponent<TogglesProps> = (props: TogglesPro
selectedSearchContextSpec,
submitSearch,
showCopyQueryButton = true,
structuralSearchDisabled,
} = props
const structuralSearchDisabled = window.context?.experimentalFeatures?.structuralSearch === 'disabled'
const submitOnToggle = useCallback(
(args: { newPatternType: SearchPatternType } | { newCaseSensitivity: boolean }): void => {
submitSearch?.({

View File

@ -0,0 +1,3 @@
export * from './CopyQueryButton'
export * from './QueryInputToggle'
export * from './Toggles'

View File

@ -3,31 +3,23 @@ import CloseIcon from 'mdi-react/CloseIcon'
import ExternalLinkIcon from 'mdi-react/ExternalLinkIcon'
import React, { useCallback, useEffect } from 'react'
import { SearchContextProps } from '@sourcegraph/search'
import { SyntaxHighlightedSearchQuery, Toggles } from '@sourcegraph/search-ui'
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
import { NoResultsSectionID as SectionID } from '@sourcegraph/shared/src/settings/temporary/searchSidebar'
import { useTemporarySetting } from '@sourcegraph/shared/src/settings/temporary/useTemporarySetting'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { ThemeProps } from '@sourcegraph/shared/src/theme'
import { buildSearchURLQuery } from '@sourcegraph/shared/src/util/url'
import { Button, Link } from '@sourcegraph/wildcard'
import { SearchContextProps } from '..'
import { SyntaxHighlightedSearchQuery } from '../../components/SyntaxHighlightedSearchQuery'
import { useTemporarySetting } from '../../settings/temporary/useTemporarySetting'
import { useExperimentalFeatures } from '../../stores'
import { ModalVideo } from '../documentation/ModalVideo'
import searchBoxStyle from '../input/SearchBox.module.scss'
import searchContextDropDownStyles from '../input/SearchContextDropdown.module.scss'
import { Toggles } from '../input/toggles/Toggles'
import { AnnotatedSearchInput } from './AnnotatedSearchExample'
import styles from './NoResultsPage.module.scss'
export enum SectionID {
SEARCH_BAR = 'search-bar',
LITERAL_SEARCH = 'literal-search',
COMMON_PROBLEMS = 'common-problems',
VIDEOS = 'videos',
}
const noop = (): void => {}
interface SearchInputExampleProps {
@ -173,6 +165,9 @@ const videos = [
interface NoResultsPageProps extends ThemeProps, TelemetryProps, Pick<SearchContextProps, 'searchContextsEnabled'> {
isSourcegraphDotCom: boolean
showSearchContext: boolean
/** Available to web app through JS Context */
assetsRoot?: string
}
export const NoResultsPage: React.FunctionComponent<NoResultsPageProps> = ({
@ -180,9 +175,10 @@ export const NoResultsPage: React.FunctionComponent<NoResultsPageProps> = ({
isLightTheme,
telemetryService,
isSourcegraphDotCom,
showSearchContext,
assetsRoot,
}) => {
const [hiddenSectionIDs, setHiddenSectionIds] = useTemporarySetting('search.hiddenNoResultsSections')
const showSearchContext = useExperimentalFeatures(features => features.showSearchContext ?? false)
const onClose = useCallback(
sectionID => {
@ -227,6 +223,7 @@ export const NoResultsPage: React.FunctionComponent<NoResultsPageProps> = ({
telemetryService.log('NoResultsVideoPlayed', { video: video.title })
}
}}
assetsRoot={assetsRoot}
/>
))}
</Container>

View File

@ -1,19 +1,18 @@
import classNames from 'classnames'
import React from 'react'
import { ErrorAlert } from '@sourcegraph/branded/src/components/alerts'
import { AggregateStreamingSearchResults } from '@sourcegraph/shared/src/search/stream'
import { LoadingSpinner } from '@sourcegraph/wildcard'
import { ErrorAlert } from '../../components/alerts'
import { StreamingProgressCount } from './progress/StreamingProgressCount'
import styles from './StreamingSearchResults.module.scss'
import styles from './StreamingSearchResultsList.module.scss'
export const StreamingSearchResultFooter: React.FunctionComponent<{
results?: AggregateStreamingSearchResults
children?: React.ReactChild | React.ReactChild[]
}> = ({ results, children }) => (
<div className={classNames(styles.streamingSearchResultsContentCentered, 'd-flex flex-column align-items-center')}>
<div className={classNames(styles.contentCentered, 'd-flex flex-column align-items-center')}>
{(!results || results?.state === 'loading') && (
<div className="text-center my-4" data-testid="loading-container">
<LoadingSpinner />

View File

@ -0,0 +1,4 @@
.content-centered {
max-width: 65rem;
margin: auto;
}

View File

@ -8,10 +8,14 @@ import SourceRepositoryIcon from 'mdi-react/SourceRepositoryIcon'
import React, { useCallback } from 'react'
import { Observable } from 'rxjs'
import { SearchContextProps } from '@sourcegraph/search'
import { SearchResult } from '@sourcegraph/search-ui'
import { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
import { FetchFileParameters } from '@sourcegraph/shared/src/components/CodeExcerpt'
import { FileMatch } from '@sourcegraph/shared/src/components/FileMatch'
import { displayRepoName } from '@sourcegraph/shared/src/components/RepoFileLink'
import { VirtualList } from '@sourcegraph/shared/src/components/VirtualList'
import { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
import {
AggregateStreamingSearchResults,
ContentMatch,
@ -24,27 +28,28 @@ import { SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { ThemeProps } from '@sourcegraph/shared/src/theme'
import { SearchContextProps } from '..'
import { AuthenticatedUser } from '../../auth'
import { SearchResult } from '../../components/SearchResult'
import { SearchUserNeedsCodeHost } from '../../user/settings/codeHosts/OrgUserNeedsCodeHost'
import { NoResultsPage } from './NoResultsPage'
import styles from './StreamingSearchResults.module.scss'
import { StreamingSearchResultFooter } from './StreamingSearchResultsFooter'
import styles from './StreamingSearchResultsList.module.scss'
import { useItemsToShow } from './use-items-to-show'
export interface StreamingSearchResultsListProps
extends ThemeProps,
SettingsCascadeProps,
TelemetryProps,
Pick<SearchContextProps, 'searchContextsEnabled' | 'selectedSearchContextSpec'> {
Pick<SearchContextProps, 'searchContextsEnabled'>,
PlatformContextProps<'requestGraphQL'> {
isSourcegraphDotCom: boolean
results?: AggregateStreamingSearchResults
location: H.Location
allExpanded: boolean
fetchHighlightedFileLineRanges: (parameters: FetchFileParameters, force?: boolean) => Observable<string[][]>
authenticatedUser: AuthenticatedUser | null
showSearchContext: boolean
/** Available to web app through JS Context */
assetsRoot?: string
/** Render prop for `<SearchUserNeedsCodeHost>` */
renderSearchUserNeedsCodeHost?: (user: AuthenticatedUser) => JSX.Element
}
export const StreamingSearchResultsList: React.FunctionComponent<StreamingSearchResultsListProps> = ({
@ -57,8 +62,11 @@ export const StreamingSearchResultsList: React.FunctionComponent<StreamingSearch
isLightTheme,
isSourcegraphDotCom,
searchContextsEnabled,
selectedSearchContextSpec,
authenticatedUser,
showSearchContext,
assetsRoot,
renderSearchUserNeedsCodeHost,
platformContext,
}) => {
const resultsNumber = results?.results.length || 0
const { itemsToShow, handleBottomHit } = useItemsToShow(location.search, resultsNumber)
@ -92,7 +100,7 @@ export const StreamingSearchResultsList: React.FunctionComponent<StreamingSearch
icon={SourceCommitIcon}
result={result}
repoName={result.repository}
telemetryService={telemetryService}
platformContext={platformContext}
/>
)
case 'repo':
@ -101,7 +109,7 @@ export const StreamingSearchResultsList: React.FunctionComponent<StreamingSearch
icon={SourceRepositoryIcon}
result={result}
repoName={result.repository}
telemetryService={telemetryService}
platformContext={platformContext}
/>
)
}
@ -113,28 +121,21 @@ export const StreamingSearchResultsList: React.FunctionComponent<StreamingSearch
allExpanded,
fetchHighlightedFileLineRanges,
settingsCascade,
platformContext,
]
)
return (
<>
<div
className={classNames(
styles.streamingSearchResultsContentCentered,
'd-flex flex-column align-items-center'
)}
>
<div className={classNames(styles.contentCentered, 'd-flex flex-column align-items-center')}>
<div className="align-self-stretch">
{isSourcegraphDotCom &&
{renderSearchUserNeedsCodeHost &&
isSourcegraphDotCom &&
searchContextsEnabled &&
authenticatedUser &&
results?.state === 'complete' &&
results?.results.length === 0 && (
<SearchUserNeedsCodeHost
user={authenticatedUser}
orgSearchContext={selectedSearchContextSpec}
/>
)}
results?.results.length === 0 &&
renderSearchUserNeedsCodeHost(authenticatedUser)}
</div>
</div>
<VirtualList<SearchMatch>
@ -156,6 +157,8 @@ export const StreamingSearchResultsList: React.FunctionComponent<StreamingSearch
isSourcegraphDotCom={isSourcegraphDotCom}
isLightTheme={isLightTheme}
telemetryService={telemetryService}
showSearchContext={showSearchContext}
assetsRoot={assetsRoot}
/>
)}
</>

View File

@ -2,13 +2,12 @@ import { storiesOf } from '@storybook/react'
import * as React from 'react'
import sinon from 'sinon'
import { BrandedStory } from '@sourcegraph/branded/src/components/BrandedStory'
import { Progress } from '@sourcegraph/shared/src/search/stream'
import { WebStory } from '../../../components/WebStory'
import { StreamingProgress } from './StreamingProgress'
const { add } = storiesOf('web/search/results/progress/StreamingProgress', module)
const { add } = storiesOf('search-ui/search/results/progress/StreamingProgress', module)
.addParameters({
design: {
type: 'figma',
@ -28,9 +27,9 @@ add('0 results, in progress', () => {
}
return (
<WebStory>
<BrandedStory>
{() => <StreamingProgress progress={progress} state="loading" onSearchAgain={onSearchAgain} />}
</WebStory>
</BrandedStory>
)
})
@ -43,11 +42,11 @@ add('0 results, in progress, traced', () => {
}
return (
<WebStory>
<BrandedStory>
{() => (
<StreamingProgress progress={progress} state="loading" onSearchAgain={onSearchAgain} showTrace={true} />
)}
</WebStory>
</BrandedStory>
)
})
@ -60,9 +59,9 @@ add('1 result from 1 repository, in progress', () => {
}
return (
<WebStory>
<BrandedStory>
{() => <StreamingProgress progress={progress} state="loading" onSearchAgain={onSearchAgain} />}
</WebStory>
</BrandedStory>
)
})
@ -75,9 +74,9 @@ add('big numbers, done', () => {
}
return (
<WebStory>
<BrandedStory>
{() => <StreamingProgress progress={progress} state="complete" onSearchAgain={onSearchAgain} />}
</WebStory>
</BrandedStory>
)
})
@ -91,7 +90,7 @@ add('big numbers, done, traced', () => {
}
return (
<WebStory>
<BrandedStory>
{() => (
<StreamingProgress
progress={progress}
@ -100,7 +99,7 @@ add('big numbers, done, traced', () => {
showTrace={true}
/>
)}
</WebStory>
</BrandedStory>
)
})
@ -134,9 +133,9 @@ add('2 results from 2 repositories, complete, skipped with info', () => {
}
return (
<WebStory>
<BrandedStory>
{() => <StreamingProgress progress={progress} state="complete" onSearchAgain={onSearchAgain} />}
</WebStory>
</BrandedStory>
)
})
@ -170,9 +169,9 @@ add('2 results from 2 repositories, loading, skipped with info', () => {
}
return (
<WebStory>
<BrandedStory>
{() => <StreamingProgress progress={progress} state="loading" onSearchAgain={onSearchAgain} />}
</WebStory>
</BrandedStory>
)
})
@ -216,9 +215,9 @@ add('2 results from 2 repositories, complete, skipped with warning', () => {
}
return (
<WebStory>
<BrandedStory>
{() => <StreamingProgress progress={progress} state="complete" onSearchAgain={onSearchAgain} />}
</WebStory>
</BrandedStory>
)
})
@ -262,8 +261,8 @@ add('2 results from 2 repositories, loading, skipped with warning', () => {
}
return (
<WebStory>
<BrandedStory>
{() => <StreamingProgress progress={progress} state="loading" onSearchAgain={onSearchAgain} />}
</WebStory>
</BrandedStory>
)
})

View File

@ -1,13 +1,12 @@
import { storiesOf } from '@storybook/react'
import * as React from 'react'
import { BrandedStory } from '@sourcegraph/branded/src/components/BrandedStory'
import { Progress } from '@sourcegraph/shared/src/search/stream'
import { WebStory } from '../../../components/WebStory'
import { StreamingProgressSkippedPopover } from './StreamingProgressSkippedPopover'
const { add } = storiesOf('web/search/results/progress/StreamingProgressSkippedPopover', module).addParameters({
const { add } = storiesOf('search-ui/search/results/progress/StreamingProgressSkippedPopover', module).addParameters({
design: {
type: 'figma',
url: 'https://www.figma.com/file/IyiXZIbPHK447NCXov0AvK/13928-Streaming-search?node-id=280%3A17768',
@ -62,7 +61,11 @@ add('popover', () => {
],
}
return <WebStory>{() => <StreamingProgressSkippedPopover progress={progress} onSearchAgain={() => {}} />}</WebStory>
return (
<BrandedStory>
{() => <StreamingProgressSkippedPopover progress={progress} onSearchAgain={() => {}} />}
</BrandedStory>
)
})
add('only info, all should be closed', () => {
@ -94,7 +97,11 @@ add('only info, all should be closed', () => {
],
}
return <WebStory>{() => <StreamingProgressSkippedPopover progress={progress} onSearchAgain={() => {}} />}</WebStory>
return (
<BrandedStory>
{() => <StreamingProgressSkippedPopover progress={progress} onSearchAgain={() => {}} />}
</BrandedStory>
)
})
add('only one info, should be open', () => {
@ -116,5 +123,9 @@ add('only one info, should be open', () => {
],
}
return <WebStory>{() => <StreamingProgressSkippedPopover progress={progress} onSearchAgain={() => {}} />}</WebStory>
return (
<BrandedStory>
{() => <StreamingProgressSkippedPopover progress={progress} onSearchAgain={() => {}} />}
</BrandedStory>
)
})

View File

@ -7,13 +7,12 @@ import SearchIcon from 'mdi-react/SearchIcon'
import React, { useCallback, useState } from 'react'
import { Collapse, Form, FormGroup, Input, Label } from 'reactstrap'
import { SyntaxHighlightedSearchQuery } from '@sourcegraph/search-ui'
import { Markdown } from '@sourcegraph/shared/src/components/Markdown'
import { Skipped } from '@sourcegraph/shared/src/search/stream'
import { renderMarkdown } from '@sourcegraph/shared/src/util/markdown'
import { Button } from '@sourcegraph/wildcard'
import { SyntaxHighlightedSearchQuery } from '../../../components/SyntaxHighlightedSearchQuery'
import { StreamingProgressProps } from './StreamingProgress'
import styles from './StreamingProgressSkippedPopover.module.scss'

View File

@ -3,10 +3,9 @@ import userEvent from '@testing-library/user-event'
import React from 'react'
import sinon from 'sinon'
import { SearchScope } from '@sourcegraph/shared/src/schema/settings.schema'
import { Filter } from '@sourcegraph/shared/src/search/stream'
import { SearchScope } from '../../../schema/settings.schema'
import { getDynamicFilterLinks, getRepoFilterLinks, getSearchSnippetLinks } from './FilterLink'
describe('FilterLink', () => {

View File

@ -1,17 +1,16 @@
import classNames from 'classnames'
import React from 'react'
import { SyntaxHighlightedSearchQuery } from '@sourcegraph/search-ui'
import { displayRepoName } from '@sourcegraph/shared/src/components/RepoFileLink'
import { RepoIcon } from '@sourcegraph/shared/src/components/RepoIcon'
import { Settings } from '@sourcegraph/shared/src/schema/settings.schema'
import { FilterType } from '@sourcegraph/shared/src/search/query/filters'
import { Filter } from '@sourcegraph/shared/src/search/stream'
import { isSettingsValid, SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
import { pluralize } from '@sourcegraph/shared/src/util/strings'
import { Button } from '@sourcegraph/wildcard'
import { SyntaxHighlightedSearchQuery } from '../../../components/SyntaxHighlightedSearchQuery'
import { Settings } from '../../../schema/settings.schema'
import { getFiltersOfKind } from './helpers'
import styles from './SearchSidebarSection.module.scss'

View File

@ -1,9 +1,8 @@
import React from 'react'
import { QuickLink } from '@sourcegraph/shared/src/schema/settings.schema'
import { renderWithRouter } from '@sourcegraph/shared/src/testing/render-with-router'
import { QuickLink } from '../../../schema/settings.schema'
import { getQuickLinks } from './QuickLink'
describe('QuickLink', () => {

View File

@ -1,11 +1,10 @@
import LinkIcon from 'mdi-react/LinkIcon'
import React from 'react'
import { Settings } from '@sourcegraph/shared/src/schema/settings.schema'
import { isSettingsValid, SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
import { Link } from '@sourcegraph/wildcard'
import { Settings } from '../../../schema/settings.schema'
import styles from './SearchSidebarSection.module.scss'
export const getQuickLinks = (settingsCascade: SettingsCascadeProps['settingsCascade']): React.ReactElement[] => {

View File

@ -7,6 +7,13 @@ import ExternalLinkIcon from 'mdi-react/ExternalLinkIcon'
import React, { ReactElement, useCallback, useMemo, useState } from 'react'
import { Collapse } from 'reactstrap'
import {
QueryChangeSource,
SearchQueryState,
createQueryExampleFromString,
updateQueryWithFilterAndExample,
QueryExample,
} from '@sourcegraph/search'
import { Markdown } from '@sourcegraph/shared/src/components/Markdown'
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
import { FILTERS, FilterType, isNegatableFilter } from '@sourcegraph/shared/src/search/query/filters'
@ -15,10 +22,6 @@ import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryServi
import { renderMarkdown } from '@sourcegraph/shared/src/util/markdown'
import { Button, useLocalStorage, Link } from '@sourcegraph/wildcard'
import { NavbarQueryState } from '../../../stores/navbarSearchQueryState'
import { QueryChangeSource } from '../../helpers'
import { createQueryExampleFromString, updateQueryWithFilterAndExample, QueryExample } from '../../helpers/queryExample'
import styles from './SearchReference.module.scss'
import sidebarStyles from './SearchSidebarSection.module.scss'
@ -462,7 +465,7 @@ const FilterInfoList = ({ filters, onClick, onExampleClick }: FilterInfoListProp
</ul>
)
export interface SearchReferenceProps extends TelemetryProps, Pick<NavbarQueryState, 'setQueryState'> {
export interface SearchReferenceProps extends TelemetryProps, Pick<SearchQueryState, 'setQueryState'> {
filter: string
}

View File

@ -1,17 +1,25 @@
import { storiesOf } from '@storybook/react'
import React from 'react'
// We need to import `create` to make a mock store just for this story.
// eslint-disable-next-line no-restricted-imports
import create from 'zustand'
import { BrandedStory } from '@sourcegraph/branded/src/components/BrandedStory'
import {
BuildSearchQueryURLParameters,
SearchPatternType,
SearchQueryState,
SearchQueryStateStoreProvider,
} from '@sourcegraph/search'
import { QuickLink, SearchScope } from '@sourcegraph/shared/src/schema/settings.schema'
import { Filter } from '@sourcegraph/shared/src/search/stream'
import { EMPTY_SETTINGS_CASCADE } from '@sourcegraph/shared/src/settings/settings'
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { WebStory } from '../../../components/WebStory'
import { SearchPatternType } from '../../../graphql-operations'
import { QuickLink, SearchScope } from '../../../schema/settings.schema'
import { buildSearchURLQuery } from '@sourcegraph/shared/src/util/url'
import { SearchSidebar, SearchSidebarProps } from './SearchSidebar'
const { add } = storiesOf('web/search/results/sidebar/SearchSidebar', module).addParameters({
const { add } = storiesOf('search-ui/search/results/sidebar/SearchSidebar', module).addParameters({
design: {
type: 'figma',
url: 'https://www.figma.com/file/NIsN34NH7lPu04olBzddTw/?node-id=1018%3A13883',
@ -19,12 +27,38 @@ const { add } = storiesOf('web/search/results/sidebar/SearchSidebar', module).ad
chromatic: { viewports: [544, 577, 993] },
})
const mockUseQueryState = create<SearchQueryState>((set, get) => ({
queryState: { query: '' },
searchCaseSensitivity: false,
searchPatternType: SearchPatternType.literal,
searchQueryFromURL: '',
setQueryState: queryStateUpdate => {
if (typeof queryStateUpdate === 'function') {
set({ queryState: queryStateUpdate(get().queryState) })
} else {
set({ queryState: queryStateUpdate })
}
},
submitSearch: () => {},
}))
const defaultProps: SearchSidebarProps = {
caseSensitive: false,
patternType: SearchPatternType.literal,
selectedSearchContextSpec: 'global',
settingsCascade: EMPTY_SETTINGS_CASCADE,
telemetryService: NOOP_TELEMETRY_SERVICE,
buildSearchURLQueryFromQueryState: (parameters: BuildSearchQueryURLParameters) => {
const currentState = mockUseQueryState.getState()
return buildSearchURLQuery(
parameters.query,
parameters.patternType ?? currentState.searchPatternType,
parameters.caseSensitive ?? currentState.searchCaseSensitivity,
parameters.searchContextSpec,
parameters.searchParametersList
)
},
}
const quicklinks: QuickLink[] = [
@ -111,16 +145,26 @@ const filters: Filter[] = [
})),
]
add('empty sidebar', () => <WebStory>{() => <SearchSidebar {...defaultProps} />}</WebStory>)
add('empty sidebar', () => (
<BrandedStory>
{() => (
<SearchQueryStateStoreProvider useSearchQueryState={mockUseQueryState}>
<SearchSidebar {...defaultProps} />
</SearchQueryStateStoreProvider>
)}
</BrandedStory>
))
add('with everything', () => (
<WebStory>
<BrandedStory>
{() => (
<SearchSidebar
{...defaultProps}
settingsCascade={{ subjects: [], final: { quicklinks, 'search.scopes': scopes } }}
filters={filters}
/>
<SearchQueryStateStoreProvider useSearchQueryState={mockUseQueryState}>
<SearchSidebar
{...defaultProps}
settingsCascade={{ subjects: [], final: { quicklinks, 'search.scopes': scopes } }}
filters={filters}
/>
</SearchQueryStateStoreProvider>
)}
</WebStory>
</BrandedStory>
))

View File

@ -4,22 +4,25 @@ import { useHistory } from 'react-router'
import StickyBox from 'react-sticky-box'
import shallow from 'zustand/shallow'
import {
BuildSearchQueryURLParameters,
QueryUpdate,
SearchQueryState,
SubmitSearchParameters,
useSearchQueryStateStoreContext,
} from '@sourcegraph/search'
import { FilterType } from '@sourcegraph/shared/src/search/query/filters'
import { Filter } from '@sourcegraph/shared/src/search/stream'
import { SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
import { SectionID } from '@sourcegraph/shared/src/settings/temporary/searchSidebar'
import { TemporarySettings } from '@sourcegraph/shared/src/settings/temporary/TemporarySettings'
import { useTemporarySetting } from '@sourcegraph/shared/src/settings/temporary/useTemporarySetting'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { OnboardingTour } from '../../../onboarding-tour/OnboardingTour'
import { TemporarySettings } from '../../../settings/temporary/TemporarySettings'
import { useTemporarySetting } from '../../../settings/temporary/useTemporarySetting'
import { useNavbarQueryState } from '../../../stores'
import { NavbarQueryState, QueryUpdate } from '../../../stores/navbarSearchQueryState'
import { SubmitSearchParameters } from '../../helpers'
import { getDynamicFilterLinks, getRepoFilterLinks, getSearchSnippetLinks } from './FilterLink'
import { getFiltersOfKind, useLastRepoName } from './helpers'
import { getQuickLinks } from './QuickLink'
import { getRevisions } from './Revisions'
import { RevisionsProps } from './revisions'
import { getSearchReferenceFactory } from './SearchReference'
import styles from './SearchSidebar.module.scss'
import { SearchSidebarSection } from './SearchSidebarSection'
@ -32,26 +35,28 @@ export interface SearchSidebarProps
filters?: Filter[]
className?: string
showOnboardingTour?: boolean
}
export enum SectionID {
SEARCH_REFERENCE = 'reference',
SEARCH_TYPES = 'types',
DYNAMIC_FILTERS = 'filters',
REPOSITORIES = 'repositories',
SEARCH_SNIPPETS = 'snippets',
QUICK_LINKS = 'quicklinks',
REVISIONS = 'revisions',
/**
* Not yet implemented in the VS Code extension (blocked on Apollo Client integration).
* */
getRevisions?: (revisionsProps: Omit<RevisionsProps, 'query'>) => (query: string) => JSX.Element
/**
* Content to render inside sidebar, but before other sections.
*/
prefixContent?: JSX.Element
buildSearchURLQueryFromQueryState: (queryParameters: BuildSearchQueryURLParameters) => string
}
const selectFromQueryState = ({
queryState: { query },
setQueryState,
submitSearch,
}: NavbarQueryState): {
}: SearchQueryState): {
query: string
setQueryState: NavbarQueryState['setQueryState']
submitSearch: NavbarQueryState['submitSearch']
setQueryState: SearchQueryState['setQueryState']
submitSearch: SearchQueryState['submitSearch']
} => ({
query,
setQueryState,
@ -61,7 +66,11 @@ const selectFromQueryState = ({
export const SearchSidebar: React.FunctionComponent<SearchSidebarProps> = props => {
const history = useHistory()
const [collapsedSections, setCollapsedSections] = useTemporarySetting('search.collapsedSidebarSections', {})
const { query, setQueryState, submitSearch } = useNavbarQueryState(selectFromQueryState, shallow)
// The zustand store for search query state is referenced through context
// because there may be different global stores across clients
// (e.g. VS Code extension, web app)
const { query, setQueryState, submitSearch } = useSearchQueryStateStoreContext()(selectFromQueryState, shallow)
// Unlike onFilterClicked, this function will always append or update a filter
const submitQueryWithProps = useCallback(
@ -139,6 +148,7 @@ export const SearchSidebar: React.FunctionComponent<SearchSidebarProps> = props
onNavbarQueryChange: setQueryState,
query,
selectedSearchContextSpec: props.selectedSearchContextSpec,
buildSearchURLQueryFromQueryState: props.buildSearchURLQueryFromQueryState,
})}
</SearchSidebarSection>
<SearchSidebarSection
@ -168,7 +178,7 @@ export const SearchSidebar: React.FunctionComponent<SearchSidebarProps> = props
{repoFilterLinks}
</SearchSidebarSection>
) : null}
{repoName ? (
{props.getRevisions && repoName ? (
<SearchSidebarSection
sectionId={SectionID.REVISIONS}
className={styles.searchSidebarItem}
@ -178,7 +188,7 @@ export const SearchSidebar: React.FunctionComponent<SearchSidebarProps> = props
showSearch={true}
clearSearchOnChange={repoName}
>
{getRevisions({ repoName, onFilterClick: submitQueryWithProps })}
{props.getRevisions({ repoName, onFilterClick: submitQueryWithProps })}
</SearchSidebarSection>
) : null}
<SearchSidebarSection
@ -221,7 +231,7 @@ export const SearchSidebar: React.FunctionComponent<SearchSidebarProps> = props
return (
<div className={classNames(styles.searchSidebar, props.className)}>
{props.showOnboardingTour && <OnboardingTour className="mb-1" telemetryService={props.telemetryService} />}
{props.prefixContent}
{body}
</div>
)

View File

@ -1,22 +1,26 @@
import classNames from 'classnames'
import React, { ReactElement, useCallback } from 'react'
import {
BuildSearchQueryURLParameters,
QueryChangeSource,
QueryState,
SearchContextProps,
createQueryExampleFromString,
updateQueryWithFilterAndExample,
} from '@sourcegraph/search'
import { FilterType } from '@sourcegraph/shared/src/search/query/filters'
import { updateFilter } from '@sourcegraph/shared/src/search/query/transformer'
import { containsLiteralOrPattern } from '@sourcegraph/shared/src/search/query/validate'
import { SearchType } from '@sourcegraph/shared/src/search/stream'
import { Button, Link } from '@sourcegraph/wildcard'
import { SearchContextProps } from '../..'
import { buildSearchURLQueryFromQueryState } from '../../../stores'
import { QueryChangeSource, QueryState } from '../../helpers'
import { createQueryExampleFromString, updateQueryWithFilterAndExample } from '../../helpers/queryExample'
import { SearchType } from '../StreamingSearchResults'
import styles from './SearchSidebarSection.module.scss'
export interface SearchTypeLinksProps extends Pick<SearchContextProps, 'selectedSearchContextSpec'> {
query: string
onNavbarQueryChange: (queryState: QueryState) => void
buildSearchURLQueryFromQueryState: (queryParameters: BuildSearchQueryURLParameters) => string
}
interface SearchTypeLinkProps extends SearchTypeLinksProps {
@ -33,6 +37,7 @@ const SearchTypeLink: React.FunctionComponent<SearchTypeLinkProps> = ({
query,
selectedSearchContextSpec,
children,
buildSearchURLQueryFromQueryState,
}) => {
const builtURLQuery = buildSearchURLQueryFromQueryState({
query: updateFilter(query, FilterType.type, type as string),

View File

@ -0,0 +1,16 @@
import { QueryUpdate } from '@sourcegraph/search'
export enum TabIndex {
BRANCHES,
TAGS,
}
export interface RevisionsProps {
repoName: string
onFilterClick: (updates: QueryUpdate[]) => void
query: string
/**
* This property is only exposed for storybook tests.
*/
_initialTab?: TabIndex
}

View File

@ -0,0 +1,20 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"sourceRoot": "src",
"rootDir": ".",
"outDir": "./out",
"baseUrl": "./src",
"jsx": "react",
},
"include": ["./src/**/*", "./*.ts"],
"exclude": ["../../node_modules", "./node_modules", "./out"],
"references": [
{ "path": "../search" },
{ "path": "../shared" },
{ "path": "../branded" },
{ "path": "../http-client" },
{ "path": "../common" },
],
}

Some files were not shown because too many files have changed in this diff Show More