Skip to content

Commit 096d7d0

Browse files
Add Workflow Diagnostics tab with placeholder content (#945)
* Add useIsWorkflowDiagnosticsEnabled hook * Add Workflow Diagnostics tab to tabs config * Show tab depending on config value * Create Workflow Diagnostics root component with placeholder, and error panel if diagnostics is disabled
1 parent 448469d commit 096d7d0

File tree

8 files changed

+224
-23
lines changed

8 files changed

+224
-23
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { type Props as ErrorPanelProps } from '@/components/error-panel/error-panel.types';
2+
3+
const workflowDiagnosticsDisabledErrorPanelConfig: ErrorPanelProps = {
4+
message: 'Workflow Diagnostics is currently disabled',
5+
omitLogging: true,
6+
actions: [
7+
{
8+
kind: 'link-internal',
9+
link: './summary',
10+
label: 'Go to workflow summary',
11+
},
12+
],
13+
};
14+
15+
export default workflowDiagnosticsDisabledErrorPanelConfig;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use client';
2+
3+
import React from 'react';
4+
5+
import ErrorPanel from '@/components/error-panel/error-panel';
6+
import PageSection from '@/components/page-section/page-section';
7+
import PanelSection from '@/components/panel-section/panel-section';
8+
import { type WorkflowPageTabContentProps } from '@/views/workflow-page/workflow-page-tab-content/workflow-page-tab-content.types';
9+
10+
import useSuspenseIsWorkflowDiagnosticsEnabled from '../workflow-page/hooks/use-is-workflow-diagnostics-enabled/use-suspense-is-workflow-diagnostics-enabled';
11+
12+
import workflowDiagnosticsDisabledErrorPanelConfig from './config/workflow-diagnostics-disabled-error-panel.config';
13+
14+
export default function WorkflowDiagnostics(_: WorkflowPageTabContentProps) {
15+
const { data: isWorkflowDiagnosticsEnabled } =
16+
useSuspenseIsWorkflowDiagnosticsEnabled();
17+
18+
if (!isWorkflowDiagnosticsEnabled) {
19+
return (
20+
<PanelSection>
21+
<ErrorPanel {...workflowDiagnosticsDisabledErrorPanelConfig} />
22+
</PanelSection>
23+
);
24+
}
25+
26+
return (
27+
<PageSection>
28+
<div>Workflow Diagnostics (WIP)</div>
29+
</PageSection>
30+
);
31+
}

src/views/workflow-page/__fixtures__/workflow-page-tabs-config.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { WorkflowPageTabContentProps } from '../workflow-page-tab-content/w
44
import { type WorkflowPageTabsConfig } from '../workflow-page-tabs/workflow-page-tabs.types';
55

66
export const mockWorkflowPageTabsConfig: WorkflowPageTabsConfig<
7-
'summary' | 'history' | 'queries' | 'stack-trace'
7+
'summary' | 'history' | 'queries' | 'stack-trace' | 'diagnostics'
88
> = {
99
summary: {
1010
title: 'Summary',
@@ -22,6 +22,14 @@ export const mockWorkflowPageTabsConfig: WorkflowPageTabsConfig<
2222
createElement('div', {}, JSON.stringify(params)),
2323
getErrorConfig: () => ({ message: 'history error' }),
2424
},
25+
diagnostics: {
26+
title: 'Diagnostics',
27+
artwork: () =>
28+
createElement('div', { 'data-testid': 'diagnostics-artwork' }),
29+
content: ({ params }: WorkflowPageTabContentProps) =>
30+
createElement('div', {}, JSON.stringify(params)),
31+
getErrorConfig: () => ({ message: 'diagnostics error' }),
32+
},
2533
queries: {
2634
title: 'Queries',
2735
artwork: () => createElement('div', { 'data-testid': 'queries-artwork' }),

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {
44
MdOutlineManageSearch,
55
MdOutlineTerminal,
66
} from 'react-icons/md';
7+
import { RiStethoscopeLine } from 'react-icons/ri';
78

9+
import WorkflowDiagnostics from '@/views/workflow-diagnostics/workflow-diagnostics';
810
import WorkflowHistory from '@/views/workflow-history/workflow-history';
911
import WorkflowQueries from '@/views/workflow-queries/workflow-queries';
1012
import WorkflowStackTrace from '@/views/workflow-stack-trace/workflow-stack-trace';
@@ -15,7 +17,7 @@ import WorkflowPagePendingEventsBadge from '../workflow-page-pending-events-badg
1517
import type { WorkflowPageTabsConfig } from '../workflow-page-tabs/workflow-page-tabs.types';
1618

1719
const workflowPageTabsConfig: WorkflowPageTabsConfig<
18-
'summary' | 'history' | 'queries' | 'stack-trace'
20+
'summary' | 'history' | 'queries' | 'stack-trace' | 'diagnostics'
1921
> = {
2022
summary: {
2123
title: 'Summary',
@@ -40,6 +42,17 @@ const workflowPageTabsConfig: WorkflowPageTabsConfig<
4042
'history'
4143
),
4244
},
45+
diagnostics: {
46+
title: 'Diagnostics',
47+
artwork: RiStethoscopeLine,
48+
content: WorkflowDiagnostics,
49+
getErrorConfig: (err) =>
50+
getWorkflowPageErrorConfig(
51+
err,
52+
'Failed to load workflow diagnostics',
53+
'diagnostics'
54+
),
55+
},
4356
queries: {
4457
title: 'Queries',
4558
artwork: MdOutlineManageSearch,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { type UseQueryOptions } from '@tanstack/react-query';
2+
import queryString from 'query-string';
3+
4+
import { type GetConfigResponse } from '@/route-handlers/get-config/get-config.types';
5+
import request from '@/utils/request';
6+
import { type RequestError } from '@/utils/request/request-error';
7+
8+
export default function getIsWorkflowDiagnosticsEnabledQueryOptions(): UseQueryOptions<
9+
GetConfigResponse<'WORKFLOW_DIAGNOSTICS_ENABLED'>,
10+
RequestError,
11+
GetConfigResponse<'WORKFLOW_DIAGNOSTICS_ENABLED'>,
12+
[string, { configKey: 'WORKFLOW_DIAGNOSTICS_ENABLED' }]
13+
> {
14+
return {
15+
queryKey: ['dynamic_config', { configKey: 'WORKFLOW_DIAGNOSTICS_ENABLED' }],
16+
queryFn: ({
17+
queryKey: [_, { configKey }],
18+
}: {
19+
queryKey: [string, { configKey: 'WORKFLOW_DIAGNOSTICS_ENABLED' }];
20+
}): Promise<GetConfigResponse<'WORKFLOW_DIAGNOSTICS_ENABLED'>> =>
21+
request(
22+
queryString.stringifyUrl({
23+
url: '/api/config',
24+
query: {
25+
configKey,
26+
},
27+
})
28+
).then((res) => res.json()),
29+
};
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { useSuspenseQuery } from '@tanstack/react-query';
2+
3+
import getIsWorkflowDiagnosticsEnabledQueryOptions from './get-is-workflow-diagnostics-enabled-query-options';
4+
5+
export default function useSuspenseIsWorkflowDiagnosticsEnabled() {
6+
return useSuspenseQuery(getIsWorkflowDiagnosticsEnabledQueryOptions());
7+
}

src/views/workflow-page/workflow-page-tabs/__tests__/workflow-page-tabs.test.tsx

Lines changed: 103 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import React from 'react';
1+
import React, { Suspense } from 'react';
22

3-
import { render, screen } from '@/test-utils/rtl';
3+
import { HttpResponse } from 'msw';
4+
5+
import { render, screen, act, fireEvent } from '@/test-utils/rtl';
6+
7+
import ErrorBoundary from '@/components/error-boundary/error-boundary';
48

59
import { mockWorkflowPageTabsConfig } from '../../__fixtures__/workflow-page-tabs-config';
6-
import workflowPageTabsConfig from '../../config/workflow-page-tabs.config';
710
import WorkflowPageTabs from '../workflow-page-tabs';
811

912
const mockPushFn = jest.fn();
@@ -46,30 +49,111 @@ describe('WorkflowPageTabs', () => {
4649
jest.clearAllMocks();
4750
});
4851

49-
it('renders tabs titles correctly', () => {
50-
setup();
51-
Object.values(workflowPageTabsConfig).forEach(({ title }) => {
52-
expect(screen.getByText(title)).toBeInTheDocument();
53-
});
52+
it('renders tabs titles correctly with diagnostics disabled', async () => {
53+
await setup({ enableDiagnostics: false });
54+
55+
expect(screen.getByText('Summary')).toBeInTheDocument();
56+
expect(screen.getByText('History')).toBeInTheDocument();
57+
expect(screen.getByText('Queries')).toBeInTheDocument();
58+
expect(screen.getByText('Stack Trace')).toBeInTheDocument();
59+
expect(screen.queryByText('Diagnostics')).toBeNull();
60+
});
61+
62+
it('renders tabs with diagnostics enabled', async () => {
63+
await setup({ enableDiagnostics: true });
64+
65+
expect(screen.getByText('Summary')).toBeInTheDocument();
66+
expect(screen.getByText('History')).toBeInTheDocument();
67+
expect(screen.getByText('Queries')).toBeInTheDocument();
68+
expect(screen.getByText('Stack Trace')).toBeInTheDocument();
69+
expect(screen.getByText('Diagnostics')).toBeInTheDocument();
5470
});
5571

56-
it('renders tabs buttons correctly', () => {
57-
setup();
72+
it('renders tabs buttons correctly', async () => {
73+
await setup({ enableDiagnostics: false });
74+
5875
expect(screen.getByText('CLI Commands')).toBeInTheDocument();
5976
expect(screen.getByText('Actions')).toBeInTheDocument();
6077
});
6178

62-
it('renders tabs artworks correctly', () => {
63-
setup();
64-
Object.entries(workflowPageTabsConfig).forEach(([key, { artwork }]) => {
65-
if (typeof artwork !== 'undefined')
66-
expect(screen.getByTestId(`${key}-artwork`)).toBeInTheDocument();
67-
else
68-
expect(screen.queryByTestId(`${key}-artwork`)).not.toBeInTheDocument();
79+
it('renders tabs artworks correctly with diagnostics disabled', async () => {
80+
await setup({ enableDiagnostics: false });
81+
82+
expect(screen.getByTestId('summary-artwork')).toBeInTheDocument();
83+
expect(screen.getByTestId('history-artwork')).toBeInTheDocument();
84+
expect(screen.getByTestId('queries-artwork')).toBeInTheDocument();
85+
expect(screen.getByTestId('stack-trace-artwork')).toBeInTheDocument();
86+
expect(screen.queryByTestId('diagnostics-artwork')).toBeNull();
87+
});
88+
89+
it('renders tabs artworks correctly with diagnostics enabled', async () => {
90+
await setup({ enableDiagnostics: true });
91+
92+
expect(screen.getByTestId('summary-artwork')).toBeInTheDocument();
93+
expect(screen.getByTestId('history-artwork')).toBeInTheDocument();
94+
expect(screen.getByTestId('queries-artwork')).toBeInTheDocument();
95+
expect(screen.getByTestId('stack-trace-artwork')).toBeInTheDocument();
96+
expect(screen.getByTestId('diagnostics-artwork')).toBeInTheDocument();
97+
});
98+
99+
it('reroutes when new tab is clicked', async () => {
100+
await setup({ enableDiagnostics: false });
101+
102+
const historyTab = await screen.findByText('History');
103+
act(() => {
104+
fireEvent.click(historyTab);
69105
});
106+
107+
expect(mockPushFn).toHaveBeenCalledWith('history');
108+
});
109+
110+
it('handles errors gracefully', async () => {
111+
await setup({ error: true });
112+
113+
expect(
114+
await screen.findByText('Error: Failed to fetch config')
115+
).toBeInTheDocument();
70116
});
71117
});
72118

73-
function setup() {
74-
return render(<WorkflowPageTabs />);
119+
async function setup({
120+
error,
121+
enableDiagnostics,
122+
}: {
123+
error?: boolean;
124+
enableDiagnostics?: boolean;
125+
}) {
126+
render(
127+
<ErrorBoundary
128+
fallbackRender={({ error }) => <div>Error: {error.message}</div>}
129+
>
130+
<Suspense fallback={<div>Loading...</div>}>
131+
<WorkflowPageTabs />
132+
</Suspense>
133+
</ErrorBoundary>,
134+
{
135+
endpointsMocks: [
136+
{
137+
path: '/api/config',
138+
httpMethod: 'GET',
139+
mockOnce: false,
140+
httpResolver: async () => {
141+
if (error) {
142+
return HttpResponse.json(
143+
{ message: 'Failed to fetch config' },
144+
{ status: 500 }
145+
);
146+
} else {
147+
return HttpResponse.json(enableDiagnostics ?? false);
148+
}
149+
},
150+
},
151+
],
152+
}
153+
);
154+
155+
if (!error) {
156+
// Wait for the first tab to load
157+
await screen.findByText('Summary');
158+
}
75159
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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 ErrorBoundary from '@/components/error-boundary/error-boundary';
@@ -9,6 +10,7 @@ import decodeUrlParams from '@/utils/decode-url-params';
910
import WorkflowActions from '@/views/workflow-actions/workflow-actions';
1011

1112
import workflowPageTabsConfig from '../config/workflow-page-tabs.config';
13+
import useSuspenseIsWorkflowDiagnosticsEnabled from '../hooks/use-is-workflow-diagnostics-enabled/use-suspense-is-workflow-diagnostics-enabled';
1214
import WorkflowPageCliCommandsButton from '../workflow-page-cli-commands-button/workflow-page-cli-commands-button';
1315

1416
import { styled } from './workflow-page-tabs.styles';
@@ -19,15 +21,26 @@ export default function WorkflowPageTabs() {
1921
const params = useParams<WorkflowPageTabsParams>();
2022
const decodedParams = decodeUrlParams(params) as WorkflowPageTabsParams;
2123

24+
const { data: isWorkflowDiagnosticsEnabled } =
25+
useSuspenseIsWorkflowDiagnosticsEnabled();
26+
27+
const filteredTabsConfig = useMemo(
28+
() =>
29+
isWorkflowDiagnosticsEnabled
30+
? workflowPageTabsConfig
31+
: omit(workflowPageTabsConfig, 'diagnostics'),
32+
[isWorkflowDiagnosticsEnabled]
33+
);
34+
2235
const tabList = useMemo(
2336
() =>
24-
Object.entries(workflowPageTabsConfig).map(([key, tabConfig]) => ({
37+
Object.entries(filteredTabsConfig).map(([key, tabConfig]) => ({
2538
key,
2639
title: tabConfig.title,
2740
artwork: tabConfig.artwork,
2841
endEnhancer: tabConfig.endEnhancer,
2942
})),
30-
[]
43+
[filteredTabsConfig]
3144
);
3245

3346
return (

0 commit comments

Comments
 (0)