Skip to content

Commit f479e4e

Browse files
Show current duration for pending timeline events (#3346)
* Show current duration for running timeline events * Switch to use endTime * Update src/lib/components/lines-and-dots/svg/timeline-graph-row.svelte Co-authored-by: Laura Whitaker <laura.whitaker@temporal.io> --------- Co-authored-by: Laura Whitaker <laura.whitaker@temporal.io>
1 parent 90e1fe5 commit f479e4e

7 files changed

Lines changed: 185 additions & 15 deletions

File tree

src/lib/components/event/event-summary-row.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import { isCloud } from '$lib/stores/advanced-visibility';
2222
import type { IterableEvent, WorkflowEvent } from '$lib/types/events';
2323
import { decodeLocalActivity } from '$lib/utilities/decode-local-activity';
24+
import { formatEventGroupDuration } from '$lib/utilities/event-group-duration';
2425
import { spaceBetweenCapitalLetters } from '$lib/utilities/format-camel-case';
2526
import { formatAttributes } from '$lib/utilities/format-event-attributes';
2627
import { formatDistanceAbbreviated } from '$lib/utilities/format-time';
@@ -97,9 +98,8 @@
9798
9899
const duration = $derived(
99100
isEventGroup(event)
100-
? formatDistanceAbbreviated({
101-
start: event.initialEvent?.eventTime,
102-
end: event.lastEvent?.eventTime,
101+
? formatEventGroupDuration({
102+
group: event,
103103
includeMillisecondsForUnderSecond: true,
104104
})
105105
: '',

src/lib/components/lines-and-dots/end-time-interval.svelte

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
export let workflow: WorkflowExecution;
1010
export let startTime: string | Timestamp;
1111
12+
let currentTime = Date.now();
13+
1214
const rightNow = () => {
13-
const now = new Date();
14-
now.setSeconds(now.getSeconds() + 1);
15-
return now;
15+
currentTime = Date.now();
16+
return currentTime + 1000;
1617
};
1718
1819
$: endTime = workflow?.endTime || rightNow();
@@ -52,4 +53,4 @@
5253
});
5354
</script>
5455

55-
<slot {endTime} {duration} />
56+
<slot {endTime} {duration} {currentTime} />

src/lib/components/lines-and-dots/svg/group-details-row.svelte

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
import { translate } from '$lib/i18n/translate';
1111
import type { EventGroup } from '$lib/models/event-groups/event-groups';
1212
import { activeGroupHeight, setActiveGroup } from '$lib/stores/active-events';
13-
import { formatDistanceAbbreviated } from '$lib/utilities/format-time';
13+
import { formatEventGroupDuration } from '$lib/utilities/event-group-duration';
1414
import { isChildWorkflowExecutionStartedEvent } from '$lib/utilities/is-event-type';
1515
1616
import GraphWidget from './graph-widget.svelte';
1717
1818
export let group: EventGroup;
1919
export let canvasWidth: number;
20+
export let endTime: string | Date | number = Date.now();
2021
export let x = 0;
2122
export let y: number;
2223
@@ -37,9 +38,9 @@
3738
$: childWorkflowStartedEvent =
3839
group && group.eventList.find(isChildWorkflowExecutionStartedEvent);
3940
40-
$: duration = formatDistanceAbbreviated({
41-
start: group?.initialEvent?.eventTime,
42-
end: group?.lastEvent?.eventTime,
41+
$: duration = formatEventGroupDuration({
42+
group,
43+
endTime,
4344
includeMilliseconds: true,
4445
});
4546

src/lib/components/lines-and-dots/svg/timeline-graph-row.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
y: number;
3535
group: EventGroup;
3636
startTime: string | Timestamp;
37-
endTime: string | Date;
37+
endTime: string | Date | number;
3838
canvasWidth: number;
3939
readOnly: boolean;
4040
};
@@ -76,7 +76,7 @@
7676
});
7777
7878
const getDistancePointsAndPositions = (
79-
endTime: string | Date,
79+
endTime: string | Date | number,
8080
timelineWidth: number,
8181
y: number,
8282
) => {

src/lib/components/lines-and-dots/svg/timeline-graph.svelte

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,13 @@
6464
style={viewportHeight ? `max-height: ${viewportHeight}px;` : ''}
6565
on:scroll={handleScroll}
6666
>
67-
<EndTimeInterval {workflow} {startTime} let:endTime let:duration>
67+
<EndTimeInterval
68+
{workflow}
69+
{startTime}
70+
let:endTime
71+
let:duration
72+
let:currentTime
73+
>
6874
<div
6975
class="pointer-events-none sticky top-[120px]"
7076
class:invisible={!!$activeGroups.length}
@@ -120,7 +126,12 @@
120126
{/key}
121127
{/if}
122128
{#if !readOnly && $activeGroups.includes(group.id)}
123-
<GroupDetailsRow y={y + 1.33 * radius} {group} {canvasWidth} />
129+
<GroupDetailsRow
130+
y={y + 1.33 * radius}
131+
{group}
132+
{canvasWidth}
133+
endTime={workflow?.endTime ? endTime : currentTime}
134+
/>
124135
{/if}
125136
{/each}
126137
</svg>
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import type { EventGroup } from '$lib/models/event-groups/event-groups';
4+
5+
import {
6+
eventGroupUsesCurrentDuration,
7+
formatEventGroupDuration,
8+
getEventGroupDurationEnd,
9+
} from './event-group-duration';
10+
11+
const event = (id: string, eventTime: string) => ({ id, eventTime });
12+
13+
const group = ({
14+
initialEvent = event('1', '2026-04-28T10:00:00Z'),
15+
lastEvent = event('2', '2026-04-28T10:05:00Z'),
16+
isPending = false,
17+
pendingActivity = undefined,
18+
pendingNexusOperation = undefined,
19+
}: Partial<EventGroup> = {}): EventGroup =>
20+
({
21+
initialEvent,
22+
lastEvent,
23+
isPending,
24+
pendingActivity,
25+
pendingNexusOperation,
26+
}) as EventGroup;
27+
28+
describe('eventGroupUsesCurrentDuration', () => {
29+
it('uses the current time for pending activity groups', () => {
30+
const pendingActivityGroup = group({
31+
isPending: true,
32+
pendingActivity: {
33+
activityId: 'activity-id',
34+
} as EventGroup['pendingActivity'],
35+
});
36+
37+
expect(eventGroupUsesCurrentDuration(pendingActivityGroup)).toBe(true);
38+
});
39+
40+
it('uses the current time for pending Nexus operation groups', () => {
41+
const pendingNexusGroup = group({
42+
isPending: true,
43+
pendingNexusOperation: {
44+
scheduledEventId: '1',
45+
} as EventGroup['pendingNexusOperation'],
46+
});
47+
48+
expect(eventGroupUsesCurrentDuration(pendingNexusGroup)).toBe(true);
49+
});
50+
51+
it('does not use the current time for closed groups', () => {
52+
expect(eventGroupUsesCurrentDuration(group())).toBe(false);
53+
});
54+
55+
it('does not use the current time for pending non-activity groups', () => {
56+
expect(eventGroupUsesCurrentDuration(group({ isPending: true }))).toBe(
57+
false,
58+
);
59+
});
60+
});
61+
62+
describe('getEventGroupDurationEnd', () => {
63+
it('returns the last event time for closed groups', () => {
64+
const closedGroup = group();
65+
66+
expect(getEventGroupDurationEnd(closedGroup, '2026-04-28T10:20:00Z')).toBe(
67+
closedGroup.lastEvent.eventTime,
68+
);
69+
});
70+
71+
it('returns the current time for pending activity and Nexus groups', () => {
72+
const endTime = '2026-04-28T10:20:00Z';
73+
74+
expect(
75+
getEventGroupDurationEnd(
76+
group({
77+
isPending: true,
78+
pendingActivity: {
79+
activityId: 'activity-id',
80+
} as EventGroup['pendingActivity'],
81+
}),
82+
endTime,
83+
),
84+
).toBe(endTime);
85+
86+
expect(
87+
getEventGroupDurationEnd(
88+
group({
89+
isPending: true,
90+
pendingNexusOperation: {
91+
scheduledEventId: '1',
92+
} as EventGroup['pendingNexusOperation'],
93+
}),
94+
endTime,
95+
),
96+
).toBe(endTime);
97+
});
98+
});
99+
100+
describe('formatEventGroupDuration', () => {
101+
it('formats closed groups from scheduled event time to last event time', () => {
102+
expect(formatEventGroupDuration({ group: group() })).toBe('5m');
103+
});
104+
105+
it('formats pending activity groups from scheduled event time to current time', () => {
106+
expect(
107+
formatEventGroupDuration({
108+
group: group({
109+
isPending: true,
110+
pendingActivity: {
111+
activityId: 'activity-id',
112+
} as EventGroup['pendingActivity'],
113+
}),
114+
endTime: '2026-04-28T10:20:00Z',
115+
}),
116+
).toBe('20m');
117+
});
118+
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { EventGroup } from '$lib/models/event-groups/event-groups';
2+
3+
import { formatDistanceAbbreviated, type ValidTime } from './format-time';
4+
5+
export const eventGroupUsesCurrentDuration = (
6+
group: EventGroup | undefined,
7+
): boolean => {
8+
return Boolean(
9+
group?.isPending && (group.pendingActivity || group.pendingNexusOperation),
10+
);
11+
};
12+
13+
export const getEventGroupDurationEnd = (
14+
group: EventGroup | undefined,
15+
endTime: ValidTime = new Date(),
16+
): ValidTime | null | undefined => {
17+
if (!group) return undefined;
18+
if (eventGroupUsesCurrentDuration(group)) return endTime;
19+
return group.lastEvent?.eventTime;
20+
};
21+
22+
export const formatEventGroupDuration = ({
23+
group,
24+
endTime,
25+
includeMilliseconds = false,
26+
includeMillisecondsForUnderSecond = false,
27+
}: {
28+
group: EventGroup | undefined;
29+
endTime?: ValidTime;
30+
includeMilliseconds?: boolean;
31+
includeMillisecondsForUnderSecond?: boolean;
32+
}): string => {
33+
return formatDistanceAbbreviated({
34+
start: group?.initialEvent?.eventTime,
35+
end: getEventGroupDurationEnd(group, endTime),
36+
includeMilliseconds,
37+
includeMillisecondsForUnderSecond,
38+
});
39+
};

0 commit comments

Comments
 (0)