@workflow/web-shared 4.1.0-beta.47 → 4.1.0-beta.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -52
- package/dist/components/error-boundary.d.ts.map +1 -0
- package/dist/{error-boundary.js → components/error-boundary.js} +1 -1
- package/dist/components/error-boundary.js.map +1 -0
- package/dist/{event-list-view.d.ts → components/event-list-view.d.ts} +2 -3
- package/dist/components/event-list-view.d.ts.map +1 -0
- package/dist/{event-list-view.js → components/event-list-view.js} +9 -17
- package/dist/components/event-list-view.js.map +1 -0
- package/dist/{hook-actions.d.ts → components/hook-actions.d.ts} +2 -3
- package/dist/components/hook-actions.d.ts.map +1 -0
- package/dist/{hook-actions.js → components/hook-actions.js} +3 -4
- package/dist/components/hook-actions.js.map +1 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +8 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/run-trace-view.d.ts +22 -0
- package/dist/components/run-trace-view.d.ts.map +1 -0
- package/dist/components/run-trace-view.js +11 -0
- package/dist/components/run-trace-view.js.map +1 -0
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -0
- package/dist/{sidebar → components/sidebar}/attribute-panel.js +3 -3
- package/dist/components/sidebar/attribute-panel.js.map +1 -0
- package/dist/components/sidebar/conversation-view.d.ts.map +1 -0
- package/dist/components/sidebar/conversation-view.js.map +1 -0
- package/dist/components/sidebar/detail-card.d.ts.map +1 -0
- package/dist/components/sidebar/detail-card.js.map +1 -0
- package/dist/components/sidebar/entity-detail-panel.d.ts +32 -0
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -0
- package/dist/{sidebar → components/sidebar}/entity-detail-panel.js +61 -49
- package/dist/components/sidebar/entity-detail-panel.js.map +1 -0
- package/dist/components/sidebar/events-list.d.ts +8 -0
- package/dist/components/sidebar/events-list.d.ts.map +1 -0
- package/dist/components/sidebar/events-list.js +16 -0
- package/dist/components/sidebar/events-list.js.map +1 -0
- package/dist/components/sidebar/resolve-hook-modal.d.ts.map +1 -0
- package/dist/components/sidebar/resolve-hook-modal.js.map +1 -0
- package/dist/components/stream-viewer.d.ts +18 -0
- package/dist/components/stream-viewer.d.ts.map +1 -0
- package/dist/{stream-viewer.js → components/stream-viewer.js} +1 -59
- package/dist/components/stream-viewer.js.map +1 -0
- package/dist/components/trace-viewer/components/map.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/map.js.map +1 -0
- package/dist/components/trace-viewer/components/markers.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/markers.js.map +1 -0
- package/dist/components/trace-viewer/components/node.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/node.js.map +1 -0
- package/dist/components/trace-viewer/components/search-input.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/search-input.js.map +1 -0
- package/dist/components/trace-viewer/components/search.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/search.js.map +1 -0
- package/dist/components/trace-viewer/components/span-detail-panel.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/span-detail-panel.js.map +1 -0
- package/dist/components/trace-viewer/components/ui.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/ui.js.map +1 -0
- package/dist/components/trace-viewer/components/zoom-button.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/zoom-button.js.map +1 -0
- package/dist/components/trace-viewer/components/zoom-icons.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/zoom-icons.js.map +1 -0
- package/dist/components/trace-viewer/context.d.ts.map +1 -0
- package/dist/components/trace-viewer/context.js.map +1 -0
- package/dist/components/trace-viewer/index.d.ts.map +1 -0
- package/dist/components/trace-viewer/index.js.map +1 -0
- package/dist/components/trace-viewer/trace-viewer.d.ts.map +1 -0
- package/dist/components/trace-viewer/trace-viewer.js.map +1 -0
- package/dist/components/trace-viewer/types.d.ts.map +1 -0
- package/dist/components/trace-viewer/types.js.map +1 -0
- package/dist/components/trace-viewer/util/constants.d.ts.map +1 -0
- package/dist/components/trace-viewer/util/constants.js.map +1 -0
- package/dist/components/trace-viewer/util/scrollbar-width.d.ts.map +1 -0
- package/dist/components/trace-viewer/util/scrollbar-width.js.map +1 -0
- package/dist/{trace-viewer → components/trace-viewer}/util/timing.d.ts +1 -1
- package/dist/components/trace-viewer/util/timing.d.ts.map +1 -0
- package/dist/{trace-viewer → components/trace-viewer}/util/timing.js +1 -1
- package/dist/components/trace-viewer/util/timing.js.map +1 -0
- package/dist/components/trace-viewer/util/tree.d.ts.map +1 -0
- package/dist/components/trace-viewer/util/tree.js.map +1 -0
- package/dist/components/trace-viewer/util/use-immediate-style.d.ts.map +1 -0
- package/dist/components/trace-viewer/util/use-immediate-style.js.map +1 -0
- package/dist/components/trace-viewer/util/use-streaming-spans.d.ts.map +1 -0
- package/dist/components/trace-viewer/util/use-streaming-spans.js.map +1 -0
- package/dist/components/trace-viewer/util/use-trackpad-zoom.d.ts.map +1 -0
- package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +1 -0
- package/dist/components/trace-viewer/worker.d.ts.map +1 -0
- package/dist/components/trace-viewer/worker.js.map +1 -0
- package/dist/components/workflow-trace-view.d.ts +24 -0
- package/dist/components/workflow-trace-view.d.ts.map +1 -0
- package/dist/components/workflow-trace-view.js +152 -0
- package/dist/components/workflow-trace-view.js.map +1 -0
- package/dist/components/workflow-traces/event-colors.d.ts.map +1 -0
- package/dist/components/workflow-traces/event-colors.js.map +1 -0
- package/dist/components/workflow-traces/trace-colors.d.ts.map +1 -0
- package/dist/components/workflow-traces/trace-colors.js.map +1 -0
- package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -0
- package/dist/components/workflow-traces/trace-span-construction.js.map +1 -0
- package/dist/components/workflow-traces/trace-time-utils.d.ts.map +1 -0
- package/dist/components/workflow-traces/trace-time-utils.js.map +1 -0
- package/dist/index.d.ts +3 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -9
- package/dist/index.js.map +1 -1
- package/package.json +15 -11
- package/src/components/error-boundary.tsx +79 -0
- package/src/components/event-list-view.tsx +429 -0
- package/src/components/hook-actions.tsx +167 -0
- package/src/components/index.d.ts +1 -0
- package/src/components/index.ts +23 -0
- package/src/components/run-trace-view.tsx +75 -0
- package/src/components/sidebar/attribute-panel.tsx +938 -0
- package/src/components/sidebar/conversation-view.tsx +235 -0
- package/src/components/sidebar/detail-card.tsx +43 -0
- package/src/components/sidebar/entity-detail-panel.tsx +338 -0
- package/src/components/sidebar/events-list.tsx +119 -0
- package/src/components/sidebar/resolve-hook-modal.tsx +219 -0
- package/src/components/stream-viewer.tsx +143 -0
- package/src/components/trace-viewer/components/map.tsx +226 -0
- package/src/components/trace-viewer/components/markers.tsx +564 -0
- package/src/components/trace-viewer/components/node.tsx +259 -0
- package/src/components/trace-viewer/components/search-input.tsx +52 -0
- package/src/components/trace-viewer/components/search.tsx +47 -0
- package/src/components/trace-viewer/components/span-detail-panel.tsx +650 -0
- package/src/components/trace-viewer/components/ui.tsx +156 -0
- package/src/components/trace-viewer/components/zoom-button.tsx +61 -0
- package/src/components/trace-viewer/components/zoom-icons.tsx +65 -0
- package/src/components/trace-viewer/context.tsx +633 -0
- package/src/components/trace-viewer/index.tsx +4 -0
- package/src/components/trace-viewer/modules.d.ts +16 -0
- package/src/components/trace-viewer/trace-viewer.module.css +1292 -0
- package/src/components/trace-viewer/trace-viewer.tsx +448 -0
- package/src/components/trace-viewer/types.ts +234 -0
- package/src/components/trace-viewer/util/constants.ts +8 -0
- package/src/components/trace-viewer/util/scrollbar-width.ts +13 -0
- package/src/components/trace-viewer/util/timing.ts +33 -0
- package/src/components/trace-viewer/util/tree.ts +277 -0
- package/src/components/trace-viewer/util/use-immediate-style.ts +38 -0
- package/src/components/trace-viewer/util/use-streaming-spans.ts +415 -0
- package/src/components/trace-viewer/util/use-trackpad-zoom.tsx +51 -0
- package/src/components/trace-viewer/worker.ts +128 -0
- package/src/components/ui/alert.tsx +59 -0
- package/src/components/ui/card.tsx +88 -0
- package/src/components/ui/error-card.tsx +65 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/workflow-trace-view.tsx +306 -0
- package/src/components/workflow-traces/event-colors.ts +94 -0
- package/src/components/workflow-traces/trace-colors.ts +112 -0
- package/src/components/workflow-traces/trace-span-construction.ts +299 -0
- package/src/components/workflow-traces/trace-time-utils.ts +50 -0
- package/src/hooks/use-dark-mode.ts +34 -0
- package/src/index.d.ts +1 -0
- package/src/index.ts +29 -0
- package/src/lib/event-analysis.ts +231 -0
- package/src/lib/utils.ts +166 -0
- package/dist/api/workflow-api-client.d.ts +0 -543
- package/dist/api/workflow-api-client.d.ts.map +0 -1
- package/dist/api/workflow-api-client.js +0 -953
- package/dist/api/workflow-api-client.js.map +0 -1
- package/dist/api/workflow-server-actions.d.ts +0 -230
- package/dist/api/workflow-server-actions.d.ts.map +0 -1
- package/dist/api/workflow-server-actions.js +0 -861
- package/dist/api/workflow-server-actions.js.map +0 -1
- package/dist/error-boundary.d.ts.map +0 -1
- package/dist/error-boundary.js.map +0 -1
- package/dist/event-list-view.d.ts.map +0 -1
- package/dist/event-list-view.js.map +0 -1
- package/dist/hook-actions.d.ts.map +0 -1
- package/dist/hook-actions.js.map +0 -1
- package/dist/run-trace-view.d.ts +0 -8
- package/dist/run-trace-view.d.ts.map +0 -1
- package/dist/run-trace-view.js +0 -15
- package/dist/run-trace-view.js.map +0 -1
- package/dist/sidebar/attribute-panel.d.ts.map +0 -1
- package/dist/sidebar/attribute-panel.js.map +0 -1
- package/dist/sidebar/conversation-view.d.ts.map +0 -1
- package/dist/sidebar/conversation-view.js.map +0 -1
- package/dist/sidebar/detail-card.d.ts.map +0 -1
- package/dist/sidebar/detail-card.js.map +0 -1
- package/dist/sidebar/entity-detail-panel.d.ts +0 -12
- package/dist/sidebar/entity-detail-panel.d.ts.map +0 -1
- package/dist/sidebar/entity-detail-panel.js.map +0 -1
- package/dist/sidebar/events-list.d.ts +0 -9
- package/dist/sidebar/events-list.d.ts.map +0 -1
- package/dist/sidebar/events-list.js +0 -36
- package/dist/sidebar/events-list.js.map +0 -1
- package/dist/sidebar/resolve-hook-modal.d.ts.map +0 -1
- package/dist/sidebar/resolve-hook-modal.js.map +0 -1
- package/dist/stream-viewer.d.ts +0 -13
- package/dist/stream-viewer.d.ts.map +0 -1
- package/dist/stream-viewer.js.map +0 -1
- package/dist/trace-viewer/components/map.d.ts.map +0 -1
- package/dist/trace-viewer/components/map.js.map +0 -1
- package/dist/trace-viewer/components/markers.d.ts.map +0 -1
- package/dist/trace-viewer/components/markers.js.map +0 -1
- package/dist/trace-viewer/components/node.d.ts.map +0 -1
- package/dist/trace-viewer/components/node.js.map +0 -1
- package/dist/trace-viewer/components/search-input.d.ts.map +0 -1
- package/dist/trace-viewer/components/search-input.js.map +0 -1
- package/dist/trace-viewer/components/search.d.ts.map +0 -1
- package/dist/trace-viewer/components/search.js.map +0 -1
- package/dist/trace-viewer/components/span-detail-panel.d.ts.map +0 -1
- package/dist/trace-viewer/components/span-detail-panel.js.map +0 -1
- package/dist/trace-viewer/components/ui.d.ts.map +0 -1
- package/dist/trace-viewer/components/ui.js.map +0 -1
- package/dist/trace-viewer/components/zoom-button.d.ts.map +0 -1
- package/dist/trace-viewer/components/zoom-button.js.map +0 -1
- package/dist/trace-viewer/components/zoom-icons.d.ts.map +0 -1
- package/dist/trace-viewer/components/zoom-icons.js.map +0 -1
- package/dist/trace-viewer/context.d.ts.map +0 -1
- package/dist/trace-viewer/context.js.map +0 -1
- package/dist/trace-viewer/index.d.ts.map +0 -1
- package/dist/trace-viewer/index.js.map +0 -1
- package/dist/trace-viewer/trace-viewer.d.ts.map +0 -1
- package/dist/trace-viewer/trace-viewer.js.map +0 -1
- package/dist/trace-viewer/types.d.ts.map +0 -1
- package/dist/trace-viewer/types.js.map +0 -1
- package/dist/trace-viewer/util/constants.d.ts.map +0 -1
- package/dist/trace-viewer/util/constants.js.map +0 -1
- package/dist/trace-viewer/util/scrollbar-width.d.ts.map +0 -1
- package/dist/trace-viewer/util/scrollbar-width.js.map +0 -1
- package/dist/trace-viewer/util/timing.d.ts.map +0 -1
- package/dist/trace-viewer/util/timing.js.map +0 -1
- package/dist/trace-viewer/util/tree.d.ts.map +0 -1
- package/dist/trace-viewer/util/tree.js.map +0 -1
- package/dist/trace-viewer/util/use-immediate-style.d.ts.map +0 -1
- package/dist/trace-viewer/util/use-immediate-style.js.map +0 -1
- package/dist/trace-viewer/util/use-streaming-spans.d.ts.map +0 -1
- package/dist/trace-viewer/util/use-streaming-spans.js.map +0 -1
- package/dist/trace-viewer/util/use-trackpad-zoom.d.ts.map +0 -1
- package/dist/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
- package/dist/trace-viewer/worker.d.ts.map +0 -1
- package/dist/trace-viewer/worker.js.map +0 -1
- package/dist/workflow-trace-view.d.ts +0 -14
- package/dist/workflow-trace-view.d.ts.map +0 -1
- package/dist/workflow-trace-view.js +0 -135
- package/dist/workflow-trace-view.js.map +0 -1
- package/dist/workflow-traces/event-colors.d.ts.map +0 -1
- package/dist/workflow-traces/event-colors.js.map +0 -1
- package/dist/workflow-traces/trace-colors.d.ts.map +0 -1
- package/dist/workflow-traces/trace-colors.js.map +0 -1
- package/dist/workflow-traces/trace-span-construction.d.ts.map +0 -1
- package/dist/workflow-traces/trace-span-construction.js.map +0 -1
- package/dist/workflow-traces/trace-time-utils.d.ts.map +0 -1
- package/dist/workflow-traces/trace-time-utils.js.map +0 -1
- package/server/README.md +0 -1
- package/server/package.json +0 -4
- /package/dist/{error-boundary.d.ts → components/error-boundary.d.ts} +0 -0
- /package/dist/{sidebar → components/sidebar}/attribute-panel.d.ts +0 -0
- /package/dist/{sidebar → components/sidebar}/conversation-view.d.ts +0 -0
- /package/dist/{sidebar → components/sidebar}/conversation-view.js +0 -0
- /package/dist/{sidebar → components/sidebar}/detail-card.d.ts +0 -0
- /package/dist/{sidebar → components/sidebar}/detail-card.js +0 -0
- /package/dist/{sidebar → components/sidebar}/resolve-hook-modal.d.ts +0 -0
- /package/dist/{sidebar → components/sidebar}/resolve-hook-modal.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/map.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/map.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/markers.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/markers.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/node.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/node.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/search-input.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/search-input.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/search.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/search.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/span-detail-panel.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/span-detail-panel.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/ui.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/ui.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/zoom-button.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/zoom-button.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/zoom-icons.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/zoom-icons.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/context.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/context.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/index.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/index.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/trace-viewer.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/trace-viewer.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/trace-viewer.module.css +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/types.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/types.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/constants.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/constants.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/scrollbar-width.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/scrollbar-width.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/tree.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/tree.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/use-immediate-style.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/use-immediate-style.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/use-streaming-spans.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/use-streaming-spans.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/use-trackpad-zoom.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/use-trackpad-zoom.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/worker.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/worker.js +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/event-colors.d.ts +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/event-colors.js +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/trace-colors.d.ts +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/trace-colors.js +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/trace-span-construction.d.ts +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/trace-span-construction.js +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/trace-time-utils.d.ts +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/trace-time-utils.js +0 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functions for constructing OpenTelemetry spans from workflow entities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
|
|
6
|
+
import type { Event, Step, WorkflowRun } from '@workflow/world';
|
|
7
|
+
import type { Span, SpanEvent } from '../trace-viewer/types';
|
|
8
|
+
import { shouldShowVerticalLine } from './event-colors';
|
|
9
|
+
import { calculateDuration, dateToOtelTime } from './trace-time-utils';
|
|
10
|
+
|
|
11
|
+
export const WORKFLOW_LIBRARY = {
|
|
12
|
+
name: 'workflow-development-kit',
|
|
13
|
+
version: '4.0.0',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Event types that should be displayed as visual markers in the trace viewer
|
|
18
|
+
*/
|
|
19
|
+
const MARKER_EVENT_TYPES: Set<Event['eventType']> = new Set([
|
|
20
|
+
'hook_created',
|
|
21
|
+
'hook_received',
|
|
22
|
+
'hook_disposed',
|
|
23
|
+
'step_started',
|
|
24
|
+
'step_retrying',
|
|
25
|
+
'step_failed',
|
|
26
|
+
'run_failed',
|
|
27
|
+
'wait_created',
|
|
28
|
+
'wait_completed',
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert workflow events to span events
|
|
33
|
+
* Only includes events that should be displayed as markers
|
|
34
|
+
*/
|
|
35
|
+
export function convertEventsToSpanEvents(
|
|
36
|
+
events: Event[],
|
|
37
|
+
filterTypes = true
|
|
38
|
+
): SpanEvent[] {
|
|
39
|
+
return events
|
|
40
|
+
.filter((event) =>
|
|
41
|
+
filterTypes ? MARKER_EVENT_TYPES.has(event.eventType) : true
|
|
42
|
+
)
|
|
43
|
+
.map((event) => ({
|
|
44
|
+
name: event.eventType,
|
|
45
|
+
timestamp: dateToOtelTime(event.createdAt),
|
|
46
|
+
attributes: {
|
|
47
|
+
eventId: event.eventId,
|
|
48
|
+
correlationId: event.correlationId,
|
|
49
|
+
eventData: 'eventData' in event ? event.eventData : undefined,
|
|
50
|
+
},
|
|
51
|
+
// Control whether to show vertical line in timeline
|
|
52
|
+
showVerticalLine: shouldShowVerticalLine(event.eventType),
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const waitEventsToWaitEntity = (
|
|
57
|
+
events: Event[]
|
|
58
|
+
): {
|
|
59
|
+
waitId: string;
|
|
60
|
+
runId: string;
|
|
61
|
+
createdAt: Date;
|
|
62
|
+
resumeAt: Date;
|
|
63
|
+
completedAt?: Date;
|
|
64
|
+
} | null => {
|
|
65
|
+
const startEvent = events.find((event) => event.eventType === 'wait_created');
|
|
66
|
+
if (!startEvent) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const completedEvent = events.find(
|
|
70
|
+
(event) => event.eventType === 'wait_completed'
|
|
71
|
+
);
|
|
72
|
+
return {
|
|
73
|
+
waitId: startEvent.correlationId,
|
|
74
|
+
runId: startEvent.runId,
|
|
75
|
+
createdAt: startEvent.createdAt,
|
|
76
|
+
resumeAt: startEvent.eventData?.resumeAt,
|
|
77
|
+
completedAt: completedEvent?.createdAt,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Converts a workflow Wait to an OpenTelemetry Span
|
|
83
|
+
*/
|
|
84
|
+
export function waitToSpan(
|
|
85
|
+
events: Event[],
|
|
86
|
+
run: WorkflowRun,
|
|
87
|
+
nowTime: Date
|
|
88
|
+
): Span | null {
|
|
89
|
+
const wait = waitEventsToWaitEntity(events);
|
|
90
|
+
if (!wait) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const viewerEndTime = new Date(run.completedAt || nowTime) ?? nowTime;
|
|
94
|
+
const startTime = wait?.createdAt ?? nowTime;
|
|
95
|
+
const endTime = wait?.completedAt ?? viewerEndTime;
|
|
96
|
+
const start = dateToOtelTime(startTime);
|
|
97
|
+
const end = dateToOtelTime(endTime);
|
|
98
|
+
const duration = calculateDuration(startTime, endTime);
|
|
99
|
+
const spanEvents = convertEventsToSpanEvents(events);
|
|
100
|
+
return {
|
|
101
|
+
spanId: wait.waitId,
|
|
102
|
+
name: 'sleep',
|
|
103
|
+
kind: 1, // INTERNAL span kind
|
|
104
|
+
resource: 'sleep',
|
|
105
|
+
library: WORKFLOW_LIBRARY,
|
|
106
|
+
status: { code: 0 },
|
|
107
|
+
traceFlags: 1,
|
|
108
|
+
attributes: {
|
|
109
|
+
resource: 'sleep' as const,
|
|
110
|
+
data: wait,
|
|
111
|
+
},
|
|
112
|
+
links: [],
|
|
113
|
+
events: spanEvents,
|
|
114
|
+
duration,
|
|
115
|
+
startTime: start,
|
|
116
|
+
endTime: end,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Converts a workflow Step to an OpenTelemetry Span
|
|
122
|
+
*/
|
|
123
|
+
export function stepToSpan(
|
|
124
|
+
step: Step,
|
|
125
|
+
stepEvents: Event[],
|
|
126
|
+
nowTime?: Date
|
|
127
|
+
): Span {
|
|
128
|
+
const now = nowTime ?? new Date();
|
|
129
|
+
const parsedName = parseStepName(String(step.stepName));
|
|
130
|
+
|
|
131
|
+
// Simplified attributes: only store resource type and full data
|
|
132
|
+
const attributes = {
|
|
133
|
+
resource: 'step' as const,
|
|
134
|
+
data: step,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const resource = 'step';
|
|
138
|
+
const endTime = new Date(step.completedAt ?? now);
|
|
139
|
+
|
|
140
|
+
// Convert step-related events to span events (for markers like hook_created, step_retrying, etc.)
|
|
141
|
+
// This determines which events are displayed as markers. In the detail view,
|
|
142
|
+
// we'll show all events that correlate with the selected resource.
|
|
143
|
+
const events = convertEventsToSpanEvents(stepEvents);
|
|
144
|
+
|
|
145
|
+
// Use createdAt as span start time, with activeStartTime for when execution began
|
|
146
|
+
// This allows visualization of the "queued" period before execution
|
|
147
|
+
const spanStartTime = new Date(step.createdAt);
|
|
148
|
+
let activeStartTime = step.startedAt ? new Date(step.startedAt) : undefined;
|
|
149
|
+
const firstStartEvent = stepEvents.find(
|
|
150
|
+
(event) => event.eventType === 'step_started'
|
|
151
|
+
);
|
|
152
|
+
if (firstStartEvent) {
|
|
153
|
+
// `step.startedAt` is the server-side creation timestamp, and `event.createdAt` is
|
|
154
|
+
// the client-side creation timestamp. For now, to align the event marker with the
|
|
155
|
+
// line we show for step.startedAt, we overwrite here to always use client-side time.
|
|
156
|
+
activeStartTime = new Date(firstStartEvent.createdAt);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
spanId: String(step.stepId),
|
|
161
|
+
name: parsedName?.shortName ?? '',
|
|
162
|
+
kind: 1, // INTERNAL span kind
|
|
163
|
+
resource,
|
|
164
|
+
library: WORKFLOW_LIBRARY,
|
|
165
|
+
status: { code: 0 },
|
|
166
|
+
traceFlags: 1,
|
|
167
|
+
attributes,
|
|
168
|
+
links: [],
|
|
169
|
+
events,
|
|
170
|
+
startTime: dateToOtelTime(spanStartTime),
|
|
171
|
+
endTime: dateToOtelTime(endTime),
|
|
172
|
+
duration: calculateDuration(spanStartTime, endTime),
|
|
173
|
+
// Only set activeStartTime if it differs from startTime (i.e., there was a queued period)
|
|
174
|
+
activeStartTime:
|
|
175
|
+
activeStartTime && activeStartTime.getTime() > spanStartTime.getTime()
|
|
176
|
+
? dateToOtelTime(activeStartTime)
|
|
177
|
+
: undefined,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const hookEventsToHookEntity = (
|
|
182
|
+
events: Event[]
|
|
183
|
+
): {
|
|
184
|
+
hookId: string;
|
|
185
|
+
runId: string;
|
|
186
|
+
createdAt: Date;
|
|
187
|
+
receivedCount: number;
|
|
188
|
+
lastReceivedAt?: Date;
|
|
189
|
+
disposedAt?: Date;
|
|
190
|
+
} | null => {
|
|
191
|
+
const createdEvent = events.find(
|
|
192
|
+
(event) => event.eventType === 'hook_created'
|
|
193
|
+
);
|
|
194
|
+
if (!createdEvent) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const receivedEvents = events.filter(
|
|
198
|
+
(event) => event.eventType === 'hook_received'
|
|
199
|
+
);
|
|
200
|
+
const disposedEvents = events.filter(
|
|
201
|
+
(event) => event.eventType === 'hook_disposed'
|
|
202
|
+
);
|
|
203
|
+
const lastReceivedEvent = receivedEvents.at(-1);
|
|
204
|
+
return {
|
|
205
|
+
hookId: createdEvent.correlationId,
|
|
206
|
+
runId: createdEvent.runId,
|
|
207
|
+
createdAt: createdEvent.createdAt,
|
|
208
|
+
receivedCount: receivedEvents.length,
|
|
209
|
+
lastReceivedAt: lastReceivedEvent?.createdAt || undefined,
|
|
210
|
+
disposedAt: disposedEvents.at(-1)?.createdAt || undefined,
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Converts a workflow Hook to an OpenTelemetry Span
|
|
216
|
+
*/
|
|
217
|
+
export function hookToSpan(
|
|
218
|
+
hookEvents: Event[],
|
|
219
|
+
run: WorkflowRun,
|
|
220
|
+
nowTime: Date
|
|
221
|
+
): Span | null {
|
|
222
|
+
const hook = hookEventsToHookEntity(hookEvents);
|
|
223
|
+
if (!hook) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Convert hook-related events to span events
|
|
228
|
+
const events = convertEventsToSpanEvents(hookEvents);
|
|
229
|
+
|
|
230
|
+
// We display hooks as a minimum span size of 10 seconds, just to ensure
|
|
231
|
+
// it's clickable even if there is no
|
|
232
|
+
const viewerEndTime = new Date(run.completedAt || nowTime) ?? nowTime;
|
|
233
|
+
const endTime = hook.disposedAt || viewerEndTime;
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
spanId: String(hook.hookId),
|
|
237
|
+
name: String(hook.hookId),
|
|
238
|
+
kind: 1, // INTERNAL span kind
|
|
239
|
+
resource: 'hook',
|
|
240
|
+
library: WORKFLOW_LIBRARY,
|
|
241
|
+
status: { code: 1 },
|
|
242
|
+
traceFlags: 1,
|
|
243
|
+
attributes: {
|
|
244
|
+
resource: 'hook' as const,
|
|
245
|
+
data: hook,
|
|
246
|
+
},
|
|
247
|
+
links: [],
|
|
248
|
+
events,
|
|
249
|
+
startTime: dateToOtelTime(hook.createdAt),
|
|
250
|
+
endTime: dateToOtelTime(endTime),
|
|
251
|
+
duration: calculateDuration(hook.createdAt, endTime),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Creates a root span for the workflow run
|
|
257
|
+
*/
|
|
258
|
+
export function runToSpan(
|
|
259
|
+
run: WorkflowRun,
|
|
260
|
+
runEvents: Event[],
|
|
261
|
+
nowTime?: Date
|
|
262
|
+
): Span {
|
|
263
|
+
const now = nowTime ?? new Date();
|
|
264
|
+
|
|
265
|
+
// Simplified attributes: only store resource type and full data
|
|
266
|
+
const attributes = {
|
|
267
|
+
resource: 'run' as const,
|
|
268
|
+
data: run,
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Use createdAt as span start time, with activeStartTime for when execution began
|
|
272
|
+
const spanStartTime = new Date(run.createdAt);
|
|
273
|
+
const activeStartTime = run.startedAt ? new Date(run.startedAt) : undefined;
|
|
274
|
+
const endTime = run.completedAt ?? now;
|
|
275
|
+
|
|
276
|
+
// Convert run-level events to span events
|
|
277
|
+
const events = convertEventsToSpanEvents(runEvents);
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
spanId: String(run.runId),
|
|
281
|
+
name: String(parseWorkflowName(run.workflowName)?.shortName ?? '?'),
|
|
282
|
+
kind: 1, // INTERNAL span kind
|
|
283
|
+
resource: 'run',
|
|
284
|
+
library: WORKFLOW_LIBRARY,
|
|
285
|
+
status: { code: 0 },
|
|
286
|
+
traceFlags: 1,
|
|
287
|
+
attributes,
|
|
288
|
+
links: [],
|
|
289
|
+
events,
|
|
290
|
+
startTime: dateToOtelTime(spanStartTime),
|
|
291
|
+
endTime: dateToOtelTime(endTime),
|
|
292
|
+
duration: calculateDuration(spanStartTime, endTime),
|
|
293
|
+
// Only set activeStartTime if it differs from startTime (i.e., there was a queued period)
|
|
294
|
+
activeStartTime:
|
|
295
|
+
activeStartTime && activeStartTime.getTime() > spanStartTime.getTime()
|
|
296
|
+
? dateToOtelTime(activeStartTime)
|
|
297
|
+
: undefined,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time conversion utilities for workflow trace construction
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Converts a Date to OpenTelemetry time format [seconds, nanoseconds]
|
|
7
|
+
*/
|
|
8
|
+
export function dateToOtelTime(
|
|
9
|
+
date: Date | string | unknown
|
|
10
|
+
): [number, number] {
|
|
11
|
+
if (typeof date === 'string') {
|
|
12
|
+
date = new Date(date);
|
|
13
|
+
}
|
|
14
|
+
if (!date || !(date instanceof Date)) {
|
|
15
|
+
return [0, 0];
|
|
16
|
+
}
|
|
17
|
+
const ms = date.getTime();
|
|
18
|
+
const seconds = Math.floor(ms / 1000);
|
|
19
|
+
const nanoseconds = (ms % 1000) * 1_000_000;
|
|
20
|
+
return [seconds, nanoseconds];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function otelTimeToMs(time: [number, number]): number {
|
|
24
|
+
const secondsToMs = time[0] * 1_000;
|
|
25
|
+
const nanosecondsToMs = time[1] / 1_000_000;
|
|
26
|
+
return secondsToMs + nanosecondsToMs;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Calculates duration in [seconds, nanoseconds] format
|
|
31
|
+
*/
|
|
32
|
+
export function calculateDuration(
|
|
33
|
+
start: Date | string | unknown,
|
|
34
|
+
end: Date | string | unknown
|
|
35
|
+
): [number, number] {
|
|
36
|
+
if (typeof start === 'string') {
|
|
37
|
+
start = new Date(start);
|
|
38
|
+
}
|
|
39
|
+
if (typeof end === 'string') {
|
|
40
|
+
end = new Date(end);
|
|
41
|
+
}
|
|
42
|
+
if (!start || !(start instanceof Date)) {
|
|
43
|
+
return [0, 0];
|
|
44
|
+
}
|
|
45
|
+
const endTime = end && end instanceof Date ? end : new Date();
|
|
46
|
+
const durationMs = endTime.getTime() - start.getTime();
|
|
47
|
+
const seconds = Math.floor(durationMs / 1000);
|
|
48
|
+
const nanoseconds = (durationMs % 1000) * 1_000_000;
|
|
49
|
+
return [seconds, nanoseconds];
|
|
50
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook that detects if dark mode is active and reacts to theme changes.
|
|
7
|
+
* Observes the 'dark' class on the document element, which is how
|
|
8
|
+
* next-themes and similar libraries apply the theme.
|
|
9
|
+
*
|
|
10
|
+
* @returns `true` if dark mode is active, `false` otherwise
|
|
11
|
+
*/
|
|
12
|
+
export const useDarkMode = (): boolean => {
|
|
13
|
+
const [isDark, setIsDark] = useState(() => {
|
|
14
|
+
if (typeof document === 'undefined') return false;
|
|
15
|
+
return document.documentElement.classList.contains('dark');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (typeof document === 'undefined') return;
|
|
20
|
+
|
|
21
|
+
const observer = new MutationObserver(() => {
|
|
22
|
+
setIsDark(document.documentElement.classList.contains('dark'));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
observer.observe(document.documentElement, {
|
|
26
|
+
attributes: true,
|
|
27
|
+
attributeFilter: ['class'],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return () => observer.disconnect();
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return isDark;
|
|
34
|
+
};
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './index';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
HealthCheckEndpoint,
|
|
3
|
+
HealthCheckResult,
|
|
4
|
+
} from '@workflow/core/runtime';
|
|
5
|
+
export {
|
|
6
|
+
parseStepName,
|
|
7
|
+
parseWorkflowName,
|
|
8
|
+
} from '@workflow/utils/parse-name';
|
|
9
|
+
export type { Event, Hook, Step, WorkflowRun } from '@workflow/world';
|
|
10
|
+
export * from './components';
|
|
11
|
+
export {
|
|
12
|
+
hookEventsToHookEntity,
|
|
13
|
+
waitEventsToWaitEntity,
|
|
14
|
+
} from './components/workflow-traces/trace-span-construction';
|
|
15
|
+
export type { EventAnalysis } from './lib/event-analysis';
|
|
16
|
+
export {
|
|
17
|
+
analyzeEvents,
|
|
18
|
+
hasPendingHooksFromEvents,
|
|
19
|
+
hasPendingStepsFromEvents,
|
|
20
|
+
isTerminalStatus,
|
|
21
|
+
shouldShowReenqueueButton,
|
|
22
|
+
} from './lib/event-analysis';
|
|
23
|
+
export type { StreamStep } from './lib/utils';
|
|
24
|
+
export {
|
|
25
|
+
extractConversation,
|
|
26
|
+
formatDuration,
|
|
27
|
+
identifyStreamSteps,
|
|
28
|
+
isDoStreamStep,
|
|
29
|
+
} from './lib/utils';
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for analyzing workflow events.
|
|
3
|
+
* Used by run-actions and trace viewer components.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Event, WorkflowRunStatus } from '@workflow/world';
|
|
7
|
+
|
|
8
|
+
// Time thresholds for Re-enqueue button visibility
|
|
9
|
+
const STEP_ACTIVITY_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
10
|
+
const STEP_IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Result of analyzing events for a workflow run
|
|
14
|
+
*/
|
|
15
|
+
export interface EventAnalysis {
|
|
16
|
+
/** Whether there are pending sleep/wait calls */
|
|
17
|
+
hasPendingSleeps: boolean;
|
|
18
|
+
/** Whether there are pending steps (started but not completed/failed) */
|
|
19
|
+
hasPendingSteps: boolean;
|
|
20
|
+
/** Whether there are pending hooks (created but not disposed) */
|
|
21
|
+
hasPendingHooks: boolean;
|
|
22
|
+
/** Correlation IDs of pending sleeps */
|
|
23
|
+
pendingSleepIds: string[];
|
|
24
|
+
/** Correlation IDs of pending steps */
|
|
25
|
+
pendingStepIds: string[];
|
|
26
|
+
/** Correlation IDs of pending hooks */
|
|
27
|
+
pendingHookIds: string[];
|
|
28
|
+
/** Timestamp of the last step_started or step_retrying event */
|
|
29
|
+
lastStepActivityAt: Date | null;
|
|
30
|
+
/** Timestamp of the last step completion (step_completed or step_failed) */
|
|
31
|
+
lastStepCompletionAt: Date | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Analyze events to determine pending sleeps, steps, and hooks.
|
|
36
|
+
*/
|
|
37
|
+
export function analyzeEvents(events: Event[] | undefined): EventAnalysis {
|
|
38
|
+
if (!events || events.length === 0) {
|
|
39
|
+
return {
|
|
40
|
+
hasPendingSleeps: false,
|
|
41
|
+
hasPendingSteps: false,
|
|
42
|
+
hasPendingHooks: false,
|
|
43
|
+
pendingSleepIds: [],
|
|
44
|
+
pendingStepIds: [],
|
|
45
|
+
pendingHookIds: [],
|
|
46
|
+
lastStepActivityAt: null,
|
|
47
|
+
lastStepCompletionAt: null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Group events by correlation ID for each type
|
|
52
|
+
const waitCreated = new Map<string, Event>();
|
|
53
|
+
const waitCompleted = new Set<string>();
|
|
54
|
+
const stepStarted = new Map<string, Event>();
|
|
55
|
+
const stepCompleted = new Set<string>();
|
|
56
|
+
const hookCreated = new Map<string, Event>();
|
|
57
|
+
const hookDisposed = new Set<string>();
|
|
58
|
+
|
|
59
|
+
let lastStepActivityAt: Date | null = null;
|
|
60
|
+
let lastStepCompletionAt: Date | null = null;
|
|
61
|
+
|
|
62
|
+
for (const event of events) {
|
|
63
|
+
const correlationId = event.correlationId;
|
|
64
|
+
if (!correlationId) continue;
|
|
65
|
+
|
|
66
|
+
switch (event.eventType) {
|
|
67
|
+
// Sleeps/Waits
|
|
68
|
+
case 'wait_created':
|
|
69
|
+
waitCreated.set(correlationId, event);
|
|
70
|
+
break;
|
|
71
|
+
case 'wait_completed':
|
|
72
|
+
waitCompleted.add(correlationId);
|
|
73
|
+
break;
|
|
74
|
+
|
|
75
|
+
// Steps
|
|
76
|
+
case 'step_started':
|
|
77
|
+
stepStarted.set(correlationId, event);
|
|
78
|
+
if (
|
|
79
|
+
!lastStepActivityAt ||
|
|
80
|
+
new Date(event.createdAt) > lastStepActivityAt
|
|
81
|
+
) {
|
|
82
|
+
lastStepActivityAt = new Date(event.createdAt);
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
case 'step_retrying':
|
|
86
|
+
if (
|
|
87
|
+
!lastStepActivityAt ||
|
|
88
|
+
new Date(event.createdAt) > lastStepActivityAt
|
|
89
|
+
) {
|
|
90
|
+
lastStepActivityAt = new Date(event.createdAt);
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
case 'step_completed':
|
|
94
|
+
case 'step_failed':
|
|
95
|
+
stepCompleted.add(correlationId);
|
|
96
|
+
if (
|
|
97
|
+
!lastStepCompletionAt ||
|
|
98
|
+
new Date(event.createdAt) > lastStepCompletionAt
|
|
99
|
+
) {
|
|
100
|
+
lastStepCompletionAt = new Date(event.createdAt);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
// Hooks
|
|
105
|
+
case 'hook_created':
|
|
106
|
+
hookCreated.set(correlationId, event);
|
|
107
|
+
break;
|
|
108
|
+
case 'hook_disposed':
|
|
109
|
+
hookDisposed.add(correlationId);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Find pending items (created but not completed)
|
|
115
|
+
const pendingSleepIds = Array.from(waitCreated.keys()).filter(
|
|
116
|
+
(id) => !waitCompleted.has(id)
|
|
117
|
+
);
|
|
118
|
+
const pendingStepIds = Array.from(stepStarted.keys()).filter(
|
|
119
|
+
(id) => !stepCompleted.has(id)
|
|
120
|
+
);
|
|
121
|
+
const pendingHookIds = Array.from(hookCreated.keys()).filter(
|
|
122
|
+
(id) => !hookDisposed.has(id)
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
hasPendingSleeps: pendingSleepIds.length > 0,
|
|
127
|
+
hasPendingSteps: pendingStepIds.length > 0,
|
|
128
|
+
hasPendingHooks: pendingHookIds.length > 0,
|
|
129
|
+
pendingSleepIds,
|
|
130
|
+
pendingStepIds,
|
|
131
|
+
pendingHookIds,
|
|
132
|
+
lastStepActivityAt,
|
|
133
|
+
lastStepCompletionAt,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if a workflow run status is terminal (completed, failed, or cancelled)
|
|
139
|
+
*/
|
|
140
|
+
export function isTerminalStatus(
|
|
141
|
+
status: WorkflowRunStatus | undefined
|
|
142
|
+
): boolean {
|
|
143
|
+
return (
|
|
144
|
+
status === 'completed' || status === 'failed' || status === 'cancelled'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Determine if the Re-enqueue button should be shown without the debug flag.
|
|
150
|
+
*
|
|
151
|
+
* The Re-enqueue button is shown when the workflow appears to be stuck:
|
|
152
|
+
* - The workflow is not in a terminal state
|
|
153
|
+
* - There are no pending sleeps (which would show the Wake up button instead)
|
|
154
|
+
* - There are no pending hooks (which are waiting for external input)
|
|
155
|
+
* - Either:
|
|
156
|
+
* - The last step_started or step_retrying event was >30 minutes ago, OR
|
|
157
|
+
* - There have been no pending steps for >5 minutes (all steps completed/failed)
|
|
158
|
+
*/
|
|
159
|
+
export function shouldShowReenqueueButton(
|
|
160
|
+
events: Event[] | undefined,
|
|
161
|
+
status: WorkflowRunStatus | undefined
|
|
162
|
+
): boolean {
|
|
163
|
+
// Never show if in terminal state
|
|
164
|
+
if (isTerminalStatus(status)) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const analysis = analyzeEvents(events);
|
|
169
|
+
|
|
170
|
+
// Don't show if there are pending sleeps (Wake up button handles this)
|
|
171
|
+
if (analysis.hasPendingSleeps) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Don't show if there are pending hooks (waiting for external input)
|
|
176
|
+
if (analysis.hasPendingHooks) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const now = Date.now();
|
|
181
|
+
|
|
182
|
+
// Check if last step activity was >30 minutes ago
|
|
183
|
+
if (analysis.lastStepActivityAt) {
|
|
184
|
+
const timeSinceLastActivity = now - analysis.lastStepActivityAt.getTime();
|
|
185
|
+
if (timeSinceLastActivity > STEP_ACTIVITY_TIMEOUT_MS) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check if there are no pending steps and last completion was >5 minutes ago
|
|
191
|
+
if (!analysis.hasPendingSteps && analysis.lastStepCompletionAt) {
|
|
192
|
+
const timeSinceLastCompletion =
|
|
193
|
+
now - analysis.lastStepCompletionAt.getTime();
|
|
194
|
+
if (timeSinceLastCompletion > STEP_IDLE_TIMEOUT_MS) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// If there's no step activity at all but the run is not terminal,
|
|
200
|
+
// and we've been waiting for a while, show the button
|
|
201
|
+
if (
|
|
202
|
+
!analysis.lastStepActivityAt &&
|
|
203
|
+
!analysis.hasPendingSteps &&
|
|
204
|
+
!analysis.hasPendingSleeps &&
|
|
205
|
+
!analysis.hasPendingHooks
|
|
206
|
+
) {
|
|
207
|
+
// This case handles runs that haven't started any steps yet
|
|
208
|
+
// but aren't in a terminal state - they might be stuck
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Check if there are pending steps from an events list.
|
|
217
|
+
*/
|
|
218
|
+
export function hasPendingStepsFromEvents(
|
|
219
|
+
events: Event[] | undefined
|
|
220
|
+
): boolean {
|
|
221
|
+
return analyzeEvents(events).hasPendingSteps;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Check if there are pending hooks from an events list.
|
|
226
|
+
*/
|
|
227
|
+
export function hasPendingHooksFromEvents(
|
|
228
|
+
events: Event[] | undefined
|
|
229
|
+
): boolean {
|
|
230
|
+
return analyzeEvents(events).hasPendingHooks;
|
|
231
|
+
}
|