Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit 043589f

Browse files
Code monitors: tabs persist on refresh (#57512)
* Refactor to use url params so that tabs persist on refresh and are shareable. * pass init function instead of init value to state * fix linting * fix broken unit test
1 parent bd6ca40 commit 043589f

File tree

2 files changed

+79
-61
lines changed

2 files changed

+79
-61
lines changed

client/web/src/enterprise/code-monitoring/CodeMonitoringPage.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const generateMockFetchMonitors =
4949
describe('CodeMonitoringListPage', () => {
5050
test('Clicking enabled toggle calls toggleCodeMonitorEnabled', () => {
5151
const component = render(
52-
<MemoryRouter initialEntries={['/code-monitoring']}>
52+
<MemoryRouter initialEntries={['/code-monitoring?tab=list']}>
5353
<CodeMonitoringPage {...additionalProps} fetchUserCodeMonitors={generateMockFetchMonitors(1)} />
5454
</MemoryRouter>
5555
)
@@ -60,7 +60,7 @@ describe('CodeMonitoringListPage', () => {
6060

6161
test('Switching tabs from getting started to empty list works', () => {
6262
const component = render(
63-
<MemoryRouter initialEntries={['/code-monitoring']}>
63+
<MemoryRouter initialEntries={['/code-monitoring?tab=getting-started']}>
6464
<CodeMonitoringPage {...additionalProps} fetchUserCodeMonitors={generateMockFetchMonitors(0)} />
6565
</MemoryRouter>
6666
)
@@ -73,7 +73,7 @@ describe('CodeMonitoringListPage', () => {
7373

7474
test('Switching tabs from list to getting started works', () => {
7575
const component = render(
76-
<MemoryRouter initialEntries={['/code-monitoring']}>
76+
<MemoryRouter initialEntries={['/code-monitoring?tab=list']}>
7777
<CodeMonitoringPage {...additionalProps} fetchUserCodeMonitors={generateMockFetchMonitors(0)} />
7878
</MemoryRouter>
7979
)

client/web/src/enterprise/code-monitoring/CodeMonitoringPage.tsx

Lines changed: 76 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import React, { useMemo, useEffect, useState, useLayoutEffect } from 'react'
1+
import React, { useMemo, useEffect, useState, useLayoutEffect, useCallback } from 'react'
22

33
import { mdiPlus } from '@mdi/js'
44
import classNames from 'classnames'
5+
import { type Location, useNavigate, useLocation, type NavigateFunction } from 'react-router-dom'
56
import { of } from 'rxjs'
67
import { catchError, map } from 'rxjs/operators'
78

89
import { asError, isErrorLike } from '@sourcegraph/common'
910
import type { Settings } from '@sourcegraph/shared/src/schema/settings.schema'
10-
import { type SettingsCascadeProps, useExperimentalFeatures } from '@sourcegraph/shared/src/settings/settings'
11+
import { type SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
1112
import {
1213
PageHeader,
1314
LoadingSpinner,
@@ -16,6 +17,7 @@ import {
1617
Link,
1718
ProductStatusBadge,
1819
Icon,
20+
ButtonLink,
1921
} from '@sourcegraph/wildcard'
2022

2123
import type { AuthenticatedUser } from '../../auth'
@@ -32,6 +34,34 @@ import { CodeMonitoringGettingStarted } from './CodeMonitoringGettingStarted'
3234
import { CodeMonitoringLogs } from './CodeMonitoringLogs'
3335
import { CodeMonitorList } from './CodeMonitorList'
3436

37+
type MonitorsTab = 'list' | 'getting-started' | 'logs'
38+
type Tabs = { tab: MonitorsTab; title: string; isActive: boolean }[]
39+
40+
function getSelectedTabFromLocation(
41+
locationSearch: string,
42+
userHasCodeMonitors: boolean | Error | undefined
43+
): MonitorsTab {
44+
const urlParameters = new URLSearchParams(locationSearch)
45+
switch (urlParameters.get('tab')) {
46+
case 'list':
47+
return 'list'
48+
case 'getting-started':
49+
return 'getting-started'
50+
case 'logs':
51+
return 'logs'
52+
}
53+
54+
return userHasCodeMonitors ? 'list' : 'getting-started'
55+
}
56+
57+
function setSelectedLocationTab(location: Location, navigate: NavigateFunction, selectedTab: MonitorsTab): void {
58+
const urlParameters = new URLSearchParams(location.search)
59+
urlParameters.set('tab', selectedTab)
60+
if (location.search !== urlParameters.toString()) {
61+
navigate({ ...location, search: urlParameters.toString() }, { replace: true })
62+
}
63+
}
64+
3565
export interface CodeMonitoringPageProps extends SettingsCascadeProps<Settings> {
3666
authenticatedUser: AuthenticatedUser | null
3767
fetchUserCodeMonitors?: typeof _fetchUserCodeMonitors
@@ -67,18 +97,20 @@ export const CodeMonitoringPage: React.FunctionComponent<React.PropsWithChildren
6797
)
6898
)
6999

70-
const [currentTab, setCurrentTab] = useState<'list' | 'getting-started' | 'logs' | null>(null)
100+
const navigate = useNavigate()
101+
const location = useLocation()
71102

72-
// Select the appropriate tab after loading:
73-
// - If the user has code monitors, show the list tab
74-
// - If the user has no code monitors, show the getting started tab
75-
useLayoutEffect(() => {
76-
if (userHasCodeMonitors === true) {
77-
setCurrentTab('list')
78-
} else if (userHasCodeMonitors === false) {
79-
setCurrentTab('getting-started')
80-
}
81-
}, [userHasCodeMonitors])
103+
const [currentTab, setCurrentTab] = useState<MonitorsTab>(() =>
104+
getSelectedTabFromLocation(location.search, userHasCodeMonitors)
105+
)
106+
107+
const onSelectTab = useCallback(
108+
(tab: MonitorsTab) => {
109+
setCurrentTab(tab)
110+
setSelectedLocationTab(location, navigate, tab)
111+
},
112+
[navigate, location, setCurrentTab]
113+
)
82114

83115
// Force tab for testing
84116
useLayoutEffect(() => {
@@ -105,8 +137,26 @@ export const CodeMonitoringPage: React.FunctionComponent<React.PropsWithChildren
105137

106138
const showList = userHasCodeMonitors !== undefined && !isErrorLike(userHasCodeMonitors) && currentTab === 'list'
107139

108-
const showLogsTab =
109-
useExperimentalFeatures(features => features.showCodeMonitoringLogs) && authenticatedUser && !isCodyApp
140+
const tabs: Tabs = useMemo(
141+
() => [
142+
{
143+
tab: 'list',
144+
title: 'Code monitors',
145+
isActive: currentTab === 'list',
146+
},
147+
{
148+
tab: 'getting-started',
149+
title: 'Getting started',
150+
isActive: currentTab === 'getting-started',
151+
},
152+
{
153+
tab: 'logs',
154+
title: 'Logs',
155+
isActive: currentTab === 'logs',
156+
},
157+
],
158+
[currentTab]
159+
)
110160

111161
return (
112162
<div className="code-monitoring-page" data-testid="code-monitoring-page">
@@ -136,56 +186,24 @@ export const CodeMonitoringPage: React.FunctionComponent<React.PropsWithChildren
136186
<div className="d-flex flex-column">
137187
<div className="code-monitoring-page-tabs mb-4">
138188
<div className="nav nav-tabs">
139-
{!isCodyApp && (
140-
<div className="nav-item">
141-
<Link
189+
{tabs.map(({ tab, title, isActive }) => (
190+
<div className="nav-item" key={tab}>
191+
<ButtonLink
142192
to=""
143-
onClick={event => {
144-
event.preventDefault()
145-
setCurrentTab('list')
146-
}}
147-
className={classNames('nav-link', currentTab === 'list' && 'active')}
148193
role="button"
149-
>
150-
<span className="text-content" data-tab-content="Code monitors">
151-
Code monitors
152-
</span>
153-
</Link>
154-
</div>
155-
)}
156-
<div className="nav-item">
157-
<Link
158-
to=""
159-
onClick={event => {
160-
event.preventDefault()
161-
setCurrentTab('getting-started')
162-
}}
163-
className={classNames('nav-link', currentTab === 'getting-started' && 'active')}
164-
role="button"
165-
>
166-
<span className="text-content" data-tab-content="Getting started">
167-
Getting started
168-
</span>
169-
</Link>
170-
</div>
171-
{showLogsTab && (
172-
<div className="nav-item">
173-
<Link
174-
to=""
175-
onClick={event => {
194+
onSelect={event => {
176195
event.preventDefault()
177-
setCurrentTab('logs')
196+
onSelectTab(tab)
178197
}}
179-
className={classNames('nav-link flex-row', currentTab === 'logs' && 'active')}
180-
role="button"
198+
className={classNames('nav-link', isActive && 'active')}
181199
>
182-
<span className="text-content" data-tab-content="Logs">
183-
Logs
200+
<span>
201+
{title}
202+
{tab === 'logs' && <ProductStatusBadge status="beta" className="ml-2" />}
184203
</span>
185-
<ProductStatusBadge status="beta" className="ml-2" />
186-
</Link>
204+
</ButtonLink>
187205
</div>
188-
)}
206+
))}
189207
</div>
190208
</div>
191209

0 commit comments

Comments
 (0)