mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 17:11:49 +00:00
search: remove all GraphQL dependencies from SearchResultsInfoBar (#15991)
This is part of the work to refactor search results UI components for streaming search. SearchResultsInfoBar will now receive its stats/progress as a JSX element property. Created a new component for the GQL search stats.
This commit is contained in:
parent
214788e2b7
commit
8f09fe258b
@ -119,7 +119,7 @@ export const MULTIPLE_MATCH_RESULT = {
|
||||
export const SEARCH_RESULT = {
|
||||
__typename: 'SearchResults' as const,
|
||||
limitHit: false,
|
||||
resultCount: 1,
|
||||
matchCount: 1,
|
||||
approximateResultCount: '1',
|
||||
missing: [] as IRepository[],
|
||||
cloning: [] as IRepository[],
|
||||
@ -144,7 +144,7 @@ export const SEARCH_RESULT = {
|
||||
export const MULTIPLE_SEARCH_RESULT = {
|
||||
...SEARCH_RESULT,
|
||||
limitHit: false,
|
||||
resultCount: 136,
|
||||
matchCount: 136,
|
||||
approximateResultCount: '136',
|
||||
results: [
|
||||
RESULT,
|
||||
|
||||
@ -1,25 +1,17 @@
|
||||
import * as GQL from '../../../../shared/src/graphql/schema'
|
||||
import * as H from 'history'
|
||||
import * as React from 'react'
|
||||
import AlertCircleIcon from 'mdi-react/AlertCircleIcon'
|
||||
import ArrowCollapseVerticalIcon from 'mdi-react/ArrowCollapseVerticalIcon'
|
||||
import ArrowExpandVerticalIcon from 'mdi-react/ArrowExpandVerticalIcon'
|
||||
import CalculatorIcon from 'mdi-react/CalculatorIcon'
|
||||
import CheckIcon from 'mdi-react/CheckIcon'
|
||||
import classNames from 'classnames'
|
||||
import CloudDownloadIcon from 'mdi-react/CloudDownloadIcon'
|
||||
import DownloadIcon from 'mdi-react/DownloadIcon'
|
||||
import FormatQuoteOpenIcon from 'mdi-react/FormatQuoteOpenIcon'
|
||||
import MapSearchIcon from 'mdi-react/MapSearchIcon'
|
||||
import TimerSandIcon from 'mdi-react/TimerSandIcon'
|
||||
import { AuthenticatedUser } from '../../auth'
|
||||
import { ContributableMenu } from '../../../../shared/src/api/protocol'
|
||||
import { ExtensionsControllerProps } from '../../../../shared/src/extensions/controller'
|
||||
import { PatternTypeProps, SearchStreamingProps } from '..'
|
||||
import { PatternTypeProps } from '..'
|
||||
import { PlatformContextProps } from '../../../../shared/src/platform/context'
|
||||
import { pluralize } from '../../../../shared/src/util/strings'
|
||||
import { SearchPatternType } from '../../graphql-operations'
|
||||
import { StreamingProgress } from './streaming-progress/StreamingProgress'
|
||||
import { TelemetryProps } from '../../../../shared/src/telemetry/telemetryService'
|
||||
import { WebActionsNavItems as ActionsNavItems } from '../../components/shared'
|
||||
|
||||
@ -27,21 +19,18 @@ interface SearchResultsInfoBarProps
|
||||
extends ExtensionsControllerProps<'executeCommand' | 'services'>,
|
||||
PlatformContextProps<'forceUpdateTooltip' | 'settings'>,
|
||||
TelemetryProps,
|
||||
PatternTypeProps,
|
||||
SearchStreamingProps {
|
||||
PatternTypeProps {
|
||||
/** The currently authenticated user or null */
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
|
||||
/** The loaded search results and metadata */
|
||||
/** The search query and if any results were found */
|
||||
query?: string
|
||||
results: GQL.ISearchResults
|
||||
onShowMoreResultsClick?: () => void
|
||||
resultsFound: boolean
|
||||
|
||||
// Expand all feature
|
||||
allExpanded: boolean
|
||||
onExpandAllResultsToggle: () => void
|
||||
|
||||
showDotComMarketing: boolean
|
||||
// Saved queries
|
||||
showSavedQueryButton?: boolean
|
||||
onDidCreateSavedQuery: () => void
|
||||
@ -51,6 +40,8 @@ interface SearchResultsInfoBarProps
|
||||
location: H.Location
|
||||
|
||||
className?: string
|
||||
|
||||
stats: JSX.Element
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,158 +67,67 @@ const QuotesInterpretedLiterallyNotice: React.FunctionComponent<SearchResultsInf
|
||||
* The info bar shown over the search results list that displays metadata
|
||||
* and a few actions like expand all and save query
|
||||
*/
|
||||
export const SearchResultsInfoBar: React.FunctionComponent<SearchResultsInfoBarProps> = props => {
|
||||
const excludeForksFilter = props.results.dynamicFilters.find(filter => filter.value === 'fork:yes')
|
||||
const excludedForksCount = excludeForksFilter?.count || 0
|
||||
const excludeArchivedFilter = props.results.dynamicFilters.find(filter => filter.value === 'archived:yes')
|
||||
const excludedArchivedCount = excludeArchivedFilter?.count || 0
|
||||
return (
|
||||
<div className={classNames(props.className, 'search-results-info-bar')} data-testid="results-info-bar">
|
||||
<small className="search-results-info-bar__row">
|
||||
{!props.searchStreaming && (
|
||||
<div className="search-results-info-bar__row-left">
|
||||
<div className="search-results-info-bar__notice test-search-results-stats">
|
||||
<span>
|
||||
<CalculatorIcon className="icon-inline" /> {props.results.approximateResultCount}{' '}
|
||||
{pluralize('result', props.results.matchCount)} in{' '}
|
||||
{(props.results.elapsedMilliseconds / 1000).toFixed(2)} seconds
|
||||
{props.results.indexUnavailable && ' (index unavailable)'}
|
||||
{props.results.limitHit && String.fromCharCode(160)}
|
||||
</span>
|
||||
export const SearchResultsInfoBar: React.FunctionComponent<SearchResultsInfoBarProps> = props => (
|
||||
<div className={classNames(props.className, 'search-results-info-bar')} data-testid="results-info-bar">
|
||||
<small className="search-results-info-bar__row">
|
||||
<div className="search-results-info-bar__row-left">
|
||||
{props.stats}
|
||||
<QuotesInterpretedLiterallyNotice {...props} />
|
||||
</div>
|
||||
|
||||
{props.results.limitHit && props.onShowMoreResultsClick && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-link btn-sm p-0"
|
||||
onClick={props.onShowMoreResultsClick}
|
||||
>
|
||||
(show more)
|
||||
</button>
|
||||
<ul className="search-results-info-bar__row-right nav align-items-center justify-content-end">
|
||||
<ActionsNavItems
|
||||
{...props}
|
||||
extraContext={{ searchQuery: props.query || null }}
|
||||
menu={ContributableMenu.SearchResultsToolbar}
|
||||
wrapInList={false}
|
||||
showLoadingSpinnerDuringExecution={true}
|
||||
actionItemClass="btn btn-link nav-link text-decoration-none"
|
||||
/>
|
||||
|
||||
{props.resultsFound && (
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onExpandAllResultsToggle}
|
||||
className="btn btn-link nav-link text-decoration-none"
|
||||
data-tooltip={`${props.allExpanded ? 'Hide' : 'Show'} more matches on all results`}
|
||||
>
|
||||
{props.allExpanded ? (
|
||||
<>
|
||||
<ArrowCollapseVerticalIcon className="icon-inline" /> Collapse all
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ArrowExpandVerticalIcon className="icon-inline" /> Expand all
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{excludedForksCount > 0 && (
|
||||
<div
|
||||
className="search-results-info-bar__notice"
|
||||
data-tooltip="add fork:yes to include forks"
|
||||
>
|
||||
<span>
|
||||
<AlertCircleIcon className="icon-inline" /> {excludedForksCount} forked{' '}
|
||||
{pluralize('repository', excludedForksCount, 'repositories')} excluded
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{excludedArchivedCount > 0 && (
|
||||
<div
|
||||
className="search-results-info-bar__notice"
|
||||
data-tooltip="add archived:yes to include archives"
|
||||
>
|
||||
<span>
|
||||
<AlertCircleIcon className="icon-inline" /> {excludedArchivedCount} archived{' '}
|
||||
{pluralize('repository', excludedArchivedCount, 'repositories')} excluded
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.results.missing.length > 0 && (
|
||||
<div
|
||||
className="search-results-info-bar__notice"
|
||||
data-tooltip={props.results.missing.map(repo => repo.name).join('\n')}
|
||||
>
|
||||
<span>
|
||||
<MapSearchIcon className="icon-inline" /> {props.results.missing.length}{' '}
|
||||
{pluralize('repository', props.results.missing.length, 'repositories')} not found
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.results.timedout.length > 0 && (
|
||||
<div
|
||||
className="search-results-info-bar__notice"
|
||||
data-tooltip={props.results.timedout.map(repo => repo.name).join('\n')}
|
||||
>
|
||||
<span>
|
||||
<TimerSandIcon className="icon-inline" /> {props.results.timedout.length}{' '}
|
||||
{pluralize('repository', props.results.timedout.length, 'repositories')} timed out
|
||||
(reload to try again, or specify a longer "timeout:" in your query)
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.results.cloning.length > 0 && (
|
||||
<div
|
||||
className="search-results-info-bar__notice"
|
||||
data-tooltip={props.results.cloning.map(repo => repo.name).join('\n')}
|
||||
>
|
||||
<span>
|
||||
<CloudDownloadIcon className="icon-inline" /> {props.results.cloning.length}{' '}
|
||||
{pluralize('repository', props.results.cloning.length, 'repositories')} cloning
|
||||
(reload to try again)
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<QuotesInterpretedLiterallyNotice {...props} />
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{props.searchStreaming && <StreamingProgress />}
|
||||
|
||||
<ul className="search-results-info-bar__row-right nav align-items-center justify-content-end">
|
||||
<ActionsNavItems
|
||||
{...props}
|
||||
extraContext={{ searchQuery: props.query || null }}
|
||||
menu={ContributableMenu.SearchResultsToolbar}
|
||||
wrapInList={false}
|
||||
showLoadingSpinnerDuringExecution={true}
|
||||
actionItemClass="btn btn-link nav-link text-decoration-none"
|
||||
/>
|
||||
|
||||
{props.results.results.length > 0 && (
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onExpandAllResultsToggle}
|
||||
className="btn btn-link nav-link text-decoration-none"
|
||||
data-tooltip={`${props.allExpanded ? 'Hide' : 'Show'} more matches on all results`}
|
||||
>
|
||||
{props.allExpanded ? (
|
||||
<>
|
||||
<ArrowCollapseVerticalIcon className="icon-inline" /> Collapse all
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ArrowExpandVerticalIcon className="icon-inline" /> Expand all
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{props.showSavedQueryButton !== false && props.authenticatedUser && (
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onSaveQueryClick}
|
||||
className="btn btn-link nav-link text-decoration-none"
|
||||
disabled={props.didSave}
|
||||
>
|
||||
{props.didSave ? (
|
||||
<>
|
||||
<CheckIcon className="icon-inline" /> Query saved
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<DownloadIcon className="icon-inline test-save-search-link" /> Save this search
|
||||
query
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</small>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{props.showSavedQueryButton !== false && props.authenticatedUser && (
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onSaveQueryClick}
|
||||
className="btn btn-link nav-link text-decoration-none"
|
||||
disabled={props.didSave}
|
||||
>
|
||||
{props.didSave ? (
|
||||
<>
|
||||
<CheckIcon className="icon-inline" /> Query saved
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<DownloadIcon className="icon-inline test-save-search-link" /> Save this search
|
||||
query
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</small>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -40,6 +40,7 @@ import { AuthenticatedUser } from '../../auth'
|
||||
import { SearchResultTypeTabs } from './SearchResultTypeTabs'
|
||||
import { QueryState } from '../helpers'
|
||||
import { PerformanceWarningAlert } from '../../site/PerformanceWarningAlert'
|
||||
import { SearchResultsStats } from './SearchResultsStats'
|
||||
|
||||
const isSearchResults = (value: unknown): value is GQL.ISearchResults =>
|
||||
typeof value === 'object' &&
|
||||
@ -377,9 +378,14 @@ export class SearchResultsList extends React.PureComponent<SearchResultsListProp
|
||||
<SearchResultsInfoBar
|
||||
{...this.props}
|
||||
query={parsedQuery}
|
||||
results={results}
|
||||
showDotComMarketing={this.props.isSourcegraphDotCom}
|
||||
resultsFound={results.results.length > 0}
|
||||
className="border-bottom flex-grow-1"
|
||||
stats={
|
||||
<SearchResultsStats
|
||||
results={results}
|
||||
onShowMoreResultsClick={this.props.onShowMoreResultsClick}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
94
client/web/src/search/results/SearchResultsStats.tsx
Normal file
94
client/web/src/search/results/SearchResultsStats.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import AlertCircleIcon from 'mdi-react/AlertCircleIcon'
|
||||
import CalculatorIcon from 'mdi-react/CalculatorIcon'
|
||||
import CloudDownloadIcon from 'mdi-react/CloudDownloadIcon'
|
||||
import MapSearchIcon from 'mdi-react/MapSearchIcon'
|
||||
import TimerSandIcon from 'mdi-react/TimerSandIcon'
|
||||
import React from 'react'
|
||||
import * as GQL from '../../../../shared/src/graphql/schema'
|
||||
import { pluralize } from '../../../../shared/src/util/strings'
|
||||
|
||||
/** Search result statistics for GraphQL searches */
|
||||
export const SearchResultsStats: React.FunctionComponent<{
|
||||
results: GQL.ISearchResults
|
||||
onShowMoreResultsClick?: () => void
|
||||
}> = ({ results, onShowMoreResultsClick }) => {
|
||||
const excludeForksFilter = results.dynamicFilters.find(filter => filter.value === 'fork:yes')
|
||||
const excludedForksCount = excludeForksFilter?.count || 0
|
||||
const excludeArchivedFilter = results.dynamicFilters.find(filter => filter.value === 'archived:yes')
|
||||
const excludedArchivedCount = excludeArchivedFilter?.count || 0
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="search-results-info-bar__notice test-search-results-stats">
|
||||
<span>
|
||||
<CalculatorIcon className="icon-inline" /> {results.approximateResultCount}{' '}
|
||||
{pluralize('result', results.matchCount)} in {(results.elapsedMilliseconds / 1000).toFixed(2)}{' '}
|
||||
seconds
|
||||
{results.indexUnavailable && ' (index unavailable)'}
|
||||
{results.limitHit && String.fromCharCode(160)}
|
||||
</span>
|
||||
|
||||
{results.limitHit && onShowMoreResultsClick && (
|
||||
<button type="button" className="btn btn-link btn-sm p-0" onClick={onShowMoreResultsClick}>
|
||||
(show more)
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{excludedForksCount > 0 && (
|
||||
<div className="search-results-info-bar__notice" data-tooltip="add fork:yes to include forks">
|
||||
<span>
|
||||
<AlertCircleIcon className="icon-inline" /> {excludedForksCount} forked{' '}
|
||||
{pluralize('repository', excludedForksCount, 'repositories')} excluded
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{excludedArchivedCount > 0 && (
|
||||
<div className="search-results-info-bar__notice" data-tooltip="add archived:yes to include archives">
|
||||
<span>
|
||||
<AlertCircleIcon className="icon-inline" /> {excludedArchivedCount} archived{' '}
|
||||
{pluralize('repository', excludedArchivedCount, 'repositories')} excluded
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{results.missing.length > 0 && (
|
||||
<div
|
||||
className="search-results-info-bar__notice"
|
||||
data-tooltip={results.missing.map(repo => repo.name).join('\n')}
|
||||
>
|
||||
<span>
|
||||
<MapSearchIcon className="icon-inline" /> {results.missing.length}{' '}
|
||||
{pluralize('repository', results.missing.length, 'repositories')} not found
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{results.timedout.length > 0 && (
|
||||
<div
|
||||
className="search-results-info-bar__notice"
|
||||
data-tooltip={results.timedout.map(repo => repo.name).join('\n')}
|
||||
>
|
||||
<span>
|
||||
<TimerSandIcon className="icon-inline" /> {results.timedout.length}{' '}
|
||||
{pluralize('repository', results.timedout.length, 'repositories')} timed out (reload to try
|
||||
again, or specify a longer "timeout:" in your query)
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{results.cloning.length > 0 && (
|
||||
<div
|
||||
className="search-results-info-bar__notice"
|
||||
data-tooltip={results.cloning.map(repo => repo.name).join('\n')}
|
||||
>
|
||||
<span>
|
||||
<CloudDownloadIcon className="icon-inline" /> {results.cloning.length}{' '}
|
||||
{pluralize('repository', results.cloning.length, 'repositories')} cloning (reload to try again)
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user