Skip to content

Commit b213011

Browse files
Highlight problematic fields in workflow history (#953)
* Use negativeFields added in 954 to decide which history event details need to be highlighted * Add red highlighting for single history event details * Add failureDetails as a JSON field * Add support for negative highlighting to the History Event Details JSON component (converting it to use styled components in the process)
1 parent c2a5327 commit b213011

18 files changed

+377
-54
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ const workflowHistoryEventDetailsConfig = [
5151
},
5252
{
5353
name: 'Json as PrettyJson',
54-
pathRegex: '(input|result|details|Error|lastCompletionResult)$',
54+
pathRegex:
55+
'(input|result|details|failureDetails|Error|lastCompletionResult)$',
5556
valueComponent: WorkflowHistoryEventDetailsJson,
5657
forceWrap: true,
5758
},

src/views/workflow-history/workflow-history-event-details-entry/__tests__/workflow-history-event-details-entry.test.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,69 @@ describe(WorkflowHistoryEventDetailsEntry.name, () => {
5656

5757
expect(getByText('value2')).toBeInTheDocument();
5858
});
59+
60+
it('passes isNegative prop to custom ValueComponent when provided', () => {
61+
const CustomComponent = ({
62+
entryKey,
63+
entryPath,
64+
entryValue,
65+
isNegative,
66+
}: WorkflowHistoryEventDetailsValueComponentProps) => (
67+
<div>
68+
{entryKey} - {entryPath} - {entryValue} -{' '}
69+
{isNegative ? 'negative' : 'positive'}
70+
</div>
71+
);
72+
73+
const props: Props = {
74+
entryKey: 'key1',
75+
entryPath: 'path1',
76+
entryValue: 'value1',
77+
isNegative: true,
78+
renderConfig: {
79+
name: 'Mock render config with custom component',
80+
customMatcher: () => true,
81+
valueComponent: CustomComponent,
82+
},
83+
...workflowPageUrlParams,
84+
};
85+
86+
const { getByText } = render(
87+
<WorkflowHistoryEventDetailsEntry {...props} />
88+
);
89+
90+
expect(getByText('key1 - path1 - value1 - negative')).toBeInTheDocument();
91+
});
92+
93+
it('passes undefined isNegative prop to custom ValueComponent when not provided', () => {
94+
const CustomComponent = ({
95+
entryKey,
96+
entryPath,
97+
entryValue,
98+
isNegative,
99+
}: WorkflowHistoryEventDetailsValueComponentProps) => (
100+
<div>
101+
{entryKey} - {entryPath} - {entryValue} -{' '}
102+
{isNegative ? 'negative' : 'positive'}
103+
</div>
104+
);
105+
106+
const props: Props = {
107+
entryKey: 'key1',
108+
entryPath: 'path1',
109+
entryValue: 'value1',
110+
renderConfig: {
111+
name: 'Mock render config with custom component',
112+
customMatcher: () => true,
113+
valueComponent: CustomComponent,
114+
},
115+
...workflowPageUrlParams,
116+
};
117+
118+
const { getByText } = render(
119+
<WorkflowHistoryEventDetailsEntry {...props} />
120+
);
121+
122+
expect(getByText('key1 - path1 - value1 - positive')).toBeInTheDocument();
123+
});
59124
});

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export default function WorkflowHistoryEventDetailsEntry({
55
entryPath,
66
entryValue,
77
renderConfig,
8+
isNegative,
89
...decodedPageUrlParams
910
}: Props) {
1011
const ValueComponent = renderConfig?.valueComponent;
@@ -15,6 +16,7 @@ export default function WorkflowHistoryEventDetailsEntry({
1516
entryKey={entryKey}
1617
entryPath={entryPath}
1718
entryValue={entryValue}
19+
isNegative={isNegative}
1820
{...decodedPageUrlParams}
1921
/>
2022
);

src/views/workflow-history/workflow-history-event-details-group/__tests__/workflow-history-event-details-group.test.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ jest.mock('../helpers/get-details-field-label', () =>
1212

1313
jest.mock(
1414
'../../workflow-history-event-details-entry/workflow-history-event-details-entry',
15-
() => jest.fn(({ entryValue }) => <div>{String(entryValue)}</div>)
15+
() =>
16+
jest.fn(({ entryValue, isNegative }) => (
17+
<div>{`${String(entryValue)}${isNegative ? '-negative' : ''}`}</div>
18+
))
1619
);
1720

1821
describe(WorkflowHistoryEventDetailsGroup.name, () => {
@@ -77,4 +80,25 @@ describe(WorkflowHistoryEventDetailsGroup.name, () => {
7780
);
7881
expect(field3SubRows).toHaveLength(2);
7982
});
83+
84+
it('passes isNegative prop to WorkflowHistoryEventDetailsEntry when entry has isNegative property', () => {
85+
render(
86+
<WorkflowHistoryEventDetailsGroup
87+
entries={[
88+
{
89+
key: 'error',
90+
path: 'error',
91+
isGroup: false,
92+
isNegative: true,
93+
value: 'error value',
94+
renderConfig: null,
95+
},
96+
]}
97+
decodedPageUrlParams={workflowPageUrlParams}
98+
/>
99+
);
100+
101+
// The mock component should receive the isNegative prop
102+
expect(screen.getByText('error value-negative')).toBeInTheDocument();
103+
});
80104
});

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

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
import { styled as createStyled, type Theme } from 'baseui';
22

3+
import { type EventDetailsLabelKind } from './workflow-history-event-details-group.types';
4+
5+
const getLabelColor = ($theme: Theme, $labelKind: EventDetailsLabelKind) => {
6+
switch ($labelKind) {
7+
case 'negative':
8+
return $theme.colors.red300;
9+
case 'group':
10+
return $theme.colors.contentPrimary;
11+
default:
12+
return $theme.colors.contentTertiary;
13+
}
14+
};
15+
316
export const styled = {
417
DetailsRow: createStyled<'div', { $forceWrap?: boolean }>(
518
'div',
@@ -13,25 +26,36 @@ export const styled = {
1326
...(!$forceWrap && { flexWrap: 'wrap' }),
1427
})
1528
),
16-
DetailsValue: createStyled<'div', { $forceWrap?: boolean }>(
29+
DetailsValue: createStyled<
1730
'div',
18-
({ $theme, $forceWrap }: { $theme: Theme; $forceWrap?: boolean }) => ({
19-
color: $theme.colors.contentPrimary,
31+
{ $forceWrap?: boolean; $isNegative?: boolean }
32+
>(
33+
'div',
34+
({
35+
$theme,
36+
$forceWrap,
37+
$isNegative,
38+
}: {
39+
$theme: Theme;
40+
$forceWrap?: boolean;
41+
$isNegative?: boolean;
42+
}) => ({
43+
color: $isNegative
44+
? $theme.colors.contentNegative
45+
: $theme.colors.contentPrimary,
2046
...$theme.typography.LabelXSmall,
2147
display: 'flex',
2248
...(!$forceWrap && { flex: '1 0 300px' }),
2349
})
2450
),
2551
DetailsLabel: createStyled<
2652
'div',
27-
{ $forceWrap?: boolean; $useBlackText?: boolean }
28-
>('div', ({ $theme, $forceWrap, $useBlackText }) => ({
53+
{ $forceWrap?: boolean; $labelKind?: EventDetailsLabelKind }
54+
>('div', ({ $theme, $forceWrap, $labelKind = 'regular' }) => ({
2955
minWidth: '200px',
3056
maxWidth: '200px',
3157
display: 'flex',
32-
color: $useBlackText
33-
? $theme.colors.contentPrimary
34-
: $theme.colors.contentTertiary,
58+
color: getLabelColor($theme, $labelKind),
3559
...$theme.typography.LabelXSmall,
3660
...($forceWrap && { whiteSpace: 'nowrap' }),
3761
})),

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import WorkflowHistoryEventDetailsEntry from '../workflow-history-event-details-
44

55
import getDetailsFieldLabel from './helpers/get-details-field-label';
66
import { styled } from './workflow-history-event-details-group.styles';
7-
import { type Props } from './workflow-history-event-details-group.types';
7+
import {
8+
type EventDetailsLabelKind,
9+
type Props,
10+
} from './workflow-history-event-details-group.types';
811

912
export default function WorkflowHistoryEventDetailsGroup({
1013
entries,
@@ -16,6 +19,13 @@ export default function WorkflowHistoryEventDetailsGroup({
1619
{entries.map((entry, index) => {
1720
const forceWrap = entry.isGroup || entry.renderConfig?.forceWrap;
1821

22+
let labelKind: EventDetailsLabelKind = 'regular';
23+
if (entry.isGroup) {
24+
labelKind = 'group';
25+
} else if (entry.isNegative) {
26+
labelKind = 'negative';
27+
}
28+
1929
return (
2030
<styled.DetailsRow
2131
data-testid="details-row"
@@ -26,13 +36,13 @@ export default function WorkflowHistoryEventDetailsGroup({
2636
: ''
2737
}`}
2838
>
29-
<styled.DetailsLabel
30-
$forceWrap={forceWrap}
31-
$useBlackText={entry.isGroup}
32-
>
39+
<styled.DetailsLabel $forceWrap={forceWrap} $labelKind={labelKind}>
3340
{getDetailsFieldLabel(entry, parentGroupPath)}
3441
</styled.DetailsLabel>
35-
<styled.DetailsValue $forceWrap={forceWrap}>
42+
<styled.DetailsValue
43+
$forceWrap={forceWrap}
44+
$isNegative={entry.isNegative}
45+
>
3646
{entry.isGroup ? (
3747
<styled.IndentedDetails>
3848
<WorkflowHistoryEventDetailsGroup
@@ -47,6 +57,7 @@ export default function WorkflowHistoryEventDetailsGroup({
4757
entryPath={entry.path}
4858
entryValue={entry.value}
4959
renderConfig={entry.renderConfig}
60+
isNegative={entry.isNegative}
5061
{...decodedPageUrlParams}
5162
/>
5263
)}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ export type Props = {
77
parentGroupPath?: string;
88
decodedPageUrlParams: WorkflowPageTabsParams;
99
};
10+
11+
export type EventDetailsLabelKind = 'regular' | 'group' | 'negative';

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

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,30 @@
1-
import { type ButtonOverrides } from 'baseui/button';
1+
import { styled as createStyled, type Theme } from 'baseui';
2+
import type { ButtonOverrides } from 'baseui/button';
23

3-
import type {
4-
StyletronCSSObject,
5-
StyletronCSSObjectOf,
6-
} from '@/hooks/use-styletron-classes';
7-
8-
const cssStylesObj = {
9-
jsonViewWrapper: {
4+
export const styled = {
5+
JsonViewWrapper: createStyled('div', {
106
position: 'relative',
117
width: '100%',
12-
},
13-
jsonViewContainer: (theme) => ({
14-
padding: theme.sizing.scale600,
15-
backgroundColor: theme.colors.backgroundSecondary,
16-
borderRadius: theme.borders.radius300,
17-
maxHeight: '50vh',
18-
overflow: 'auto',
198
}),
20-
jsonViewHeader: (theme) => ({
9+
JsonViewContainer: createStyled<'div', { $isNegative: boolean }>(
10+
'div',
11+
({ $theme, $isNegative }: { $theme: Theme; $isNegative: boolean }) => ({
12+
padding: $theme.sizing.scale600,
13+
backgroundColor: $isNegative
14+
? $theme.colors.backgroundNegativeLight
15+
: $theme.colors.backgroundSecondary,
16+
borderRadius: $theme.borders.radius300,
17+
maxHeight: '50vh',
18+
overflow: 'auto',
19+
})
20+
),
21+
JsonViewHeader: createStyled('div', ({ $theme }: { $theme: Theme }) => ({
2122
display: 'flex',
2223
position: 'absolute',
23-
right: theme.sizing.scale400,
24-
top: theme.sizing.scale400,
25-
}),
26-
} satisfies StyletronCSSObject;
27-
28-
export const cssStyles: StyletronCSSObjectOf<typeof cssStylesObj> =
29-
cssStylesObj;
24+
right: $theme.sizing.scale400,
25+
top: $theme.sizing.scale400,
26+
})),
27+
};
3028

3129
export const overrides = {
3230
copyButton: {

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,33 @@ import React, { useMemo } from 'react';
33

44
import CopyTextButton from '@/components/copy-text-button/copy-text-button';
55
import PrettyJson from '@/components/pretty-json/pretty-json';
6-
import useStyletronClasses from '@/hooks/use-styletron-classes';
76
import losslessJsonStringify from '@/utils/lossless-json-stringify';
87

98
import {
10-
cssStyles,
9+
styled,
1110
overrides,
1211
} from './workflow-history-event-details-json.styles';
1312
import type { Props } from './workflow-history-event-details-json.types';
1413

15-
export default function WorkflowHistoryEventDetailsJson({ entryValue }: Props) {
16-
const { cls } = useStyletronClasses(cssStyles);
17-
14+
export default function WorkflowHistoryEventDetailsJson({
15+
entryValue,
16+
isNegative,
17+
}: Props) {
1818
const textToCopy = useMemo(() => {
1919
return losslessJsonStringify(entryValue, null, '\t');
2020
}, [entryValue]);
21+
2122
return (
22-
<div className={cls.jsonViewWrapper}>
23-
<div className={cls.jsonViewContainer}>
24-
<div className={cls.jsonViewHeader}>
23+
<styled.JsonViewWrapper>
24+
<styled.JsonViewContainer $isNegative={isNegative ?? false}>
25+
<styled.JsonViewHeader>
2526
<CopyTextButton
2627
textToCopy={textToCopy}
2728
overrides={overrides.copyButton}
2829
/>
29-
</div>
30+
</styled.JsonViewHeader>
3031
<PrettyJson json={entryValue} />
31-
</div>
32-
</div>
32+
</styled.JsonViewContainer>
33+
</styled.JsonViewWrapper>
3334
);
3435
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ import { type WorkflowHistoryEventDetailsValueComponentProps } from '../workflow
22

33
export type Props = Pick<
44
WorkflowHistoryEventDetailsValueComponentProps,
5-
'entryValue'
5+
'entryValue' | 'isNegative'
66
>;

0 commit comments

Comments
 (0)