Skip to content

Commit fa07507

Browse files
authored
Json history event details (#688)
* history event details list * fix typecheck * use format in export json and move tasklist link dir * fix type check * json details viewer
1 parent bae1348 commit fa07507

File tree

8 files changed

+191
-36
lines changed

8 files changed

+191
-36
lines changed

src/views/workflow-history/config/workflow-history-event-details.config.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { createElement } from 'react';
2-
3-
import PrettyJson from '@/components/pretty-json/pretty-json';
41
import formatDate from '@/utils/data-formatters/format-date';
52

63
import { type WorkflowHistoryEventDetailsConfig } from '../workflow-history-event-details/workflow-history-event-details.types';
4+
import WorkflowHistoryEventDetailsJson from '../workflow-history-event-details-json/workflow-history-event-details-json';
75
import WorkflowHistoryEventDetailsTaskListLink from '../workflow-history-event-details-task-list-link/workflow-history-event-details-task-list-link';
86

97
const workflowHistoryEventDetailsConfig = [
@@ -25,11 +23,8 @@ const workflowHistoryEventDetailsConfig = [
2523
{
2624
name: 'Json as PrettyJson',
2725
pathRegex: '.*(input|result|details|Error)$',
28-
valueComponent: ({ entryValue }) => {
29-
return createElement(PrettyJson, {
30-
json: entryValue,
31-
});
32-
},
26+
valueComponent: WorkflowHistoryEventDetailsJson,
27+
forceWrap: true,
3328
},
3429
{
3530
name: 'Duration timeout & backoff seconds',
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React from 'react';
2+
3+
import copy from 'copy-to-clipboard';
4+
5+
import { render, fireEvent, screen, act } from '@/test-utils/rtl';
6+
7+
import WorkflowSummaryTabJsonView from '../workflow-history-event-details-json';
8+
9+
// Mock dependencies
10+
jest.mock('copy-to-clipboard', jest.fn);
11+
12+
jest.mock('@/components/pretty-json/pretty-json', () =>
13+
jest.fn(() => <div>PrettyJson Mock</div>)
14+
);
15+
16+
describe('WorkflowSummaryTabJsonView Component', () => {
17+
const inputJson = { input: 'inputJson' };
18+
19+
it('renders correctly with initial props', () => {
20+
const { getByText } = render(
21+
<WorkflowSummaryTabJsonView entryValue={inputJson} />
22+
);
23+
24+
expect(getByText('PrettyJson Mock')).toBeInTheDocument();
25+
});
26+
27+
it('copies JSON to clipboard', () => {
28+
render(<WorkflowSummaryTabJsonView entryValue={inputJson} />);
29+
30+
const copyButton = screen.getByRole('button');
31+
fireEvent.click(copyButton);
32+
33+
expect(copy).toHaveBeenCalledWith(JSON.stringify(inputJson, null, '\t'));
34+
});
35+
36+
it('show tooltip for 1 second and remove it', () => {
37+
jest.useFakeTimers();
38+
39+
render(<WorkflowSummaryTabJsonView entryValue={inputJson} />);
40+
41+
const copyButton = screen.getByRole('button');
42+
fireEvent.click(copyButton);
43+
const visibleTooltip = screen.getByText('Copied');
44+
expect(visibleTooltip).toBeInTheDocument();
45+
46+
act(() => {
47+
jest.advanceTimersByTime(1000 + 500); //hide + animation duration
48+
});
49+
50+
// Ensure the tooltip is hidden after 1000ms
51+
const hiddenTooltip = screen.queryByText('Copied');
52+
expect(hiddenTooltip).not.toBeInTheDocument();
53+
54+
jest.useRealTimers();
55+
});
56+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type {
2+
StyletronCSSObject,
3+
StyletronCSSObjectOf,
4+
} from '@/hooks/use-styletron-classes';
5+
6+
const cssStylesObj = {
7+
jsonViewWrapper: {
8+
position: 'relative',
9+
width: '100%',
10+
},
11+
jsonViewContainer: (theme) => ({
12+
padding: theme.sizing.scale600,
13+
backgroundColor: theme.colors.backgroundSecondary,
14+
borderRadius: theme.borders.radius300,
15+
maxHeight: '50vh',
16+
overflow: 'auto',
17+
}),
18+
jsonViewHeader: (theme) => ({
19+
display: 'flex',
20+
position: 'absolute',
21+
right: theme.sizing.scale400,
22+
top: theme.sizing.scale400,
23+
}),
24+
} satisfies StyletronCSSObject;
25+
26+
export const cssStyles: StyletronCSSObjectOf<typeof cssStylesObj> =
27+
cssStylesObj;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'use client';
2+
import React, { useEffect, useState } from 'react';
3+
4+
import { Button, KIND as BUTTON_KIND, SHAPE, SIZE } from 'baseui/button';
5+
import { ACCESSIBILITY_TYPE, Tooltip } from 'baseui/tooltip';
6+
import copy from 'copy-to-clipboard';
7+
import { MdCopyAll } from 'react-icons/md';
8+
9+
import PrettyJson from '@/components/pretty-json/pretty-json';
10+
import useStyletronClasses from '@/hooks/use-styletron-classes';
11+
12+
import { cssStyles } from './workflow-history-event-details-json.styles';
13+
import type { Props } from './workflow-history-event-details-json.types';
14+
15+
export default function WorkflowHistoryEventDetailsJson({ entryValue }: Props) {
16+
const { cls } = useStyletronClasses(cssStyles);
17+
const [showTooltip, setShowTooltip] = useState(false);
18+
19+
useEffect(() => {
20+
if (showTooltip) {
21+
const timer = setTimeout(() => {
22+
setShowTooltip(false);
23+
}, 1000);
24+
return () => clearTimeout(timer);
25+
}
26+
}, [showTooltip]);
27+
return (
28+
<div className={cls.jsonViewWrapper}>
29+
<div className={cls.jsonViewContainer}>
30+
<div className={cls.jsonViewHeader}>
31+
<Tooltip
32+
animateOutTime={400}
33+
isOpen={showTooltip}
34+
showArrow
35+
placement="bottom"
36+
accessibilityType={ACCESSIBILITY_TYPE.tooltip}
37+
content={() => <>Copied</>}
38+
>
39+
<Button
40+
onClick={() => {
41+
copy(JSON.stringify(entryValue, null, '\t'));
42+
setShowTooltip(true);
43+
}}
44+
size={SIZE.mini}
45+
shape={SHAPE.circle}
46+
kind={BUTTON_KIND.secondary}
47+
>
48+
<MdCopyAll />
49+
</Button>
50+
</Tooltip>
51+
</div>
52+
<PrettyJson json={entryValue} />
53+
</div>
54+
</div>
55+
);
56+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { type WorkflowHistoryEventDetailsValueComponentProps } from '../workflow-history-event-details/workflow-history-event-details.types';
2+
3+
export type Props = Pick<
4+
WorkflowHistoryEventDetailsValueComponentProps,
5+
'entryValue'
6+
>;

src/views/workflow-history/workflow-history-event-details/workflow-history-event-details.styles.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,48 @@
1+
import { styled as createStyled, type Theme } from 'baseui';
2+
13
import type {
24
StyletronCSSObject,
35
StyletronCSSObjectOf,
46
} from '@/hooks/use-styletron-classes';
57

8+
export const styled = {
9+
DetailsRow: createStyled<'div', { $forceWrap?: boolean }>(
10+
'div',
11+
({ $theme, $forceWrap }: { $theme: Theme; $forceWrap?: boolean }) => ({
12+
gap: $theme.sizing.scale300,
13+
paddingTop: $theme.sizing.scale200,
14+
paddingBottom: $theme.sizing.scale200,
15+
wordBreak: 'break-word',
16+
display: 'flex',
17+
flexDirection: $forceWrap ? 'column' : 'row',
18+
...(!$forceWrap && { flexWrap: 'wrap' }),
19+
})
20+
),
21+
DetailsValue: createStyled<'div', { $forceWrap?: boolean }>(
22+
'div',
23+
({ $theme, $forceWrap }: { $theme: Theme; $forceWrap?: boolean }) => ({
24+
color: $theme.colors.contentPrimary,
25+
...$theme.typography.LabelXSmall,
26+
display: 'flex',
27+
...(!$forceWrap && { flex: '1 0 300px' }),
28+
})
29+
),
30+
};
31+
632
const cssStylesObj = {
733
emptyDetails: (theme) => ({
834
...theme.typography.LabelXSmall,
935
color: theme.colors.contentTertiary,
1036
textAlign: 'center',
1137
padding: `${theme.sizing.scale700} 0`,
1238
}),
13-
detailsRow: (theme) => ({
14-
display: 'flex',
15-
flexDirection: 'row',
16-
gap: theme.sizing.scale300,
17-
flexWrap: 'wrap',
18-
paddingTop: theme.sizing.scale200,
19-
paddingBottom: theme.sizing.scale200,
20-
wordBreak: 'break-word',
21-
}),
2239
detailsLabel: (theme) => ({
2340
minWidth: '150px',
2441
maxWidth: '150px',
2542
display: 'flex',
2643
color: theme.colors.contentTertiary,
2744
...theme.typography.LabelXSmall,
2845
}),
29-
detailsValue: (theme) => ({
30-
color: theme.colors.contentPrimary,
31-
...theme.typography.LabelXSmall,
32-
display: 'flex',
33-
flex: '1 0 300px',
34-
}),
3546
} satisfies StyletronCSSObject;
3647

3748
export const cssStyles: StyletronCSSObjectOf<typeof cssStylesObj> =

src/views/workflow-history/workflow-history-event-details/workflow-history-event-details.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import useStyletronClasses from '@/hooks/use-styletron-classes';
55
import formatWorkflowHistoryEvent from '@/utils/data-formatters/format-workflow-history-event';
66

77
import generateHistoryEventDetails from './helpers/generate-history-event-details';
8-
import { cssStyles } from './workflow-history-event-details.styles';
8+
import { cssStyles, styled } from './workflow-history-event-details.styles';
99
import type {
1010
WorkflowHistoryEventDetailsEntry,
1111
Props,
@@ -28,8 +28,11 @@ export default function WorkflowHistoryEventDetails({
2828

2929
return (
3030
<div>
31-
{detailsEntries.map((entry) => (
32-
<div className={cls.detailsRow} key={entry.key}>
31+
{detailsEntries.map((entry, index) => (
32+
<styled.DetailsRow
33+
$forceWrap={entry.renderConfig?.forceWrap}
34+
key={`${entry.key}-${entry.path}-${entry.renderConfig?.name}-${index}`}
35+
>
3336
<div className={cls.detailsLabel}>
3437
{entry.renderConfig?.getLabel
3538
? entry.renderConfig.getLabel({
@@ -39,7 +42,7 @@ export default function WorkflowHistoryEventDetails({
3942
})
4043
: entry.path}
4144
</div>
42-
<div className={cls.detailsValue}>
45+
<styled.DetailsValue $forceWrap={entry.renderConfig?.forceWrap}>
4346
{entry.renderConfig?.valueComponent ? (
4447
<entry.renderConfig.valueComponent
4548
entryKey={entry.key}
@@ -50,8 +53,8 @@ export default function WorkflowHistoryEventDetails({
5053
) : (
5154
String(entry.value)
5255
)}
53-
</div>
54-
</div>
56+
</styled.DetailsValue>
57+
</styled.DetailsRow>
5558
))}
5659
</div>
5760
);

src/views/workflow-history/workflow-history-event-details/workflow-history-event-details.types.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,18 @@ export type WorkflowHistoryEventDetailsFuncArgs = {
1313
value: any;
1414
};
1515

16+
export type WorkflowHistoryEventDetailsValueComponentProps = {
17+
entryKey: string;
18+
entryPath: string;
19+
entryValue: any;
20+
} & WorfklowHistoryProps['params'];
21+
1622
export type WorkflowHistoryEventDetailsConfig = {
1723
name: string;
1824
getLabel?: (args: WorkflowHistoryEventDetailsFuncArgs) => string;
19-
valueComponent?: React.ComponentType<
20-
{
21-
entryKey: string;
22-
entryPath: string;
23-
entryValue: any;
24-
} & WorfklowHistoryProps['params']
25-
>;
25+
valueComponent?: React.ComponentType<WorkflowHistoryEventDetailsValueComponentProps>;
2626
hide?: (args: WorkflowHistoryEventDetailsFuncArgs) => boolean;
27+
forceWrap?: boolean;
2728
} & (
2829
| { key: string }
2930
| { path: string }

0 commit comments

Comments
 (0)