mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 17:31:43 +00:00
various OrgArea fixes (#63962)
- Make it a React function component (not class componnt) - Remove prop drilling for isSourcegraphDotCom to remove a bunch of code - fix issue in Back button after clicking to an org from the site admin orgs page. Repro: 1. Go to /site-admin/organizations. 2. Click on an org. 3. Click the browser's back button. This was already being done for the user routes. ## Test plan Repro described above
This commit is contained in:
parent
341bfe749f
commit
30c0184f25
@ -46,7 +46,6 @@ const BatchChangeClosePage = lazyComponent<BatchChangeClosePageProps, 'BatchChan
|
||||
|
||||
interface Props extends TelemetryProps, TelemetryV2Props, SettingsCascadeProps {
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
isSourcegraphDotCom: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,9 +53,9 @@ interface Props extends TelemetryProps, TelemetryV2Props, SettingsCascadeProps {
|
||||
*/
|
||||
export const GlobalBatchChangesArea: React.FunctionComponent<React.PropsWithChildren<Props>> = ({
|
||||
authenticatedUser,
|
||||
isSourcegraphDotCom,
|
||||
...props
|
||||
}) => {
|
||||
const isSourcegraphDotCom = Boolean(window.context?.sourcegraphDotComMode)
|
||||
const canCreate: true | string = useMemo(() => {
|
||||
if (isSourcegraphDotCom) {
|
||||
return NO_ACCESS_SOURCEGRAPH_COM
|
||||
@ -77,7 +76,6 @@ export const GlobalBatchChangesArea: React.FunctionComponent<React.PropsWithChil
|
||||
headingElement="h1"
|
||||
canCreate={canCreate}
|
||||
authenticatedUser={authenticatedUser}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { Decorator, StoryFn, Meta } from '@storybook/react'
|
||||
import { WildcardMockLink, MATCH_ANY_PARAMETERS } from 'wildcard-mock-link'
|
||||
import type { Decorator, Meta, StoryFn } from '@storybook/react'
|
||||
import { MATCH_ANY_PARAMETERS, WildcardMockLink } from 'wildcard-mock-link'
|
||||
|
||||
import { getDocumentNode } from '@sourcegraph/http-client'
|
||||
import { EMPTY_SETTINGS_CASCADE } from '@sourcegraph/shared/src/settings/settings'
|
||||
@ -11,10 +11,10 @@ import { WebStory } from '../../../components/WebStory'
|
||||
import type { GlobalChangesetsStatsResult } from '../../../graphql-operations'
|
||||
|
||||
import {
|
||||
GLOBAL_CHANGESETS_STATS,
|
||||
BATCH_CHANGES,
|
||||
BATCH_CHANGES_BY_NAMESPACE,
|
||||
GET_LICENSE_AND_USAGE_INFO,
|
||||
GLOBAL_CHANGESETS_STATS,
|
||||
} from './backend'
|
||||
import { BatchChangeListPage } from './BatchChangeListPage'
|
||||
import {
|
||||
@ -103,7 +103,6 @@ export const ListOfBatchChanges: StoryFn<Args> = args => {
|
||||
headingElement="h1"
|
||||
canCreate={args.canCreate || "You don't have permission to create batch changes"}
|
||||
settingsCascade={EMPTY_SETTINGS_CASCADE}
|
||||
isSourcegraphDotCom={args.isDotCom}
|
||||
authenticatedUser={null}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
/>
|
||||
@ -142,7 +141,6 @@ export const ListOfBatchChangesSpecificNamespace: StoryFn = () => {
|
||||
canCreate={true}
|
||||
namespaceID="test-12345"
|
||||
settingsCascade={EMPTY_SETTINGS_CASCADE}
|
||||
isSourcegraphDotCom={false}
|
||||
authenticatedUser={null}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
/>
|
||||
@ -171,7 +169,6 @@ export const ListOfBatchChangesServerSideExecutionEnabled: StoryFn = () => {
|
||||
experimentalFeatures: { batchChangesExecution: true },
|
||||
},
|
||||
}}
|
||||
isSourcegraphDotCom={false}
|
||||
authenticatedUser={null}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
/>
|
||||
@ -195,7 +192,6 @@ export const LicensingNotEnforced: StoryFn = () => {
|
||||
headingElement="h1"
|
||||
canCreate={true}
|
||||
settingsCascade={EMPTY_SETTINGS_CASCADE}
|
||||
isSourcegraphDotCom={false}
|
||||
authenticatedUser={null}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
/>
|
||||
@ -219,7 +215,6 @@ export const NoBatchChanges: StoryFn = () => {
|
||||
headingElement="h1"
|
||||
canCreate={true}
|
||||
settingsCascade={EMPTY_SETTINGS_CASCADE}
|
||||
isSourcegraphDotCom={false}
|
||||
authenticatedUser={null}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
/>
|
||||
@ -244,7 +239,6 @@ export const AllBatchChangesTabEmpty: StoryFn = () => {
|
||||
canCreate={true}
|
||||
openTab="batchChanges"
|
||||
settingsCascade={EMPTY_SETTINGS_CASCADE}
|
||||
isSourcegraphDotCom={false}
|
||||
authenticatedUser={null}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
/>
|
||||
|
||||
@ -57,7 +57,6 @@ export interface BatchChangeListPageProps extends TelemetryProps, TelemetryV2Pro
|
||||
canCreate: true | string
|
||||
headingElement: 'h1' | 'h2'
|
||||
namespaceID?: Scalars['ID']
|
||||
isSourcegraphDotCom: boolean
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
/** For testing only. */
|
||||
openTab?: SelectedTab
|
||||
@ -78,7 +77,6 @@ export const BatchChangeListPage: React.FunctionComponent<React.PropsWithChildre
|
||||
settingsCascade,
|
||||
telemetryService,
|
||||
telemetryRecorder,
|
||||
isSourcegraphDotCom,
|
||||
authenticatedUser,
|
||||
}) => {
|
||||
const location = useLocation()
|
||||
@ -92,6 +90,7 @@ export const BatchChangeListPage: React.FunctionComponent<React.PropsWithChildre
|
||||
const canUseBatchChanges = !!window.context.licenseInfo?.batchChanges
|
||||
|
||||
const { selectedFilters, setSelectedFilters, availableFilters } = useBatchChangeListFilters({ isExecutionEnabled })
|
||||
const isSourcegraphDotCom = Boolean(window.context?.sourcegraphDotComMode)
|
||||
const [selectedTab, setSelectedTab] = useState<SelectedTab>(
|
||||
openTab ?? (isSourcegraphDotCom || !canUseBatchChanges ? 'gettingStarted' : 'batchChanges')
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { type PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
import type { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
|
||||
import type { AuthenticatedUser } from '../auth'
|
||||
import type { BatchChangesProps } from '../batches'
|
||||
@ -11,13 +11,10 @@ import type { NamespaceProps } from '.'
|
||||
*/
|
||||
export interface NamespaceAreaContext extends PlatformContextProps, NamespaceProps {
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
isSourcegraphDotCom: boolean
|
||||
}
|
||||
|
||||
export interface NamespaceAreaRoute extends RouteV6Descriptor<NamespaceAreaContext> {}
|
||||
|
||||
interface NavItemDescriptorContext extends BatchChangesProps {
|
||||
isSourcegraphDotCom: boolean
|
||||
}
|
||||
interface NavItemDescriptorContext extends BatchChangesProps {}
|
||||
|
||||
export interface NamespaceAreaNavItem extends NavItemWithIconDescriptor<NavItemDescriptorContext> {}
|
||||
|
||||
@ -22,7 +22,7 @@ export const namespaceAreaRoutes: readonly NamespaceAreaRoute[] = [
|
||||
const SavedSearchesRedirect: FunctionComponent<NamespaceProps> = ({ namespace }) => {
|
||||
const navigate = useNavigate()
|
||||
useEffect(() => {
|
||||
navigate(urlToSavedSearchesList(namespace.id))
|
||||
navigate(urlToSavedSearchesList(namespace.id), { replace: true })
|
||||
}, [navigate, namespace.id])
|
||||
return null
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { Routes, Route, useParams, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
|
||||
import type { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
import type { SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
|
||||
@ -9,10 +9,10 @@ import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetry
|
||||
import type { AuthenticatedUser } from '../auth'
|
||||
import { withAuthenticatedUser } from '../auth/withAuthenticatedUser'
|
||||
import type { BatchChangesProps } from '../batches'
|
||||
import type { BreadcrumbsProps, BreadcrumbSetters } from '../components/Breadcrumbs'
|
||||
import type { BreadcrumbSetters, BreadcrumbsProps } from '../components/Breadcrumbs'
|
||||
import { NotFoundPage } from '../components/HeroPage'
|
||||
|
||||
import { OrgArea, type OrgAreaProps, type OrgAreaRoute } from './area/OrgArea'
|
||||
import { OrgArea, type OrgAreaRoute } from './area/OrgArea'
|
||||
import type { OrgAreaHeaderNavItem } from './area/OrgHeader'
|
||||
import { OrgInvitationPage } from './invitations/OrgInvitationPage'
|
||||
import { NewOrganizationPage } from './new/NewOrganizationPage'
|
||||
@ -32,7 +32,6 @@ export interface Props
|
||||
orgSettingsAreaRoutes: readonly OrgSettingsAreaRoute[]
|
||||
|
||||
authenticatedUser: AuthenticatedUser
|
||||
isSourcegraphDotCom: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,28 +39,17 @@ export interface Props
|
||||
*/
|
||||
const AuthenticatedOrgsArea: React.FunctionComponent<React.PropsWithChildren<Props>> = props => (
|
||||
<Routes>
|
||||
{(!props.isSourcegraphDotCom || props.authenticatedUser.siteAdmin) && (
|
||||
<Route
|
||||
path="new"
|
||||
element={<NewOrganizationPage telemetryRecorder={props.platformContext.telemetryRecorder} />}
|
||||
/>
|
||||
)}
|
||||
<Route
|
||||
path="new"
|
||||
element={<NewOrganizationPage telemetryRecorder={props.platformContext.telemetryRecorder} />}
|
||||
/>
|
||||
<Route
|
||||
path="invitation/:token"
|
||||
element={<OrgInvitationPage {...props} telemetryRecorder={props.platformContext.telemetryRecorder} />}
|
||||
/>
|
||||
<Route path=":orgName/*" element={<OrgAreaWithRouteProps {...props} />} />
|
||||
<Route path=":orgName/*" element={<OrgArea {...props} />} />
|
||||
<Route path="*" element={<NotFoundPage pageType="organization" />} />
|
||||
</Routes>
|
||||
)
|
||||
|
||||
// TODO: Migrate this into the OrgArea component once it's migrated to a function component.
|
||||
function OrgAreaWithRouteProps(props: Omit<OrgAreaProps, 'orgName' | 'location' | 'navigate'>): JSX.Element {
|
||||
const { orgName } = useParams<{ orgName: string }>()
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
return <OrgArea {...props} orgName={orgName!} location={location} navigate={navigate} />
|
||||
}
|
||||
|
||||
export const OrgsArea = withAuthenticatedUser(AuthenticatedOrgsArea)
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
import * as React from 'react'
|
||||
import React, { Suspense, useMemo } from 'react'
|
||||
|
||||
import type * as H from 'history'
|
||||
import AlertCircleIcon from 'mdi-react/AlertCircleIcon'
|
||||
import MapSearchIcon from 'mdi-react/MapSearchIcon'
|
||||
import { Route, Routes, type NavigateFunction } from 'react-router-dom'
|
||||
import { Subject, Subscription, combineLatest, merge, of, type Observable } from 'rxjs'
|
||||
import { catchError, distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators'
|
||||
import { Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
import { asError, isErrorLike, logger, type ErrorLike } from '@sourcegraph/common'
|
||||
import { dataOrThrowErrors, gql } from '@sourcegraph/http-client'
|
||||
import { gql, useQuery } from '@sourcegraph/http-client'
|
||||
import type { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
import type { SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
|
||||
import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
@ -16,68 +12,20 @@ import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetry
|
||||
import { ErrorMessage, LoadingSpinner } from '@sourcegraph/wildcard'
|
||||
|
||||
import type { AuthenticatedUser } from '../../auth'
|
||||
import { requestGraphQL } from '../../backend/graphql'
|
||||
import type { BatchChangesProps } from '../../batches'
|
||||
import type { BreadcrumbSetters, BreadcrumbsProps } from '../../components/Breadcrumbs'
|
||||
import { RouteError } from '../../components/ErrorBoundary'
|
||||
import { HeroPage } from '../../components/HeroPage'
|
||||
import { Page } from '../../components/Page'
|
||||
import type { OrgAreaOrganizationFields, OrganizationResult, OrganizationVariables } from '../../graphql-operations'
|
||||
import type { OrgAreaOrganizationFields } from '../../graphql-operations'
|
||||
import type { NamespaceProps } from '../../namespaces'
|
||||
import type { RouteV6Descriptor } from '../../util/contributions'
|
||||
import type { OrgSettingsAreaRoute } from '../settings/OrgSettingsArea'
|
||||
import type { OrgSettingsSidebarItems } from '../settings/OrgSettingsSidebar'
|
||||
|
||||
import { OrgHeader, type OrgAreaHeaderNavItem } from './OrgHeader'
|
||||
import { type OrgAreaHeaderNavItem, OrgHeader } from './OrgHeader'
|
||||
import { OrgInvitationPageLegacy } from './OrgInvitationPageLegacy'
|
||||
|
||||
function queryOrganization(args: { name: string }): Observable<OrgAreaOrganizationFields> {
|
||||
return requestGraphQL<OrganizationResult, OrganizationVariables>(
|
||||
gql`
|
||||
query Organization($name: String!) {
|
||||
organization(name: $name) {
|
||||
...OrgAreaOrganizationFields
|
||||
}
|
||||
}
|
||||
|
||||
fragment OrgAreaOrganizationFields on Org {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
displayName
|
||||
url
|
||||
settingsURL
|
||||
viewerPendingInvitation {
|
||||
id
|
||||
sender {
|
||||
username
|
||||
displayName
|
||||
avatarURL
|
||||
createdAt
|
||||
}
|
||||
respondURL
|
||||
}
|
||||
viewerIsMember
|
||||
viewerCanAdminister
|
||||
createdAt
|
||||
}
|
||||
`,
|
||||
args
|
||||
).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(data => {
|
||||
if (!data.organization) {
|
||||
throw new Error(`Organization not found: ${JSON.stringify(args.name)}`)
|
||||
}
|
||||
return data.organization
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const NotFoundPage: React.FunctionComponent<React.PropsWithChildren<unknown>> = () => (
|
||||
<HeroPage icon={MapSearchIcon} title="404: Not Found" subtitle="Sorry, the requested organization was not found." />
|
||||
)
|
||||
|
||||
export interface OrgAreaRoute extends RouteV6Descriptor<OrgAreaRouteContext> {
|
||||
/** When true, the header is not rendered and the component is not wrapped in a container. */
|
||||
fullPage?: boolean
|
||||
@ -99,19 +47,41 @@ export interface OrgAreaProps
|
||||
* The currently authenticated user.
|
||||
*/
|
||||
authenticatedUser: AuthenticatedUser
|
||||
isSourcegraphDotCom: boolean
|
||||
|
||||
location: H.Location
|
||||
navigate: NavigateFunction
|
||||
orgName: string
|
||||
}
|
||||
|
||||
interface State extends BreadcrumbSetters {
|
||||
/**
|
||||
* The fetched org or an error if an error occurred; undefined while loading.
|
||||
*/
|
||||
orgOrError?: OrgAreaOrganizationFields | ErrorLike
|
||||
}
|
||||
const ORGANIZATION_QUERY = gql`
|
||||
query Organization($name: String!) {
|
||||
organization(name: $name) {
|
||||
...OrgAreaOrganizationFields
|
||||
}
|
||||
}
|
||||
|
||||
fragment OrgAreaOrganizationFields on Org {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
displayName
|
||||
url
|
||||
settingsURL
|
||||
viewerPendingInvitation {
|
||||
id
|
||||
sender {
|
||||
username
|
||||
displayName
|
||||
avatarURL
|
||||
createdAt
|
||||
}
|
||||
respondURL
|
||||
}
|
||||
viewerIsMember
|
||||
viewerCanAdminister
|
||||
createdAt
|
||||
}
|
||||
`
|
||||
|
||||
const NotFoundPage: React.FunctionComponent<React.PropsWithChildren<unknown>> = () => (
|
||||
<HeroPage icon={MapSearchIcon} title="404: Not Found" subtitle="Sorry, the requested organization was not found." />
|
||||
)
|
||||
|
||||
/**
|
||||
* Properties passed to all page components in the org area.
|
||||
@ -134,164 +104,116 @@ export interface OrgAreaRouteContext
|
||||
/** The currently authenticated user. */
|
||||
authenticatedUser: AuthenticatedUser
|
||||
|
||||
isSourcegraphDotCom: boolean
|
||||
|
||||
orgSettingsSideBarItems: OrgSettingsSidebarItems
|
||||
orgSettingsAreaRoutes: readonly OrgSettingsAreaRoute[]
|
||||
}
|
||||
|
||||
/**
|
||||
* An organization's public profile area.
|
||||
*/
|
||||
export class OrgArea extends React.Component<OrgAreaProps> {
|
||||
public state: State
|
||||
|
||||
private componentUpdates = new Subject<OrgAreaProps>()
|
||||
private refreshRequests = new Subject<void>()
|
||||
private subscriptions = new Subscription()
|
||||
|
||||
constructor(props: OrgAreaProps) {
|
||||
super(props)
|
||||
this.state = {
|
||||
setBreadcrumb: props.setBreadcrumb,
|
||||
useBreadcrumb: props.useBreadcrumb,
|
||||
}
|
||||
export const OrgArea: React.FunctionComponent<OrgAreaProps> = ({
|
||||
orgAreaRoutes,
|
||||
orgAreaHeaderNavItems,
|
||||
orgSettingsSideBarItems,
|
||||
orgSettingsAreaRoutes,
|
||||
authenticatedUser,
|
||||
useBreadcrumb,
|
||||
...props
|
||||
}) => {
|
||||
const { orgName } = useParams<{ orgName: string }>()
|
||||
if (!orgName) {
|
||||
throw new Error('orgName is required')
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
// Changes to the route-matched org name.
|
||||
const nameChanges = this.componentUpdates.pipe(
|
||||
map(props => props.orgName),
|
||||
distinctUntilChanged()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
const { data, error, loading, refetch } = useQuery(ORGANIZATION_QUERY, {
|
||||
variables: { name: orgName },
|
||||
})
|
||||
|
||||
const childBreadcrumbSetters = useBreadcrumb(
|
||||
useMemo(
|
||||
() =>
|
||||
data?.organization
|
||||
? {
|
||||
key: 'OrgArea',
|
||||
link: { to: data.organization.url, label: data.organization.name },
|
||||
}
|
||||
: null,
|
||||
[data]
|
||||
)
|
||||
)
|
||||
|
||||
// Fetch organization.
|
||||
this.subscriptions.add(
|
||||
combineLatest([nameChanges, merge(this.refreshRequests.pipe(map(() => false)), of(true))])
|
||||
.pipe(
|
||||
switchMap(([name, forceRefresh]) => {
|
||||
type PartialStateUpdate = Pick<State, 'orgOrError'>
|
||||
return queryOrganization({ name }).pipe(
|
||||
catchError((error): [ErrorLike] => [asError(error)]),
|
||||
map((orgOrError): PartialStateUpdate => ({ orgOrError })),
|
||||
// Don't clear old org data while we reload, to avoid unmounting all components during
|
||||
// loading.
|
||||
startWith<PartialStateUpdate>(forceRefresh ? { orgOrError: undefined } : {})
|
||||
)
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
stateUpdate => {
|
||||
if (stateUpdate.orgOrError && !isErrorLike(stateUpdate.orgOrError)) {
|
||||
const childBreadcrumbSetters = this.props.setBreadcrumb({
|
||||
key: 'OrgArea',
|
||||
link: { to: stateUpdate.orgOrError.url, label: stateUpdate.orgOrError.name },
|
||||
})
|
||||
this.subscriptions.add(childBreadcrumbSetters)
|
||||
this.setState({
|
||||
useBreadcrumb: childBreadcrumbSetters.useBreadcrumb,
|
||||
setBreadcrumb: childBreadcrumbSetters.setBreadcrumb,
|
||||
orgOrError: stateUpdate.orgOrError,
|
||||
})
|
||||
} else {
|
||||
this.setState(stateUpdate)
|
||||
}
|
||||
},
|
||||
error => logger.error(error)
|
||||
)
|
||||
)
|
||||
|
||||
this.componentUpdates.next(this.props)
|
||||
if (loading && !data) {
|
||||
return <LoadingSpinner className="m-2" />
|
||||
}
|
||||
|
||||
public componentDidUpdate(): void {
|
||||
this.componentUpdates.next(this.props)
|
||||
if (error) {
|
||||
return <HeroPage icon={AlertCircleIcon} title="Error" subtitle={<ErrorMessage error={error} />} />
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.subscriptions.unsubscribe()
|
||||
if (!data?.organization) {
|
||||
return <NotFoundPage />
|
||||
}
|
||||
|
||||
public render(): JSX.Element | null {
|
||||
if (!this.state.orgOrError) {
|
||||
return null // loading
|
||||
}
|
||||
if (isErrorLike(this.state.orgOrError)) {
|
||||
return (
|
||||
<HeroPage
|
||||
icon={AlertCircleIcon}
|
||||
title="Error"
|
||||
subtitle={<ErrorMessage error={this.state.orgOrError} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const context: OrgAreaRouteContext = {
|
||||
authenticatedUser: this.props.authenticatedUser,
|
||||
org: this.state.orgOrError,
|
||||
onOrganizationUpdate: this.onDidUpdateOrganization,
|
||||
platformContext: this.props.platformContext,
|
||||
settingsCascade: this.props.settingsCascade,
|
||||
namespace: this.state.orgOrError,
|
||||
telemetryService: this.props.telemetryService,
|
||||
telemetryRecorder: this.props.platformContext.telemetryRecorder,
|
||||
isSourcegraphDotCom: this.props.isSourcegraphDotCom,
|
||||
batchChangesEnabled: this.props.batchChangesEnabled,
|
||||
batchChangesExecutionEnabled: this.props.batchChangesExecutionEnabled,
|
||||
batchChangesWebhookLogsEnabled: this.props.batchChangesWebhookLogsEnabled,
|
||||
breadcrumbs: this.props.breadcrumbs,
|
||||
setBreadcrumb: this.state.setBreadcrumb,
|
||||
useBreadcrumb: this.state.useBreadcrumb,
|
||||
orgSettingsAreaRoutes: this.props.orgSettingsAreaRoutes,
|
||||
orgSettingsSideBarItems: this.props.orgSettingsSideBarItems,
|
||||
}
|
||||
|
||||
if (this.props.location.pathname === `/organizations/${this.props.orgName}/invitation`) {
|
||||
// The OrgInvitationPageLegacy is displayed without the OrgHeader because it is modal-like.
|
||||
return <OrgInvitationPageLegacy {...context} onDidRespondToInvitation={this.onDidRespondToInvitation} />
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Suspense fallback={<LoadingSpinner className="m-2" />}>
|
||||
<Routes>
|
||||
{this.props.orgAreaRoutes.map(
|
||||
({ path, render, condition = () => true, fullPage }) =>
|
||||
condition(context) && (
|
||||
<Route
|
||||
path={path}
|
||||
key="hardcoded-key" // see https://github.com/ReactTraining/react-router/issues/4578#issuecomment-334489490
|
||||
errorElement={<RouteError />}
|
||||
element={
|
||||
fullPage ? (
|
||||
render(context)
|
||||
) : (
|
||||
<Page className="org-area">
|
||||
<OrgHeader
|
||||
{...this.props}
|
||||
{...context}
|
||||
navItems={this.props.orgAreaHeaderNavItems}
|
||||
className="mb-3"
|
||||
/>
|
||||
<div className="container">{render(context)}</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</React.Suspense>
|
||||
)
|
||||
const context: OrgAreaRouteContext = {
|
||||
authenticatedUser,
|
||||
org: data.organization,
|
||||
onOrganizationUpdate: refetch,
|
||||
platformContext: props.platformContext,
|
||||
settingsCascade: props.settingsCascade,
|
||||
namespace: data.organization,
|
||||
telemetryService: props.telemetryService,
|
||||
telemetryRecorder: props.platformContext.telemetryRecorder,
|
||||
batchChangesEnabled: props.batchChangesEnabled,
|
||||
batchChangesExecutionEnabled: props.batchChangesExecutionEnabled,
|
||||
batchChangesWebhookLogsEnabled: props.batchChangesWebhookLogsEnabled,
|
||||
breadcrumbs: props.breadcrumbs,
|
||||
...childBreadcrumbSetters,
|
||||
orgSettingsAreaRoutes,
|
||||
orgSettingsSideBarItems,
|
||||
}
|
||||
|
||||
private onDidRespondToInvitation = (accepted: boolean): void => {
|
||||
const handleRespondToInvitation = (accepted: boolean): void => {
|
||||
if (!accepted) {
|
||||
this.props.navigate('/user/settings')
|
||||
navigate('/user/settings')
|
||||
return
|
||||
}
|
||||
this.refreshRequests.next()
|
||||
refetch()
|
||||
}
|
||||
|
||||
private onDidUpdateOrganization = (): void => this.refreshRequests.next()
|
||||
if (location.pathname === `/organizations/${orgName}/invitation`) {
|
||||
return <OrgInvitationPageLegacy {...context} onDidRespondToInvitation={handleRespondToInvitation} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<LoadingSpinner className="m-2" />}>
|
||||
<Routes>
|
||||
{orgAreaRoutes.map(
|
||||
({ path, render, condition = () => true, fullPage }) =>
|
||||
condition(context) && (
|
||||
<Route
|
||||
path={path}
|
||||
key="hardcoded-key"
|
||||
errorElement={<RouteError />}
|
||||
element={
|
||||
fullPage ? (
|
||||
render(context)
|
||||
) : (
|
||||
<Page className="org-area">
|
||||
<OrgHeader
|
||||
{...props}
|
||||
{...context}
|
||||
navItems={orgAreaHeaderNavItems}
|
||||
className="mb-3"
|
||||
/>
|
||||
<div className="container">{render(context)}</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
@ -11,14 +11,11 @@ import { OrgAvatar } from '../OrgAvatar'
|
||||
import type { OrgAreaRouteContext } from './OrgArea'
|
||||
|
||||
interface Props extends OrgAreaRouteContext {
|
||||
isSourcegraphDotCom: boolean
|
||||
navItems: readonly OrgAreaHeaderNavItem[]
|
||||
className?: string
|
||||
}
|
||||
|
||||
export interface OrgAreaHeaderContext extends BatchChangesProps, Pick<Props, 'org'> {
|
||||
isSourcegraphDotCom: boolean
|
||||
}
|
||||
export interface OrgAreaHeaderContext extends BatchChangesProps, Pick<Props, 'org'> {}
|
||||
|
||||
export interface OrgAreaHeaderNavItem extends NavItemWithIconDescriptor<OrgAreaHeaderContext> {}
|
||||
|
||||
@ -32,14 +29,12 @@ export const OrgHeader: React.FunctionComponent<React.PropsWithChildren<Props>>
|
||||
org,
|
||||
navItems,
|
||||
className = '',
|
||||
isSourcegraphDotCom,
|
||||
}) => {
|
||||
const context: OrgAreaHeaderContext = {
|
||||
batchChangesEnabled,
|
||||
batchChangesExecutionEnabled,
|
||||
batchChangesWebhookLogsEnabled,
|
||||
org,
|
||||
isSourcegraphDotCom,
|
||||
}
|
||||
|
||||
const url = `/organizations/${org.name}`
|
||||
|
||||
@ -51,12 +51,12 @@ export const orgAreaRoutes: readonly OrgAreaRoute[] = [
|
||||
// Redirect from /organizations/:orgname -> /organizations/:orgname/settings/profile.
|
||||
{
|
||||
path: '',
|
||||
render: () => <Navigate to="./settings/profile" />,
|
||||
render: () => <Navigate to="./settings/profile" replace={true} />,
|
||||
},
|
||||
// Redirect from previous /organizations/:orgname/account -> /organizations/:orgname/settings/profile.
|
||||
{
|
||||
path: 'account',
|
||||
render: () => <Navigate to="../settings/profile" />,
|
||||
render: () => <Navigate to="../settings/profile" replace={true} />,
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
@ -19,7 +19,6 @@ import styles from './OrgSettingsSidebar.module.scss'
|
||||
export interface OrgSettingsSidebarItemConditionContext extends BatchChangesProps {
|
||||
org: OrgAreaOrganizationFields
|
||||
authenticatedUser: AuthenticatedUser
|
||||
isSourcegraphDotCom: boolean
|
||||
}
|
||||
|
||||
type OrgSettingsSidebarItem = NavItemDescriptor<OrgSettingsSidebarItemConditionContext> & {
|
||||
@ -30,7 +29,6 @@ export type OrgSettingsSidebarItems = readonly OrgSettingsSidebarItem[]
|
||||
|
||||
export interface OrgSettingsSidebarProps extends OrgSettingsAreaRouteContext, BatchChangesProps {
|
||||
items: OrgSettingsSidebarItems
|
||||
isSourcegraphDotCom: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
@ -53,7 +51,6 @@ export const OrgSettingsSidebar: React.FunctionComponent<React.PropsWithChildren
|
||||
batchChangesWebhookLogsEnabled: props.batchChangesWebhookLogsEnabled,
|
||||
org,
|
||||
authenticatedUser,
|
||||
isSourcegraphDotCom: props.isSourcegraphDotCom,
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -38,8 +38,8 @@ export const orgSettingsAreaRoutes: readonly OrgSettingsAreaRoute[] = [
|
||||
telemetryRecorder={props.platformContext.telemetryRecorder}
|
||||
/>
|
||||
),
|
||||
condition: ({ org: { viewerCanAdminister } }) =>
|
||||
viewerCanAdminister && window.context?.codeSearchEnabledOnInstance,
|
||||
condition: ({ batchChangesEnabled, org: { viewerCanAdminister } }) =>
|
||||
batchChangesEnabled && viewerCanAdminister && window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user