Skip to content

Commit 7d862a5

Browse files
authored
User Metadata for Activity and Timers (#2529)
* WIP: decode userMetdata summary * Add MetadataDecoder for timeline and event summary row * Smaller timeline text, show only metdata on event summary row * Use secondary badge, replace primary attribute if it exists * More type stuff * Bump text size, use fallback if decoding fails * Add metadata to expanded row, better layout of expanded timeline view. Need to figure out dynamic height of rest of timeline when expanded * Finish full refactor of expanded group details * Set height after decoding * Don't do array check * Await tick to set height * Use WorkflowStatus badge and add 'Pending' | 'Retry' to status * Fix height for Child Workflows timeline and hide expand button * Put onLoad in the correct place * Nvm it was in the right spot * Use await block in metadata decoder, add Decoding failed fallback
1 parent 87a3fcb commit 7d862a5

22 files changed

+327
-596
lines changed

Diff for: src/lib/components/event/event-details-full.svelte

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
1010
import EventDetailsRowExpanded from './event-details-row-expanded.svelte';
1111
import EventLinksExpanded from './event-links-expanded.svelte';
12+
import EventMetadataExpanded from './event-metadata-expanded.svelte';
1213
1314
export let group: EventGroup | undefined = undefined;
1415
export let event: WorkflowEvent | undefined = undefined;
@@ -45,6 +46,9 @@
4546
})}
4647
</div>
4748
</div>
49+
{#if groupEvent?.userMetadata?.summary}
50+
<EventMetadataExpanded value={groupEvent.userMetadata.summary} />
51+
{/if}
4852
<EventLinksExpanded links={groupEvent?.links} />
4953
{#each details as [key, value] (key)}
5054
<EventDetailsRowExpanded {key} {value} {attributes} />

Diff for: src/lib/components/event/event-details-link.svelte

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
export let value: string;
1414
export let attributes: CombinedAttributes;
1515
export let type: EventLinkType;
16-
export let light = false;
1716
1817
$: ({ workflow, namespace } = $page.params);
1918
@@ -40,6 +39,6 @@
4039
$: href = hrefs[type];
4140
</script>
4241

43-
<Link class={$$restProps.class} {href} {light}>
42+
<Link class={$$restProps.class} {href}>
4443
{value}
4544
</Link>
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script lang="ts">
2+
import Badge from '$lib/holocene/badge.svelte';
3+
import { translate } from '$lib/i18n/translate';
4+
import type { Payload } from '$lib/types';
5+
6+
import MetadataDecoder from './metadata-decoder.svelte';
7+
8+
export let value: Payload;
9+
</script>
10+
11+
<div class="block w-full select-all px-2 py-1 text-left">
12+
<p class="text-sm">Summary</p>
13+
<MetadataDecoder
14+
{value}
15+
let:decodedValue
16+
fallback={translate('events.decode-failed')}
17+
>
18+
<Badge type="secondary" class="inline-block whitespace-pre-wrap">
19+
{decodedValue}
20+
</Badge>
21+
</MetadataDecoder>
22+
</div>

Diff for: src/lib/components/event/event-summary-row.svelte

+23-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
import { page } from '$app/stores';
66
7+
import Badge from '$lib/holocene/badge.svelte';
78
import Icon from '$lib/holocene/icon/icon.svelte';
89
import Link from '$lib/holocene/link.svelte';
910
import Tooltip from '$lib/holocene/tooltip.svelte';
@@ -35,6 +36,7 @@
3536
import EventDetailsFull from './event-details-full.svelte';
3637
import EventDetailsRow from './event-details-row.svelte';
3738
import EventLink from './event-link.svelte';
39+
import MetadataDecoder from './metadata-decoder.svelte';
3840
3941
export let event: IterableEvent;
4042
export let group: EventGroup | undefined = undefined;
@@ -198,16 +200,34 @@
198200
{/if}
199201
</div>
200202
{/if}
203+
{#if currentEvent?.userMetadata?.summary}
204+
<MetadataDecoder
205+
value={currentEvent.userMetadata.summary}
206+
let:decodedValue
207+
>
208+
{#if decodedValue}
209+
<div
210+
class="flex max-w-xl items-center gap-2 first:pt-0 last:border-b-0 md:w-auto"
211+
>
212+
<p class="whitespace-nowrap text-right text-xs">Summary</p>
213+
<Badge type="secondary" class="block select-none truncate">
214+
{decodedValue}
215+
</Badge>
216+
</div>
217+
{:else}
218+
<EventDetailsRow {...primaryAttribute} {attributes} />
219+
{/if}
220+
</MetadataDecoder>
221+
{:else if primaryAttribute?.key}
222+
<EventDetailsRow {...primaryAttribute} {attributes} />
223+
{/if}
201224
{#if currentEvent?.links?.length}
202225
<EventLink
203226
link={currentEvent.links[0]}
204227
class="max-w-xl"
205228
linkClass="truncate"
206229
/>
207230
{/if}
208-
{#if primaryAttribute?.key}
209-
<EventDetailsRow {...primaryAttribute} {attributes} />
210-
{/if}
211231
{#if nonPendingActivityAttempt}
212232
<EventDetailsRow
213233
key="attempt"

Diff for: src/lib/components/event/metadata-decoder.svelte

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<script lang="ts">
2+
import { page } from '$app/stores';
3+
4+
import type { Payload } from '$lib/types';
5+
import { decodeSingleReadablePayloadWithCodec } from '$lib/utilities/decode-payload';
6+
import {
7+
getCodecEndpoint,
8+
getCodecIncludeCredentials,
9+
getCodecPassAccessToken,
10+
} from '$lib/utilities/get-codec';
11+
12+
export let value: Payload | undefined = undefined;
13+
export let fallback: string = '';
14+
export let onDecode: (decodedValue: string) => void | undefined = undefined;
15+
16+
$: endpoint = getCodecEndpoint($page.data.settings);
17+
$: passAccessToken = getCodecPassAccessToken($page.data.settings);
18+
$: includeCredentials = getCodecIncludeCredentials($page.data.settings);
19+
$: settings = {
20+
...$page.data.settings,
21+
codec: {
22+
...$page.data.settings?.codec,
23+
endpoint,
24+
passAccessToken,
25+
includeCredentials,
26+
},
27+
};
28+
29+
$: decodePayload = async (_value: Payload | undefined) => {
30+
if (!_value) {
31+
return fallback;
32+
}
33+
34+
const metadata = await decodeSingleReadablePayloadWithCodec(
35+
_value,
36+
settings,
37+
);
38+
39+
if (typeof metadata === 'string') {
40+
if (onDecode) {
41+
onDecode(metadata);
42+
}
43+
return metadata;
44+
}
45+
46+
return fallback;
47+
};
48+
</script>
49+
50+
{#await decodePayload(value) then decodedValue}
51+
<slot {decodedValue} />
52+
{:catch}
53+
<slot decodedValue={fallback} />
54+
{/await}

Diff for: src/lib/components/lines-and-dots/constants.ts

+2-78
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,10 @@ import type {
1414
} from '$lib/types/events';
1515
import type { WorkflowStatus } from '$lib/types/workflows';
1616
import {
17-
formatAttributes,
1817
formatGroupAttributes,
1918
formatPendingAttributes,
2019
} from '$lib/utilities/format-event-attributes';
21-
import {
22-
getGroupForEventOrPendingEvent,
23-
isAssociatedPendingActivity,
24-
} from '$lib/utilities/pending-activities';
25-
26-
export const DetailsChildTimelineHeight = 200;
20+
import { getGroupForEventOrPendingEvent } from '$lib/utilities/pending-activities';
2721

2822
export type GraphConfig = {
2923
height: number;
@@ -34,8 +28,6 @@ export type GraphConfig = {
3428

3529
const baseRadius = 6;
3630

37-
export const minCompactWidth = 200;
38-
3931
export const TimelineConfig: GraphConfig = {
4032
height: baseRadius * 5,
4133
gutter: baseRadius * 8,
@@ -84,7 +76,7 @@ export const timelineTextPosition = (
8476
let textAnchor = 'start';
8577
let textIndex = 0;
8678

87-
const textToLeft = firstPoint > (1 / 3) * width;
79+
const textToLeft = firstPoint > (1 / 2) * width;
8880
let textToRight = !textToLeft && lastPoint < (2 / 3) * width && !isPending;
8981

9082
if (textToLeft) textAnchor = 'end';
@@ -267,25 +259,6 @@ export const getCategoryColor = (type: EventTypeCategory): string => {
267259
}
268260
};
269261

270-
export const activeGroupsHeightAboveGroup = (
271-
activeGroups: string[],
272-
group: EventGroup,
273-
groups: EventGroups,
274-
width: number,
275-
sort: EventSortOrder,
276-
) => {
277-
return activeGroups
278-
.filter((id) => {
279-
if (sort === 'ascending') return parseInt(id) < parseInt(group.id);
280-
return parseInt(id) > parseInt(group.id);
281-
})
282-
.map((id) => {
283-
const group = groups.find((group) => group.id === id);
284-
return getGroupDetailsBoxHeight(group, width);
285-
})
286-
.reduce((acc, height) => acc + height, 0);
287-
};
288-
289262
export const mergeEventGroupDetails = (group: EventGroup) => {
290263
const attributes = formatGroupAttributes(group);
291264
return group.pendingActivity
@@ -295,55 +268,6 @@ export const mergeEventGroupDetails = (group: EventGroup) => {
295268

296269
export const staticCodeBlockHeight = 200;
297270

298-
export const getGroupDetailsBoxHeight = (group: EventGroup, width: number) => {
299-
const isWide = width >= 960;
300-
const attributes = mergeEventGroupDetails(group);
301-
const codeBlockAttributes = Object.entries(attributes).filter(
302-
([, value]) => typeof value === 'object',
303-
);
304-
const textAttributes = Object.entries(attributes).filter(
305-
([, value]) => typeof value !== 'object',
306-
);
307-
308-
const codeBlockHeight = codeBlockAttributes.length * staticCodeBlockHeight;
309-
const textHeight =
310-
(isWide ? 1 : 2) * textAttributes.length * DetailsConfig.fontSizeRatio +
311-
DetailsConfig.fontSizeRatio;
312-
const totalTextHeight =
313-
group.category === 'child-workflow'
314-
? textHeight + DetailsChildTimelineHeight
315-
: textHeight;
316-
return (
317-
Math.max(codeBlockHeight, totalTextHeight) + 3 * DetailsConfig.fontSizeRatio
318-
);
319-
};
320-
321-
export const getEventDetailsBoxHeight = (
322-
event: WorkflowEvent,
323-
pendingActivity?: PendingActivity,
324-
) => {
325-
const attributes = formatAttributes(event);
326-
const codeBlockAttributes = Object.entries(attributes).filter(
327-
([, value]) => typeof value === 'object',
328-
);
329-
const textAttributes = Object.entries(attributes).filter(
330-
([, value]) => typeof value !== 'object',
331-
);
332-
333-
let pendingActivityHeight = 0;
334-
if (isAssociatedPendingActivity(event, pendingActivity)) {
335-
pendingActivityHeight = getPendingEventDetailHeight(pendingActivity);
336-
}
337-
const codeBlockHeight = codeBlockAttributes.length * staticCodeBlockHeight;
338-
const textHeight = textAttributes.length * DetailsConfig.fontSizeRatio;
339-
return (
340-
pendingActivityHeight +
341-
codeBlockHeight +
342-
textHeight +
343-
+2 * DetailsConfig.fontSizeRatio
344-
);
345-
};
346-
347271
export const getPendingEventDetailHeight = (event: PendingActivity) => {
348272
const textHeight = 5 * DetailsConfig.fontSizeRatio;
349273
let codeBlockHeight = 0;

Diff for: src/lib/components/lines-and-dots/event-status-filter.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import Checkbox from '$lib/holocene/checkbox.svelte';
3-
import { clearActiveEvents } from '$lib/stores/active-events';
3+
import { clearActiveGroups } from '$lib/stores/active-events';
44
import { eventStatusFilter } from '$lib/stores/filters';
55
66
const options = [
@@ -11,7 +11,7 @@
1111
];
1212
1313
const onOptionClick = () => {
14-
clearActiveEvents();
14+
clearActiveGroups();
1515
$eventStatusFilter = !$eventStatusFilter;
1616
};
1717
</script>

Diff for: src/lib/components/lines-and-dots/event-type-filter.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
allEventTypeOptions,
1616
compactEventTypeOptions,
1717
} from '$lib/models/event-history/get-event-categorization';
18-
import { clearActiveEvents } from '$lib/stores/active-events';
18+
import { clearActiveGroups } from '$lib/stores/active-events';
1919
import { eventTypeFilter } from '$lib/stores/filters';
2020
import { temporalVersion } from '$lib/stores/versions';
2121
import { nexusEnabled } from '$lib/utilities/nexus-enabled';
@@ -52,7 +52,7 @@
5252
}
5353
5454
const onOptionClick = ({ value }) => {
55-
clearActiveEvents();
55+
clearActiveGroups();
5656
$eventTypeFilter = $eventTypeFilter.some((type) => type === value)
5757
? $eventTypeFilter.filter((type) => type !== value)
5858
: [...$eventTypeFilter, value];

Diff for: src/lib/components/lines-and-dots/svg/graph-widget.svelte

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
export let namespace: string;
1111
export let workflowId: string;
1212
export let runId = '';
13-
export let height = 400;
13+
export let viewportHeight = 360;
14+
export let onLoad: () => void = () => {};
1415
1516
const getWorkflowAndEventHistory = async () => {
1617
const [workflow, history] = await Promise.all([
@@ -24,17 +25,16 @@
2425
workflow: WorkflowExecution,
2526
history: WorkflowEvents,
2627
) => {
28+
onLoad();
2729
const pendingActivities = workflow?.pendingActivities ?? [];
2830
return groupEvents(history, 'ascending', pendingActivities);
2931
};
3032
</script>
3133

3234
{#await getWorkflowAndEventHistory() then { workflow, history }}
33-
<div
34-
class="cursor-pointer overflow-auto {$$restProps.class}"
35-
style="height: {height}px;"
36-
>
35+
<div class="cursor-pointer overflow-auto {$$restProps.class}">
3736
<TimelineGraph
37+
{viewportHeight}
3838
{workflow}
3939
groups={createGroups(workflow, history)}
4040
readOnly

0 commit comments

Comments
 (0)