@acmekit/dashboard 2.13.35 → 2.13.36

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.
Files changed (39) hide show
  1. package/package.json +9 -9
  2. package/src/components/layout/main-layout/main-layout.tsx +1 -28
  3. package/src/dashboard-app/routes/get-route.map.tsx +0 -71
  4. package/src/hooks/api/workflow-executions.tsx +1 -145
  5. package/src/i18n/translations/$schema.json +4 -534
  6. package/src/i18n/translations/en.json +3 -132
  7. package/src/routes/workflow-executions/constants.ts +0 -16
  8. package/src/routes/workflow-executions/utils.ts +14 -170
  9. package/src/routes/workflow-executions/workflow-execution-detail/breadcrumb.tsx +1 -7
  10. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-history-section/workflow-execution-history-section.tsx +6 -157
  11. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-payload-section/workflow-execution-payload-section.tsx +6 -122
  12. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-timeline-section/workflow-execution-timeline-section.tsx +1 -7
  13. package/src/routes/workflow-executions/workflow-execution-detail/workflow-detail.tsx +1 -46
  14. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-columns.tsx +0 -7
  15. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-filters.tsx +1 -7
  16. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-query.tsx +2 -4
  17. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-list-table.tsx +1 -17
  18. package/src/routes/workflow-executions/workflow-execution-list/workflow-execution-list.tsx +1 -1
  19. package/src/hooks/api/workflow-definitions.tsx +0 -79
  20. package/src/hooks/api/workflow-metrics.tsx +0 -48
  21. package/src/hooks/use-workflow-sse.tsx +0 -78
  22. package/src/routes/workflow-analytics/workflow-analytics.tsx +0 -167
  23. package/src/routes/workflow-definitions/workflow-definition-detail/workflow-definition-detail.tsx +0 -98
  24. package/src/routes/workflow-definitions/workflow-definition-list/components/workflow-definition-list-table/use-workflow-definition-table-columns.tsx +0 -78
  25. package/src/routes/workflow-definitions/workflow-definition-list/components/workflow-definition-list-table/workflow-definition-list-table.tsx +0 -65
  26. package/src/routes/workflow-definitions/workflow-definition-list/workflow-definition-list.tsx +0 -15
  27. package/src/routes/workflow-executions/workflow-execution-complete-step/workflow-execution-complete-step.tsx +0 -270
  28. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-action-bar/index.ts +0 -1
  29. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-action-bar/workflow-execution-action-bar.tsx +0 -212
  30. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-error-card/index.ts +0 -1
  31. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-error-card/workflow-execution-error-card.tsx +0 -59
  32. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-waiting-banner/index.ts +0 -1
  33. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-waiting-banner/workflow-execution-waiting-banner.tsx +0 -63
  34. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-auto-refresh.tsx +0 -73
  35. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-row-actions.tsx +0 -116
  36. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-saved-views.tsx +0 -84
  37. package/src/routes/workflow-executions/workflow-execution-rerun/workflow-execution-rerun.tsx +0 -159
  38. package/src/routes/workflow-executions/workflow-execution-run/workflow-execution-run.tsx +0 -139
  39. package/src/routes/workflow-scheduled/workflow-scheduled-list.tsx +0 -269
@@ -1,140 +1,24 @@
1
1
  import { HttpTypes } from "@acmekit/types"
2
- import { Button, Container, Heading, Tabs, toast } from "@acmekit/ui"
3
- import { useState } from "react"
4
- import { useTranslation } from "react-i18next"
5
2
  import { JsonViewSection } from "../../../../../components/common/json-view-section"
6
3
 
7
4
  type WorkflowExecutionPayloadSectionProps = {
8
5
  execution: HttpTypes.AdminWorkflowExecution
9
6
  }
10
7
 
11
- function downloadJson(data: unknown, filename: string) {
12
- const blob = new Blob([JSON.stringify(data, null, 2)], {
13
- type: "application/json",
14
- })
15
- const url = URL.createObjectURL(blob)
16
- const a = document.createElement("a")
17
- a.href = url
18
- a.download = filename
19
- a.click()
20
- URL.revokeObjectURL(url)
21
- }
22
-
23
8
  export const WorkflowExecutionPayloadSection = ({
24
9
  execution,
25
10
  }: WorkflowExecutionPayloadSectionProps) => {
26
- const { t } = useTranslation()
27
-
28
11
  let payload = execution.context?.data?.payload
29
- if (payload && typeof payload !== "object") {
30
- payload = { input: payload }
31
- }
32
-
33
- const checkpoint = execution.context || {}
34
- const errors = (execution as any).context?.errors || []
35
-
36
- const stepOutputs: Record<string, unknown> = {}
37
- const steps = execution?.execution?.steps || {}
38
- for (const [stepId, step] of Object.entries(steps)) {
39
- if (stepId === "_root") continue
40
- const output = (step as any)?.invoke?.output
41
- if (output !== undefined) {
42
- stepOutputs[stepId] = output
43
- }
44
- }
45
-
46
- const hasPayload = !!payload
47
- const hasStepOutputs = Object.keys(stepOutputs).length > 0
48
- const hasErrors = errors.length > 0
49
12
 
50
- const defaultTab = hasPayload ? "input" : "checkpoint"
51
- const [activeTab, setActiveTab] = useState(defaultTab)
52
-
53
- if (!hasPayload && !hasStepOutputs && !hasErrors) {
13
+ if (!payload) {
54
14
  return null
55
15
  }
56
16
 
57
- const activeData =
58
- activeTab === "input"
59
- ? payload
60
- : activeTab === "checkpoint"
61
- ? checkpoint
62
- : activeTab === "outputs"
63
- ? stepOutputs
64
- : errors
65
-
66
- const handleCopyAll = () => {
67
- navigator.clipboard.writeText(JSON.stringify(activeData, null, 2))
68
- toast.success("Copied to clipboard")
69
- }
70
-
71
- const handleDownload = () => {
72
- downloadJson(
73
- activeData,
74
- `${execution.workflow_id}-${activeTab}.json`
75
- )
17
+ // payloads may be a primitive, so we need to wrap them in an object
18
+ // to ensure the JsonViewSection component can render them.
19
+ if (typeof payload !== "object") {
20
+ payload = { input: payload }
76
21
  }
77
22
 
78
- return (
79
- <Container className="divide-y p-0">
80
- <div className="flex items-center justify-between px-6 py-4">
81
- <Heading level="h2">
82
- {t("workflowExecutions.payload.inputPayload")}
83
- </Heading>
84
- <div className="flex items-center gap-x-2">
85
- <Button size="small" variant="transparent" onClick={handleCopyAll}>
86
- {t("workflowExecutions.payload.copyAll")}
87
- </Button>
88
- <Button size="small" variant="transparent" onClick={handleDownload}>
89
- {t("workflowExecutions.payload.downloadJson")}
90
- </Button>
91
- </div>
92
- </div>
93
- <Tabs
94
- value={activeTab}
95
- onValueChange={setActiveTab}
96
- >
97
- <div className="border-ui-border-base border-b px-6">
98
- <Tabs.List>
99
- {hasPayload && (
100
- <Tabs.Trigger value="input">
101
- {t("workflowExecutions.payload.inputPayload")}
102
- </Tabs.Trigger>
103
- )}
104
- <Tabs.Trigger value="checkpoint">
105
- {t("workflowExecutions.payload.fullCheckpoint")}
106
- </Tabs.Trigger>
107
- {hasStepOutputs && (
108
- <Tabs.Trigger value="outputs">
109
- {t("workflowExecutions.payload.stepOutputs")}
110
- </Tabs.Trigger>
111
- )}
112
- {hasErrors && (
113
- <Tabs.Trigger value="errors">
114
- {t("workflowExecutions.payload.errors")}
115
- </Tabs.Trigger>
116
- )}
117
- </Tabs.List>
118
- </div>
119
- {hasPayload && (
120
- <Tabs.Content value="input" className="p-0">
121
- <JsonViewSection data={payload as object} />
122
- </Tabs.Content>
123
- )}
124
- <Tabs.Content value="checkpoint" className="p-0">
125
- <JsonViewSection data={checkpoint as object} />
126
- </Tabs.Content>
127
- {hasStepOutputs && (
128
- <Tabs.Content value="outputs" className="p-0">
129
- <JsonViewSection data={stepOutputs} />
130
- </Tabs.Content>
131
- )}
132
- {hasErrors && (
133
- <Tabs.Content value="errors" className="p-0">
134
- <JsonViewSection data={errors as object} />
135
- </Tabs.Content>
136
- )}
137
- </Tabs>
138
- </Container>
139
- )
23
+ return <JsonViewSection data={payload as object} />
140
24
  }
@@ -18,7 +18,6 @@ import {
18
18
  STEP_OK_STATES,
19
19
  STEP_SKIPPED_STATES,
20
20
  } from "../../../constants"
21
- import { TransactionStepState } from "../../../types"
22
21
  import { useDocumentDirection } from "../../../../../hooks/use-document-direction"
23
22
 
24
23
  type WorkflowExecutionTimelineSectionProps = {
@@ -399,8 +398,6 @@ const Node = ({ step }: { step: HttpTypes.AdminWorkflowExecutionStep }) => {
399
398
  }, 100)
400
399
  }
401
400
 
402
- const isInvoking = step.invoke.state === TransactionStepState.INVOKING
403
-
404
401
  return (
405
402
  <Link
406
403
  to={`#${stepId}`}
@@ -408,10 +405,7 @@ const Node = ({ step }: { step: HttpTypes.AdminWorkflowExecutionStep }) => {
408
405
  className="focus-visible:shadow-borders-focus transition-fg rounded-md outline-none"
409
406
  >
410
407
  <div
411
- className={clx(
412
- "bg-ui-bg-base shadow-borders-base flex min-w-[120px] items-center gap-x-0.5 rounded-md p-0.5",
413
- { "animate-pulse": isInvoking }
414
- )}
408
+ className="bg-ui-bg-base shadow-borders-base flex min-w-[120px] items-center gap-x-0.5 rounded-md p-0.5"
415
409
  data-step-id={step.id}
416
410
  >
417
411
  <div className="flex size-5 items-center justify-center">
@@ -1,20 +1,9 @@
1
1
  import { useParams } from "react-router-dom"
2
- import { useQueryClient } from "@tanstack/react-query"
3
2
 
4
3
  import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
5
4
  import { SingleColumnPage } from "../../../components/layout/pages"
6
- import {
7
- useWorkflowExecution,
8
- workflowExecutionsQueryKeys,
9
- } from "../../../hooks/api/workflow-executions"
5
+ import { useWorkflowExecution } from "../../../hooks/api/workflow-executions"
10
6
  import { useExtension } from "../../../providers/extension-provider"
11
- import { useWorkflowSSE } from "../../../hooks/use-workflow-sse"
12
- import { TRANSACTION_ACTIVE_STATES } from "../constants"
13
- import { TransactionState } from "../types"
14
- import { isTerminalState, mergeStepEvent } from "../utils"
15
- import { WorkflowExecutionActionBar } from "./components/workflow-execution-action-bar"
16
- import { WorkflowExecutionErrorCard } from "./components/workflow-execution-error-card"
17
- import { WorkflowExecutionWaitingBanner } from "./components/workflow-execution-waiting-banner"
18
7
  import { WorkflowExecutionGeneralSection } from "./components/workflow-execution-general-section"
19
8
  import { WorkflowExecutionHistorySection } from "./components/workflow-execution-history-section"
20
9
  import { WorkflowExecutionPayloadSection } from "./components/workflow-execution-payload-section"
@@ -22,42 +11,12 @@ import { WorkflowExecutionTimelineSection } from "./components/workflow-executio
22
11
 
23
12
  export const ExecutionDetail = () => {
24
13
  const { id } = useParams()
25
- const queryClient = useQueryClient()
26
14
 
27
15
  const { workflow_execution, isLoading, isError, error } =
28
16
  useWorkflowExecution(id!)
29
17
 
30
18
  const { getWidgets } = useExtension()
31
19
 
32
- const isActive = workflow_execution
33
- ? TRANSACTION_ACTIVE_STATES.includes(
34
- workflow_execution.state as TransactionState
35
- )
36
- : false
37
-
38
- useWorkflowSSE(workflow_execution?.workflow_id || "", {
39
- enabled: isActive && !!workflow_execution?.workflow_id,
40
- onEvent: (event) => {
41
- queryClient.setQueryData(
42
- workflowExecutionsQueryKeys.detail(id!),
43
- (current: any) => {
44
- if (!current) return current
45
- return mergeStepEvent(current, event)
46
- }
47
- )
48
-
49
- if (
50
- event.event_type === "onFinish" ||
51
- (event.response?.state &&
52
- isTerminalState(event.response.state as TransactionState))
53
- ) {
54
- queryClient.invalidateQueries({
55
- queryKey: workflowExecutionsQueryKeys.detail(id!),
56
- })
57
- }
58
- },
59
- })
60
-
61
20
  if (isLoading || !workflow_execution) {
62
21
  return <SingleColumnPageSkeleton sections={4} showJSON />
63
22
  }
@@ -74,11 +33,7 @@ export const ExecutionDetail = () => {
74
33
  }}
75
34
  data={workflow_execution}
76
35
  showJSON
77
- hasOutlet
78
36
  >
79
- <WorkflowExecutionActionBar execution={workflow_execution} />
80
- <WorkflowExecutionErrorCard execution={workflow_execution} />
81
- <WorkflowExecutionWaitingBanner execution={workflow_execution} />
82
37
  <WorkflowExecutionGeneralSection execution={workflow_execution} />
83
38
  <WorkflowExecutionTimelineSection execution={workflow_execution} />
84
39
  <WorkflowExecutionPayloadSection execution={workflow_execution} />
@@ -7,7 +7,6 @@ import { TransactionStepState } from "../../../types"
7
7
  import { getTransactionState, getTransactionStateColor } from "../../../utils"
8
8
  import { HttpTypes } from "@acmekit/types"
9
9
  import { DataTableStatusCell } from "../../../../../components/data-table/components/data-table-status-cell/data-table-status-cell"
10
- import { WorkflowExecutionRowActions } from "./workflow-execution-row-actions"
11
10
 
12
11
  const columnHelper =
13
12
  createColumnHelper<
@@ -187,12 +186,6 @@ export const useWorkflowExecutionTableColumns = (): ColumnDef<
187
186
  return <DateCell date={date} />
188
187
  },
189
188
  }),
190
- columnHelper.display({
191
- id: "actions",
192
- cell: ({ row }) => (
193
- <WorkflowExecutionRowActions execution={row.original} />
194
- ),
195
- }),
196
189
  ],
197
190
  [t]
198
191
  )
@@ -52,12 +52,6 @@ export const useWorkflowExecutionTableFilters = (): Filter[] => {
52
52
  { key: "updated_at", label: t("fields.updatedAt"), type: "date" },
53
53
  ]
54
54
 
55
- const workflowIdFilter: Filter = {
56
- key: "workflow_id",
57
- label: t("workflowExecutions.workflowIdLabel"),
58
- type: "string",
59
- }
60
-
61
- return [stateFilter, workflowIdFilter, ...dateFilters]
55
+ return [stateFilter, ...dateFilters]
62
56
  }
63
57
 
@@ -9,19 +9,17 @@ export const useWorkflowExecutionTableQuery = ({
9
9
  prefix?: string
10
10
  }) => {
11
11
  const raw = useQueryParams(
12
- ["q", "offset", "order", "state", "workflow_id", "created_at", "updated_at"],
12
+ ["q", "offset", "order", "state", "created_at", "updated_at"],
13
13
  prefix
14
14
  )
15
15
 
16
- const { offset, order, state, workflow_id, created_at, updated_at, ...rest } =
17
- raw
16
+ const { offset, order, state, created_at, updated_at, ...rest } = raw
18
17
 
19
18
  const searchParams: HttpTypes.AdminGetWorkflowExecutionsParams = {
20
19
  limit: pageSize,
21
20
  offset: offset ? parseInt(offset) : 0,
22
21
  order: order ?? "-created_at",
23
22
  state: state ? state.split(",") : undefined,
24
- workflow_id: workflow_id ? workflow_id.split(",") : undefined,
25
23
  created_at: created_at ? JSON.parse(created_at) : undefined,
26
24
  updated_at: updated_at ? JSON.parse(updated_at) : undefined,
27
25
  ...rest,
@@ -1,13 +1,9 @@
1
- import { PlayMiniSolid } from "@acmekit/icons"
2
- import { Button, Container, Heading, Text } from "@acmekit/ui"
1
+ import { Container, Heading, Text } from "@acmekit/ui"
3
2
  import { keepPreviousData } from "@tanstack/react-query"
4
3
  import { useTranslation } from "react-i18next"
5
- import { Link } from "react-router-dom"
6
4
  import { _DataTable } from "../../../../../components/table/data-table"
7
5
  import { useWorkflowExecutions } from "../../../../../hooks/api/workflow-executions"
8
6
  import { useDataTable } from "../../../../../hooks/use-data-table"
9
- import { WorkflowExecutionAutoRefresh } from "./workflow-execution-auto-refresh"
10
- import { WorkflowExecutionSavedViews } from "./workflow-execution-saved-views"
11
7
  import { useWorkflowExecutionTableColumns } from "./use-workflow-execution-table-columns"
12
8
  import { useWorkflowExecutionTableFilters } from "./use-workflow-execution-table-filters"
13
9
  import { useWorkflowExecutionTableQuery } from "./use-workflow-execution-table-query"
@@ -56,18 +52,6 @@ export const WorkflowExecutionListTable = () => {
56
52
  {t(`workflowExecutions.subtitle`)}
57
53
  </Text>
58
54
  </div>
59
- <div className="flex items-center gap-x-2">
60
- <WorkflowExecutionAutoRefresh />
61
- <Button variant="secondary" size="small" asChild>
62
- <Link to="run">
63
- <PlayMiniSolid className="mr-1" />
64
- {t("workflowExecutions.actions.runWorkflow")}
65
- </Link>
66
- </Button>
67
- </div>
68
- </div>
69
- <div className="border-ui-border-base border-b px-6 py-2">
70
- <WorkflowExecutionSavedViews />
71
55
  </div>
72
56
  <_DataTable
73
57
  table={table}
@@ -12,7 +12,7 @@ export const WorkflowExcecutionList = () => {
12
12
  after: getWidgets("workflow.list.after"),
13
13
  before: getWidgets("workflow.list.before"),
14
14
  }}
15
- hasOutlet
15
+ hasOutlet={false}
16
16
  >
17
17
  <WorkflowExecutionListTable />
18
18
  </SingleColumnPage>
@@ -1,79 +0,0 @@
1
- import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
2
- import { sdk } from "../../lib/client"
3
- import { queryKeysFactory } from "../../lib/query-key-factory"
4
- import { FetchError } from "@acmekit/js-sdk"
5
-
6
- export type WorkflowDefinitionResponse = {
7
- workflow_definitions: Array<{
8
- id: string
9
- handler_count: number
10
- steps: Array<{
11
- action: string
12
- depth: number
13
- noCompensation: boolean
14
- }>
15
- options: Record<string, unknown>
16
- }>
17
- count: number
18
- }
19
-
20
- export type WorkflowDefinitionDetailResponse = {
21
- workflow_definition: {
22
- id: string
23
- handler_count: number
24
- steps: Array<{
25
- action: string
26
- depth: number
27
- noCompensation: boolean
28
- }>
29
- flow: Record<string, unknown> | null
30
- options: Record<string, unknown>
31
- }
32
- }
33
-
34
- const WORKFLOW_DEFINITIONS_QUERY_KEY = "workflow_definitions" as const
35
- export const workflowDefinitionsQueryKeys = queryKeysFactory(
36
- WORKFLOW_DEFINITIONS_QUERY_KEY
37
- )
38
-
39
- export const useWorkflowDefinitions = (
40
- query?: Record<string, unknown>,
41
- options?: Omit<
42
- UseQueryOptions<
43
- WorkflowDefinitionResponse,
44
- FetchError,
45
- WorkflowDefinitionResponse,
46
- QueryKey
47
- >,
48
- "queryKey" | "queryFn"
49
- >
50
- ) => {
51
- const { data, ...rest } = useQuery({
52
- queryFn: () => sdk.admin.workflowDefinition.list(query),
53
- queryKey: workflowDefinitionsQueryKeys.list(query),
54
- ...options,
55
- })
56
-
57
- return { ...data, ...rest }
58
- }
59
-
60
- export const useWorkflowDefinition = (
61
- id: string,
62
- options?: Omit<
63
- UseQueryOptions<
64
- WorkflowDefinitionDetailResponse,
65
- FetchError,
66
- WorkflowDefinitionDetailResponse,
67
- QueryKey
68
- >,
69
- "queryKey" | "queryFn"
70
- >
71
- ) => {
72
- const { data, ...rest } = useQuery({
73
- queryFn: () => sdk.admin.workflowDefinition.retrieve(id),
74
- queryKey: workflowDefinitionsQueryKeys.detail(id),
75
- ...options,
76
- })
77
-
78
- return { ...data, ...rest }
79
- }
@@ -1,48 +0,0 @@
1
- import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
2
- import { sdk } from "../../lib/client"
3
- import { queryKeysFactory } from "../../lib/query-key-factory"
4
- import { FetchError } from "@acmekit/js-sdk"
5
-
6
- export type WorkflowMetricsResponse = {
7
- total_24h: number
8
- success_rate_24h: number
9
- avg_duration_ms_24h: number
10
- running_now: number
11
- per_workflow: Array<{
12
- workflow_id: string
13
- runs: number
14
- success_rate: number
15
- avg_duration_ms: number
16
- }>
17
- }
18
-
19
- const WORKFLOW_METRICS_QUERY_KEY = "workflow_metrics" as const
20
- export const workflowMetricsQueryKeys = queryKeysFactory(
21
- WORKFLOW_METRICS_QUERY_KEY
22
- )
23
-
24
- export const useWorkflowMetrics = (
25
- options?: Omit<
26
- UseQueryOptions<
27
- WorkflowMetricsResponse,
28
- FetchError,
29
- WorkflowMetricsResponse,
30
- QueryKey
31
- >,
32
- "queryKey" | "queryFn"
33
- >
34
- ) => {
35
- const { data, ...rest } = useQuery({
36
- queryFn: async () => {
37
- const result = await sdk.client.fetch<WorkflowMetricsResponse>(
38
- "/admin/workflows-executions/metrics"
39
- )
40
- return result as unknown as WorkflowMetricsResponse
41
- },
42
- queryKey: workflowMetricsQueryKeys.all,
43
- refetchInterval: 30000,
44
- ...options,
45
- })
46
-
47
- return { ...data, ...rest }
48
- }
@@ -1,78 +0,0 @@
1
- import { useEffect, useRef, useState } from "react"
2
- import { backendUrl } from "../lib/client"
3
-
4
- export interface WorkflowSSEEvent {
5
- event_type: string
6
- workflow_id: string
7
- transaction_id: string
8
- step?: Record<string, unknown>
9
- response?: Record<string, unknown>
10
- result?: Record<string, unknown>
11
- errors?: Array<Record<string, unknown>>
12
- }
13
-
14
- interface UseWorkflowSSEOptions {
15
- enabled?: boolean
16
- onEvent?: (event: WorkflowSSEEvent) => void
17
- }
18
-
19
- export const useWorkflowSSE = (
20
- workflowId: string | undefined,
21
- options?: UseWorkflowSSEOptions
22
- ) => {
23
- const { enabled = true, onEvent } = options || {}
24
- const [isConnected, setIsConnected] = useState(false)
25
- const eventSourceRef = useRef<EventSource | null>(null)
26
- const onEventRef = useRef(onEvent)
27
- onEventRef.current = onEvent
28
-
29
- useEffect(() => {
30
- if (!enabled || !workflowId) {
31
- if (eventSourceRef.current) {
32
- eventSourceRef.current.close()
33
- eventSourceRef.current = null
34
- setIsConnected(false)
35
- }
36
- return
37
- }
38
-
39
- const base = backendUrl === "/" ? "" : backendUrl
40
- const url = `${base}/admin/workflows-executions/${workflowId}/subscribe`
41
-
42
- const es = new EventSource(url, { withCredentials: true })
43
- eventSourceRef.current = es
44
-
45
- es.onopen = () => {
46
- setIsConnected(true)
47
- }
48
-
49
- es.onmessage = (e) => {
50
- try {
51
- const data = JSON.parse(e.data) as WorkflowSSEEvent
52
- onEventRef.current?.(data)
53
- } catch {
54
- // ignore parse errors
55
- }
56
- }
57
-
58
- es.onerror = () => {
59
- setIsConnected(false)
60
- }
61
-
62
- return () => {
63
- es.close()
64
- eventSourceRef.current = null
65
- setIsConnected(false)
66
- }
67
- }, [enabled, workflowId])
68
-
69
- const disconnect = () => {
70
- if (eventSourceRef.current) {
71
- eventSourceRef.current.close()
72
- eventSourceRef.current = null
73
- setIsConnected(false)
74
- }
75
- }
76
-
77
- return { isConnected, disconnect }
78
- }