Skip to content

Commit a5ae89a

Browse files
feat: Add retry attempt to summary fields for history events (#1105)
* Add "attempt" to summary fields * Allow conditionally hiding summary details * Hide "attempt" from summary details in History V1 * Support custom tooltip labels for summary details * (unrelated) Update the icon for timeouts from a stopwatch to an hourglass Signed-off-by: Adhitya Mamallan <[email protected]>
1 parent dd85890 commit a5ae89a

10 files changed

+141
-5
lines changed

src/views/workflow-history/config/workflow-history-event-summary-field-parsers.config.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MdOutlineMonitorHeart, MdOutlineTimer } from 'react-icons/md';
1+
import { MdHourglassBottom, MdOutlineMonitorHeart } from 'react-icons/md';
22

33
import { type WorkflowHistoryEventSummaryFieldParser } from '../workflow-history-event-summary/workflow-history-event-summary.types';
44
import WorkflowHistoryEventSummaryJson from '../workflow-history-event-summary-json/workflow-history-event-summary-json';
@@ -25,7 +25,13 @@ const workflowHistoryEventSummaryFieldParsersConfig: Array<WorkflowHistoryEventS
2525
name: 'Timeouts with timer icon',
2626
matcher: (name) =>
2727
new RegExp('(TimeoutSeconds|BackoffSeconds|InSeconds)$').test(name),
28-
icon: MdOutlineTimer,
28+
icon: MdHourglassBottom,
29+
},
30+
{
31+
name: 'Hide retryAttempt from summary in History V1',
32+
matcher: (name) => name === 'attempt',
33+
shouldHide: () => true,
34+
icon: null,
2935
},
3036
];
3137

src/views/workflow-history/helpers/get-history-group-from-events/__tests__/get-activity-group-from-events.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ describe('getActivityGroupFromEvents', () => {
395395
expect(group.eventsMetadata[1].summaryFields).toEqual([
396396
'heartbeatDetails',
397397
'lastHeartbeatTime',
398+
'attempt',
398399
]);
399400

400401
// The completed event should also have summaryFields
@@ -522,6 +523,7 @@ describe('getActivityGroupFromEvents', () => {
522523
expect(pendingStartEventMetadata?.summaryFields).toEqual([
523524
'lastFailureReason',
524525
'lastFailureDetails',
526+
'attempt',
525527
]);
526528

527529
// Other events should not have the same summaryFields

src/views/workflow-history/helpers/get-history-group-from-events/__tests__/get-decision-group-from-events.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ describe('getDecisionGroupFromEvents', () => {
370370
);
371371
expect(scheduledEventMetadata?.summaryFields).toEqual([
372372
'startToCloseTimeoutSeconds',
373+
'attempt',
373374
]);
374375

375376
// Other events should not have summaryFields
@@ -380,4 +381,27 @@ describe('getDecisionGroupFromEvents', () => {
380381
expect(metadata.summaryFields).toBeUndefined();
381382
});
382383
});
384+
385+
it('should include summaryFields for pending decision start events', () => {
386+
const events: ExtendedDecisionHistoryEvent[] = [
387+
scheduleDecisionTaskEvent,
388+
pendingDecisionTaskStartEvent,
389+
];
390+
const group = getDecisionGroupFromEvents(events);
391+
392+
// The pending start event should have summaryFields
393+
const pendingStartEventMetadata = group.eventsMetadata.find(
394+
(metadata) => metadata.label === 'Starting'
395+
);
396+
expect(pendingStartEventMetadata?.summaryFields).toEqual(['attempt']);
397+
398+
// Other events should not have the same summaryFields
399+
const scheduledEventMetadata = group.eventsMetadata.find(
400+
(metadata) => metadata.label === 'Scheduled'
401+
);
402+
expect(scheduledEventMetadata?.summaryFields).toEqual([
403+
'startToCloseTimeoutSeconds',
404+
'attempt',
405+
]);
406+
});
383407
});

src/views/workflow-history/helpers/get-history-group-from-events/__tests__/get-single-event-group-from-events.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ describe('getSingleEventGroupFromEvents', () => {
186186
expect(eventMetadata.summaryFields).toEqual([
187187
'input',
188188
'executionStartToCloseTimeoutSeconds',
189+
'attempt',
189190
]);
190191
});
191192

src/views/workflow-history/helpers/get-history-group-from-events/get-activity-group-from-events.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,12 @@ export default function getActivityGroupFromEvents(
165165
pendingActivityTaskStartEventAttributes: [
166166
'lastFailureReason',
167167
'lastFailureDetails',
168+
'attempt',
168169
],
169170
activityTaskStartedEventAttributes: [
170171
'heartbeatDetails',
171172
'lastHeartbeatTime',
173+
'attempt',
172174
],
173175
activityTaskCompletedEventAttributes: ['result'],
174176
activityTaskFailedEventAttributes: ['details', 'reason'],

src/views/workflow-history/helpers/get-history-group-from-events/get-decision-group-from-events.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,11 @@ export default function getDecisionGroupFromEvents(
122122

123123
const eventToSummaryFields: HistoryGroupEventToSummaryFieldsMap<DecisionHistoryGroup> =
124124
{
125-
decisionTaskScheduledEventAttributes: ['startToCloseTimeoutSeconds'],
125+
decisionTaskScheduledEventAttributes: [
126+
'startToCloseTimeoutSeconds',
127+
'attempt',
128+
],
129+
pendingDecisionTaskStartEventAttributes: ['attempt'],
126130
};
127131

128132
return {

src/views/workflow-history/helpers/get-history-group-from-events/get-single-event-group-from-events.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export default function getSingleEventGroupFromEvents(
9999
workflowExecutionStartedEventAttributes: [
100100
'input',
101101
'executionStartToCloseTimeoutSeconds',
102+
'attempt',
102103
],
103104
workflowExecutionCompletedEventAttributes: ['result'],
104105
workflowExecutionFailedEventAttributes: ['details', 'reason'],

src/views/workflow-history/workflow-history-event-summary/helpers/__tests__/get-history-event-summary-items.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,38 @@ jest.mock(
8181
isGroup: true,
8282
groupEntries: [],
8383
},
84+
{
85+
key: 'hiddenField',
86+
path: 'hiddenField',
87+
value: 'hide-me',
88+
renderConfig: {
89+
name: 'Test Config',
90+
key: 'test',
91+
getLabel: ({ path }) => `Label: ${path}`,
92+
},
93+
isGroup: false,
94+
},
95+
{
96+
key: 'tooltipField',
97+
path: 'tooltipField',
98+
value: 'test-value',
99+
renderConfig: {
100+
name: 'Test Config',
101+
key: 'test',
102+
getLabel: ({ path }) => `Label: ${path}`,
103+
},
104+
isGroup: false,
105+
},
106+
{
107+
key: 'defaultPathField',
108+
path: 'defaultPathField',
109+
value: 'test-value',
110+
renderConfig: {
111+
name: 'Test Config',
112+
key: 'test',
113+
},
114+
isGroup: false,
115+
},
84116
] satisfies WorkflowHistoryEventDetailsEntries
85117
)
86118
);
@@ -112,6 +144,18 @@ jest.mock(
112144
matcher: (path) => path === 'firstExecutionRunId',
113145
icon: jest.fn(),
114146
},
147+
{
148+
name: 'Hidden Field Parser',
149+
matcher: (path) => path === 'hiddenField',
150+
icon: jest.fn(),
151+
shouldHide: jest.fn((_, value) => value === 'hide-me'),
152+
},
153+
{
154+
name: 'Tooltip Label Parser',
155+
matcher: (path) => path === 'tooltipField',
156+
icon: jest.fn(),
157+
tooltipLabel: 'Custom Tooltip Label',
158+
},
115159
] satisfies Array<WorkflowHistoryEventSummaryFieldParser>
116160
);
117161

@@ -279,4 +323,46 @@ describe(getHistoryEventSummaryItems.name, () => {
279323
expect(result[0].path).toBe('workflowExecution');
280324
expect(result[0].renderValue).toBeDefined();
281325
});
326+
327+
it('should exclude fields when shouldHide returns true', () => {
328+
const details = { hiddenField: 'hide-me' };
329+
const summaryFields = ['hiddenField'];
330+
331+
const result = getHistoryEventSummaryItems({ details, summaryFields });
332+
333+
expect(result).toHaveLength(0);
334+
});
335+
336+
it('should use tooltipLabel from parser config when set', () => {
337+
const details = { tooltipField: 'test-value' };
338+
const summaryFields = ['tooltipField'];
339+
340+
const result = getHistoryEventSummaryItems({ details, summaryFields });
341+
342+
expect(result).toHaveLength(1);
343+
expect(result[0].path).toBe('tooltipField');
344+
expect(result[0].label).toBe('Custom Tooltip Label');
345+
});
346+
347+
it('should use getLabel from renderConfig when tooltipLabel is not set in parser config', () => {
348+
const details = { input: { data: 'test' } };
349+
const summaryFields = ['input'];
350+
351+
const result = getHistoryEventSummaryItems({ details, summaryFields });
352+
353+
expect(result).toHaveLength(1);
354+
expect(result[0].path).toBe('input');
355+
expect(result[0].label).toBe('Label: input');
356+
});
357+
358+
it('should default to path when neither tooltipLabel nor getLabel is available', () => {
359+
const details = { defaultPathField: 'test-value' };
360+
const summaryFields = ['defaultPathField'];
361+
362+
const result = getHistoryEventSummaryItems({ details, summaryFields });
363+
364+
expect(result).toHaveLength(1);
365+
expect(result[0].path).toBe('defaultPathField');
366+
expect(result[0].label).toBe('defaultPathField');
367+
});
282368
});

src/views/workflow-history/workflow-history-event-summary/helpers/get-history-event-summary-items.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ export default function getHistoryEventSummaryItems({
3333
);
3434

3535
let renderValue: ComponentType<EventSummaryValueComponentProps>;
36-
if (summaryFieldParserConfig?.customRenderValue) {
36+
if (summaryFieldParserConfig?.shouldHide?.(path, value)) {
37+
return acc;
38+
} else if (summaryFieldParserConfig?.customRenderValue) {
3739
renderValue = summaryFieldParserConfig.customRenderValue;
3840
} else if (renderConfig?.valueComponent) {
3941
const detailsRenderValue = renderConfig?.valueComponent;
@@ -49,9 +51,15 @@ export default function getHistoryEventSummaryItems({
4951
renderValue = ({ value }) => String(value);
5052
}
5153

54+
let tooltipLabel = path;
55+
if (summaryFieldParserConfig?.tooltipLabel) {
56+
tooltipLabel = summaryFieldParserConfig.tooltipLabel;
57+
} else if (renderConfig?.getLabel)
58+
tooltipLabel = renderConfig.getLabel({ key, path, value });
59+
5260
acc.push({
5361
path,
54-
label: renderConfig?.getLabel?.({ key, path, value }) ?? path,
62+
label: tooltipLabel,
5563
value,
5664
icon: summaryFieldParserConfig?.icon ?? null,
5765
renderValue,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export type WorkflowHistoryEventSummaryFieldParser = {
2222
size?: IconProps['size'];
2323
color?: IconProps['color'];
2424
}> | null;
25+
shouldHide?: (path: string, value: unknown) => boolean;
26+
tooltipLabel?: string;
2527
customRenderValue?: ComponentType<EventSummaryValueComponentProps>;
2628
hideDefaultTooltip?: boolean;
2729
};

0 commit comments

Comments
 (0)