diff --git a/src/lib/i18n/locales/en/common.ts b/src/lib/i18n/locales/en/common.ts
index 8b91707b05..79e4345ab8 100644
--- a/src/lib/i18n/locales/en/common.ts
+++ b/src/lib/i18n/locales/en/common.ts
@@ -68,6 +68,9 @@ export const Strings = {
'workflow-type': 'Workflow Type',
'workflow-id': 'Workflow ID',
'run-id': 'Run ID',
+ 'event-id': 'Event ID',
+ 'event-type': 'Event Type',
+ 'request-id': 'Request ID',
'task-queue': 'Task Queue',
preview: 'Preview',
status: 'Status',
diff --git a/src/lib/i18n/locales/en/nexus.ts b/src/lib/i18n/locales/en/nexus.ts
index 966026636f..0a3c781d0c 100644
--- a/src/lib/i18n/locales/en/nexus.ts
+++ b/src/lib/i18n/locales/en/nexus.ts
@@ -60,7 +60,19 @@ export const Strings = {
'last-attempt-failure': 'Last Attempt Failure',
'blocked-reason': 'Blocked Reason',
link: 'Link',
+ 'links-empty-state': 'No Nexus Links found.',
'link-namespace': 'Link Namespace',
+ 'nexus-service': 'Nexus Service',
+ 'nexus-endpoint-simple': 'Nexus Endpoint',
+ 'nexus-operation': 'Nexus Operation',
+ 'caller-event': 'Caller Event',
+ 'caller-link': 'Caller Link',
+ 'caller-workflow': 'Caller Workflow',
+ 'source-event': 'Source Event',
+ 'caller-namespace': 'Caller Namespace',
+ 'handler-namespace': 'Handler Namespace',
+ 'handler-workflow': 'Handler Workflow',
+ 'handler-event': 'Handler Event',
service: 'Service',
operation: 'Operation',
'operation-token': 'Operation Token',
diff --git a/src/lib/i18n/locales/en/workflows.ts b/src/lib/i18n/locales/en/workflows.ts
index e93ee6525d..48f4c151f6 100644
--- a/src/lib/i18n/locales/en/workflows.ts
+++ b/src/lib/i18n/locales/en/workflows.ts
@@ -125,6 +125,7 @@ export const Strings = {
'workflow-history': 'Workflow History',
'workers-tab': 'Workers',
'pending-activities-tab': 'Pending Activities',
+ 'nexus-links-tab': 'Nexus Links',
'call-stack-tab': 'Call Stack',
'queries-tab': 'Queries',
'metadata-tab': 'Metadata',
diff --git a/src/lib/layouts/workflow-header.svelte b/src/lib/layouts/workflow-header.svelte
index f7e7a83611..3b128d5b8d 100644
--- a/src/lib/layouts/workflow-header.svelte
+++ b/src/lib/layouts/workflow-header.svelte
@@ -18,6 +18,7 @@
import Tab from '$lib/holocene/tab/tab.svelte';
import Tabs from '$lib/holocene/tab/tabs.svelte';
import { translate } from '$lib/i18n/translate';
+ import { getInboundNexusLinkEvents } from '$lib/runes/inbound-nexus-links.svelte';
import { getWorkflowPollersWithVersions } from '$lib/runes/workflow-versions.svelte';
import { fullEventHistory } from '$lib/stores/events';
import { namespaces } from '$lib/stores/namespaces';
@@ -25,11 +26,15 @@
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 {
+ getWorkflowNexusLinksFromHistory,
+ getWorkflowRelationships,
+ } from '$lib/utilities/get-workflow-relationships';
import { pathMatches } from '$lib/utilities/path-matches';
import {
routeForCallStack,
routeForEventHistory,
+ routeForNexusLinks,
routeForPendingActivities,
routeForRelationships,
routeForWorkers,
@@ -64,6 +69,10 @@
$fullEventHistory,
$namespaces,
);
+ $: outboundLinks =
+ getWorkflowNexusLinksFromHistory($fullEventHistory)?.length || 0;
+ $: inboundLinks = getInboundNexusLinkEvents($fullEventHistory)?.length || 0;
+ $: linkCount = outboundLinks + inboundLinks;
@@ -204,6 +213,21 @@
{workflowRelationships.relationshipCount}
+ {#if linkCount > 0}
+
+
+ {linkCount}
+
+
+ {/if}
- {#if activitiesCanceled}
+ {#if activitiesCanceled}
+
{/if}
{workflow?.pendingActivities?.length}
diff --git a/src/lib/models/event-groups/event-groups.d.ts b/src/lib/models/event-groups/event-groups.d.ts
index 2e3eb402dc..fb787e96a1 100644
--- a/src/lib/models/event-groups/event-groups.d.ts
+++ b/src/lib/models/event-groups/event-groups.d.ts
@@ -1,6 +1,5 @@
-import type { Payload } from '$lib/types';
+import type { EventLink, Payload } from '$lib/types';
import type {
- EventLink,
PendingActivity,
PendingNexusOperation,
WorkflowEvent,
diff --git a/src/lib/pages/workflow-history-event.svelte b/src/lib/pages/workflow-history-event.svelte
index 82b1a27db9..f4a7a60719 100644
--- a/src/lib/pages/workflow-history-event.svelte
+++ b/src/lib/pages/workflow-history-event.svelte
@@ -1,5 +1,7 @@
+ import { page } from '$app/state';
+
+ import Link from '$lib/holocene/link.svelte';
+ import TableHeaderRow from '$lib/holocene/table/table-header-row.svelte';
+ import TableRow from '$lib/holocene/table/table-row.svelte';
+ import Table from '$lib/holocene/table/table.svelte';
+ import { translate } from '$lib/i18n/translate';
+ import { groupEvents } from '$lib/models/event-groups';
+ import {
+ getInboundLinkForEvent,
+ getInboundNexusLinkEvents,
+ } from '$lib/runes/inbound-nexus-links.svelte';
+ import { fullEventHistory } from '$lib/stores/events';
+ import { workflowRun } from '$lib/stores/workflow-run';
+ import { getEventLinkHref } from '$lib/utilities/event-link-href';
+ import { isNexusOperationScheduledEvent } from '$lib/utilities/is-event-type';
+ import {
+ routeForEventHistory,
+ routeForEventHistoryEvent,
+ routeForNamespace,
+ } from '$lib/utilities/route-for';
+ import { fromScreamingEnum } from '$lib/utilities/screaming-enums';
+
+ const { namespace, workflow: workflowId, run } = $derived(page.params);
+ const { workflow } = $derived($workflowRun);
+ const pendingActivities = $derived(workflow?.pendingActivities);
+ const pendingNexusOperations = $derived(workflow?.pendingNexusOperations);
+
+ const groups = $derived(
+ groupEvents(
+ $fullEventHistory,
+ 'ascending',
+ pendingActivities,
+ pendingNexusOperations,
+ ),
+ );
+ const nexusGroups = $derived(
+ groups.filter((group) => group.category === 'nexus' && group.links?.length),
+ );
+ const inboundLinkEvents = $derived(
+ getInboundNexusLinkEvents($fullEventHistory),
+ );
+
+
+
+
+ {translate('workflows.nexus-links-tab')}
+
+ {#if !inboundLinkEvents?.length && !nexusGroups.length}
+
+ {translate('nexus.links-empty-state')}
+
+ {/if}
+ {#if inboundLinkEvents?.length}
+ Inbound
+
+ {translate('workflows.workers-tab')}
+
+ | {translate('nexus.caller-event')} |
+ {translate('nexus.caller-workflow')} |
+ {translate('nexus.caller-namespace')} |
+ {translate('nexus.handler-event')} |
+
+ {#each inboundLinkEvents as event}
+ {@const link = getInboundLinkForEvent(event)}
+
+
+ {#if link?.workflowEvent}
+
+ {link.workflowEvent?.eventRef?.eventId}
+
+ {/if}
+ |
+
+ {#if link?.workflowEvent}
+ {link.workflowEvent.workflowId}
+ {/if}
+ |
+
+ {#if link?.workflowEvent}
+ {link.workflowEvent.namespace}
+ {/if}
+ |
+
+ {event.name}
+ ({event.id})
+
+ |
+
+ {/each}
+
+ {/if}
+ {#if nexusGroups.length}
+ Outbound
+
+ {translate('workflows.workers-tab')}
+
+ | {translate('nexus.source-event')} |
+ {translate('nexus.nexus-endpoint-simple')} |
+ {translate('nexus.nexus-service')} |
+ {translate('nexus.nexus-operation')} |
+ {translate('nexus.handler-namespace')} |
+ {translate('nexus.handler-workflow')} |
+ {translate('nexus.handler-event')} |
+
+ {#each nexusGroups as group}
+ {@const link = group.links?.[0]}
+ {@const scheduledEvent = group.eventList.find((e) =>
+ isNexusOperationScheduledEvent(e),
+ )}
+
+
+ {scheduledEvent.id}
+ |
+
+ {scheduledEvent?.nexusOperationScheduledEventAttributes?.endpoint}
+ |
+
+ {scheduledEvent?.nexusOperationScheduledEventAttributes?.service}
+ |
+
+ {scheduledEvent?.nexusOperationScheduledEventAttributes?.operation}
+ |
+
+ {#if link?.workflowEvent}
+ {link?.workflowEvent?.namespace}
+ {/if}
+ |
+
+ {#if link?.workflowEvent}
+ {link.workflowEvent.workflowId}
+ {/if}
+ |
+
+ {#if link?.workflowEvent}
+ {fromScreamingEnum(
+ link.workflowEvent?.eventRef?.eventType ||
+ link.workflowEvent?.requestIdRef?.eventType,
+ 'EventType',
+ )}
+ {#if link.workflowEvent?.eventRef?.eventId}
+ ({link.workflowEvent.eventRef.eventId})
+ {/if}
+
+ {/if}
+ |
+
+ {/each}
+
+ {/if}
+
diff --git a/src/lib/runes/inbound-nexus-links.svelte.ts b/src/lib/runes/inbound-nexus-links.svelte.ts
new file mode 100644
index 0000000000..6b364109b2
--- /dev/null
+++ b/src/lib/runes/inbound-nexus-links.svelte.ts
@@ -0,0 +1,34 @@
+import type { WorkflowEvent } from '$lib/types/events';
+import {
+ isWorkflowExecutionOptionsUpdatedEvent,
+ isWorkflowExecutionStartedEvent,
+} from '$lib/utilities/is-event-type';
+
+export const getInboundNexusLinkEvents = (history: WorkflowEvent[]) => {
+ try {
+ const workflowExecutionStartedEvent = $derived(
+ history.find((event) => isWorkflowExecutionStartedEvent(event)),
+ );
+ const workflowExecutionOptionsUpdatedEvents = $derived(
+ history.filter((event) => isWorkflowExecutionOptionsUpdatedEvent(event)),
+ );
+ const matchingEvents = $derived([
+ workflowExecutionStartedEvent,
+ ...workflowExecutionOptionsUpdatedEvents,
+ ]);
+
+ return matchingEvents.filter(getInboundLinkForEvent);
+ } catch (error) {
+ return [];
+ }
+};
+
+export const getInboundLinkForEvent = (event: WorkflowEvent) => {
+ return (
+ event?.links?.[0] ||
+ (isWorkflowExecutionOptionsUpdatedEvent(event) &&
+ event?.attributes?.attachedCompletionCallbacks?.[0]?.links?.[0]) ||
+ (isWorkflowExecutionStartedEvent(event) &&
+ event?.attributes?.completionCallbacks?.[0]?.links?.[0])
+ );
+};
diff --git a/src/lib/types/events.ts b/src/lib/types/events.ts
index 3f82d35f97..1016906d0c 100644
--- a/src/lib/types/events.ts
+++ b/src/lib/types/events.ts
@@ -56,10 +56,7 @@ export type PendingActivityState =
| 'CancelRequested';
export type PendingChildren = import('$lib/types').PendingChildrenInfo;
-export type PendingNexusOperation = import('$lib/types').PendingNexusInfo & {
- scheduledEventId: string;
- scheduleToCloseTimeout: string;
-};
+export type PendingNexusOperation = import('$lib/types').PendingNexusInfo;
export type Callbacks = import('$lib/types').CallbackInfo[];
export type EventRequestMetadata = {
@@ -111,7 +108,8 @@ export type CommonEventKey =
| 'classification'
| 'category'
| 'workerMayIgnore'
- | 'name';
+ | 'name'
+ | 'links';
export type CommonHistoryEvent = Pick
;
diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts
index ca0014b768..ec32468af9 100644
--- a/src/lib/types/index.ts
+++ b/src/lib/types/index.ts
@@ -41,6 +41,10 @@ export type UpdateWorkflowRequest =
temporal.api.workflowservice.v1.IUpdateWorkflowExecutionRequest;
export type UpdateWorkflowResponse =
temporal.api.workflowservice.v1.IUpdateWorkflowExecutionResponse;
+export type PendingWorkflowTaskInfo =
+ temporal.api.workflow.v1.IPendingWorkflowTaskInfo;
+export type WorkflowExtendedInfo =
+ temporal.api.workflow.v1.IWorkflowExecutionExtendedInfo;
// api.history
@@ -168,8 +172,6 @@ export type TaskReachability = temporal.api.enums.v1.TaskReachability;
export type PendingNexusOperationState =
temporal.api.enums.v1.PendingNexusOperationState;
export type CallbackState = temporal.api.enums.v1.CallbackState;
-export type PendingWorkflowTaskInfo =
- temporal.api.workflow.v1.IPendingWorkflowTaskInfo;
export type VersioningBehavior = temporal.api.enums.v1.VersioningBehavior;
export type EventType = temporal.api.enums.v1.EventType;
diff --git a/src/lib/types/workflows.ts b/src/lib/types/workflows.ts
index 02705732aa..361885efa8 100644
--- a/src/lib/types/workflows.ts
+++ b/src/lib/types/workflows.ts
@@ -3,12 +3,13 @@ import type {
Payloads,
PendingWorkflowTaskInfo,
WorkflowExecutionStatus,
+ WorkflowExtendedInfo,
WorkflowVersionTimpstamp,
} from '$lib/types';
+import type { Callback } from '$lib/types/nexus';
import type { VersioningInfo } from './deployments';
import type {
- Callbacks,
Payload,
PendingActivity,
PendingActivityInfo,
@@ -78,22 +79,13 @@ export type UserMetadata = {
details?: Payload;
};
-export type WorkflowExecutionConfigWithMetadata = WorkflowExecutionConfig & {
- userMetadata?: UserMetadata;
-};
-
-export type WorkflowExtendedInfo = {
- resetRunId?: string;
- originalStartTime?: string;
-};
-
export type WorkflowExecutionAPIResponse = Optional<{
workflowExecutionInfo: WorkflowExecutionInfo;
pendingActivities: PendingActivityInfo[];
pendingChildren: PendingChildren[];
pendingNexusOperations: PendingNexusOperation[];
- executionConfig: WorkflowExecutionConfigWithMetadata;
- callbacks: Callbacks;
+ executionConfig: WorkflowExecutionConfig;
+ callbacks: Callback[];
pendingWorkflowTask: PendingWorkflowTaskInfo;
workflowExtendedInfo: WorkflowExtendedInfo;
}>;
@@ -188,7 +180,7 @@ export type WorkflowExecution = {
isRunning: boolean;
defaultWorkflowTaskTimeout: Duration;
canBeTerminated: boolean;
- callbacks: Callbacks;
+ callbacks: Callback[];
versioningInfo?: VersioningInfo;
summary?: Payload;
details?: Payload;
diff --git a/src/lib/utilities/get-workflow-relationships.ts b/src/lib/utilities/get-workflow-relationships.ts
index e8498540d6..23a738e0a7 100644
--- a/src/lib/utilities/get-workflow-relationships.ts
+++ b/src/lib/utilities/get-workflow-relationships.ts
@@ -1,4 +1,4 @@
-import type { DescribeNamespaceResponse } from '$lib/types';
+import type { DescribeNamespaceResponse, EventLink } from '$lib/types';
import type {
ChildWorkflowExecutionCanceledEvent,
ChildWorkflowExecutionCompletedEvent,
@@ -134,3 +134,22 @@ export const getWorkflowRelationships = (
relationshipCount,
};
};
+
+export const getWorkflowNexusLinksFromHistory = (
+ history: WorkflowEvents,
+): EventLink[] => {
+ try {
+ const links = new Set();
+ for (const event of history) {
+ if (event.category === 'nexus' && event.links && event.links.length > 0) {
+ for (const link of event.links) {
+ links.add(link);
+ }
+ }
+ }
+
+ return Array.from(links);
+ } catch (error) {
+ return [];
+ }
+};
diff --git a/src/lib/utilities/route-for.ts b/src/lib/utilities/route-for.ts
index e0559937d9..9cfaa4a5f4 100644
--- a/src/lib/utilities/route-for.ts
+++ b/src/lib/utilities/route-for.ts
@@ -263,6 +263,10 @@ export const routeForPendingActivities = (
return `${routeForWorkflow(parameters)}/pending-activities`;
};
+export const routeForNexusLinks = (parameters: WorkflowParameters): string => {
+ return `${routeForWorkflow(parameters)}/nexus-links`;
+};
+
export const routeForAuthentication = (
parameters: AuthenticationParameters,
): string => {
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
index 862c6b2dc9..cd6b4e0164 100644
--- a/src/routes/(app)/+layout.svelte
+++ b/src/routes/(app)/+layout.svelte
@@ -158,7 +158,10 @@
icon: 'nexus',
label: translate('nexus.nexus'),
hidden: !$page.data?.systemInfo?.capabilities?.nexus,
- isActive: (path) => path.includes(nexusRoute),
+ isActive: (path) => {
+ const match = path.split('/').find((segment) => segment === 'nexus');
+ return !!match;
+ },
},
{
href: historyImportRoute,
diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/nexus-links/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/nexus-links/+page.svelte
new file mode 100644
index 0000000000..6b6820596c
--- /dev/null
+++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/nexus-links/+page.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+
+