web: upgrade react-router to v6 (#47595)

- Closes #33834
- Upgraded react-router to v6
- Migrated the web application to [the data-aware router introduced in
v6.4.0](https://reactrouter.com/en/main/routers/picking-a-router#using-v64-data-apis).
- Migrated `history.block` usages to the `unstable_useBlock` hook
[introduced in
v6.7.0](https://github.com/remix-run/react-router/issues/8139).
- Removed explicit history reference from the `renderWithBrandedContext`
utility used in unit tests.
- Migrated the search-query state observer from `history.listen` to
`useLocation` API.

## Test plan

CI and manually visiting all the pages.
This commit is contained in:
Valery Bugakov 2023-02-20 22:32:51 -08:00 committed by GitHub
parent 39488a9491
commit 77541f2c30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
286 changed files with 994 additions and 1175 deletions

View File

@ -34,8 +34,8 @@ describe('TabbedPanel', () => {
const panelButton = await renderResult.findByRole('tab', { name: panelToSelect.title })
fireEvent.click(panelButton)
expect(renderResult.history.location.pathname).toEqual(location.pathname)
expect(renderResult.history.location.search).toEqual(location.search)
expect(renderResult.history.location.hash).toEqual(`#tab=${panelToSelect.id}`)
expect(renderResult.locationRef.current?.pathname).toEqual(location.pathname)
expect(renderResult.locationRef.current?.search).toEqual(location.search)
expect(renderResult.locationRef.current?.hash).toEqual(`#tab=${panelToSelect.id}`)
})
})

View File

@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { mdiClose } from '@mdi/js'
import classNames from 'classnames'
import { useLocation, useNavigate } from 'react-router-dom-v5-compat'
import { useLocation, useNavigate } from 'react-router-dom'
import { BehaviorSubject, Observable } from 'rxjs'
import { map } from 'rxjs/operators'

View File

@ -1,7 +1,7 @@
import React, { useCallback, KeyboardEvent, MouseEvent } from 'react'
import classNames from 'classnames'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'

View File

@ -2,7 +2,7 @@ import React, { useCallback, useState } from 'react'
import { mdiOpenInNew } from '@mdi/js'
import classNames from 'classnames'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
import { EditorHint, QueryState } from '@sourcegraph/shared/src/search'

View File

@ -1,7 +1,7 @@
import React, { useCallback, KeyboardEvent, MouseEvent } from 'react'
import classNames from 'classnames'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'

View File

@ -1,6 +1,6 @@
import React, { MouseEvent, KeyboardEvent } from 'react'
import { NavigateFunction } from 'react-router-dom-v5-compat'
import { NavigateFunction } from 'react-router-dom'
/**
* A helper function to replicate browser behavior when clicking on links.

View File

@ -26,7 +26,7 @@ import {
WidgetType,
} from '@codemirror/view'
import classNames from 'classnames'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { renderMarkdown } from '@sourcegraph/common'
import { TraceSpanProvider } from '@sourcegraph/observability-client'

View File

@ -46,7 +46,7 @@ import {
mdiWrench,
} from '@mdi/js'
import { isEqual, startCase } from 'lodash'
import { NavigateFunction } from 'react-router-dom-v5-compat'
import { NavigateFunction } from 'react-router-dom'
import { isDefined } from '@sourcegraph/common'
import { SymbolKind } from '@sourcegraph/shared/src/graphql-operations'

View File

@ -1,6 +1,6 @@
import { ChangeSpec, EditorState, Extension } from '@codemirror/state'
import { EditorView, ViewUpdate } from '@codemirror/view'
import { NavigateFunction } from 'react-router-dom-v5-compat'
import { NavigateFunction } from 'react-router-dom'
import { Observable } from 'rxjs'
import { createCancelableFetchSuggestions } from '@sourcegraph/shared/src/search/query/providers-utils'

View File

@ -4,7 +4,7 @@ import { defaultKeymap, historyKeymap, history as codemirrorHistory } from '@cod
import { Compartment, EditorState, Extension, Prec } from '@codemirror/state'
import { EditorView, keymap, drawSelection } from '@codemirror/view'
import inRange from 'lodash/inRange'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import useResizeObserver from 'use-resize-observer'
import * as uuid from 'uuid'

View File

@ -1,7 +1,7 @@
import React, { useCallback, useState } from 'react'
import classNames from 'classnames'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { Observable } from 'rxjs'
import { TraceSpanProvider } from '@sourcegraph/observability-client'

View File

@ -2,7 +2,6 @@ import bitbucketStyles from '@atlassian/aui/dist/aui/css/aui.css'
import { DecoratorFn, Meta, Story } from '@storybook/react'
import classNames from 'classnames'
import { BrowserRouter } from 'react-router-dom'
import { CompatRouter } from 'react-router-dom-v5-compat'
import { registerHighlightContributions } from '@sourcegraph/common'
import { NotificationType } from '@sourcegraph/shared/src/api/extension/extensionHostApi'
@ -58,18 +57,16 @@ const BITBUCKET_CLASS_PROPS: HoverOverlayClassProps = {
export const BitbucketStyles: Story = (props = {}) => (
<BrowserRouter>
<CompatRouter>
<HoverOverlay
{...commonProps()}
{...BITBUCKET_CLASS_PROPS}
{...props}
hoverOrError={{
contents: [FIXTURE_CONTENT],
aggregatedBadges: [FIXTURE_SEMANTIC_BADGE],
}}
actionsOrError={FIXTURE_ACTIONS}
/>
</CompatRouter>
<HoverOverlay
{...commonProps()}
{...BITBUCKET_CLASS_PROPS}
{...props}
hoverOrError={{
contents: [FIXTURE_CONTENT],
aggregatedBadges: [FIXTURE_SEMANTIC_BADGE],
}}
actionsOrError={FIXTURE_ACTIONS}
/>
</BrowserRouter>
)
BitbucketStyles.storyName = 'Bitbucket styles'

View File

@ -1,4 +1,11 @@
import { Falsey } from 'utility-types'
/**
* Returns true if `val` is not `null` or `undefined`
*/
export const isDefined = <T>(value: T): value is NonNullable<T> => value !== undefined && value !== null
/**
* Returns true if `val` is truthy.
*/
export const isTruthy = <T>(value: T | Falsey): value is T => !!value

View File

@ -1,5 +1,5 @@
import { History } from 'history'
import { NavigateFunction, NavigateOptions, To } from 'react-router-dom-v5-compat'
import { NavigateFunction, NavigateOptions, To } from 'react-router-dom'
export type HistoryOrNavigate = History | NavigateFunction

View File

@ -2,7 +2,6 @@ import { useEffect, useRef } from 'react'
import { DecoratorFn, Meta, Story } from '@storybook/react'
import { BrowserRouter } from 'react-router-dom'
import { CompatRouter } from 'react-router-dom-v5-compat'
import { EMPTY, NEVER } from 'rxjs'
import { useDarkMode } from 'storybook-dark-mode'
@ -45,45 +44,43 @@ export const JetBrainsSearchBoxStory: Story = () => {
return (
<WildcardThemeContext.Provider value={{ isBranded: true }}>
<BrowserRouter>
<CompatRouter>
<div ref={rootElementRef}>
<div className="d-flex justify-content-center">
<div className="mx-6">
<JetBrainsSearchBox
caseSensitive={true}
setCaseSensitivity={() => {}}
patternType={SearchPatternType.regexp}
setPatternType={() => {}}
isSourcegraphDotCom={false}
structuralSearchDisabled={false}
queryState={{ query: 'type:file test AND test repo:contains.file(CHANGELOG)' }}
onChange={() => {}}
onSubmit={() => {}}
authenticatedUser={null}
searchContextsEnabled={true}
showSearchContext={true}
showSearchContextManagement={false}
setSelectedSearchContextSpec={() => {}}
selectedSearchContextSpec={undefined}
fetchSearchContexts={() => {
throw new Error('fetchSearchContexts')
}}
getUserSearchContextNamespaces={() => []}
fetchStreamSuggestions={() => NEVER}
settingsCascade={EMPTY_SETTINGS_CASCADE}
globbing={false}
isLightTheme={!isDarkTheme}
telemetryService={NOOP_TELEMETRY_SERVICE}
platformContext={{ requestGraphQL: () => EMPTY }}
className=""
containerClassName=""
autoFocus={true}
hideHelpButton={true}
/>
</div>
<div ref={rootElementRef}>
<div className="d-flex justify-content-center">
<div className="mx-6">
<JetBrainsSearchBox
caseSensitive={true}
setCaseSensitivity={() => {}}
patternType={SearchPatternType.regexp}
setPatternType={() => {}}
isSourcegraphDotCom={false}
structuralSearchDisabled={false}
queryState={{ query: 'type:file test AND test repo:contains.file(CHANGELOG)' }}
onChange={() => {}}
onSubmit={() => {}}
authenticatedUser={null}
searchContextsEnabled={true}
showSearchContext={true}
showSearchContextManagement={false}
setSelectedSearchContextSpec={() => {}}
selectedSearchContextSpec={undefined}
fetchSearchContexts={() => {
throw new Error('fetchSearchContexts')
}}
getUserSearchContextNamespaces={() => []}
fetchStreamSuggestions={() => NEVER}
settingsCascade={EMPTY_SETTINGS_CASCADE}
globbing={false}
isLightTheme={!isDarkTheme}
telemetryService={NOOP_TELEMETRY_SERVICE}
platformContext={{ requestGraphQL: () => EMPTY }}
className=""
containerClassName=""
autoFocus={true}
hideHelpButton={true}
/>
</div>
</div>
</CompatRouter>
</div>
</BrowserRouter>
</WildcardThemeContext.Provider>
)

View File

@ -5,8 +5,7 @@ import React, { useMemo } from 'react'
import { VSCodeProgressRing } from '@vscode/webview-ui-toolkit/react'
import * as Comlink from 'comlink'
import { createRoot } from 'react-dom/client'
import { MemoryRouter } from 'react-router'
import { CompatRouter } from 'react-router-dom-v5-compat'
import { MemoryRouter } from 'react-router-dom'
import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common'
import { ShortcutProvider } from '@sourcegraph/shared/src/react-shortcuts'
@ -117,9 +116,7 @@ root.render(
<WildcardThemeContext.Provider value={{ isBranded: true }}>
{/* Required for shared components that depend on `location`. */}
<MemoryRouter>
<CompatRouter>
<Main />
</CompatRouter>
<Main />
</MemoryRouter>
</WildcardThemeContext.Provider>
</ShortcutProvider>

View File

@ -1,6 +1,6 @@
import React, { FC, ReactElement, ReactNode, useCallback, useMemo } from 'react'
import { useLocation, useNavigate } from 'react-router-dom-v5-compat'
import { useLocation, useNavigate } from 'react-router-dom'
import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect'
import create from 'zustand'

View File

@ -49,7 +49,8 @@ export function getAPIProxySettings(options: GetAPIProxySettingsOptions): ProxyS
// the index.html generated by `getLocalIndexHTML`.
if (
getLocalIndexHTML &&
proxyRes.statusCode === 200 &&
// router.go is not up to date with client routes and still serves index.html with 404
(proxyRes.statusCode === 200 || proxyRes.statusCode === 404) &&
proxyRes.headers['content-type'] &&
proxyRes.headers['content-type'].includes('text/html')
) {

View File

@ -1,7 +1,7 @@
import React, { Suspense, useCallback, useRef, useState } from 'react'
import classNames from 'classnames'
import { useLocation, Navigate, Outlet } from 'react-router-dom-v5-compat'
import { Outlet, useLocation, Navigate } from 'react-router-dom'
import { Observable } from 'rxjs'
import { TabbedPanelContent } from '@sourcegraph/branded/src/components/panel/TabbedPanelContent'
@ -40,6 +40,7 @@ import type { NotebookProps } from './notebooks'
import { EnterprisePageRoutes, PageRoutes } from './routes.constants'
import { parseSearchURLQuery, SearchAggregationProps, SearchStreamingProps } from './search'
import { NotepadContainer } from './search/Notepad'
import { SearchQueryStateObserver } from './SearchQueryStateObserver'
import { useExperimentalFeatures } from './stores'
import { ThemePreferenceProps, useTheme } from './theme'
import { getExperimentalFeatures } from './util/get-experimental-features'
@ -274,6 +275,12 @@ export const Layout: React.FC<LegacyLayoutProps> = props => {
userHistory={userHistory}
/>
)}
<SearchQueryStateObserver
platformContext={props.platformContext}
searchContextsEnabled={props.searchAggregationEnabled}
setSelectedSearchContextSpec={props.setSelectedSearchContextSpec}
selectedSearchContextSpec={props.selectedSearchContextSpec}
/>
</div>
)
}

View File

@ -1,7 +1,7 @@
import React, { Suspense, useCallback, useRef, useState } from 'react'
import classNames from 'classnames'
import { matchPath, useLocation, Route, Routes, Navigate } from 'react-router-dom-v5-compat'
import { matchPath, useLocation, Route, Routes, Navigate } from 'react-router-dom'
import { Observable } from 'rxjs'
import { TabbedPanelContent } from '@sourcegraph/branded/src/components/panel/TabbedPanelContent'
@ -51,6 +51,7 @@ import type { LegacyLayoutRouteComponentProps, LayoutRouteProps } from './routes
import { EnterprisePageRoutes, PageRoutes } from './routes.constants'
import { parseSearchURLQuery, SearchAggregationProps, SearchStreamingProps } from './search'
import { NotepadContainer } from './search/Notepad'
import { SearchQueryStateObserver } from './SearchQueryStateObserver'
import type { SiteAdminAreaRoute } from './site-admin/SiteAdminArea'
import type { SiteAdminSideBarGroups } from './site-admin/SiteAdminSidebar'
import { useExperimentalFeatures } from './stores'
@ -328,6 +329,12 @@ export const LegacyLayout: React.FunctionComponent<React.PropsWithChildren<Legac
userHistory={userHistory}
/>
)}
<SearchQueryStateObserver
platformContext={props.platformContext}
searchContextsEnabled={props.searchAggregationEnabled}
setSelectedSearchContextSpec={props.setSelectedSearchContextSpec}
selectedSearchContextSpec={props.selectedSearchContextSpec}
/>
</div>
)
}

View File

@ -4,10 +4,9 @@ import * as React from 'react'
import { ApolloProvider } from '@apollo/client'
import ServerIcon from 'mdi-react/ServerIcon'
import { Router } from 'react-router'
import { CompatRouter, Routes, Route } from 'react-router-dom-v5-compat'
import { RouterProvider, createBrowserRouter, createRoutesFromElements, Route } from 'react-router-dom'
import { combineLatest, from, Subscription, fromEvent, of, Subject, Observable } from 'rxjs'
import { first, startWith, switchMap, map, distinctUntilChanged } from 'rxjs/operators'
import { startWith, switchMap } from 'rxjs/operators'
import { logger } from '@sourcegraph/common'
import { GraphQLClient, HTTPStatusError } from '@sourcegraph/http-client'
@ -36,7 +35,6 @@ import {
getDefaultSearchContextSpec,
} from '@sourcegraph/shared/src/search'
import { FilterType } from '@sourcegraph/shared/src/search/query/filters'
import { omitFilter } from '@sourcegraph/shared/src/search/query/transformer'
import { filterExists } from '@sourcegraph/shared/src/search/query/validate'
import { aggregateStreamingSearch } from '@sourcegraph/shared/src/search/stream'
import { EMPTY_SETTINGS_CASCADE, SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
@ -68,8 +66,9 @@ import type { RepoRevisionContainerRoute } from './repo/RepoRevisionContainer'
import type { RepoSettingsAreaRoute } from './repo/settings/RepoSettingsArea'
import type { RepoSettingsSideBarGroup } from './repo/settings/RepoSettingsSidebar'
import type { LayoutRouteProps } from './routes'
import { parseSearchURL, getQueryStateFromLocation, SearchAggregationProps } from './search'
import { parseSearchURL, SearchAggregationProps } from './search'
import { SearchResultsCacheProvider } from './search/results/SearchResultsCacheProvider'
import { GLOBAL_SEARCH_CONTEXT_SPEC } from './SearchQueryStateObserver'
import type { SiteAdminAreaRoute } from './site-admin/SiteAdminArea'
import type { SiteAdminSideBarGroups } from './site-admin/SiteAdminSidebar'
import {
@ -77,18 +76,13 @@ import {
setExperimentalFeaturesFromSettings,
getExperimentalFeatures,
useNavbarQueryState,
observeStore,
useExperimentalFeatures,
} from './stores'
import { setQueryStateFromURL } from './stores/navbarSearchQueryState'
import { eventLogger } from './tracking/eventLogger'
import type { UserAreaRoute } from './user/area/UserArea'
import type { UserAreaHeaderNavItem } from './user/area/UserAreaHeader'
import type { UserSettingsAreaRoute } from './user/settings/UserSettingsArea'
import type { UserSettingsSidebarItems } from './user/settings/UserSettingsSidebar'
import { UserSessionStores } from './UserSessionStores'
import { globalHistory } from './util/globalHistory'
import { observeLocation } from './util/location'
import { siteSubjectNoAdmin, viewerSubjectFromSettings } from './util/settings'
import styles from './LegacySourcegraphWebApp.module.scss'
@ -160,8 +154,6 @@ const WILDCARD_THEME: WildcardTheme = {
isBranded: true,
}
const GLOBAL_SEARCH_CONTEXT_SPEC = 'global'
setLinkComponent(RouterLink)
/**
@ -268,58 +260,6 @@ export class LegacySourcegraphWebApp extends React.Component<
logger.error('Error sending search context to extensions!', error)
})
// Update search query state whenever the URL changes
this.subscriptions.add(
combineLatest([
observeStore(useExperimentalFeatures).pipe(
map(([features]) => features.searchQueryInput === 'experimental'),
// This ensures that the query stays unmodified until we know
// whether the feature flag is set or not.
startWith(true),
distinctUntilChanged()
),
getQueryStateFromLocation({
location: observeLocation(globalHistory).pipe(startWith(globalHistory.location)),
isSearchContextAvailable: (searchContext: string) =>
this.props.searchContextsEnabled
? isSearchContextSpecAvailable({
spec: searchContext,
platformContext: this.platformContext,
})
.pipe(first())
.toPromise()
: Promise.resolve(false),
}),
]).subscribe(([enableExperimentalSearchInput, parsedSearchURLAndContext]) => {
if (parsedSearchURLAndContext.query) {
// Only override filters and update query from URL if there
// is a search query.
if (!parsedSearchURLAndContext.searchContextSpec) {
// If no search context is present we have to fall back
// to the global search context to match the server
// behavior.
this.setSelectedSearchContextSpec(GLOBAL_SEARCH_CONTEXT_SPEC)
} else if (
parsedSearchURLAndContext.searchContextSpec.spec !== this.state.selectedSearchContextSpec
) {
this.setSelectedSearchContextSpec(parsedSearchURLAndContext.searchContextSpec.spec)
}
const processedQuery =
!enableExperimentalSearchInput &&
parsedSearchURLAndContext.searchContextSpec &&
this.props.searchContextsEnabled
? omitFilter(
parsedSearchURLAndContext.query,
parsedSearchURLAndContext.searchContextSpec.filter
)
: parsedSearchURLAndContext.query
setQueryStateFromURL(parsedSearchURLAndContext, processedQuery)
}
})
)
this.userRepositoriesUpdates.next()
}
@ -359,6 +299,45 @@ export class LegacySourcegraphWebApp extends React.Component<
return null
}
const router = createBrowserRouter(
createRoutesFromElements(
<Route
path="*"
element={
<LegacyLayout
{...this.props}
authenticatedUser={authenticatedUser}
viewerSubject={this.state.viewerSubject}
settingsCascade={this.state.settingsCascade}
batchChangesEnabled={this.props.batchChangesEnabled}
batchChangesExecutionEnabled={isBatchChangesExecutionEnabled(this.state.settingsCascade)}
batchChangesWebhookLogsEnabled={window.context.batchChangesWebhookLogsEnabled}
// Search query
fetchHighlightedFileLineRanges={this.fetchHighlightedFileLineRanges}
// Extensions
platformContext={this.platformContext}
extensionsController={this.extensionsController}
telemetryService={eventLogger}
isSourcegraphDotCom={window.context.sourcegraphDotComMode}
searchContextsEnabled={this.props.searchContextsEnabled}
selectedSearchContextSpec={this.getSelectedSearchContextSpec()}
setSelectedSearchContextSpec={this.setSelectedSearchContextSpec}
getUserSearchContextNamespaces={getUserSearchContextNamespaces}
fetchSearchContexts={fetchSearchContexts}
fetchSearchContextBySpec={fetchSearchContextBySpec}
fetchSearchContext={fetchSearchContext}
createSearchContext={createSearchContext}
updateSearchContext={updateSearchContext}
deleteSearchContext={deleteSearchContext}
isSearchContextSpecAvailable={isSearchContextSpecAvailable}
globbing={this.state.globbing}
streamSearch={aggregateStreamingSearch}
/>
}
/>
)
)
return (
<ComponentsComposer
components={[
@ -376,48 +355,7 @@ export class LegacySourcegraphWebApp extends React.Component<
/* eslint-enable react/no-children-prop, react/jsx-key */
]}
>
<Router history={globalHistory}>
<CompatRouter>
<Routes>
<Route
path="*"
element={
<LegacyLayout
{...this.props}
authenticatedUser={authenticatedUser}
viewerSubject={this.state.viewerSubject}
settingsCascade={this.state.settingsCascade}
batchChangesEnabled={this.props.batchChangesEnabled}
batchChangesExecutionEnabled={isBatchChangesExecutionEnabled(
this.state.settingsCascade
)}
batchChangesWebhookLogsEnabled={window.context.batchChangesWebhookLogsEnabled}
// Search query
fetchHighlightedFileLineRanges={this.fetchHighlightedFileLineRanges}
// Extensions
platformContext={this.platformContext}
extensionsController={this.extensionsController}
telemetryService={eventLogger}
isSourcegraphDotCom={window.context.sourcegraphDotComMode}
searchContextsEnabled={this.props.searchContextsEnabled}
selectedSearchContextSpec={this.getSelectedSearchContextSpec()}
setSelectedSearchContextSpec={this.setSelectedSearchContextSpec}
getUserSearchContextNamespaces={getUserSearchContextNamespaces}
fetchSearchContexts={fetchSearchContexts}
fetchSearchContextBySpec={fetchSearchContextBySpec}
fetchSearchContext={fetchSearchContext}
createSearchContext={createSearchContext}
updateSearchContext={updateSearchContext}
deleteSearchContext={deleteSearchContext}
isSearchContextSpecAvailable={isSearchContextSpecAvailable}
globbing={this.state.globbing}
streamSearch={aggregateStreamingSearch}
/>
}
/>
</Routes>
</CompatRouter>
</Router>
<RouterProvider router={router} />
{this.extensionsController !== null && window.context.enableLegacyExtensions ? (
<Notifications
key={2}

View File

@ -0,0 +1,99 @@
import { FC, useEffect, useRef, useState } from 'react'
import { Location, useLocation } from 'react-router-dom'
import { BehaviorSubject } from 'rxjs'
import { first } from 'rxjs/operators'
import { PlatformContext } from '@sourcegraph/shared/src/platform/context'
import { isSearchContextSpecAvailable } from '@sourcegraph/shared/src/search'
import { omitFilter } from '@sourcegraph/shared/src/search/query/transformer'
import { getQueryStateFromLocation } from './search'
import { useExperimentalFeatures } from './stores'
import { setQueryStateFromURL } from './stores/navbarSearchQueryState'
export const GLOBAL_SEARCH_CONTEXT_SPEC = 'global'
interface SearchQueryStateObserverProps {
searchContextsEnabled: boolean
platformContext: PlatformContext
selectedSearchContextSpec?: string
setSelectedSearchContextSpec: (spec: string) => void
}
// Update search query state whenever the URL changes
export const SearchQueryStateObserver: FC<SearchQueryStateObserverProps> = props => {
const { searchContextsEnabled, platformContext, setSelectedSearchContextSpec, selectedSearchContextSpec } = props
const location = useLocation()
const selectedSearchContextSpecRef = useRef(selectedSearchContextSpec)
selectedSearchContextSpecRef.current = selectedSearchContextSpec
const { searchQueryInput, isInitialized } = useExperimentalFeatures()
// This ensures that the query stays unmodified until we know
// whether the feature flag is set or not.
const enableExperimentalSearchInput = isInitialized ? searchQueryInput === 'experimental' : true
const enableExperimentalSearchInputRef = useRef(enableExperimentalSearchInput)
enableExperimentalSearchInputRef.current = enableExperimentalSearchInput
// Create `locationSubject` once on mount. New values are provided in the `useEffect` hook.
// eslint-disable-next-line react-hooks/exhaustive-deps
const [locationSubject] = useState(() => new BehaviorSubject<Location>(location))
useEffect(() => {
locationSubject.next(location)
}, [location, locationSubject])
useEffect(() => {
const subscription = getQueryStateFromLocation({
location: locationSubject,
isSearchContextAvailable: (searchContext: string) =>
searchContextsEnabled
? isSearchContextSpecAvailable({
spec: searchContext,
platformContext,
})
.pipe(first())
.toPromise()
: Promise.resolve(false),
}).subscribe(parsedSearchURLAndContext => {
if (parsedSearchURLAndContext.query) {
// Only override filters and update query from URL if there
// is a search query.
if (!parsedSearchURLAndContext.searchContextSpec) {
// If no search context is present we have to fall back
// to the global search context to match the server
// behavior.
setSelectedSearchContextSpec(GLOBAL_SEARCH_CONTEXT_SPEC)
} else if (parsedSearchURLAndContext.searchContextSpec.spec !== selectedSearchContextSpecRef.current) {
setSelectedSearchContextSpec(parsedSearchURLAndContext.searchContextSpec.spec)
}
const processedQuery =
!enableExperimentalSearchInputRef.current &&
parsedSearchURLAndContext.searchContextSpec &&
searchContextsEnabled
? omitFilter(
parsedSearchURLAndContext.query,
parsedSearchURLAndContext.searchContextSpec.filter
)
: parsedSearchURLAndContext.query
setQueryStateFromURL(parsedSearchURLAndContext, processedQuery)
}
})
return () => subscription.unsubscribe()
}, [
locationSubject,
platformContext,
searchContextsEnabled,
selectedSearchContextSpecRef,
enableExperimentalSearchInputRef,
setSelectedSearchContextSpec,
])
return null
}

View File

@ -5,12 +5,11 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { ApolloProvider } from '@apollo/client'
import ServerIcon from 'mdi-react/ServerIcon'
import { Router } from 'react-router'
import { CompatRouter, Routes, Route } from 'react-router-dom-v5-compat'
import { RouterProvider, createBrowserRouter } from 'react-router-dom'
import { combineLatest, from, Subscription, fromEvent, of, Subject, Observable } from 'rxjs'
import { distinctUntilChanged, first, map, startWith, switchMap } from 'rxjs/operators'
import { startWith, switchMap } from 'rxjs/operators'
import { isMacPlatform, logger } from '@sourcegraph/common'
import { isTruthy, isMacPlatform, logger } from '@sourcegraph/common'
import { GraphQLClient, HTTPStatusError } from '@sourcegraph/http-client'
import { SharedSpanName, TraceSpanProvider } from '@sourcegraph/observability-client'
import { FetchFileParameters, fetchHighlightedFileLineRanges } from '@sourcegraph/shared/src/backend/file'
@ -30,7 +29,6 @@ import {
getDefaultSearchContextSpec,
} from '@sourcegraph/shared/src/search'
import { FilterType } from '@sourcegraph/shared/src/search/query/filters'
import { omitFilter } from '@sourcegraph/shared/src/search/query/transformer'
import { filterExists } from '@sourcegraph/shared/src/search/query/validate'
import { aggregateStreamingSearch } from '@sourcegraph/shared/src/search/stream'
import {
@ -68,8 +66,9 @@ import type { RepoRevisionContainerRoute } from './repo/RepoRevisionContainer'
import type { RepoSettingsAreaRoute } from './repo/settings/RepoSettingsArea'
import type { RepoSettingsSideBarGroup } from './repo/settings/RepoSettingsSidebar'
import type { LayoutRouteProps, LegacyLayoutRouteComponentProps } from './routes'
import { parseSearchURL, getQueryStateFromLocation, SearchAggregationProps } from './search'
import { parseSearchURL, SearchAggregationProps } from './search'
import { SearchResultsCacheProvider } from './search/results/SearchResultsCacheProvider'
import { GLOBAL_SEARCH_CONTEXT_SPEC } from './SearchQueryStateObserver'
import type { SiteAdminAreaRoute } from './site-admin/SiteAdminArea'
import type { SiteAdminSideBarGroups } from './site-admin/SiteAdminSidebar'
import {
@ -77,10 +76,7 @@ import {
setExperimentalFeaturesFromSettings,
getExperimentalFeatures,
useNavbarQueryState,
observeStore,
useExperimentalFeatures,
} from './stores'
import { setQueryStateFromURL } from './stores/navbarSearchQueryState'
import { useThemeProps } from './theme'
import { eventLogger } from './tracking/eventLogger'
import type { UserAreaRoute } from './user/area/UserArea'
@ -88,8 +84,6 @@ import type { UserAreaHeaderNavItem } from './user/area/UserAreaHeader'
import type { UserSettingsAreaRoute } from './user/settings/UserSettingsArea'
import type { UserSettingsSidebarItems } from './user/settings/UserSettingsSidebar'
import { UserSessionStores } from './UserSessionStores'
import { globalHistory } from './util/globalHistory'
import { observeLocation } from './util/location'
import { siteSubjectNoAdmin, viewerSubjectFromSettings } from './util/settings'
import styles from './LegacySourcegraphWebApp.module.scss'
@ -125,8 +119,6 @@ const WILDCARD_THEME: WildcardTheme = {
isBranded: true,
}
const GLOBAL_SEARCH_CONTEXT_SPEC = 'global'
setLinkComponent(RouterLink)
export const SourcegraphWebApp: React.FC<SourcegraphWebAppProps> = props => {
@ -298,55 +290,6 @@ export const SourcegraphWebApp: React.FC<SourcegraphWebAppProps> = props => {
setWorkspaceSearchContext(selectedSearchContextSpec)
// Update search query state whenever the URL changes
subscriptions.add(
combineLatest([
observeStore(useExperimentalFeatures).pipe(
map(([features]) => features.searchQueryInput === 'experimental'),
// This ensures that the query stays unmodified until we know
// whether the feature flag is set or not.
startWith(true),
distinctUntilChanged()
),
getQueryStateFromLocation({
location: observeLocation(globalHistory).pipe(startWith(globalHistory.location)),
isSearchContextAvailable: (searchContext: string) =>
props.searchContextsEnabled
? isSearchContextSpecAvailable({ spec: searchContext, platformContext })
.pipe(first())
.toPromise()
: Promise.resolve(false),
}),
]).subscribe(([enableExperimentalQueryInput, parsedSearchURLAndContext]) => {
if (parsedSearchURLAndContext.query) {
// Only override filters and update query from URL if there
// is a search query.
if (!parsedSearchURLAndContext.searchContextSpec) {
// If no search context is present we have to fall back
// to the global search context to match the server
// behavior.
setSelectedSearchContextSpec(GLOBAL_SEARCH_CONTEXT_SPEC)
} else if (
parsedSearchURLAndContext.searchContextSpec.spec !== selectedSearchContextSpecRef.current
) {
setSelectedSearchContextSpec(parsedSearchURLAndContext.searchContextSpec.spec)
}
const processedQuery =
!enableExperimentalQueryInput &&
parsedSearchURLAndContext.searchContextSpec &&
props.searchContextsEnabled
? omitFilter(
parsedSearchURLAndContext.query,
parsedSearchURLAndContext.searchContextSpec.filter
)
: parsedSearchURLAndContext.query
setQueryStateFromURL(parsedSearchURLAndContext, processedQuery)
}
})
)
userRepositoriesUpdates.next()
return () => subscriptions.unsubscribe()
@ -421,6 +364,55 @@ export const SourcegraphWebApp: React.FC<SourcegraphWebAppProps> = props => {
return null
}
const router = createBrowserRouter([
{
element: (
<Layout
authenticatedUser={resolvedAuthenticatedUser}
viewerSubject={viewerSubject}
settingsCascade={settingsCascade}
batchChangesEnabled={props.batchChangesEnabled}
batchChangesExecutionEnabled={isBatchChangesExecutionEnabled(settingsCascade)}
batchChangesWebhookLogsEnabled={window.context.batchChangesWebhookLogsEnabled}
// Search query
fetchHighlightedFileLineRanges={_fetchHighlightedFileLineRanges}
// Extensions
platformContext={platformContext}
extensionsController={null}
telemetryService={eventLogger}
isSourcegraphDotCom={window.context.sourcegraphDotComMode}
searchContextsEnabled={props.searchContextsEnabled}
selectedSearchContextSpec={getSelectedSearchContextSpec()}
setSelectedSearchContextSpec={setSelectedSearchContextSpec}
getUserSearchContextNamespaces={getUserSearchContextNamespaces}
fetchSearchContexts={fetchSearchContexts}
fetchSearchContextBySpec={fetchSearchContextBySpec}
fetchSearchContext={fetchSearchContext}
createSearchContext={createSearchContext}
updateSearchContext={updateSearchContext}
deleteSearchContext={deleteSearchContext}
isSearchContextSpecAvailable={isSearchContextSpecAvailable}
globbing={globbing}
streamSearch={aggregateStreamingSearch}
codeIntelligenceEnabled={!!props.codeInsightsEnabled}
notebooksEnabled={props.notebooksEnabled}
codeMonitoringEnabled={props.codeMonitoringEnabled}
searchAggregationEnabled={props.searchAggregationEnabled}
themeProps={themeProps}
/>
),
children: props.routes
.map(
({ condition = () => true, render, path }) =>
condition(context) && {
path: path.slice(1), // remove leading slash
element: render(context),
}
)
.filter(isTruthy),
},
])
return (
<ComponentsComposer
components={[
@ -438,61 +430,7 @@ export const SourcegraphWebApp: React.FC<SourcegraphWebAppProps> = props => {
/* eslint-enable react/no-children-prop, react/jsx-key */
]}
>
<Router history={globalHistory}>
<CompatRouter>
<Routes>
<Route
path="*"
element={
<Layout
authenticatedUser={resolvedAuthenticatedUser}
viewerSubject={viewerSubject}
settingsCascade={settingsCascade}
batchChangesEnabled={props.batchChangesEnabled}
batchChangesExecutionEnabled={isBatchChangesExecutionEnabled(settingsCascade)}
batchChangesWebhookLogsEnabled={window.context.batchChangesWebhookLogsEnabled}
// Search query
fetchHighlightedFileLineRanges={_fetchHighlightedFileLineRanges}
// Extensions
platformContext={platformContext}
extensionsController={null}
telemetryService={eventLogger}
isSourcegraphDotCom={window.context.sourcegraphDotComMode}
searchContextsEnabled={props.searchContextsEnabled}
selectedSearchContextSpec={getSelectedSearchContextSpec()}
setSelectedSearchContextSpec={setSelectedSearchContextSpec}
getUserSearchContextNamespaces={getUserSearchContextNamespaces}
fetchSearchContexts={fetchSearchContexts}
fetchSearchContextBySpec={fetchSearchContextBySpec}
fetchSearchContext={fetchSearchContext}
createSearchContext={createSearchContext}
updateSearchContext={updateSearchContext}
deleteSearchContext={deleteSearchContext}
isSearchContextSpecAvailable={isSearchContextSpecAvailable}
globbing={globbing}
streamSearch={aggregateStreamingSearch}
codeIntelligenceEnabled={!!props.codeInsightsEnabled}
notebooksEnabled={props.notebooksEnabled}
codeMonitoringEnabled={props.codeMonitoringEnabled}
searchAggregationEnabled={props.searchAggregationEnabled}
themeProps={themeProps}
/>
}
>
{props.routes.map(
({ condition = () => true, ...route }) =>
condition(context) && (
<Route
key="hardcoded-key" // see https://github.com/ReactTraining/react-router/issues/4578#issuecomment-334489490
path={route.path.slice(1)} // remove leading slash
element={route.render(context)}
/>
)
)}
</Route>
</Routes>
</CompatRouter>
</Router>
<RouterProvider router={router} />
<UserSessionStores />
</ComponentsComposer>
)

View File

@ -2,7 +2,7 @@ import * as React from 'react'
import * as _graphiqlModule from 'graphiql' // type only
import * as H from 'history'
import { useNavigate, useLocation, type NavigateFunction } from 'react-router-dom-v5-compat'
import { useNavigate, useLocation, type NavigateFunction } from 'react-router-dom'
import { from as fromPromise, Subject, Subscription } from 'rxjs'
import { catchError, debounceTime } from 'rxjs/operators'

View File

@ -1,7 +1,7 @@
import * as React from 'react'
import classNames from 'classnames'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { asError, ErrorLike, isErrorLike, logger } from '@sourcegraph/common'
import { Button, Link, LoadingSpinner, Alert, Text, Input, ErrorAlert, Form } from '@sourcegraph/wildcard'

View File

@ -1,5 +1,5 @@
import { within } from '@testing-library/dom'
import { Route, Routes } from 'react-router-dom-v5-compat'
import { Route, Routes } from 'react-router-dom'
import { renderWithBrandedContext } from '@sourcegraph/wildcard/src/testing'

View File

@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'
import { mdiBitbucket, mdiGithub, mdiGitlab, mdiEmail } from '@mdi/js'
import classNames from 'classnames'
import { partition } from 'lodash'
import { Navigate, useLocation, useSearchParams } from 'react-router-dom-v5-compat'
import { Navigate, useLocation, useSearchParams } from 'react-router-dom'
import { Alert, Icon, Text, Link, Button, ErrorAlert, AnchorLink } from '@sourcegraph/wildcard'

View File

@ -1,4 +1,4 @@
import { Route, Routes } from 'react-router-dom-v5-compat'
import { Route, Routes } from 'react-router-dom'
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { MockedTestProvider } from '@sourcegraph/shared/src/testing/apollo'

View File

@ -1,7 +1,7 @@
import React, { useEffect } from 'react'
import classNames from 'classnames'
import { Navigate, useLocation } from 'react-router-dom-v5-compat'
import { Navigate, useLocation } from 'react-router-dom'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { ThemeProps } from '@sourcegraph/shared/src/theme'

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'
import { Navigate, useLocation, useParams } from 'react-router-dom-v5-compat'
import { Navigate, useLocation, useParams } from 'react-router-dom'
import { Alert, Link, LoadingSpinner, ErrorAlert } from '@sourcegraph/wildcard'

View File

@ -1,7 +1,7 @@
import React, { useCallback, useState } from 'react'
import classNames from 'classnames'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { asError, logger } from '@sourcegraph/common'
import { Label, Button, LoadingSpinner, Link, Text, Input, Form } from '@sourcegraph/wildcard'

View File

@ -1,6 +1,6 @@
import React from 'react'
import { Navigate } from 'react-router-dom-v5-compat'
import { Navigate } from 'react-router-dom'
import { AuthenticatedUser } from '../auth'

View File

@ -1,15 +0,0 @@
import React from 'react'
import { LinkWithIcon } from '../components/LinkWithIcon'
import { CodeMonitoringLogo } from './CodeMonitoringLogo'
export const CodeMonitoringNavItem: React.FunctionComponent<React.PropsWithChildren<unknown>> = () => (
<LinkWithIcon
to="/code-monitoring"
text="Monitoring"
icon={CodeMonitoringLogo}
className="nav-link text-decoration-none"
activeClassName="active"
/>
)

View File

@ -1,4 +1,5 @@
import { within, fireEvent } from '@testing-library/react'
import { createPath } from 'react-router-dom'
import { MockedTestProvider, waitForNextApolloResponse } from '@sourcegraph/shared/src/testing/apollo'
import '@sourcegraph/shared/dev/mockReactVisibilitySensor'
@ -53,7 +54,7 @@ describe('ReferencesPanel', () => {
})
it('renders a code view when clicking on a location', async () => {
const { history, ...result } = await renderReferencesPanel()
const { locationRef, ...result } = await renderReferencesPanel()
const definitionsList = result.getByTestId('definitions')
const referencesList = result.getByTestId('references')
@ -95,11 +96,11 @@ describe('ReferencesPanel', () => {
expect(codeView).toHaveTextContent('package diff import')
// Assert the current URL points at the reference panel
expect(history.createHref(history.location)).toBe(
expect(createPath(locationRef.current!)).toBe(
'/github.com/sourcegraph/go-diff@9d1f353a285b3094bc33bdae277a19aedabe8b71/-/blob/diff/diff.go?L16:2&subtree=true#tab=references'
)
// Click on reference the second time promotes the active location to the URL (and main blob view)
fireEvent.click(referenceButton)
expect(history.createHref(history.location)).toBe(fullReferenceURL)
expect(createPath(locationRef.current!)).toBe(fullReferenceURL)
})
})

View File

@ -4,7 +4,7 @@ import { mdiArrowCollapseRight, mdiChevronDown, mdiChevronUp, mdiFilterOutline,
import classNames from 'classnames'
import * as H from 'history'
import { capitalize, uniqBy } from 'lodash'
import { useNavigate, useLocation } from 'react-router-dom-v5-compat'
import { useNavigate, useLocation } from 'react-router-dom'
import { Observable, of } from 'rxjs'
import { map } from 'rxjs/operators'

View File

@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react'
import { mdiSourceRepositoryMultiple, mdiGithub, mdiGitlab, mdiBitbucket } from '@mdi/js'
import classNames from 'classnames'
import { useNavigate, useLocation } from 'react-router-dom-v5-compat'
import { useNavigate, useLocation } from 'react-router-dom'
import { catchError, startWith } from 'rxjs/operators'
import { SyntaxHighlightedSearchQuery } from '@sourcegraph/branded'

View File

@ -1,16 +1,12 @@
import { FC, useEffect, useState, useRef } from 'react'
import { FC, useState, useCallback } from 'react'
import * as H from 'history'
import { useNavigate } from 'react-router-dom-v5-compat'
import { Location, useNavigate, unstable_useBlocker as useBlocker, unstable_BlockerFunction } from 'react-router-dom'
import { Button, Modal, H3 } from '@sourcegraph/wildcard'
import { globalHistory } from '../util/globalHistory'
type Func = () => void
interface Props {
message: string
when: Func | boolean
when: boolean | (() => boolean)
header?: string
button_ok_text?: string
button_cancel_text?: string
@ -22,42 +18,42 @@ export const AwayPrompt: FC<Props> = props => {
const { message, when, header = 'Navigate away?', button_ok_text = 'OK', button_cancel_text = 'Cancel' } = props
const navigate = useNavigate()
const [pendingLocation, setPendingLocation] = useState<H.Location>()
const unblock = useRef<() => void>()
const [pendingLocation, setPendingLocation] = useState<Location>()
const blocker = useBlocker(
useCallback<unstable_BlockerFunction>(
({ currentLocation, nextLocation }) => {
if (nextLocation.state === ALLOW_NAVIGATION) {
return false
}
const shouldBlock = typeof when === 'boolean' ? when : when()
if (shouldBlock) {
setPendingLocation(nextLocation)
// prevent navigation for now - pop-up is shown
return true
}
return false
},
[when]
)
)
const closeModal = (shouldNavigate: boolean): void => {
// close modal
setPendingLocation(undefined)
if (pendingLocation && shouldNavigate) {
unblock.current?.()
if (blocker.state === 'blocked') {
blocker.reset()
}
navigate(pendingLocation)
}
}
useEffect(() => {
unblock.current = globalHistory.block(location => {
if (location.state === ALLOW_NAVIGATION) {
return unblock.current?.()
}
const shouldBlock = typeof when === 'boolean' ? when : when()
if (shouldBlock) {
setPendingLocation(location)
// prevent navigation for now - pop-up is shown
return false
}
return unblock.current?.()
})
return () => {
unblock.current?.()
}
}, [when])
return pendingLocation ? (
<Modal aria-labelledby={header}>
<H3 className="text-dark mb-4">{header}</H3>

View File

@ -3,7 +3,7 @@ import React, { FC, useState, useEffect, useMemo, useCallback } from 'react'
import { mdiChevronRight } from '@mdi/js'
import classNames from 'classnames'
import { sortBy } from 'lodash'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { Unsubscribable } from 'rxjs'
import { isDefined } from '@sourcegraph/common'

View File

@ -2,7 +2,7 @@ import { useEffect } from 'react'
import { cleanup, fireEvent, render, screen, waitFor, act } from '@testing-library/react'
import * as H from 'history'
import { MemoryRouter, useLocation } from 'react-router-dom-v5-compat'
import { MemoryRouter, useLocation } from 'react-router-dom'
import { BehaviorSubject } from 'rxjs'
import sinon from 'sinon'

View File

@ -2,7 +2,7 @@ import * as React from 'react'
import * as H from 'history'
import { isEqual, uniq } from 'lodash'
import { NavigateFunction, useLocation, useNavigate } from 'react-router-dom-v5-compat'
import { NavigateFunction, useLocation, useNavigate } from 'react-router-dom'
import { combineLatest, merge, Observable, of, Subject, Subscription } from 'rxjs'
import {
catchError,

View File

@ -251,7 +251,7 @@ describe('usePageSwitcherPagination', () => {
expect(page.getByText('Next page')).toBeVisible()
expect(page.getByText('Last page')).toBeVisible()
expect(page.history.location.search).toBe('')
expect(page.locationRef.current?.search).toBe('')
})
it('supports forward pagination', async () => {
@ -270,7 +270,7 @@ describe('usePageSwitcherPagination', () => {
expect(page.getByText('Next page')).toBeVisible()
expect(page.getByText('Last page')).toBeVisible()
expect(page.history.location.search).toBe(`?after=${getCursorForId('3')}`)
expect(page.locationRef.current?.search).toBe(`?after=${getCursorForId('3')}`)
await goToNextPage(page)
@ -285,7 +285,7 @@ describe('usePageSwitcherPagination', () => {
expect(page.getByText('Next page')).toBeVisible()
expect(page.getByText('Last page')).toBeVisible()
expect(page.history.location.search).toBe(`?after=${getCursorForId('6')}`)
expect(page.locationRef.current?.search).toBe(`?after=${getCursorForId('6')}`)
await goToNextPage(page)
@ -298,7 +298,7 @@ describe('usePageSwitcherPagination', () => {
expect(() => page.getByText('Next page')).toThrowError(/Unable to find an element/)
expect(page.getByText('Last page')).toBeVisible()
expect(page.history.location.search).toBe(`?after=${getCursorForId('9')}`)
expect(page.locationRef.current?.search).toBe(`?after=${getCursorForId('9')}`)
})
it('supports restoration from forward pagination URL', async () => {
@ -332,7 +332,7 @@ describe('usePageSwitcherPagination', () => {
expect(page.getByText('Next page')).toBeVisible()
expect(page.getByText('Last page')).toBeVisible()
expect(page.history.location.search).toBe('')
expect(page.locationRef.current?.search).toBe('')
})
it('supports jumping to the last page', async () => {
@ -351,7 +351,7 @@ describe('usePageSwitcherPagination', () => {
expect(() => page.getByText('Next page')).toThrowError(/Unable to find an element/)
expect(page.getByText('Last page')).toBeVisible()
expect(page.history.location.search).toBe('?last=3')
expect(page.locationRef.current?.search).toBe('?last=3')
})
it('supports restoration from last page URL', async () => {
@ -386,7 +386,7 @@ describe('usePageSwitcherPagination', () => {
expect(page.getByText('Next page')).toBeVisible()
expect(page.getByText('Last page')).toBeVisible()
expect(page.history.location.search).toBe(`?before=${getCursorForId('8')}`)
expect(page.locationRef.current?.search).toBe(`?before=${getCursorForId('8')}`)
await goToPreviousPage(page)
@ -401,7 +401,7 @@ describe('usePageSwitcherPagination', () => {
expect(page.getByText('Next page')).toBeVisible()
expect(page.getByText('Last page')).toBeVisible()
expect(page.history.location.search).toBe(`?before=${getCursorForId('5')}`)
expect(page.locationRef.current?.search).toBe(`?before=${getCursorForId('5')}`)
await goToPreviousPage(page)
@ -414,7 +414,7 @@ describe('usePageSwitcherPagination', () => {
expect(page.getByText('Next page')).toBeVisible()
expect(page.getByText('Last page')).toBeVisible()
expect(page.history.location.search).toBe(`?before=${getCursorForId('2')}`)
expect(page.locationRef.current?.search).toBe(`?before=${getCursorForId('2')}`)
})
it('supports restoration from backward pagination URL', async () => {
@ -448,6 +448,6 @@ describe('usePageSwitcherPagination', () => {
await goToLastPage(page)
expect(page.history.location.search).toBe(`?after=${getCursorForId('6')}`)
expect(page.locationRef.current?.search).toBe(`?after=${getCursorForId('6')}`)
})
})

View File

@ -1,7 +1,7 @@
import { useCallback, useMemo } from 'react'
import { ApolloError, WatchQueryFetchPolicy } from '@apollo/client'
import { useNavigate, useLocation } from 'react-router-dom-v5-compat'
import { useNavigate, useLocation } from 'react-router-dom'
import { GraphQLResult, useQuery } from '@sourcegraph/http-client'

View File

@ -177,7 +177,7 @@ describe('useShowMorePagination', () => {
expect(queries.getByText('repo-A')).toBeVisible()
expect(queries.getByText('Total count: 4')).toBeVisible()
expect(queries.getByText('Fetch more')).toBeVisible()
expect(queries.history.location.search).toBe('')
expect(queries.locationRef.current?.search).toBe('')
})
it('fetches next page of results correctly', async () => {
@ -194,7 +194,7 @@ describe('useShowMorePagination', () => {
expect(queries.getByText('Fetch more')).toBeVisible()
// URL updates to match visible results
expect(queries.history.location.search).toBe('?visible=2')
expect(queries.locationRef.current?.search).toBe('?visible=2')
})
it('fetches final page of results correctly', async () => {
@ -217,7 +217,7 @@ describe('useShowMorePagination', () => {
expect(queries.queryByText('Fetch more')).not.toBeInTheDocument()
// URL updates to match visible results
expect(queries.history.location.search).toBe('?visible=4')
expect(queries.locationRef.current?.search).toBe('?visible=4')
})
it('fetches correct amount of results when navigating directly with a URL', async () => {
@ -249,7 +249,7 @@ describe('useShowMorePagination', () => {
expect(queries.getByText('Total count: 4')).toBeVisible()
// URL should be overidden
expect(queries.history.location.search).toBe('?visible=4')
expect(queries.locationRef.current?.search).toBe('?visible=4')
})
})
@ -290,7 +290,7 @@ describe('useShowMorePagination', () => {
expect(queries.getByText('repo-A')).toBeVisible()
expect(queries.getByText('Total count: 4')).toBeVisible()
expect(queries.getByText('Fetch more')).toBeVisible()
expect(queries.history.location.search).toBe('')
expect(queries.locationRef.current?.search).toBe('')
})
it('fetches next page of results correctly', async () => {
@ -307,7 +307,7 @@ describe('useShowMorePagination', () => {
expect(queries.getByText('Fetch more')).toBeVisible()
// URL updates to match the new request
expect(queries.history.location.search).toBe('?first=2')
expect(queries.locationRef.current?.search).toBe('?first=2')
})
it('fetches final page of results correctly', async () => {
@ -329,7 +329,7 @@ describe('useShowMorePagination', () => {
expect(queries.queryByText('Fetch more')).not.toBeInTheDocument()
// URL updates to match the new request
expect(queries.history.location.search).toBe('?first=4')
expect(queries.locationRef.current?.search).toBe('?first=4')
})
it('fetches correct amount of results when navigating directly with a URL', async () => {
@ -349,7 +349,7 @@ describe('useShowMorePagination', () => {
expect(queries.getByText('Total count: 4')).toBeVisible()
// URL should be overidden
expect(queries.history.location.search).toBe('?first=4')
expect(queries.locationRef.current?.search).toBe('?first=4')
})
})
})

View File

@ -1,6 +1,6 @@
import { useEffect } from 'react'
import { useNavigate, useLocation } from 'react-router-dom-v5-compat'
import { useNavigate, useLocation } from 'react-router-dom'
import { getUrlQuery, GetUrlQueryParameters } from '../utils'

View File

@ -1,8 +0,0 @@
.link-with-icon {
padding: var(--nav-link-padding-y) var(--nav-link-padding-x);
&:global(.active):not(:global(.focus)):not(:focus) {
// To override default active btn shadow
box-shadow: none !important;
}
}

View File

@ -1,36 +0,0 @@
import React from 'react'
import classNames from 'classnames'
import { kebabCase } from 'lodash'
import { NavLink, NavLinkProps } from 'react-router-dom'
import { Button, Icon } from '@sourcegraph/wildcard'
import styles from './LinkWithIcon.module.scss'
interface LinkWithIconProps extends NavLinkProps {
text: string
icon: React.ComponentType<{ className?: string }>
}
/**
* A link displaying an icon along with text.
*/
export const LinkWithIcon: React.FunctionComponent<React.PropsWithChildren<LinkWithIconProps>> = props => {
const { to, exact, text, className, activeClassName, icon: linkIcon } = props
return (
<Button
as={NavLink}
to={to}
exact={exact}
className={classNames('d-flex', 'align-items-center', styles.linkWithIcon, className)}
activeClassName={activeClassName}
variant="link"
data-testid={kebabCase(text)}
>
<Icon className="mr-1" as={linkIcon} aria-hidden={true} />
<span className="inline-block">{text}</span>
</Button>
)
}

View File

@ -1,6 +1,6 @@
import { FC } from 'react'
import { Location, Navigate, Params, useLocation, useParams } from 'react-router-dom-v5-compat'
import { Location, Navigate, Params, useLocation, useParams } from 'react-router-dom'
interface RedirectRouteProps {
getRedirectURL: (data: { location: Location; params: Params }) => string

View File

@ -1,9 +1,9 @@
import React from 'react'
import React, { FC, PropsWithChildren } from 'react'
import { mdiMenuDown, mdiMenuUp } from '@mdi/js'
import classNames from 'classnames'
import kebabCase from 'lodash/kebabCase'
import { useRouteMatch } from 'react-router-dom'
import { useMatch } from 'react-router-dom'
import {
AnchorLink,
@ -19,20 +19,28 @@ import {
import styles from './Sidebar.module.scss'
interface SidebarNavItemProps {
to: string
className?: string
source?: string
onClick?: () => void
exact?: boolean
}
/**
* Item of `SideBarGroup`.
*/
export const SidebarNavItem: React.FunctionComponent<
React.PropsWithChildren<{
to: string
className?: string
exact?: boolean
source?: string
onClick?: () => void
}>
> = ({ children, className, to, exact, source, onClick }) => {
export const SidebarNavItem: FC<PropsWithChildren<SidebarNavItemProps>> = ({
children,
className,
to,
source,
onClick,
exact = false,
}) => {
// Match nested routes too.
const routeMatch = useMatch(to + (exact ? '' : '/*'))
const buttonClassNames = classNames('text-left d-flex', styles.linkInactive, className)
const routeMatch = useRouteMatch({ path: to, exact })
if (source === 'server') {
return (
@ -43,12 +51,7 @@ export const SidebarNavItem: React.FunctionComponent<
}
return (
<ButtonLink
to={to}
className={buttonClassNames}
variant={routeMatch?.isExact ? 'primary' : undefined}
onClick={onClick}
>
<ButtonLink to={to} className={buttonClassNames} variant={routeMatch ? 'primary' : undefined} onClick={onClick}>
{children}
</ButtonLink>
)

View File

@ -1,7 +1,7 @@
import React, { useCallback, useEffect } from 'react'
import classNames from 'classnames'
import { Location, useLocation } from 'react-router-dom-v5-compat'
import { Location, useLocation } from 'react-router-dom'
import { fromEvent, Observable } from 'rxjs'
import { finalize, tap } from 'rxjs/operators'

View File

@ -1,7 +1,6 @@
import { FC } from 'react'
import { MemoryRouter, MemoryRouterProps } from 'react-router'
import { Routes, Route, CompatRouter } from 'react-router-dom-v5-compat'
import { RouterProvider, createMemoryRouter, MemoryRouterProps } from 'react-router-dom'
import { MockedStoryProvider, MockedStoryProviderProps } from '@sourcegraph/shared/src/stories'
import { NOOP_TELEMETRY_SERVICE, TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
@ -40,7 +39,8 @@ export const WebStory: FC<WebStoryProps> = ({
mocks,
path = '*',
useStrictMocking,
...memoryRouterProps
initialEntries = ['/'],
initialIndex = 1,
}) => {
const isLightTheme = useTheme()
const breadcrumbSetters = useBreadcrumbs()
@ -48,25 +48,28 @@ export const WebStory: FC<WebStoryProps> = ({
usePrependStyles('web-styles', webStyles)
setExperimentalFeaturesForTesting()
const routes = [
{
path,
element: (
<Children
{...breadcrumbSetters}
isLightTheme={isLightTheme}
telemetryService={NOOP_TELEMETRY_SERVICE}
/>
),
},
]
const router = createMemoryRouter(routes, {
initialEntries,
initialIndex,
})
return (
<MockedStoryProvider mocks={mocks} useStrictMocking={useStrictMocking}>
<WildcardThemeContext.Provider value={{ isBranded: true }}>
<MemoryRouter {...memoryRouterProps}>
<CompatRouter>
<Routes>
<Route
path={path}
element={
<Children
{...breadcrumbSetters}
isLightTheme={isLightTheme}
telemetryService={NOOP_TELEMETRY_SERVICE}
/>
}
/>
</Routes>
</CompatRouter>
</MemoryRouter>
<RouterProvider router={router} />
</WildcardThemeContext.Provider>
</MockedStoryProvider>
)

View File

@ -1,14 +1,11 @@
import { render } from '@testing-library/react'
import { createMemoryHistory } from 'history'
import { Router } from 'react-router-dom'
import { CompatRouter } from 'react-router-dom-v5-compat'
import { MemoryRouter } from 'react-router-dom'
import { DiffHunkLineType, FileDiffHunkFields } from '../../graphql-operations'
import { DiffHunk } from './DiffHunk'
describe('DiffHunk', () => {
const history = createMemoryHistory()
const hunk: FileDiffHunkFields = {
oldRange: { startLine: 159, lines: 7 },
oldNoNewlineAt: false,
@ -56,15 +53,13 @@ describe('DiffHunk', () => {
it('renders a unified diff view for the given diff hunk', () => {
expect(
render(
<Router history={history}>
<CompatRouter>
<table>
<tbody>
<DiffHunk hunk={hunk} lineNumbers={true} fileDiffAnchor="anchor_" />
</tbody>
</table>
</CompatRouter>
</Router>
<MemoryRouter>
<table>
<tbody>
<DiffHunk hunk={hunk} lineNumbers={true} fileDiffAnchor="anchor_" />
</tbody>
</table>
</MemoryRouter>
).asFragment()
).toMatchSnapshot()
})

View File

@ -3,7 +3,7 @@ import * as React from 'react'
import { VisuallyHidden } from '@reach/visually-hidden'
import classNames from 'classnames'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { createLinkUrl, Link } from '@sourcegraph/wildcard'

View File

@ -1,7 +1,5 @@
import { cleanup, fireEvent, render, RenderResult } from '@testing-library/react'
import { createMemoryHistory } from 'history'
import { Router } from 'react-router'
import { CompatRouter } from 'react-router-dom-v5-compat'
import { MemoryRouter } from 'react-router-dom'
import { DiffHunkLineType, FileDiffHunkFields } from '../../graphql-operations'
@ -54,19 +52,16 @@ describe('DiffSplitHunk', () => {
},
}
const history = createMemoryHistory()
let queries: RenderResult
const renderWithProps = (props: DiffHunkProps): RenderResult =>
render(
<Router history={history}>
<CompatRouter>
<table>
<tbody>
<DiffSplitHunk {...props} />
</tbody>
</table>
</CompatRouter>
</Router>
<MemoryRouter>
<table>
<tbody>
<DiffSplitHunk {...props} />
</tbody>
</table>
</MemoryRouter>
)
afterEach(cleanup)

View File

@ -1,7 +1,7 @@
import * as React from 'react'
import classNames from 'classnames'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { DiffHunkLineType, FileDiffHunkFields } from '../../graphql-operations'

View File

@ -3,7 +3,7 @@ import React, { useState, useCallback } from 'react'
import { mdiChevronDown, mdiChevronUp } from '@mdi/js'
import classNames from 'classnames'
import prettyBytes from 'pretty-bytes'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { Button, Badge, Link, Icon, Text, createLinkUrl, Tooltip } from '@sourcegraph/wildcard'

View File

@ -1,6 +1,6 @@
import { FC, useEffect, useCallback, useState } from 'react'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { asError, isErrorLike, logger, renderMarkdown } from '@sourcegraph/common'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'

View File

@ -1,7 +1,7 @@
import { FC } from 'react'
import { mdiInformation } from '@mdi/js'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { ThemeProps } from '@sourcegraph/shared/src/theme'

View File

@ -1,7 +1,7 @@
import React, { FC, useEffect, useState, useCallback } from 'react'
import { mdiCog } from '@mdi/js'
import { Navigate, useParams } from 'react-router-dom-v5-compat'
import { Navigate, useParams } from 'react-router-dom'
import { useQuery } from '@sourcegraph/http-client'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState, useCallback, useMemo, FC } from 'react'
import { mdiCog, mdiConnection, mdiDelete } from '@mdi/js'
import MapSearchIcon from 'mdi-react/MapSearchIcon'
import { useNavigate, useParams } from 'react-router-dom-v5-compat'
import { useNavigate, useParams } from 'react-router-dom'
import { Subject } from 'rxjs'
import { asError, isErrorLike } from '@sourcegraph/common'

View File

@ -1,7 +1,7 @@
import { FC, useEffect } from 'react'
import { mdiPlus } from '@mdi/js'
import { Navigate } from 'react-router-dom-v5-compat'
import { Navigate } from 'react-router-dom'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { Link, ButtonLink, Icon, PageHeader, Container } from '@sourcegraph/wildcard'

View File

@ -1,7 +1,7 @@
import { useEffect, useMemo } from 'react'
import * as H from 'history'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { parseBrowserRepoURL } from '../util/url'

View File

@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom-v5-compat'
import { useLocation, useNavigate } from 'react-router-dom'
import { Subscription } from 'rxjs'
import { ExtensionsControllerProps } from '@sourcegraph/shared/src/extensions/controller'

View File

@ -1,7 +1,7 @@
import React, { FC, useCallback, useMemo, useState } from 'react'
import AlertCircleIcon from 'mdi-react/AlertCircleIcon'
import { useNavigate, useParams } from 'react-router-dom-v5-compat'
import { useNavigate, useParams } from 'react-router-dom'
import { useQuery } from '@sourcegraph/http-client'
import { Settings, SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'

View File

@ -1,7 +1,7 @@
import React, { useState, useCallback } from 'react'
import { mdiChevronDoubleLeft, mdiChevronDoubleRight, mdiOpenInNew } from '@mdi/js'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { animated, useSpring } from 'react-spring'
import { Button, useLocalStorage, H3, H4, Icon, Link, Text, VIEWPORT_XL } from '@sourcegraph/wildcard'

View File

@ -1,6 +1,6 @@
import { useCallback, useState } from 'react'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { useMutation } from '@sourcegraph/http-client'
import { Scalars } from '@sourcegraph/shared/src/graphql-operations'

View File

@ -1,6 +1,6 @@
import React, { FC, useCallback, useRef, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom-v5-compat'
import { useLocation, useNavigate } from 'react-router-dom'
import { Input, Form } from '@sourcegraph/wildcard'

View File

@ -2,7 +2,7 @@ import React, { useCallback, useState } from 'react'
import { mdiChevronDown, mdiAlertCircle, mdiPencil, mdiClose, mdiSync, mdiCloseCircleOutline } from '@mdi/js'
import { noop } from 'lodash'
import { useNavigate, useLocation } from 'react-router-dom-v5-compat'
import { useNavigate, useLocation } from 'react-router-dom'
import { useMutation } from '@sourcegraph/http-client'
import {

View File

@ -4,7 +4,7 @@ import { mdiProgressClock } from '@mdi/js'
import { VisuallyHidden } from '@reach/visually-hidden'
import AlertCircleIcon from 'mdi-react/AlertCircleIcon'
import MapSearchIcon from 'mdi-react/MapSearchIcon'
import { Navigate, Route, Routes, useParams } from 'react-router-dom-v5-compat'
import { Navigate, Route, Routes, useParams } from 'react-router-dom'
import { Timestamp } from '@sourcegraph/branded/src/components/Timestamp'
import { useQuery } from '@sourcegraph/http-client'

View File

@ -1,6 +1,6 @@
import React, { FC, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { useMutation } from '@sourcegraph/http-client'
import { ThemeProps } from '@sourcegraph/shared/src/theme'

View File

@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef } from 'react'
import { mdiClose } from '@mdi/js'
import { VisuallyHidden } from '@reach/visually-hidden'
import { useNavigate, useParams } from 'react-router-dom-v5-compat'
import { useNavigate, useParams } from 'react-router-dom'
import { ThemeProps } from '@sourcegraph/shared/src/theme'
import { Card, CardBody, H3, H1, Icon, Text, Code, ErrorAlert } from '@sourcegraph/wildcard'

View File

@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { lowerCase, upperFirst } from 'lodash'
import { useLocation, useNavigate } from 'react-router-dom-v5-compat'
import { useLocation, useNavigate } from 'react-router-dom'
import { Input, Select, Form } from '@sourcegraph/wildcard'

View File

@ -1,6 +1,6 @@
import { FC, useCallback } from 'react'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { DiffStat } from '../../../../../components/diff/DiffStat'
import {

View File

@ -1,6 +1,6 @@
import React, { useCallback, useState } from 'react'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { isErrorLike, asError, pluralize } from '@sourcegraph/common'
import { Button, AlertLink, CardBody, Card, Alert, Checkbox, Text, ErrorAlert } from '@sourcegraph/wildcard'

View File

@ -2,7 +2,7 @@ import React, { useState, useMemo, useCallback } from 'react'
import { subDays } from 'date-fns'
import AlertCircleIcon from 'mdi-react/AlertCircleIcon'
import { useParams } from 'react-router-dom-v5-compat'
import { useParams } from 'react-router-dom'
import { ErrorLike, isErrorLike } from '@sourcegraph/common'
import { PageHeader, LoadingSpinner, useObservable } from '@sourcegraph/wildcard'

View File

@ -3,7 +3,7 @@ import React, { useCallback, useState } from 'react'
import { mdiInformationOutline, mdiLock } from '@mdi/js'
import classNames from 'classnames'
import { noop } from 'lodash'
import { useNavigate, useLocation } from 'react-router-dom-v5-compat'
import { useNavigate, useLocation } from 'react-router-dom'
import { useMutation } from '@sourcegraph/http-client'
import { UserSettingFields, OrgSettingFields } from '@sourcegraph/shared/src/graphql-operations'

View File

@ -1,4 +1,4 @@
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { Settings, SettingsCascadeOrError } from '@sourcegraph/shared/src/settings/settings'

View File

@ -1,4 +1,4 @@
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import helloWorldSample from '../batch-spec/edit/library/hello-world.batch.yaml'
import { insertQueryIntoLibraryItem, insertNameIntoLibraryItem } from '../batch-spec/yaml-util'

View File

@ -1,7 +1,7 @@
import React, { useCallback, useState } from 'react'
import { mdiInformation, mdiClose, mdiDelete, mdiPencil } from '@mdi/js'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { isErrorLike, asError } from '@sourcegraph/common'
import { Settings } from '@sourcegraph/shared/src/schema/settings.schema'

View File

@ -2,7 +2,7 @@ import React, { useEffect, useMemo } from 'react'
import { subDays, startOfDay } from 'date-fns'
import AlertCircleIcon from 'mdi-react/AlertCircleIcon'
import { useParams } from 'react-router-dom-v5-compat'
import { useParams } from 'react-router-dom'
import { useQuery } from '@sourcegraph/http-client'
import { AuthenticatedUser } from '@sourcegraph/shared/src/auth'

View File

@ -1,7 +1,7 @@
import React, { useCallback, useMemo, useState } from 'react'
import { mdiSourceBranch, mdiChartLineVariant, mdiFileDocument, mdiArchive, mdiMonitorStar } from '@mdi/js'
import { useNavigate, useLocation } from 'react-router-dom-v5-compat'
import { useNavigate, useLocation } from 'react-router-dom'
import { Settings, SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'

View File

@ -1,6 +1,6 @@
import React from 'react'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { pluralize } from '@sourcegraph/common'
import { BulkOperationState } from '@sourcegraph/shared/src/graphql-operations'

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'
import { mdiArchive } from '@mdi/js'
import { useLocation, useNavigate } from 'react-router-dom-v5-compat'
import { useLocation, useNavigate } from 'react-router-dom'
import { pluralize } from '@sourcegraph/common'
import { Link, Icon } from '@sourcegraph/wildcard'

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef, useCallback } from 'react'
import { useLocation, useNavigate } from 'react-router-dom-v5-compat'
import { useLocation, useNavigate } from 'react-router-dom'
import { Input, Form } from '@sourcegraph/wildcard'

View File

@ -1,6 +1,6 @@
import React from 'react'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { Link, H3, Text } from '@sourcegraph/wildcard'

View File

@ -1,6 +1,6 @@
import React from 'react'
import { Routes, Route } from 'react-router-dom-v5-compat'
import { Routes, Route } from 'react-router-dom'
import { Scalars } from '@sourcegraph/shared/src/graphql-operations'
import { SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'

View File

@ -1,7 +1,7 @@
import React, { useEffect, useCallback, useState, useMemo } from 'react'
import classNames from 'classnames'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { pluralize } from '@sourcegraph/common'
import { dataOrThrowErrors, useQuery } from '@sourcegraph/http-client'

View File

@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom-v5-compat'
import { useLocation, useNavigate } from 'react-router-dom'
import { LegacyBatchChangesFilter } from '@sourcegraph/shared/src/settings/temporary/TemporarySettings'
import { useTemporarySetting } from '@sourcegraph/shared/src/settings/temporary/useTemporarySetting'

View File

@ -1,7 +1,7 @@
import { FC, useEffect } from 'react'
import AlertCircleIcon from 'mdi-react/AlertCircleIcon'
import { useParams } from 'react-router-dom-v5-compat'
import { useParams } from 'react-router-dom'
import { useQuery } from '@sourcegraph/http-client'
import { Alert, LoadingSpinner, PageHeader } from '@sourcegraph/wildcard'

View File

@ -1,7 +1,7 @@
import React, { useCallback } from 'react'
import { mdiSourceBranch, mdiFileDocument } from '@mdi/js'
import { useNavigate, useLocation } from 'react-router-dom-v5-compat'
import { useNavigate, useLocation } from 'react-router-dom'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { ThemeProps } from '@sourcegraph/shared/src/theme'

View File

@ -1,7 +1,7 @@
import React, { useCallback, useContext, useState } from 'react'
import classNames from 'classnames'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { isErrorLike } from '@sourcegraph/common'
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'

View File

@ -1,6 +1,6 @@
import React, { FC, useCallback, useContext, useEffect, useRef } from 'react'
import { useLocation, useNavigate } from 'react-router-dom-v5-compat'
import { useLocation, useNavigate } from 'react-router-dom'
import { Input, Form } from '@sourcegraph/wildcard'

View File

@ -1,6 +1,6 @@
import React, { useCallback } from 'react'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { of } from 'rxjs'
import { buildCloudTrialURL } from '@sourcegraph/shared/src/util/url'

View File

@ -1,6 +1,5 @@
import { render, fireEvent } from '@testing-library/react'
import { MemoryRouter } from 'react-router'
import { CompatRouter } from 'react-router-dom-v5-compat'
import { MemoryRouter } from 'react-router-dom'
import { of } from 'rxjs'
import sinon from 'sinon'
@ -50,9 +49,7 @@ describe('CodeMonitoringListPage', () => {
test('Clicking enabled toggle calls toggleCodeMonitorEnabled', () => {
const component = render(
<MemoryRouter initialEntries={['/code-monitoring']}>
<CompatRouter>
<CodeMonitoringPage {...additionalProps} fetchUserCodeMonitors={generateMockFetchMonitors(1)} />
</CompatRouter>
<CodeMonitoringPage {...additionalProps} fetchUserCodeMonitors={generateMockFetchMonitors(1)} />
</MemoryRouter>
)
const toggle = component.getByTestId('toggle-monitor-enabled')
@ -63,9 +60,7 @@ describe('CodeMonitoringListPage', () => {
test('Switching tabs from getting started to empty list works', () => {
const component = render(
<MemoryRouter initialEntries={['/code-monitoring']}>
<CompatRouter>
<CodeMonitoringPage {...additionalProps} fetchUserCodeMonitors={generateMockFetchMonitors(0)} />
</CompatRouter>
<CodeMonitoringPage {...additionalProps} fetchUserCodeMonitors={generateMockFetchMonitors(0)} />
</MemoryRouter>
)
const codeMonitorsButton = component.getByRole('button', { name: 'Code monitors' })
@ -78,9 +73,7 @@ describe('CodeMonitoringListPage', () => {
test('Switching tabs from list to getting started works', () => {
const component = render(
<MemoryRouter initialEntries={['/code-monitoring']}>
<CompatRouter>
<CodeMonitoringPage {...additionalProps} fetchUserCodeMonitors={generateMockFetchMonitors(0)} />
</CompatRouter>
<CodeMonitoringPage {...additionalProps} fetchUserCodeMonitors={generateMockFetchMonitors(0)} />
</MemoryRouter>
)
const gettingStartedButton = component.getByRole('button', { name: 'Getting started' })

View File

@ -1,6 +1,6 @@
import { screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Route, Routes } from 'react-router-dom-v5-compat'
import { Route, Routes } from 'react-router-dom'
import { NEVER, of } from 'rxjs'
import sinon from 'sinon'

View File

@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useMemo } from 'react'
import { VisuallyHidden } from '@reach/visually-hidden'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { Observable } from 'rxjs'
import { ThemeProps } from '@sourcegraph/shared/src/theme'

View File

@ -1,6 +1,6 @@
import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Route, Routes } from 'react-router-dom-v5-compat/'
import { Route, Routes } from 'react-router-dom'
import { of } from 'rxjs'
import sinon from 'sinon'
@ -204,8 +204,7 @@ describe('ManageCodeMonitorPage', () => {
})
test('Clicking delete code monitor opens deletion confirmation modal', () => {
let currentPathname = ''
renderWithBrandedContext(
const { locationRef } = renderWithBrandedContext(
<MockedTestProvider>
<Routes>
<Route path="/code-monitoring/:id" element={<ManageCodeMonitorPage {...props} />} />
@ -214,7 +213,6 @@ describe('ManageCodeMonitorPage', () => {
</MockedTestProvider>,
{
route: '/code-monitoring/test-monitor-id',
onLocationChange: location => (currentPathname = location.pathname),
}
)
userEvent.click(screen.getByTestId('delete-monitor'))
@ -225,6 +223,6 @@ describe('ManageCodeMonitorPage', () => {
userEvent.click(confirmDeleteButton)
sinon.assert.calledOnce(props.deleteCodeMonitor)
expect(currentPathname).toEqual('/code-monitoring')
expect(locationRef.current?.pathname).toEqual('/code-monitoring')
})
})

View File

@ -1,7 +1,7 @@
import React, { useEffect } from 'react'
import { VisuallyHidden } from '@reach/visually-hidden'
import { useParams } from 'react-router-dom-v5-compat'
import { useParams } from 'react-router-dom'
import { Observable } from 'rxjs'
import { startWith, catchError, tap } from 'rxjs/operators'

View File

@ -2,7 +2,7 @@ import React, { useCallback, useMemo, useState } from 'react'
import classNames from 'classnames'
import { isEqual } from 'lodash'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { Observable } from 'rxjs'
import { mergeMap, startWith, catchError, tap, filter } from 'rxjs/operators'

View File

@ -1,6 +1,6 @@
import React, { useCallback } from 'react'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { Observable, throwError } from 'rxjs'
import { mergeMap, startWith, tap, catchError } from 'rxjs/operators'

View File

@ -1,6 +1,6 @@
import React from 'react'
import { Routes, Route } from 'react-router-dom-v5-compat'
import { Routes, Route } from 'react-router-dom'
import { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
import { SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'

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