Skip to content

Commit bf27544

Browse files
Add waitTimerInfo field to Workflow History Group (cadence-workflow#1035)
* Add waitTimerInfo field to Workflow History Group, which contains the expected duration of a task/group in the workflow's history, and the prefix to show with it * Populate this field for Timer and Workflow Started groups * (misc) Use the waitTimerInfo to render timer end time in Timeline Chart
1 parent 68a7e90 commit bf27544

File tree

7 files changed

+161
-17
lines changed

7 files changed

+161
-17
lines changed

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,4 +230,56 @@ describe('getSingleEventGroupFromEvents', () => {
230230
'newExecutionRunId',
231231
]);
232232
});
233+
234+
it('should calculate expectedEndTimeInfo for workflow execution started events with firstDecisionTaskBackoff', () => {
235+
const group = getSingleEventGroupFromEvents([startWorkflowExecutionEvent]);
236+
237+
expect(group.expectedEndTimeInfo).toEqual({
238+
timeMs: 1724747370549.3777,
239+
prefix: 'Starts in',
240+
});
241+
});
242+
243+
it('should not calculate expectedEndTimeInfo for non-workflow-started events', () => {
244+
const nonStartedEvents = [
245+
signalWorkflowExecutionEvent,
246+
recordMarkerExecutionEvent,
247+
failWorkflowExecutionEvent,
248+
completeWorkflowExecutionEvent,
249+
];
250+
251+
for (const event of nonStartedEvents) {
252+
const group = getSingleEventGroupFromEvents([event]);
253+
expect(group.expectedEndTimeInfo).toBeUndefined();
254+
}
255+
});
256+
257+
it('should not calculate expectedEndTimeInfo for workflow started events with zero firstDecisionTaskBackoff', () => {
258+
const eventWithoutBackoff = {
259+
...startWorkflowExecutionEvent,
260+
workflowExecutionStartedEventAttributes: {
261+
...startWorkflowExecutionEvent.workflowExecutionStartedEventAttributes,
262+
firstDecisionTaskBackoff: {
263+
seconds: '0',
264+
nanos: 0,
265+
},
266+
},
267+
};
268+
269+
const group = getSingleEventGroupFromEvents([eventWithoutBackoff]);
270+
expect(group.expectedEndTimeInfo).toBeUndefined();
271+
});
272+
273+
it('should not calculate expectedEndTimeInfo for workflow started events without firstDecisionTaskBackoff', () => {
274+
const eventWithoutBackoff = {
275+
...startWorkflowExecutionEvent,
276+
workflowExecutionStartedEventAttributes: {
277+
...startWorkflowExecutionEvent.workflowExecutionStartedEventAttributes,
278+
firstDecisionTaskBackoff: null,
279+
},
280+
};
281+
282+
const group = getSingleEventGroupFromEvents([eventWithoutBackoff]);
283+
expect(group.expectedEndTimeInfo).toBeUndefined();
284+
});
233285
});

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

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,19 +136,55 @@ describe('getTimerGroupFromEvents', () => {
136136
const group = getTimerGroupFromEvents(events);
137137

138138
// The started event should have summaryFields
139-
const startedEventMetadata = group.eventsMetadata.find(
140-
(metadata) => metadata.label === 'Started'
141-
);
142-
expect(startedEventMetadata?.summaryFields).toEqual([
139+
const startedEventMetadata = group.eventsMetadata[0];
140+
expect(startedEventMetadata.summaryFields).toEqual([
143141
'startToFireTimeoutSeconds',
144142
]);
145143

146144
// Other events should not have summaryFields
147-
const otherEventsMetadata = group.eventsMetadata.filter(
148-
(metadata) => metadata.label !== 'Started'
149-
);
150-
otherEventsMetadata.forEach((metadata) => {
151-
expect(metadata.summaryFields).toBeUndefined();
145+
const otherEventMetadata = group.eventsMetadata[1];
146+
expect(otherEventMetadata.summaryFields).toBeUndefined();
147+
});
148+
149+
it('should calculate expectedEndTimeInfo for started timer events without close event', () => {
150+
const eventsWithoutClose: TimerHistoryEvent[] = [startTimerTaskEvent];
151+
const group = getTimerGroupFromEvents(eventsWithoutClose);
152+
153+
expect(group.expectedEndTimeInfo).toEqual({
154+
timeMs: 1725748375632.0728,
155+
prefix: 'Fires in',
152156
});
153157
});
158+
159+
it('should not calculate expectedEndTimeInfo for timer events with close event', () => {
160+
const eventsWithClose: TimerHistoryEvent[] = [
161+
startTimerTaskEvent,
162+
fireTimerTaskEvent,
163+
];
164+
const group = getTimerGroupFromEvents(eventsWithClose);
165+
166+
// Should not calculate expectedEndTimeInfo when there's a close event
167+
expect(group.expectedEndTimeInfo).toBeUndefined();
168+
});
169+
170+
it('should not calculate expectedEndTimeInfo for timer events without started event', () => {
171+
const eventsWithoutStart: TimerHistoryEvent[] = [fireTimerTaskEvent];
172+
const group = getTimerGroupFromEvents(eventsWithoutStart);
173+
174+
// Should not calculate expectedEndTimeInfo when there's no started event
175+
expect(group.expectedEndTimeInfo).toBeUndefined();
176+
});
177+
178+
it('should not calculate expectedEndTimeInfo for started timer events without startToFireTimeout', () => {
179+
const eventWithoutTimeout = {
180+
...startTimerTaskEvent,
181+
timerStartedEventAttributes: {
182+
...startTimerTaskEvent.timerStartedEventAttributes,
183+
startToFireTimeout: null,
184+
},
185+
};
186+
187+
const group = getTimerGroupFromEvents([eventWithoutTimeout]);
188+
expect(group.expectedEndTimeInfo).toBeUndefined();
189+
});
154190
});

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import parseGrpcTimestamp from '@/utils/datetime/parse-grpc-timestamp';
2+
13
import type {
24
HistoryGroupEventToNegativeFieldsMap,
35
HistoryGroupEventToStatusMap,
@@ -108,6 +110,21 @@ export default function getSingleEventGroupFromEvents(
108110
],
109111
};
110112

113+
let expectedFirstDecisionScheduleTimeMs: number | undefined;
114+
if (
115+
event.eventTime &&
116+
event.attributes === 'workflowExecutionStartedEventAttributes' &&
117+
event.workflowExecutionStartedEventAttributes?.firstDecisionTaskBackoff
118+
) {
119+
const firstDecisionTaskBackoffMs = parseGrpcTimestamp(
120+
event.workflowExecutionStartedEventAttributes.firstDecisionTaskBackoff
121+
);
122+
123+
if (firstDecisionTaskBackoffMs > 0)
124+
expectedFirstDecisionScheduleTimeMs =
125+
parseGrpcTimestamp(event.eventTime) + firstDecisionTaskBackoffMs;
126+
}
127+
111128
return {
112129
label,
113130
hasMissingEvents,
@@ -123,5 +140,13 @@ export default function getSingleEventGroupFromEvents(
123140
undefined,
124141
eventToSummaryFields
125142
),
143+
...(expectedFirstDecisionScheduleTimeMs
144+
? {
145+
expectedEndTimeInfo: {
146+
timeMs: expectedFirstDecisionScheduleTimeMs,
147+
prefix: 'Starts in',
148+
},
149+
}
150+
: {}),
126151
};
127152
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import parseGrpcTimestamp from '@/utils/datetime/parse-grpc-timestamp';
2+
13
import type {
24
HistoryGroupEventToStatusMap,
35
HistoryGroupEventToStringMap,
@@ -37,6 +39,7 @@ export default function getTimerGroupFromEvents(
3739
timerFiredEventAttributes: 'Fired',
3840
timerCanceledEventAttributes: 'Canceled',
3941
};
42+
4043
const eventToStatus: HistoryGroupEventToStatusMap<TimerHistoryGroup> = {
4144
timerStartedEventAttributes: (_, events, index) =>
4245
index < events.length - 1 ? 'COMPLETED' : 'ONGOING',
@@ -49,6 +52,23 @@ export default function getTimerGroupFromEvents(
4952
timerStartedEventAttributes: ['startToFireTimeoutSeconds'],
5053
};
5154

55+
let expectedTimerFireTimeMs: number | undefined;
56+
57+
if (
58+
startedEvent &&
59+
startedEvent.eventTime &&
60+
!closeEvent &&
61+
startedEvent.timerStartedEventAttributes?.startToFireTimeout
62+
) {
63+
const timeToFire = parseGrpcTimestamp(
64+
startedEvent.timerStartedEventAttributes.startToFireTimeout
65+
);
66+
67+
if (timeToFire > 0)
68+
expectedTimerFireTimeMs =
69+
parseGrpcTimestamp(startedEvent.eventTime) + timeToFire;
70+
}
71+
5272
return {
5373
label,
5474
hasMissingEvents,
@@ -63,5 +83,13 @@ export default function getTimerGroupFromEvents(
6383
undefined,
6484
eventToSummaryFields
6585
),
86+
...(expectedTimerFireTimeMs
87+
? {
88+
expectedEndTimeInfo: {
89+
timeMs: expectedTimerFireTimeMs,
90+
prefix: 'Fires in',
91+
},
92+
}
93+
: {}),
6694
};
6795
}

src/views/workflow-history/workflow-history-timeline-chart/helpers/__tests__/convert-event-group-to-timeline-item.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ describe(convertEventGroupToTimelineItem.name, () => {
5757
...mockTimerEventGroup,
5858
timeMs: null,
5959
status: 'ONGOING',
60+
expectedEndTimeInfo: {
61+
timeMs: 1725748375632,
62+
prefix: 'Starts in',
63+
},
6064
},
6165
index: 1,
6266
classes: {} as any,

src/views/workflow-history/workflow-history-timeline-chart/helpers/convert-event-group-to-timeline-item.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,10 @@ export default function convertEventGroupToTimelineItem({
3333

3434
if (
3535
group.groupType === 'Timer' &&
36-
['ONGOING', 'WAITING'].includes(group.status)
36+
['ONGOING', 'WAITING'].includes(group.status) &&
37+
group.expectedEndTimeInfo
3738
) {
38-
const timerDuration =
39-
group.events[0].timerStartedEventAttributes?.startToFireTimeout;
40-
41-
if (timerDuration) {
42-
const timerDurationMs = parseGrpcTimestamp(timerDuration);
43-
groupEndDayjs = groupStartDayjs.add(timerDurationMs, 'milliseconds');
44-
}
39+
groupEndDayjs = dayjs(group.expectedEndTimeInfo.timeMs);
4540
} else if (
4641
group.timeMs &&
4742
['COMPLETED', 'FAILED', 'CANCELED'].includes(group.status)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ type BaseHistoryGroup = {
7272
timeMs: number | null;
7373
startTimeMs: number | null;
7474
closeTimeMs?: number | null;
75+
expectedEndTimeInfo?: {
76+
timeMs: number;
77+
prefix: string;
78+
};
7579
timeLabel: string;
7680
firstEventId: string | null;
7781
badges?: HistoryGroupBadge[];

0 commit comments

Comments
 (0)