Skip to content

Commit 78e685a

Browse files
Add failover history tab (#1071)
Signed-off-by: Adhitya Mamallan <adhitya.mamallan@uber.com>
1 parent 02ef22d commit 78e685a

File tree

4 files changed

+166
-42
lines changed

4 files changed

+166
-42
lines changed

src/views/domain-page/config/domain-page-tabs.config.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1-
import { MdArchive, MdListAlt, MdSettings, MdSort } from 'react-icons/md';
1+
import {
2+
MdArchive,
3+
MdListAlt,
4+
MdSettings,
5+
MdSort,
6+
MdSyncAlt,
7+
} from 'react-icons/md';
28

39
import DomainWorkflows from '@/views/domain-workflows/domain-workflows';
410
import DomainWorkflowsArchival from '@/views/domain-workflows-archival/domain-workflows-archival';
511

12+
import DomainPageFailovers from '../domain-page-failovers/domain-page-failovers';
613
import DomainPageMetadata from '../domain-page-metadata/domain-page-metadata';
714
import DomainPageSettings from '../domain-page-settings/domain-page-settings';
815
import type { DomainPageTabsConfig } from '../domain-page-tabs/domain-page-tabs.types';
916

1017
const domainPageTabsConfig: DomainPageTabsConfig<
11-
'workflows' | 'metadata' | 'settings' | 'archival'
18+
'workflows' | 'metadata' | 'failovers' | 'settings' | 'archival'
1219
> = {
1320
workflows: {
1421
title: 'Workflows',
@@ -28,6 +35,15 @@ const domainPageTabsConfig: DomainPageTabsConfig<
2835
actions: [{ kind: 'retry', label: 'Retry' }],
2936
}),
3037
},
38+
failovers: {
39+
title: 'Failovers',
40+
artwork: MdSyncAlt,
41+
content: DomainPageFailovers,
42+
getErrorConfig: () => ({
43+
message: 'Failed to load failovers',
44+
actions: [{ kind: 'retry', label: 'Retry' }],
45+
}),
46+
},
3147
settings: {
3248
title: 'Settings',
3349
artwork: MdSettings,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use client';
2+
import React from 'react';
3+
4+
import { type DomainPageTabContentProps } from '../domain-page-content/domain-page-content.types';
5+
6+
export default function DomainPageFailovers(_: DomainPageTabContentProps) {
7+
return <div>WIP: Domain Page Failovers Tab</div>;
8+
}
Lines changed: 118 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
import React from 'react';
1+
import React, { Suspense } from 'react';
22

3-
import { render, screen, act, fireEvent } from '@/test-utils/rtl';
3+
import { HttpResponse } from 'msw';
4+
5+
import { render, screen, userEvent } from '@/test-utils/rtl';
6+
7+
import ErrorBoundary from '@/components/error-boundary/error-boundary';
8+
import { type GetConfigResponse } from '@/route-handlers/get-config/get-config.types';
49

5-
import domainPageTabsConfig from '../../config/domain-page-tabs.config';
610
import DomainPageTabs from '../domain-page-tabs';
711

812
const mockPushFn = jest.fn();
@@ -36,40 +40,63 @@ jest.mock('../../config/domain-page-tabs.config', () => ({
3640
title: 'Workflows',
3741
artwork: () => <div data-testid="workflows-artwork" />,
3842
},
39-
'page-2': {
40-
title: 'Page 2',
43+
metadata: {
44+
title: 'Metadata',
45+
artwork: () => <div data-testid="metadata-artwork" />,
46+
},
47+
failovers: {
48+
title: 'Failovers',
49+
artwork: () => <div data-testid="failovers-artwork" />,
50+
},
51+
settings: {
52+
title: 'Settings',
53+
artwork: () => <div data-testid="settings-artwork" />,
54+
},
55+
archival: {
56+
title: 'Archival',
57+
artwork: () => <div data-testid="archival-artwork" />,
4158
},
4259
}));
4360

4461
jest.mock('../../domain-page-help/domain-page-help', () =>
4562
jest.fn(() => <button data-testid="domain-page-help">Help Button</button>)
4663
);
4764

48-
describe('DomainPageTabs', () => {
65+
describe(DomainPageTabs.name, () => {
4966
afterEach(() => {
5067
jest.clearAllMocks();
5168
});
5269

53-
it('renders tabs titles correctly', () => {
54-
render(<DomainPageTabs />);
70+
it('renders tabs titles correctly with failover history disabled', async () => {
71+
await setup({ enableFailoverHistory: false });
5572

56-
Object.values(domainPageTabsConfig).forEach(({ title }) => {
57-
expect(screen.getByText(title)).toBeInTheDocument();
58-
});
73+
expect(screen.getByText('Workflows')).toBeInTheDocument();
74+
expect(screen.getByText('Metadata')).toBeInTheDocument();
75+
expect(screen.getByText('Settings')).toBeInTheDocument();
76+
expect(screen.getByText('Archival')).toBeInTheDocument();
77+
expect(screen.queryByText('Failovers')).toBeNull();
5978
});
6079

61-
it('reroutes when new tab is clicked', () => {
62-
render(<DomainPageTabs />);
80+
it('renders tabs with failover history enabled', async () => {
81+
await setup({ enableFailoverHistory: true });
6382

64-
const page2Tab = screen.getByText('Page 2');
65-
act(() => {
66-
fireEvent.click(page2Tab);
67-
});
83+
expect(screen.getByText('Workflows')).toBeInTheDocument();
84+
expect(screen.getByText('Metadata')).toBeInTheDocument();
85+
expect(screen.getByText('Failovers')).toBeInTheDocument();
86+
expect(screen.getByText('Settings')).toBeInTheDocument();
87+
expect(screen.getByText('Archival')).toBeInTheDocument();
88+
});
6889

69-
expect(mockPushFn).toHaveBeenCalledWith('page-2');
90+
it('reroutes when new tab is clicked', async () => {
91+
const { user } = await setup({ enableFailoverHistory: false });
92+
93+
const metadataTab = await screen.findByText('Metadata');
94+
await user.click(metadataTab);
95+
96+
expect(mockPushFn).toHaveBeenCalledWith('metadata');
7097
});
7198

72-
it('retains query params when new tab is clicked', () => {
99+
it('retains query params when new tab is clicked', async () => {
73100
// TODO: this is a bit hacky, see if there is a better way to mock the window search property
74101
const originalWindow = window;
75102
window = Object.create(window);
@@ -81,41 +108,95 @@ describe('DomainPageTabs', () => {
81108
writable: true,
82109
});
83110

84-
render(<DomainPageTabs />);
111+
const { user } = await setup({ enableFailoverHistory: false });
85112

86-
const page2Tab = screen.getByText('Page 2');
87-
act(() => {
88-
fireEvent.click(page2Tab);
89-
});
113+
const metadataTab = await screen.findByText('Metadata');
114+
await user.click(metadataTab);
90115

91116
expect(mockPushFn).toHaveBeenCalledWith(
92-
'page-2?queryParam1=one&queryParam2=two'
117+
'metadata?queryParam1=one&queryParam2=two'
93118
);
94119

95120
window = originalWindow;
96121
});
97122

98-
it('renders tabs artworks correctly', () => {
99-
render(<DomainPageTabs />);
123+
it('renders tabs artworks correctly', async () => {
124+
await setup({ enableFailoverHistory: false });
100125

101-
Object.entries(domainPageTabsConfig).forEach(([key, { artwork }]) => {
102-
if (typeof artwork !== 'undefined')
103-
expect(screen.getByTestId(`${key}-artwork`)).toBeInTheDocument();
104-
else
105-
expect(screen.queryByTestId(`${key}-artwork`)).not.toBeInTheDocument();
106-
});
126+
expect(screen.getByTestId('workflows-artwork')).toBeInTheDocument();
127+
expect(screen.getByTestId('metadata-artwork')).toBeInTheDocument();
128+
expect(screen.getByTestId('settings-artwork')).toBeInTheDocument();
129+
expect(screen.getByTestId('archival-artwork')).toBeInTheDocument();
130+
expect(screen.queryByTestId('failovers-artwork')).toBeNull();
107131
});
108132

109-
it('renders the help button as endEnhancer', () => {
110-
render(<DomainPageTabs />);
133+
it('handles errors gracefully', async () => {
134+
await setup({ error: true });
135+
136+
expect(
137+
await screen.findByText('Error: Failed to fetch config')
138+
).toBeInTheDocument();
139+
});
140+
141+
it('renders the help button as endEnhancer', async () => {
142+
await setup({});
111143

112144
expect(screen.getByTestId('domain-page-help')).toBeInTheDocument();
113145
expect(screen.getByText('Help Button')).toBeInTheDocument();
114146
});
115147

116-
it('renders the start workflow button', () => {
117-
render(<DomainPageTabs />);
148+
it('renders the start workflow button', async () => {
149+
await setup({});
118150

119151
expect(screen.getByTestId('start-workflow-button')).toBeInTheDocument();
120152
});
121153
});
154+
155+
async function setup({
156+
error,
157+
enableFailoverHistory,
158+
}: {
159+
error?: boolean;
160+
enableFailoverHistory?: boolean;
161+
}) {
162+
const user = userEvent.setup();
163+
164+
render(
165+
<ErrorBoundary
166+
fallbackRender={({ error }) => <div>Error: {error.message}</div>}
167+
>
168+
<Suspense fallback={<div>Loading...</div>}>
169+
<DomainPageTabs />
170+
</Suspense>
171+
</ErrorBoundary>,
172+
{
173+
endpointsMocks: [
174+
{
175+
path: '/api/config',
176+
httpMethod: 'GET',
177+
mockOnce: false,
178+
httpResolver: async () => {
179+
if (error) {
180+
return HttpResponse.json(
181+
{ message: 'Failed to fetch config' },
182+
{ status: 500 }
183+
);
184+
} else {
185+
return HttpResponse.json(
186+
(enableFailoverHistory ??
187+
false) satisfies GetConfigResponse<'FAILOVER_HISTORY_ENABLED'>
188+
);
189+
}
190+
},
191+
},
192+
],
193+
}
194+
);
195+
196+
if (!error) {
197+
// Wait for the first tab to load
198+
await screen.findByText('Workflows');
199+
}
200+
201+
return { user };
202+
}

src/views/domain-page/domain-page-tabs/domain-page-tabs.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,50 @@
11
'use client';
22
import React, { useMemo } from 'react';
33

4+
import omit from 'lodash/omit';
45
import { useRouter, useParams } from 'next/navigation';
56

67
import PageTabs from '@/components/page-tabs/page-tabs';
8+
import useSuspenseConfigValue from '@/hooks/use-config-value/use-suspense-config-value';
79
import decodeUrlParams from '@/utils/decode-url-params';
810

911
import domainPageTabsConfig from '../config/domain-page-tabs.config';
1012
import DomainPageHelp from '../domain-page-help/domain-page-help';
1113
import DomainPageStartWorkflowButton from '../domain-page-start-workflow-button/domain-page-start-workflow-button';
1214

1315
import { styled } from './domain-page-tabs.styles';
14-
import type { DomainPageTabsParams } from './domain-page-tabs.types';
16+
import {
17+
type DomainPageTabName,
18+
type DomainPageTabsParams,
19+
} from './domain-page-tabs.types';
1520

1621
export default function DomainPageTabs() {
1722
const router = useRouter();
1823
const params = useParams<DomainPageTabsParams>();
1924
const decodedParams = decodeUrlParams(params) as DomainPageTabsParams;
2025

26+
const { data: isFailoverHistoryEnabled } = useSuspenseConfigValue(
27+
'FAILOVER_HISTORY_ENABLED'
28+
);
29+
30+
const tabsConfig = useMemo<Partial<typeof domainPageTabsConfig>>(() => {
31+
const tabsToHide: Array<DomainPageTabName> = [];
32+
33+
if (!isFailoverHistoryEnabled) {
34+
tabsToHide.push('failovers');
35+
}
36+
37+
return omit(domainPageTabsConfig, tabsToHide);
38+
}, [isFailoverHistoryEnabled]);
39+
2140
const tabList = useMemo(
2241
() =>
23-
Object.entries(domainPageTabsConfig).map(([key, tabConfig]) => ({
42+
Object.entries(tabsConfig).map(([key, tabConfig]) => ({
2443
key,
2544
title: tabConfig.title,
2645
artwork: tabConfig.artwork,
2746
})),
28-
[]
47+
[tabsConfig]
2948
);
3049

3150
return (

0 commit comments

Comments
 (0)