Skip to content

Commit 88a5010

Browse files
Implement Workflow Diagnostics JSON viewer (#950)
Implement Workflow Diagnostics JSON viewer with Pretty JSON, copy, and download functionality.
1 parent a15cc7d commit 88a5010

File tree

3 files changed

+193
-4
lines changed

3 files changed

+193
-4
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { ZodError } from 'zod';
2+
3+
import { render, screen, userEvent } from '@/test-utils/rtl';
4+
5+
import { mockWorkflowDiagnosticsResult } from '@/route-handlers/diagnose-workflow/__fixtures__/mock-workflow-diagnostics-result';
6+
import { type DiagnoseWorkflowResponse } from '@/route-handlers/diagnose-workflow/diagnose-workflow.types';
7+
8+
import WorkflowDiagnosticsJson from '../workflow-diagnostics-json';
9+
10+
const mockDownloadJson = jest.fn();
11+
jest.mock('@/utils/download-json', () =>
12+
jest.fn((json, filename) => mockDownloadJson(json, filename))
13+
);
14+
15+
jest.mock('@/components/copy-text-button/copy-text-button', () =>
16+
jest.fn(({ textToCopy }) => (
17+
<div data-testid="copy-text-button">{textToCopy}</div>
18+
))
19+
);
20+
21+
jest.mock('@/components/pretty-json/pretty-json', () =>
22+
jest.fn(({ json }) => (
23+
<div data-testid="pretty-json">{JSON.stringify(json)}</div>
24+
))
25+
);
26+
27+
const mockDiagnosticsResponse = {
28+
result: mockWorkflowDiagnosticsResult,
29+
parsingError: null,
30+
};
31+
32+
describe(WorkflowDiagnosticsJson.name, () => {
33+
beforeEach(() => {
34+
jest.clearAllMocks();
35+
});
36+
37+
it('renders the PrettyJson component with diagnostics result', () => {
38+
setup({});
39+
expect(screen.getByTestId('pretty-json')).toBeInTheDocument();
40+
expect(screen.getByTestId('pretty-json')).toHaveTextContent(
41+
'"DiagnosticsResult"'
42+
);
43+
});
44+
45+
it('renders copy text button with text to copy', () => {
46+
setup({});
47+
const copyButton = screen.getByTestId('copy-text-button');
48+
expect(copyButton).toBeInTheDocument();
49+
expect(copyButton).toHaveTextContent('"DiagnosticsResult"');
50+
});
51+
52+
it('downloads JSON when download button is clicked', async () => {
53+
const { user } = setup({});
54+
const downloadButton = screen.getByTestId('download-json-button');
55+
await user.click(downloadButton);
56+
57+
expect(mockDownloadJson).toHaveBeenCalledWith(
58+
mockDiagnosticsResponse.result,
59+
'diagnostics_test-workflow-id_test-run-id'
60+
);
61+
});
62+
63+
it('renders with empty diagnostics result', () => {
64+
setup({
65+
diagnosticsResponse: {
66+
result: {
67+
DiagnosticsResult: {},
68+
DiagnosticsCompleted: true,
69+
},
70+
parsingError: null,
71+
},
72+
});
73+
74+
expect(screen.getByTestId('pretty-json')).toBeInTheDocument();
75+
expect(screen.getByTestId('pretty-json')).toHaveTextContent(
76+
'"DiagnosticsResult"'
77+
);
78+
});
79+
80+
it('renders JSON even if there is a parsing error', () => {
81+
setup({
82+
diagnosticsResponse: {
83+
result: {
84+
DiagnosticsResult: {
85+
invalidParsedField: 'invalid-value',
86+
},
87+
DiagnosticsCompleted: true,
88+
},
89+
parsingError: new ZodError([]),
90+
},
91+
});
92+
93+
expect(screen.getByTestId('pretty-json')).toBeInTheDocument();
94+
expect(screen.getByTestId('pretty-json')).toHaveTextContent(
95+
'"invalid-value"'
96+
);
97+
});
98+
});
99+
100+
function setup({
101+
diagnosticsResponse = mockDiagnosticsResponse,
102+
}: {
103+
diagnosticsResponse?: DiagnoseWorkflowResponse;
104+
}) {
105+
const user = userEvent.setup();
106+
const defaultProps = {
107+
domain: 'test-domain',
108+
cluster: 'test-cluster',
109+
workflowId: 'test-workflow-id',
110+
runId: 'test-run-id',
111+
diagnosticsResponse,
112+
};
113+
114+
const view = render(<WorkflowDiagnosticsJson {...defaultProps} />);
115+
return { user, ...view };
116+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { styled as createStyled, type Theme } from 'baseui';
2+
import { type ButtonOverrides } from 'baseui/button';
3+
import { type StyleObject } from 'styletron-react';
4+
5+
export const overrides = {
6+
jsonButton: {
7+
BaseButton: {
8+
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
9+
width: $theme.sizing.scale950,
10+
height: $theme.sizing.scale950,
11+
backgroundColor: 'rgba(0, 0, 0, 0)',
12+
}),
13+
},
14+
} satisfies ButtonOverrides,
15+
};
16+
17+
export const styled = {
18+
ViewContainer: createStyled('div', ({ $theme }) => ({
19+
position: 'relative',
20+
gap: $theme.sizing.scale600,
21+
padding: $theme.sizing.scale600,
22+
backgroundColor: $theme.colors.backgroundSecondary,
23+
borderRadius: $theme.borders.radius300,
24+
})),
25+
ButtonsContainer: createStyled('div', {
26+
position: 'absolute',
27+
top: '10px',
28+
right: '10px',
29+
display: 'flex',
30+
}),
31+
};
Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,54 @@
1+
import { useMemo } from 'react';
2+
3+
import { Button } from 'baseui/button';
4+
import { MdCopyAll, MdOutlineCloudDownload } from 'react-icons/md';
5+
6+
import CopyTextButton from '@/components/copy-text-button/copy-text-button';
7+
import PrettyJson from '@/components/pretty-json/pretty-json';
8+
import downloadJson from '@/utils/download-json';
9+
import losslessJsonStringify from '@/utils/lossless-json-stringify';
10+
111
import { type ViewComponentProps } from '../workflow-diagnostics-content/workflow-diagnostics-content.types';
212

13+
import { styled, overrides } from './workflow-diagnostics-json.styles';
14+
315
export default function WorkflowDiagnosticsJson({
16+
workflowId,
17+
runId,
418
diagnosticsResponse,
519
}: ViewComponentProps) {
20+
const diagnosticsResult = diagnosticsResponse.result;
21+
22+
const textToCopy = useMemo(() => {
23+
return losslessJsonStringify(diagnosticsResult, null, '\t');
24+
}, [diagnosticsResult]);
25+
626
return (
7-
<div>
8-
<div>Diagnostics JSON (WIP)</div>
9-
{JSON.stringify(diagnosticsResponse.result, null, 2)}
10-
</div>
27+
<styled.ViewContainer>
28+
<PrettyJson json={diagnosticsResult as Record<string, any>} />
29+
<styled.ButtonsContainer>
30+
<Button
31+
data-testid="download-json-button"
32+
size="mini"
33+
kind="secondary"
34+
shape="circle"
35+
overrides={overrides.jsonButton}
36+
onClick={() =>
37+
downloadJson(
38+
diagnosticsResult,
39+
`diagnostics_${workflowId}_${runId}`
40+
)
41+
}
42+
>
43+
<MdOutlineCloudDownload size={16} />
44+
</Button>
45+
<CopyTextButton
46+
textToCopy={textToCopy}
47+
overrides={overrides.jsonButton}
48+
>
49+
<MdCopyAll size={16} />
50+
</CopyTextButton>
51+
</styled.ButtonsContainer>
52+
</styled.ViewContainer>
1153
);
1254
}

0 commit comments

Comments
 (0)