From f97800556156ae14a8c9333024b7b6b30c3d7812 Mon Sep 17 00:00:00 2001 From: Alex Tideman Date: Thu, 9 Jan 2025 11:45:12 -0600 Subject: [PATCH] Workflow Relationship Tree (#2487) * Accordion component updates (#2316) * Fix styling for non-expandable Accordion * Update padding on Card to match Accordion * Add AccordionGroup * Make ring inset on focus-within * Fix imports and A11y warning * Highlight active link in nav bar (#2324) * Add active styling for nav item * Add isActive check for nav links * Account for if isActive is undefined * Add workflow electron * Add http api port and async update flag (#2314) * Show all possible failures for WorkflowTaskError (#2328) * Use AccordionGroup to show all possible failures * Dont use red text * Move source to accordion * Add pending task component and move into accordion group * Fixes from Laura's review * Add border * WIP: filter * Bump express from 4.19.2 to 4.20.0 (#2323) Bumps [express](https://github.com/expressjs/express) from 4.19.2 to 4.20.0. - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0) --- updated-dependencies: - dependency-name: express dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump vite from 5.0.13 to 5.2.14 (#2329) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.13 to 5.2.14. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.2.14/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.2.14/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix the go things * MVP of electron. Needs some performance tweaks and to add links to each node * Add icons, clean up * Remove commented out code * Update snapshots * Update go to 1.22.6 (#2334) * Update go to 1.23 * Use 1.22.6 * Remove work files * Add go.work to gitignore * Small UI fixes (#2345) * Remove unnecessary coerced Boolean * Pluralize Namespaces badge for Nexus endpoints * Fix action slot on Accordion component * Ignore a11y test on Accordion with action * 2.31.1 (#2346) Co-authored-by: Temporal Data (cicd) * Update version.go to v2.31.0 (#2347) * Use go 1.23.0 (#2348) * Use 1.23 (#2349) * Update go version to 1.23 in update ui-server (#2351) * Update go version to 1.23 in update ui-server * Use v4 * Add breadcrumbs for nodes * Add hover/click state with opacity * Add back child table * Fix overfetching issue * Fix icon type * WIP Family Tree * Get math right for tree positions * I did things but can't remember what * Remove electron stuff * Get rid of pane * Add side panel descriptions * Better styles and status color * Better side nav buttons. Better lines to dots. Need to fix the tree node expanding * Add link, needs a better place * Better styles on description, add links, fix opening nodes * Add table of info and link vis/sidenav with open/close * Add table * Better description * fix lines * Better isActive, isCurrent checks * isCurrent check * Make split 50/50 * Fix types * Better styles for active, better borders. * Refactor to include root, clean up styles, better basis widths * Add root child count on node tree * Make tree sticky on lg screens * Add CaN tree * Add schedule tree * Delete all unused tables * Delete breadcrumbs * Use justify-end to move links to far right * Fix sm screen layout * Make visual a little shorter at 240px, hide times if small screen * Add dash and min-w for end time, py-1 * Fix root node click and accessibility issues * Bump maxZoomOut for a lot of children * Add back old tables in case api not supported or if we wanna go labs mode route * Small style updates based on feedback, better responsive padding * Better count placement --------- Signed-off-by: dependabot[bot] Co-authored-by: Laura Whitaker Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: temporal-cicd[bot] <138905749+temporal-cicd[bot]@users.noreply.github.com> Co-authored-by: Temporal Data (cicd) --- pnpm-lock.yaml | 2 +- .../components/event/payload-decoder.svelte | 9 +- .../lines-and-dots/workflow-details.svelte | 6 +- .../relationships/continue-as-new-node.svelte | 30 ++ .../relationships/continue-as-new-tree.svelte | 66 ++++ .../relationships/schedule-tree.svelte | 39 +++ ...low-family-node-description-details.svelte | 70 ++++ ...rkflow-family-node-description-tree.svelte | 24 ++ .../workflow-family-node-description.svelte | 131 ++++++++ .../workflow-family-node-tree.svelte | 301 ++++++++++++++++++ .../relationships/workflow-family-tree.svelte | 86 +++++ .../workflow-relationships-old.svelte | 66 ++++ .../workflow/workflow-relationships.svelte | 89 +++--- src/lib/holocene/icon/paths.ts | 2 + src/lib/holocene/icon/svg/target.svelte | 40 +++ src/lib/holocene/zoom-svg.svelte | 129 ++++++++ src/lib/i18n/locales/en/common.ts | 1 + src/lib/i18n/locales/en/workflows.ts | 1 + src/lib/layouts/workflow-header.svelte | 31 +- .../layouts/workflow-history-layout.svelte | 4 +- src/lib/layouts/workflow-padded-layout.svelte | 2 +- src/lib/layouts/workflow-run-layout.svelte | 2 +- .../workflow-execution.test.ts.snap | 13 + ...flow-execution.write-disabled.test.ts.snap | 13 + src/lib/models/workflow-execution.ts | 2 + src/lib/services/workflow-service.ts | 91 ++++++ src/lib/theme/plugin.ts | 2 +- src/lib/types/workflows.ts | 1 + src/lib/utilities/decode-payload.ts | 13 +- .../[run]/relationships/+page.svelte | 5 +- 30 files changed, 1180 insertions(+), 91 deletions(-) create mode 100644 src/lib/components/workflow/relationships/continue-as-new-node.svelte create mode 100644 src/lib/components/workflow/relationships/continue-as-new-tree.svelte create mode 100644 src/lib/components/workflow/relationships/schedule-tree.svelte create mode 100644 src/lib/components/workflow/relationships/workflow-family-node-description-details.svelte create mode 100644 src/lib/components/workflow/relationships/workflow-family-node-description-tree.svelte create mode 100644 src/lib/components/workflow/relationships/workflow-family-node-description.svelte create mode 100644 src/lib/components/workflow/relationships/workflow-family-node-tree.svelte create mode 100644 src/lib/components/workflow/relationships/workflow-family-tree.svelte create mode 100644 src/lib/components/workflow/workflow-relationships-old.svelte create mode 100644 src/lib/holocene/icon/svg/target.svelte create mode 100644 src/lib/holocene/zoom-svg.svelte diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15b9db765..1758184d9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true diff --git a/src/lib/components/event/payload-decoder.svelte b/src/lib/components/event/payload-decoder.svelte index d0993cddd..e689615d5 100644 --- a/src/lib/components/event/payload-decoder.svelte +++ b/src/lib/components/event/payload-decoder.svelte @@ -2,6 +2,7 @@ import { page } from '$app/stores'; import { authUser } from '$lib/stores/auth-user'; + import type { Memo } from '$lib/types'; import type { EventAttribute, WorkflowEvent } from '$lib/types/events'; import { cloneAllPotentialPayloadsWithCodec, @@ -15,7 +16,11 @@ } from '$lib/utilities/get-codec'; import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; - export let value: PotentiallyDecodable | EventAttribute | WorkflowEvent; + export let value: + | PotentiallyDecodable + | EventAttribute + | WorkflowEvent + | Memo; export let key = ''; export let onDecode: (decodedValue: string) => void | undefined = undefined; @@ -36,7 +41,7 @@ }; const decodePayloads = async ( - _value: PotentiallyDecodable | EventAttribute | WorkflowEvent, + _value: PotentiallyDecodable | EventAttribute | WorkflowEvent | Memo, ) => { try { const convertedAttributes = await cloneAllPotentialPayloadsWithCodec( diff --git a/src/lib/components/lines-and-dots/workflow-details.svelte b/src/lib/components/lines-and-dots/workflow-details.svelte index e752b4b62..9f886e4fd 100644 --- a/src/lib/components/lines-and-dots/workflow-details.svelte +++ b/src/lib/components/lines-and-dots/workflow-details.svelte @@ -3,7 +3,7 @@ import { translate } from '$lib/i18n/translate'; import { relativeTime, timeFormat } from '$lib/stores/time-format'; - import { workflowRun } from '$lib/stores/workflow-run'; + import type { WorkflowExecution } from '$lib/types/workflows'; import { formatDate } from '$lib/utilities/format-date'; import { formatDistanceAbbreviated } from '$lib/utilities/format-time'; import { @@ -14,8 +14,10 @@ import WorkflowDetail from './workflow-detail.svelte'; + export let workflow: WorkflowExecution; + $: ({ namespace } = $page.params); - $: ({ workflow } = $workflowRun); + $: elapsedTime = formatDistanceAbbreviated({ start: workflow?.startTime, end: workflow?.endTime || Date.now(), diff --git a/src/lib/components/workflow/relationships/continue-as-new-node.svelte b/src/lib/components/workflow/relationships/continue-as-new-node.svelte new file mode 100644 index 000000000..eb44600d9 --- /dev/null +++ b/src/lib/components/workflow/relationships/continue-as-new-node.svelte @@ -0,0 +1,30 @@ + + +
+ {#if icon} + + {/if} +

{label}

+
+ {#if href} + + {value} + + {:else} +

{value}

+ {/if} +
+
diff --git a/src/lib/components/workflow/relationships/continue-as-new-tree.svelte b/src/lib/components/workflow/relationships/continue-as-new-tree.svelte new file mode 100644 index 000000000..3f282c90a --- /dev/null +++ b/src/lib/components/workflow/relationships/continue-as-new-tree.svelte @@ -0,0 +1,66 @@ + + +
+ {#if first} + +
+ {/if} + {#if previous} + +
+ {/if} + + {#if next} +
+ + {/if} +
diff --git a/src/lib/components/workflow/relationships/schedule-tree.svelte b/src/lib/components/workflow/relationships/schedule-tree.svelte new file mode 100644 index 000000000..3044c92ef --- /dev/null +++ b/src/lib/components/workflow/relationships/schedule-tree.svelte @@ -0,0 +1,39 @@ + + +
+ +
+ +
diff --git a/src/lib/components/workflow/relationships/workflow-family-node-description-details.svelte b/src/lib/components/workflow/relationships/workflow-family-node-description-details.svelte new file mode 100644 index 000000000..0a49c8ceb --- /dev/null +++ b/src/lib/components/workflow/relationships/workflow-family-node-description-details.svelte @@ -0,0 +1,70 @@ + + +
+
+
+ +
+
+ {#if isRootWorkflow} +

{translate('common.type')}

+ {/if} +

{workflow.name}

+
+
+
+ {#if isRootWorkflow} +

{translate('common.id')}

+ {/if} +

{workflow.id}

+
+ +
diff --git a/src/lib/components/workflow/relationships/workflow-family-node-description-tree.svelte b/src/lib/components/workflow/relationships/workflow-family-node-description-tree.svelte new file mode 100644 index 000000000..99210ec28 --- /dev/null +++ b/src/lib/components/workflow/relationships/workflow-family-node-description-tree.svelte @@ -0,0 +1,24 @@ + + +{#each root?.children as child} + +{/each} diff --git a/src/lib/components/workflow/relationships/workflow-family-node-description.svelte b/src/lib/components/workflow/relationships/workflow-family-node-description.svelte new file mode 100644 index 000000000..2b279892c --- /dev/null +++ b/src/lib/components/workflow/relationships/workflow-family-node-description.svelte @@ -0,0 +1,131 @@ + + +
+ + {#if expanded} +
+ {#if root?.children?.length} + + {/if} +
+ {/if} +
+ + diff --git a/src/lib/components/workflow/relationships/workflow-family-node-tree.svelte b/src/lib/components/workflow/relationships/workflow-family-node-tree.svelte new file mode 100644 index 000000000..9fa01b744 --- /dev/null +++ b/src/lib/components/workflow/relationships/workflow-family-node-tree.svelte @@ -0,0 +1,301 @@ + + +{#if root?.children.length} + +{/if} +{#each root?.children as child, index} + {@const { childX, childY } = getPosition(index)} + {#if child.children.length && isExpanded(child)} + + {/if} + + /> + nodeClick(e, child)} + on:keypress={(e) => nodeClick(e, child)} + > + {#if child?.children?.length && isExpanded(child)} + + {/if} + {#if isActive(child)} + + {/if} + {#if isCurrent(child)} + + {/if} + + {#if child?.children?.length && !isExpanded(child)} + {child.children.length} + {/if} + +{/each} + +{#if generation === 1} + nodeClick(e, root)} + on:keypress={(e) => nodeClick(e, root)} + > + {#if root?.children?.length} + + {/if} + {#if isCurrent(root)} + + {/if} + {#if isActive(root)} + + {/if} + + {#if root?.children?.length} + {root.children.length} + {/if} + +{/if} + + diff --git a/src/lib/components/workflow/relationships/workflow-family-tree.svelte b/src/lib/components/workflow/relationships/workflow-family-tree.svelte new file mode 100644 index 000000000..f6575a15d --- /dev/null +++ b/src/lib/components/workflow/relationships/workflow-family-tree.svelte @@ -0,0 +1,86 @@ + + +
+
+ +
+ +
+ +
+
+
+ +
+
diff --git a/src/lib/components/workflow/workflow-relationships-old.svelte b/src/lib/components/workflow/workflow-relationships-old.svelte new file mode 100644 index 000000000..078ce85be --- /dev/null +++ b/src/lib/components/workflow/workflow-relationships-old.svelte @@ -0,0 +1,66 @@ + + +
+
+ {#if scheduleId} + + {/if} + {#if parent} + + {/if} + {#if first || previous || next} + + {/if} +
+ {#await fetchAllChildWorkflows(namespace, workflowId, runId) then liveChildren} + {#if liveChildren.length} + + {:else if hasChildren} + + {/if} + {/await} +
diff --git a/src/lib/components/workflow/workflow-relationships.svelte b/src/lib/components/workflow/workflow-relationships.svelte index 22c8c7020..5368676dc 100644 --- a/src/lib/components/workflow/workflow-relationships.svelte +++ b/src/lib/components/workflow/workflow-relationships.svelte @@ -1,78 +1,61 @@ -
+
{#if hasRelationships} -
- {#if scheduleId} - - {/if} - {#if parent} - - {/if} - {#if first || previous || next} - - {/if} +
+ {#await fetchAllRootWorkflows(namespace, rootWorkflowId, rootRunId)} + + {:then root} + {#if root && !!root.children.length} + + {/if} + {#if scheduleId} + + {/if} + {#if first || previous || next} + + {/if} + {:catch} + + {/await}
- {#if liveChildren.length} - - {:else if hasChildren} - - {/if} {:else} -

{translate('workflows.no-relationships')}

+

{translate('workflows.no-relationships')}

{/if}
diff --git a/src/lib/holocene/icon/paths.ts b/src/lib/holocene/icon/paths.ts index 740c1388a..ef52a3d98 100644 --- a/src/lib/holocene/icon/paths.ts +++ b/src/lib/holocene/icon/paths.ts @@ -92,6 +92,7 @@ import summary from './svg/summary.svelte'; import sun from './svg/sun.svelte'; import support from './svg/support.svelte'; import table from './svg/table.svelte'; +import target from './svg/target.svelte'; import temporalLogo from './svg/temporal-logo.svelte'; import terminal from './svg/terminal.svelte'; import timeline from './svg/timeline.svelte'; @@ -204,6 +205,7 @@ export const icons = { support, 'spinner-solid': spinnerSolid, table, + target, 'temporal-logo': temporalLogo, terminal, timeline, diff --git a/src/lib/holocene/icon/svg/target.svelte b/src/lib/holocene/icon/svg/target.svelte new file mode 100644 index 000000000..a7a6dc813 --- /dev/null +++ b/src/lib/holocene/icon/svg/target.svelte @@ -0,0 +1,40 @@ + + + + + + + + + + diff --git a/src/lib/holocene/zoom-svg.svelte b/src/lib/holocene/zoom-svg.svelte new file mode 100644 index 000000000..f80a694ad --- /dev/null +++ b/src/lib/holocene/zoom-svg.svelte @@ -0,0 +1,129 @@ + + +
+
+ + +
+ + + +
diff --git a/src/lib/i18n/locales/en/common.ts b/src/lib/i18n/locales/en/common.ts index 8b873c250..c1e56f52f 100644 --- a/src/lib/i18n/locales/en/common.ts +++ b/src/lib/i18n/locales/en/common.ts @@ -158,6 +158,7 @@ export const Strings = { 'auto-refresh': 'Auto refresh', 'auto-refresh-tooltip': '{{ interval }} second page refresh', 'view-more': 'View More...', + 'view-all': 'View All', 'view-all-runs': 'View All Runs', 'more-options': 'More options', download: 'Download', diff --git a/src/lib/i18n/locales/en/workflows.ts b/src/lib/i18n/locales/en/workflows.ts index af59dbff4..9526cadc7 100644 --- a/src/lib/i18n/locales/en/workflows.ts +++ b/src/lib/i18n/locales/en/workflows.ts @@ -146,6 +146,7 @@ export const Strings = { 'parent-workflow': 'Parent Workflow', 'first-execution': 'First Execution', 'previous-execution': 'Previous Execution', + 'current-execution': 'Current Execution', 'next-execution': 'Next Execution', 'child-id': 'Child Workflow ID', 'child-run-id': 'Child Run ID', diff --git a/src/lib/layouts/workflow-header.svelte b/src/lib/layouts/workflow-header.svelte index c06873da3..6fddd3dbb 100644 --- a/src/lib/layouts/workflow-header.svelte +++ b/src/lib/layouts/workflow-header.svelte @@ -19,12 +19,10 @@ import Tabs from '$lib/holocene/tab/tabs.svelte'; import { translate } from '$lib/i18n/translate'; import { fullEventHistory } from '$lib/stores/events'; - import { namespaces } from '$lib/stores/namespaces'; import { resetWorkflows } from '$lib/stores/reset-workflows'; import { workflowRun } from '$lib/stores/workflow-run'; import { workflowsSearchParams } from '$lib/stores/workflows'; import { isCancelInProgress } from '$lib/utilities/cancel-in-progress'; - import { getWorkflowRelationships } from '$lib/utilities/get-workflow-relationships'; import { has } from '$lib/utilities/has'; import { pathMatches } from '$lib/utilities/path-matches'; import { @@ -59,11 +57,6 @@ $: workflowUsesVersioning = workflow?.assignedBuildId ?? workflow?.mostRecentWorkerVersionStamp?.useVersioning; - $: workflowRelationships = getWorkflowRelationships( - workflow, - $fullEventHistory, - $namespaces, - ); $: summary = $workflowRun?.userMetadata?.summary; $: details = $workflowRun?.userMetadata?.details; @@ -173,7 +166,7 @@ {/if} {/if} - + {#if cancelInProgress}
+ - - {workflowRelationships.relationshipCount} - -
+
@@ -80,7 +80,7 @@
-
+
+
diff --git a/src/lib/layouts/workflow-run-layout.svelte b/src/lib/layouts/workflow-run-layout.svelte index 716affb9c..bf5059666 100644 --- a/src/lib/layouts/workflow-run-layout.svelte +++ b/src/lib/layouts/workflow-run-layout.svelte @@ -219,7 +219,7 @@ class="absolute bottom-0 left-0 right-0 {$viewDataEncoderSettings ? 'top-[540px]' : 'top-0'} - } flex h-full flex-col gap-4" + flex h-full flex-col" > {#if workflowError} diff --git a/src/lib/models/__snapshots__/workflow-execution.test.ts.snap b/src/lib/models/__snapshots__/workflow-execution.test.ts.snap index 6397028c8..cdc65db89 100644 --- a/src/lib/models/__snapshots__/workflow-execution.test.ts.snap +++ b/src/lib/models/__snapshots__/workflow-execution.test.ts.snap @@ -27,6 +27,7 @@ exports[`toWorkflowExecution > should match the snapshot for Canceled workflows "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "346e7a24-c660-4f91-a777-ffe3274d8144", "searchAttributes": { "indexedFields": { @@ -75,6 +76,7 @@ exports[`toWorkflowExecution > should match the snapshot for Completed workflows "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "202dcff6-7f35-4c65-995c-bcadce524fb1", "searchAttributes": { "indexedFields": { @@ -120,6 +122,7 @@ exports[`toWorkflowExecution > should match the snapshot for Failed workflows 1` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "7e00b341-c579-4bb5-9e28-810d34ef4329", "searchAttributes": { "indexedFields": { @@ -190,6 +193,7 @@ exports[`toWorkflowExecution > should match the snapshot for Running workflows 1 "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "2a7ba421-f74b-4b8b-b9d8-e6e30e4caac7", "searchAttributes": { "indexedFields": { @@ -269,6 +273,7 @@ exports[`toWorkflowExecution > should match the snapshot for Terminated workflow "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "16c5fbe5-f82d-43c1-acbc-18c65c449ace", "searchAttributes": { "indexedFields": { @@ -323,6 +328,7 @@ exports[`toWorkflowExecution > should match the snapshot for TimedOut workflows "startedTime": null, "state": "Scheduled", }, + "rootExecution": undefined, "runId": "445a17cb-6cb4-4508-bc59-187d08c6c6e2", "searchAttributes": { "indexedFields": { @@ -369,6 +375,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "346e7a24-c660-4f91-a777-ffe3274d8144", "searchAttributes": { "indexedFields": { @@ -411,6 +418,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "202dcff6-7f35-4c65-995c-bcadce524fb1", "searchAttributes": { "indexedFields": { @@ -453,6 +461,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "7e00b341-c579-4bb5-9e28-810d34ef4329", "searchAttributes": { "indexedFields": { @@ -491,6 +500,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "2a7ba421-f74b-4b8b-b9d8-e6e30e4caac7", "searchAttributes": { "indexedFields": { @@ -533,6 +543,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "dfd4039d-4bc0-4864-9a24-85d136814977", "searchAttributes": { "indexedFields": { @@ -575,6 +586,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "16c5fbe5-f82d-43c1-acbc-18c65c449ace", "searchAttributes": { "indexedFields": { @@ -617,6 +629,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "445a17cb-6cb4-4508-bc59-187d08c6c6e2", "searchAttributes": { "indexedFields": { diff --git a/src/lib/models/__snapshots__/workflow-execution.write-disabled.test.ts.snap b/src/lib/models/__snapshots__/workflow-execution.write-disabled.test.ts.snap index a718e1540..4d77029e4 100644 --- a/src/lib/models/__snapshots__/workflow-execution.write-disabled.test.ts.snap +++ b/src/lib/models/__snapshots__/workflow-execution.write-disabled.test.ts.snap @@ -27,6 +27,7 @@ exports[`toWorkflowExecution > should match the snapshot for Canceled workflows "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "346e7a24-c660-4f91-a777-ffe3274d8144", "searchAttributes": { "indexedFields": { @@ -75,6 +76,7 @@ exports[`toWorkflowExecution > should match the snapshot for Completed workflows "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "202dcff6-7f35-4c65-995c-bcadce524fb1", "searchAttributes": { "indexedFields": { @@ -120,6 +122,7 @@ exports[`toWorkflowExecution > should match the snapshot for Failed workflows 1` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "7e00b341-c579-4bb5-9e28-810d34ef4329", "searchAttributes": { "indexedFields": { @@ -190,6 +193,7 @@ exports[`toWorkflowExecution > should match the snapshot for Running workflows 1 "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "2a7ba421-f74b-4b8b-b9d8-e6e30e4caac7", "searchAttributes": { "indexedFields": { @@ -269,6 +273,7 @@ exports[`toWorkflowExecution > should match the snapshot for Terminated workflow "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "16c5fbe5-f82d-43c1-acbc-18c65c449ace", "searchAttributes": { "indexedFields": { @@ -323,6 +328,7 @@ exports[`toWorkflowExecution > should match the snapshot for TimedOut workflows "startedTime": null, "state": "Scheduled", }, + "rootExecution": undefined, "runId": "445a17cb-6cb4-4508-bc59-187d08c6c6e2", "searchAttributes": { "indexedFields": { @@ -369,6 +375,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "346e7a24-c660-4f91-a777-ffe3274d8144", "searchAttributes": { "indexedFields": { @@ -411,6 +418,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "202dcff6-7f35-4c65-995c-bcadce524fb1", "searchAttributes": { "indexedFields": { @@ -453,6 +461,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "7e00b341-c579-4bb5-9e28-810d34ef4329", "searchAttributes": { "indexedFields": { @@ -491,6 +500,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "2a7ba421-f74b-4b8b-b9d8-e6e30e4caac7", "searchAttributes": { "indexedFields": { @@ -533,6 +543,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "dfd4039d-4bc0-4864-9a24-85d136814977", "searchAttributes": { "indexedFields": { @@ -575,6 +586,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "16c5fbe5-f82d-43c1-acbc-18c65c449ace", "searchAttributes": { "indexedFields": { @@ -617,6 +629,7 @@ exports[`toWorkflowExecutions > should match the snapshot 1`] = ` "pendingChildren": [], "pendingNexusOperations": [], "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "445a17cb-6cb4-4508-bc59-187d08c6c6e2", "searchAttributes": { "indexedFields": { diff --git a/src/lib/models/workflow-execution.ts b/src/lib/models/workflow-execution.ts index 9b468c5ed..2f649b97c 100644 --- a/src/lib/models/workflow-execution.ts +++ b/src/lib/models/workflow-execution.ts @@ -115,6 +115,7 @@ export const toWorkflowExecution = ( toPendingNexusOperations(response?.pendingNexusOperations); const pendingWorkflowTask = response?.pendingWorkflowTask; const callbacks = toCallbacks(response?.callbacks); + const rootExecution = response.workflowExecutionInfo?.rootExecution; let summary; let details; @@ -135,6 +136,7 @@ export const toWorkflowExecution = ( historySizeBytes, searchAttributes, memo, + rootExecution, url, taskQueue, assignedBuildId, diff --git a/src/lib/services/workflow-service.ts b/src/lib/services/workflow-service.ts index adc972fe0..dc76dd2f6 100644 --- a/src/lib/services/workflow-service.ts +++ b/src/lib/services/workflow-service.ts @@ -50,6 +50,7 @@ import { isForbidden, isUnauthorized, } from '$lib/utilities/handle-error'; +import { paginated } from '$lib/utilities/paginated'; import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; import { toListWorkflowQuery } from '$lib/utilities/query/list-workflow-query'; import type { ErrorCallback } from '$lib/utilities/request-from-api'; @@ -661,3 +662,93 @@ export const fetchInitialValuesForStartWorkflow = async ({ return emptyValues; } }; + +export interface RootNode { + workflow: WorkflowExecution; + children: RootNode[]; + rootPaths: { runId: string; workflowId: string }[]; +} + +const buildRoots = ( + root: WorkflowExecution, + workflows: WorkflowExecution[], +) => { + const rootMap: RootNode = { + workflow: root, + children: [], + rootPaths: [{ runId: root.runId, workflowId: root.id }], + }; + + const getOrCreateNodes = (node: RootNode, children: WorkflowExecution[]) => { + if (children?.length) { + node.children = children.map((wf) => ({ + workflow: wf, + children: [], + rootPaths: [...node.rootPaths, { runId: wf.runId, workflowId: wf.id }], + })); + node.children.forEach((child) => { + const nodeChildren = workflows.filter( + (w) => + w?.parent?.workflowId === child.workflow.id && + w?.parent?.runId === child.workflow.runId, + ); + getOrCreateNodes(child, nodeChildren); + }); + } else { + node.children = []; + } + }; + + const rootChildren = workflows.filter( + (w) => w?.parent?.workflowId === root.id && w?.parent?.runId === root.runId, + ); + + getOrCreateNodes(rootMap, rootChildren); + + return rootMap; +}; + +export async function fetchAllRootWorkflows( + namespace: string, + rootWorkflowId: string, + rootRunId?: string, +): Promise { + let query = `RootWorkflowId = "${rootWorkflowId}"`; + if (rootRunId) { + query += ` AND RootRunId = "${rootRunId}"`; + } + + const root = await fetchWorkflow({ + namespace, + workflowId: rootWorkflowId, + runId: rootRunId, + }); + const workflows = await fetchAllPaginatedWorkflows(namespace, { query }); + return buildRoots(root?.workflow, workflows); +} + +export const fetchAllPaginatedWorkflows = async ( + namespace: string, + parameters: ValidWorkflowParameters, + request = fetch, + archived = false, +): Promise => { + const rawQuery = + parameters.query || toListWorkflowQuery(parameters, archived); + let query: string; + try { + query = decodeURIComponent(rawQuery); + } catch { + query = rawQuery; + } + + const route = routeForApi('workflows', { namespace }); + const { executions } = await paginated(async (token: string) => { + return requestFromAPI(route, { + token, + request, + params: { query }, + }); + }); + return toWorkflowExecutions({ executions }); +}; diff --git a/src/lib/theme/plugin.ts b/src/lib/theme/plugin.ts index e52f56445..e2dcbf0a5 100644 --- a/src/lib/theme/plugin.ts +++ b/src/lib/theme/plugin.ts @@ -76,7 +76,7 @@ const temporal = plugin( }, '.surface-interactive': { backgroundColor: css('--color-interactive-surface'), - color: css('--color-text-primary'), + color: css('--color-text-white'), '&:focus-visible': { backgroundColor: css('--color-interactive-hover'), }, diff --git a/src/lib/types/workflows.ts b/src/lib/types/workflows.ts index a7f57e5ff..aec65d6c5 100644 --- a/src/lib/types/workflows.ts +++ b/src/lib/types/workflows.ts @@ -162,6 +162,7 @@ export type WorkflowExecution = { assignedBuildId?: string; searchAttributes?: WorkflowSearchAttributes; memo: Memo; + rootExecution?: WorkflowIdentifier; pendingChildren: PendingChildren[]; pendingNexusOperations: PendingNexusOperation[]; pendingActivities: PendingActivity[]; diff --git a/src/lib/utilities/decode-payload.ts b/src/lib/utilities/decode-payload.ts index 7b871ff29..d34f2c335 100644 --- a/src/lib/utilities/decode-payload.ts +++ b/src/lib/utilities/decode-payload.ts @@ -10,7 +10,7 @@ import type { passAccessToken, } from '$lib/stores/data-encoder-config'; import type { DownloadEventHistorySetting } from '$lib/stores/events'; -import type { Payloads, Payload as RawPayload } from '$lib/types'; +import type { Memo, Payloads, Payload as RawPayload } from '$lib/types'; import type { EventAttribute, EventRequestMetadata, @@ -232,13 +232,20 @@ export const isSinglePayload = (payload: unknown): boolean => { }; export const cloneAllPotentialPayloadsWithCodec = async ( - anyAttributes: PotentiallyDecodable | EventAttribute | WorkflowEvent | null, + anyAttributes: + | PotentiallyDecodable + | EventAttribute + | WorkflowEvent + | Memo + | null, namespace: string, settings: Settings, accessToken: string, decodeSetting: DownloadEventHistorySetting = 'readable', returnDataOnly: boolean = true, -): Promise => { +): Promise< + PotentiallyDecodable | EventAttribute | WorkflowEvent | Memo | null +> => { if (!anyAttributes) return anyAttributes; const decode = diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/relationships/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/relationships/+page.svelte index 3913b2969..552406ac2 100644 --- a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/relationships/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/relationships/+page.svelte @@ -4,7 +4,6 @@ import PageTitle from '$lib/components/page-title.svelte'; import WorkflowRelationships from '$lib/components/workflow/workflow-relationships.svelte'; import { translate } from '$lib/i18n/translate'; - import WorkflowPaddedLayout from '$lib/layouts/workflow-padded-layout.svelte'; const workflow = $page.params.workflow; @@ -13,6 +12,4 @@ title={`${translate('workflows.relationships')} | ${workflow}`} url={$page.url.href} /> - - - +