@acmekit/dashboard 2.13.33 → 2.13.35

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 (87) hide show
  1. package/dist/{api-key-management-create-4AG76FJV.mjs → api-key-management-create-U37VC624.mjs} +3 -3
  2. package/dist/{api-key-management-detail-T2TB4KST.mjs → api-key-management-detail-ZYKL4ATI.mjs} +10 -10
  3. package/dist/{api-key-management-edit-R44OHS7B.mjs → api-key-management-edit-TSZGMIBL.mjs} +3 -3
  4. package/dist/{api-key-management-list-QK4Q7Y5I.mjs → api-key-management-list-HCJFJWWB.mjs} +3 -3
  5. package/dist/app.css +31 -0
  6. package/dist/app.js +3726 -1386
  7. package/dist/app.mjs +240 -38
  8. package/dist/{chunk-GBFVWROS.mjs → chunk-5IEHCYJO.mjs} +1 -1
  9. package/dist/{chunk-DQCEH3X2.mjs → chunk-7F3CWXUH.mjs} +1 -1
  10. package/dist/chunk-A7ULKHDE.mjs +126 -0
  11. package/dist/{chunk-DN3MIYQH.mjs → chunk-FKTMBR44.mjs} +1 -1
  12. package/dist/chunk-GBPAZAJK.mjs +34 -0
  13. package/dist/{chunk-YRWSG3YM.mjs → chunk-HHPPTD3B.mjs} +1 -1
  14. package/dist/chunk-LP6CPB7N.mjs +213 -0
  15. package/dist/{chunk-EFRMWHRX.mjs → chunk-PFZQYK7R.mjs} +1 -1
  16. package/dist/{chunk-XIM7X4FB.mjs → chunk-SYACY6AL.mjs} +1 -1
  17. package/dist/{chunk-2U3RK3JG.mjs → chunk-VEI6HW6L.mjs} +3 -5
  18. package/dist/{chunk-ST2YB7JN.mjs → chunk-WLRJXEKL.mjs} +1 -1
  19. package/dist/{chunk-ULSPL3DR.mjs → chunk-XIP35KXF.mjs} +1 -1
  20. package/dist/{chunk-DTY37DDZ.mjs → chunk-YKIWIMJX.mjs} +1 -0
  21. package/dist/en.json +132 -3
  22. package/dist/{invite-XGPZZBUP.mjs → invite-3JSNOA2B.mjs} +3 -3
  23. package/dist/{login-GNP3QIPI.mjs → login-BEJ5EFGE.mjs} +9 -9
  24. package/dist/{profile-detail-YX27F7N6.mjs → profile-detail-QVTJC4JC.mjs} +3 -3
  25. package/dist/{profile-edit-2VRDU75O.mjs → profile-edit-MIO62TWH.mjs} +3 -3
  26. package/dist/{reset-password-TWRNZO6Z.mjs → reset-password-BN4KAJQL.mjs} +2 -2
  27. package/dist/{settings-3XWLL5LG.mjs → settings-GH5IWXHE.mjs} +3 -3
  28. package/dist/{translation-list-CCEQJNED.mjs → translation-list-JA22BUKN.mjs} +10 -10
  29. package/dist/{translations-edit-E57GVUFV.mjs → translations-edit-STTMANVT.mjs} +11 -11
  30. package/dist/{user-detail-KUSRRVNX.mjs → user-detail-WCXBFRGS.mjs} +3 -3
  31. package/dist/{user-edit-HTN3ZGCL.mjs → user-edit-XDVMJOS4.mjs} +3 -3
  32. package/dist/{user-invite-E3FAAU3V.mjs → user-invite-73ZDSDFC.mjs} +3 -3
  33. package/dist/{user-list-KNJ5S3IM.mjs → user-list-MPJXE3CA.mjs} +5 -5
  34. package/dist/{user-metadata-5GQK75DT.mjs → user-metadata-ADNTL3LT.mjs} +10 -10
  35. package/dist/workflow-analytics-4WCI4ODQ.mjs +152 -0
  36. package/dist/workflow-definition-detail-GI6CFBMG.mjs +94 -0
  37. package/dist/workflow-definition-list-GF3XAEPS.mjs +142 -0
  38. package/dist/workflow-execution-complete-step-WSRLO572.mjs +245 -0
  39. package/dist/workflow-execution-detail-3RH6EQSS.mjs +1411 -0
  40. package/dist/workflow-execution-list-AQEGAME4.mjs +596 -0
  41. package/dist/workflow-execution-rerun-WCYLYL3Q.mjs +138 -0
  42. package/dist/workflow-execution-run-MWN5KWNY.mjs +135 -0
  43. package/dist/workflow-scheduled-list-ZPXR7CZM.mjs +174 -0
  44. package/package.json +9 -9
  45. package/src/components/layout/main-layout/main-layout.tsx +28 -1
  46. package/src/dashboard-app/routes/get-route.map.tsx +71 -0
  47. package/src/hooks/api/workflow-definitions.tsx +79 -0
  48. package/src/hooks/api/workflow-executions.tsx +145 -1
  49. package/src/hooks/api/workflow-metrics.tsx +48 -0
  50. package/src/hooks/use-workflow-sse.tsx +78 -0
  51. package/src/i18n/translations/$schema.json +534 -4
  52. package/src/i18n/translations/en.json +132 -3
  53. package/src/routes/workflow-analytics/workflow-analytics.tsx +167 -0
  54. package/src/routes/workflow-definitions/workflow-definition-detail/workflow-definition-detail.tsx +98 -0
  55. package/src/routes/workflow-definitions/workflow-definition-list/components/workflow-definition-list-table/use-workflow-definition-table-columns.tsx +78 -0
  56. package/src/routes/workflow-definitions/workflow-definition-list/components/workflow-definition-list-table/workflow-definition-list-table.tsx +65 -0
  57. package/src/routes/workflow-definitions/workflow-definition-list/workflow-definition-list.tsx +15 -0
  58. package/src/routes/workflow-executions/constants.ts +16 -0
  59. package/src/routes/workflow-executions/utils.ts +170 -14
  60. package/src/routes/workflow-executions/workflow-execution-complete-step/workflow-execution-complete-step.tsx +270 -0
  61. package/src/routes/workflow-executions/workflow-execution-detail/breadcrumb.tsx +7 -1
  62. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-action-bar/index.ts +1 -0
  63. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-action-bar/workflow-execution-action-bar.tsx +212 -0
  64. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-error-card/index.ts +1 -0
  65. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-error-card/workflow-execution-error-card.tsx +59 -0
  66. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-history-section/workflow-execution-history-section.tsx +157 -6
  67. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-payload-section/workflow-execution-payload-section.tsx +122 -6
  68. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-timeline-section/workflow-execution-timeline-section.tsx +7 -1
  69. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-waiting-banner/index.ts +1 -0
  70. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-waiting-banner/workflow-execution-waiting-banner.tsx +63 -0
  71. package/src/routes/workflow-executions/workflow-execution-detail/workflow-detail.tsx +46 -1
  72. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-columns.tsx +7 -0
  73. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-filters.tsx +7 -1
  74. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-query.tsx +4 -2
  75. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-auto-refresh.tsx +73 -0
  76. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-list-table.tsx +17 -1
  77. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-row-actions.tsx +116 -0
  78. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-saved-views.tsx +84 -0
  79. package/src/routes/workflow-executions/workflow-execution-list/workflow-execution-list.tsx +1 -1
  80. package/src/routes/workflow-executions/workflow-execution-rerun/workflow-execution-rerun.tsx +159 -0
  81. package/src/routes/workflow-executions/workflow-execution-run/workflow-execution-run.tsx +139 -0
  82. package/src/routes/workflow-scheduled/workflow-scheduled-list.tsx +269 -0
  83. package/dist/chunk-LKWTBYYC.mjs +0 -35
  84. package/dist/chunk-RPAL6FHW.mjs +0 -73
  85. package/dist/workflow-execution-detail-5O5VCXL3.mjs +0 -870
  86. package/dist/workflow-execution-list-DETG4MRT.mjs +0 -347
  87. /package/dist/{chunk-22YYMH6M.mjs → chunk-RISX76YT.mjs} +0 -0
@@ -4,10 +4,12 @@ import {
4
4
  STEP_ERROR_STATES,
5
5
  STEP_INACTIVE_STATES,
6
6
  STEP_IN_PROGRESS_STATES,
7
- TRANSACTION_ERROR_STATES,
8
- TRANSACTION_IN_PROGRESS_STATES,
9
7
  } from "./constants"
10
- import { TransactionState, TransactionStepState } from "./types"
8
+ import {
9
+ TransactionState,
10
+ TransactionStepState,
11
+ TransactionStepStatus,
12
+ } from "./types"
11
13
 
12
14
  export const adminExecutionKey = {
13
15
  detail: (id: string) => ["workflow_executions", "detail", id],
@@ -20,18 +22,23 @@ export const adminExecutionKey = {
20
22
 
21
23
  export const getTransactionStateColor = (
22
24
  state: TransactionState
23
- ): "green" | "orange" | "red" => {
24
- let statusColor: "green" | "red" | "orange" = "green"
25
-
26
- if (TRANSACTION_ERROR_STATES.includes(state)) {
27
- statusColor = "red"
28
- }
29
-
30
- if (TRANSACTION_IN_PROGRESS_STATES.includes(state)) {
31
- statusColor = "orange"
25
+ ): "green" | "orange" | "red" | "blue" | "purple" | "grey" => {
26
+ switch (state) {
27
+ case TransactionState.DONE:
28
+ return "green"
29
+ case TransactionState.FAILED:
30
+ case TransactionState.REVERTED:
31
+ return "red"
32
+ case TransactionState.INVOKING:
33
+ return "blue"
34
+ case TransactionState.COMPENSATING:
35
+ case TransactionState.WAITING_TO_COMPENSATE:
36
+ return "orange"
37
+ case TransactionState.NOT_STARTED:
38
+ return "grey"
39
+ default:
40
+ return "grey"
32
41
  }
33
-
34
- return statusColor
35
42
  }
36
43
 
37
44
  export const getTransactionState = (
@@ -101,3 +108,152 @@ export const getStepState = (
101
108
  return t("workflowExecutions.step.state.timeout")
102
109
  }
103
110
  }
111
+
112
+ export const isTerminalState = (state: TransactionState): boolean => {
113
+ return [
114
+ TransactionState.DONE,
115
+ TransactionState.FAILED,
116
+ TransactionState.REVERTED,
117
+ ].includes(state)
118
+ }
119
+
120
+ export const getFailedSteps = (
121
+ execution: HttpTypes.AdminWorkflowExecution
122
+ ): Array<{ stepId: string; error?: any }> => {
123
+ const steps = execution?.execution?.steps || {}
124
+ const errors = execution?.context?.errors || []
125
+ const failed: Array<{ stepId: string; error?: any }> = []
126
+
127
+ for (const [stepId, step] of Object.entries(steps)) {
128
+ if (stepId === "_root") continue
129
+ const invokeStatus = (step as any)?.invoke?.status
130
+ if (invokeStatus === TransactionStepStatus.PERMANENT_FAILURE) {
131
+ const stepError = errors.find((e: any) => e.action === stepId)
132
+ failed.push({ stepId, error: stepError })
133
+ }
134
+ }
135
+
136
+ return failed
137
+ }
138
+
139
+ export const getWaitingSteps = (
140
+ execution: HttpTypes.AdminWorkflowExecution
141
+ ): Array<{ stepId: string }> => {
142
+ const steps = execution?.execution?.steps || {}
143
+ const waiting: Array<{ stepId: string }> = []
144
+
145
+ for (const [stepId, step] of Object.entries(steps)) {
146
+ if (stepId === "_root") continue
147
+ const invokeStatus = (step as any)?.invoke?.status
148
+ if (invokeStatus === TransactionStepStatus.WAITING) {
149
+ waiting.push({ stepId })
150
+ }
151
+ }
152
+
153
+ return waiting
154
+ }
155
+
156
+ export const formatStepDuration = (
157
+ startedAt?: number,
158
+ endedAt?: number
159
+ ): string => {
160
+ if (!startedAt || !endedAt) return ""
161
+
162
+ const ms = endedAt - startedAt
163
+ if (ms < 1000) return `${ms}ms`
164
+ if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`
165
+ const minutes = Math.floor(ms / 60_000)
166
+ const seconds = Math.round((ms % 60_000) / 1000)
167
+ return `${minutes}m ${seconds}s`
168
+ }
169
+
170
+ export const computeIdempotencyKey = (
171
+ workflowId: string,
172
+ transactionId: string,
173
+ stepId: string,
174
+ action: "invoke" | "compensate" = "invoke"
175
+ ): string => {
176
+ return `${workflowId}:${transactionId}:${stepId}:${action}`
177
+ }
178
+
179
+ export const mergeStepEvent = (
180
+ current: HttpTypes.AdminWorkflowExecutionResponse | undefined,
181
+ event: {
182
+ event_type: string
183
+ workflow_id: string
184
+ transaction_id: string
185
+ step?: Record<string, unknown>
186
+ response?: Record<string, unknown>
187
+ result?: Record<string, unknown>
188
+ errors?: Array<Record<string, unknown>>
189
+ }
190
+ ): HttpTypes.AdminWorkflowExecutionResponse | undefined => {
191
+ if (!current) return current
192
+
193
+ const updated = JSON.parse(
194
+ JSON.stringify(current)
195
+ ) as HttpTypes.AdminWorkflowExecutionResponse
196
+
197
+ const execution = updated.workflow_execution
198
+ if (!execution?.execution?.steps) return updated
199
+
200
+ const stepId = (event.step as any)?.id
201
+ if (!stepId) return updated
202
+
203
+ const step = execution.execution.steps[stepId] as any
204
+ if (!step) return updated
205
+
206
+ const eventType = event.event_type
207
+ switch (eventType) {
208
+ case "onStepBegin":
209
+ if (step.invoke) step.invoke.state = TransactionStepState.INVOKING
210
+ break
211
+ case "onStepSuccess":
212
+ if (step.invoke) {
213
+ step.invoke.state = TransactionStepState.DONE
214
+ step.invoke.status = TransactionStepStatus.OK
215
+ }
216
+ break
217
+ case "onStepFailure":
218
+ if (step.invoke) {
219
+ step.invoke.state = TransactionStepState.FAILED
220
+ step.invoke.status = TransactionStepStatus.PERMANENT_FAILURE
221
+ }
222
+ break
223
+ case "onStepAwaiting":
224
+ if (step.invoke) {
225
+ step.invoke.status = TransactionStepStatus.WAITING
226
+ }
227
+ break
228
+ case "onCompensateBegin":
229
+ if (step.compensate) {
230
+ step.compensate.state = TransactionStepState.COMPENSATING
231
+ }
232
+ break
233
+ case "onCompensateStepSuccess":
234
+ if (step.compensate) {
235
+ step.compensate.state = TransactionStepState.REVERTED
236
+ }
237
+ break
238
+ case "onCompensateStepFailure":
239
+ if (step.compensate) {
240
+ step.compensate.state = TransactionStepState.FAILED
241
+ }
242
+ break
243
+ case "onStepSkipped":
244
+ if (step.invoke) {
245
+ step.invoke.state = TransactionStepState.SKIPPED
246
+ }
247
+ break
248
+ case "onFinish":
249
+ execution.state = TransactionState.DONE
250
+ break
251
+ case "onTimeout":
252
+ if (step.invoke) {
253
+ step.invoke.state = TransactionStepState.TIMEOUT
254
+ }
255
+ break
256
+ }
257
+
258
+ return updated
259
+ }
@@ -0,0 +1,270 @@
1
+ import { zodResolver } from "@hookform/resolvers/zod"
2
+ import { Button, Heading, Select, Textarea, toast } from "@acmekit/ui"
3
+ import { useForm } from "react-hook-form"
4
+ import { useTranslation } from "react-i18next"
5
+ import { useParams } from "react-router-dom"
6
+ import * as zod from "zod"
7
+
8
+ import { Form } from "../../../components/common/form"
9
+ import { RouteDrawer, useRouteModal } from "../../../components/modals"
10
+ import { KeyboundForm } from "../../../components/utilities/keybound-form"
11
+ import {
12
+ useSetStepFailure,
13
+ useSetStepSuccess,
14
+ useWorkflowExecution,
15
+ } from "../../../hooks/api/workflow-executions"
16
+ import { getWaitingSteps } from "../utils"
17
+
18
+ const CompleteStepSchema = zod.object({
19
+ step_id: zod.string().min(1, "Step is required"),
20
+ outcome: zod.enum(["success", "failure"]),
21
+ response: zod.string().refine(
22
+ (val) => {
23
+ if (!val.trim()) return true
24
+ try {
25
+ JSON.parse(val)
26
+ return true
27
+ } catch {
28
+ return false
29
+ }
30
+ },
31
+ { message: "Must be valid JSON" }
32
+ ),
33
+ })
34
+
35
+ export const WorkflowExecutionCompleteStep = () => {
36
+ const { t } = useTranslation()
37
+ const { id } = useParams()
38
+ const { handleSuccess } = useRouteModal()
39
+
40
+ const {
41
+ workflow_execution,
42
+ isPending: isLoading,
43
+ isError,
44
+ error,
45
+ } = useWorkflowExecution(id!)
46
+
47
+ if (isError) {
48
+ throw error
49
+ }
50
+
51
+ if (isLoading || !workflow_execution) {
52
+ return (
53
+ <RouteDrawer>
54
+ <RouteDrawer.Header>
55
+ <Heading>{t("workflowExecutions.actions.completeStep")}</Heading>
56
+ </RouteDrawer.Header>
57
+ </RouteDrawer>
58
+ )
59
+ }
60
+
61
+ const waitingSteps = getWaitingSteps(workflow_execution)
62
+
63
+ return (
64
+ <RouteDrawer>
65
+ <RouteDrawer.Header>
66
+ <Heading>{t("workflowExecutions.actions.completeStep")}</Heading>
67
+ </RouteDrawer.Header>
68
+ <CompleteStepForm
69
+ workflowId={workflow_execution.workflow_id}
70
+ transactionId={workflow_execution.transaction_id}
71
+ waitingSteps={waitingSteps}
72
+ onSuccess={handleSuccess}
73
+ />
74
+ </RouteDrawer>
75
+ )
76
+ }
77
+
78
+ const CompleteStepForm = ({
79
+ workflowId,
80
+ transactionId,
81
+ waitingSteps,
82
+ onSuccess,
83
+ }: {
84
+ workflowId: string
85
+ transactionId: string
86
+ waitingSteps: { stepId: string }[]
87
+ onSuccess: () => void
88
+ }) => {
89
+ const { t } = useTranslation()
90
+
91
+ const form = useForm<zod.infer<typeof CompleteStepSchema>>({
92
+ defaultValues: {
93
+ step_id: waitingSteps[0]?.stepId || "",
94
+ outcome: "success",
95
+ response: "{}",
96
+ },
97
+ resolver: zodResolver(CompleteStepSchema),
98
+ })
99
+
100
+ const outcome = form.watch("outcome")
101
+
102
+ const { mutateAsync: setSuccess, isPending: isSettingSuccess } =
103
+ useSetStepSuccess(workflowId)
104
+ const { mutateAsync: setFailure, isPending: isSettingFailure } =
105
+ useSetStepFailure(workflowId)
106
+
107
+ const isPending = isSettingSuccess || isSettingFailure
108
+
109
+ const handleSubmit = form.handleSubmit(async (values) => {
110
+ const response = values.response.trim()
111
+ ? JSON.parse(values.response)
112
+ : {}
113
+
114
+ if (values.outcome === "success") {
115
+ await setSuccess(
116
+ { transaction_id: transactionId, step_id: values.step_id, response },
117
+ {
118
+ onSuccess: () => {
119
+ toast.success(
120
+ t("workflowExecutions.asyncStep.completedSuccess")
121
+ )
122
+ onSuccess()
123
+ },
124
+ onError: (err) => {
125
+ toast.error(err.message)
126
+ },
127
+ }
128
+ )
129
+ } else {
130
+ await setFailure(
131
+ { transaction_id: transactionId, step_id: values.step_id, response },
132
+ {
133
+ onSuccess: () => {
134
+ toast.success(
135
+ t("workflowExecutions.asyncStep.completedSuccess")
136
+ )
137
+ onSuccess()
138
+ },
139
+ onError: (err) => {
140
+ toast.error(err.message)
141
+ },
142
+ }
143
+ )
144
+ }
145
+ })
146
+
147
+ return (
148
+ <RouteDrawer.Form form={form}>
149
+ <KeyboundForm
150
+ onSubmit={handleSubmit}
151
+ className="flex flex-1 flex-col overflow-hidden"
152
+ >
153
+ <RouteDrawer.Body className="flex max-w-full flex-1 flex-col gap-y-4 overflow-y-auto">
154
+ <Form.Field
155
+ control={form.control}
156
+ name="step_id"
157
+ render={({ field }) => {
158
+ return (
159
+ <Form.Item>
160
+ <Form.Label>
161
+ {t("workflowExecutions.asyncStep.stepLabel")}
162
+ </Form.Label>
163
+ <Form.Control>
164
+ <Select
165
+ value={field.value}
166
+ onValueChange={field.onChange}
167
+ >
168
+ <Select.Trigger>
169
+ <Select.Value
170
+ placeholder={t(
171
+ "workflowExecutions.asyncStep.stepLabel"
172
+ )}
173
+ />
174
+ </Select.Trigger>
175
+ <Select.Content>
176
+ {waitingSteps.map((step) => (
177
+ <Select.Item
178
+ key={step.stepId}
179
+ value={step.stepId}
180
+ >
181
+ {step.stepId}
182
+ </Select.Item>
183
+ ))}
184
+ </Select.Content>
185
+ </Select>
186
+ </Form.Control>
187
+ <Form.ErrorMessage />
188
+ </Form.Item>
189
+ )
190
+ }}
191
+ />
192
+ <Form.Field
193
+ control={form.control}
194
+ name="outcome"
195
+ render={({ field }) => {
196
+ return (
197
+ <Form.Item>
198
+ <Form.Label>
199
+ {t("workflowExecutions.asyncStep.outcomeLabel")}
200
+ </Form.Label>
201
+ <Form.Control>
202
+ <Select
203
+ value={field.value}
204
+ onValueChange={field.onChange}
205
+ >
206
+ <Select.Trigger>
207
+ <Select.Value />
208
+ </Select.Trigger>
209
+ <Select.Content>
210
+ <Select.Item value="success">
211
+ {t("workflowExecutions.asyncStep.success")}
212
+ </Select.Item>
213
+ <Select.Item value="failure">
214
+ {t("workflowExecutions.asyncStep.failure")}
215
+ </Select.Item>
216
+ </Select.Content>
217
+ </Select>
218
+ </Form.Control>
219
+ <Form.ErrorMessage />
220
+ </Form.Item>
221
+ )
222
+ }}
223
+ />
224
+ <Form.Field
225
+ control={form.control}
226
+ name="response"
227
+ render={({ field }) => {
228
+ return (
229
+ <Form.Item>
230
+ <Form.Label>
231
+ {t("workflowExecutions.asyncStep.responsePayload")}
232
+ </Form.Label>
233
+ <Form.Control>
234
+ <Textarea
235
+ {...field}
236
+ className="font-mono text-xs"
237
+ rows={10}
238
+ />
239
+ </Form.Control>
240
+ <Form.ErrorMessage />
241
+ </Form.Item>
242
+ )
243
+ }}
244
+ />
245
+ </RouteDrawer.Body>
246
+ <RouteDrawer.Footer>
247
+ <div className="flex items-center justify-end gap-x-2">
248
+ <RouteDrawer.Close asChild>
249
+ <Button size="small" variant="secondary">
250
+ {t("actions.cancel")}
251
+ </Button>
252
+ </RouteDrawer.Close>
253
+ <Button
254
+ size="small"
255
+ type="submit"
256
+ isLoading={isPending}
257
+ variant={outcome === "failure" ? "danger" : "primary"}
258
+ >
259
+ {outcome === "failure"
260
+ ? t("workflowExecutions.asyncStep.markFailure")
261
+ : t("workflowExecutions.asyncStep.markSuccess")}
262
+ </Button>
263
+ </div>
264
+ </RouteDrawer.Footer>
265
+ </KeyboundForm>
266
+ </RouteDrawer.Form>
267
+ )
268
+ }
269
+
270
+ export const Component = WorkflowExecutionCompleteStep
@@ -22,5 +22,11 @@ export const WorkflowExecutionDetailBreadcrumb = (
22
22
 
23
23
  const cleanId = workflow_execution.id.replace("wf_exec_", "")
24
24
 
25
- return <span>{cleanId}</span>
25
+ return (
26
+ <span>
27
+ {workflow_execution.workflow_id}
28
+ <span className="text-ui-fg-muted"> / </span>
29
+ {cleanId}
30
+ </span>
31
+ )
26
32
  }
@@ -0,0 +1 @@
1
+ export { WorkflowExecutionActionBar } from "./workflow-execution-action-bar"
@@ -0,0 +1,212 @@
1
+ import { ArrowPathMini, EllipsisHorizontal, XMark } from "@acmekit/icons"
2
+ import { HttpTypes } from "@acmekit/types"
3
+ import {
4
+ Badge,
5
+ Button,
6
+ Container,
7
+ Copy,
8
+ StatusBadge,
9
+ toast,
10
+ usePrompt,
11
+ } from "@acmekit/ui"
12
+ import { useTranslation } from "react-i18next"
13
+ import { Link } from "react-router-dom"
14
+ import { ActionMenu } from "../../../../../components/common/action-menu"
15
+ import {
16
+ useCancelWorkflowExecution,
17
+ useRunWorkflow,
18
+ } from "../../../../../hooks/api/workflow-executions"
19
+ import {
20
+ TRANSACTION_ERROR_STATES,
21
+ TRANSACTION_IN_PROGRESS_STATES,
22
+ } from "../../../constants"
23
+ import { TransactionState } from "../../../types"
24
+ import {
25
+ getTransactionState,
26
+ getTransactionStateColor,
27
+ getWaitingSteps,
28
+ } from "../../../utils"
29
+
30
+ type WorkflowExecutionActionBarProps = {
31
+ execution: HttpTypes.AdminWorkflowExecution
32
+ }
33
+
34
+ export const WorkflowExecutionActionBar = ({
35
+ execution,
36
+ }: WorkflowExecutionActionBarProps) => {
37
+ const { t } = useTranslation()
38
+ const prompt = usePrompt()
39
+
40
+ const state = execution.state as TransactionState
41
+ const stateColor = getTransactionStateColor(state)
42
+ const translatedState = getTransactionState(t, state)
43
+
44
+ const workflowId = execution.workflow_id
45
+ const transactionId = execution.transaction_id
46
+
47
+ const isFailed = TRANSACTION_ERROR_STATES.includes(state)
48
+ const isRunning = TRANSACTION_IN_PROGRESS_STATES.includes(state)
49
+ const waitingSteps = getWaitingSteps(execution)
50
+ const hasWaitingSteps = waitingSteps.length > 0
51
+
52
+ const { mutateAsync: runAsync, isPending: isRunning_ } =
53
+ useRunWorkflow(workflowId)
54
+ const { mutateAsync: cancelAsync, isPending: isCancelling } =
55
+ useCancelWorkflowExecution(workflowId, transactionId)
56
+
57
+ const handleRetry = async () => {
58
+ const payload = (execution as any)?.context?.data?.payload
59
+ const input =
60
+ payload && typeof payload === "object"
61
+ ? (payload as Record<string, unknown>)
62
+ : {}
63
+ await runAsync(
64
+ { input },
65
+ {
66
+ onSuccess: () => {
67
+ toast.success(t("workflowExecutions.actions.retrySuccess"))
68
+ },
69
+ onError: (err) => {
70
+ toast.error(err.message)
71
+ },
72
+ }
73
+ )
74
+ }
75
+
76
+ const handleCancel = async () => {
77
+ const res = await prompt({
78
+ title: t("workflowExecutions.actions.cancelConfirmTitle"),
79
+ description: t("workflowExecutions.actions.cancelConfirmDescription", {
80
+ workflow_id: workflowId,
81
+ }),
82
+ confirmText: t("workflowExecutions.actions.cancel"),
83
+ cancelText: t("actions.cancel"),
84
+ })
85
+
86
+ if (!res) return
87
+
88
+ await cancelAsync(undefined, {
89
+ onSuccess: () => {
90
+ toast.success(t("workflowExecutions.actions.cancelSuccess"))
91
+ },
92
+ onError: (err) => {
93
+ toast.error(err.message)
94
+ },
95
+ })
96
+ }
97
+
98
+ const handleExportJson = () => {
99
+ const data = JSON.stringify(execution, null, 2)
100
+ const blob = new Blob([data], { type: "application/json" })
101
+ const url = URL.createObjectURL(blob)
102
+ const a = document.createElement("a")
103
+ a.href = url
104
+ a.download = `workflow-${workflowId}-${execution.id}.json`
105
+ a.click()
106
+ URL.revokeObjectURL(url)
107
+ }
108
+
109
+ const handleExportCsv = () => {
110
+ const steps = execution?.execution?.steps || {}
111
+ const rows = [["Step", "State", "Duration"]]
112
+ for (const [stepId, step] of Object.entries(steps)) {
113
+ if (stepId === "_root") continue
114
+ const invokeState = (step as any)?.invoke?.state || "unknown"
115
+ rows.push([stepId, invokeState, ""])
116
+ }
117
+ const csv = rows.map((r) => r.join(",")).join("\n")
118
+ const blob = new Blob([csv], { type: "text/csv" })
119
+ const url = URL.createObjectURL(blob)
120
+ const a = document.createElement("a")
121
+ a.href = url
122
+ a.download = `workflow-${workflowId}-${execution.id}.csv`
123
+ a.click()
124
+ URL.revokeObjectURL(url)
125
+ }
126
+
127
+ const cleanId = execution.id.replace("wf_exec_", "")
128
+
129
+ return (
130
+ <Container className="sticky top-14 z-10 p-0">
131
+ <div className="flex items-center justify-between px-6 py-3">
132
+ <div className="flex items-center gap-x-3">
133
+ <Copy content={workflowId} asChild>
134
+ <Badge size="2xsmall" className="cursor-pointer">
135
+ {workflowId}
136
+ </Badge>
137
+ </Copy>
138
+ <StatusBadge color={stateColor}>{translatedState}</StatusBadge>
139
+ <Copy content={execution.id} asChild>
140
+ <Badge
141
+ size="2xsmall"
142
+ color="grey"
143
+ className="cursor-pointer text-ui-fg-muted"
144
+ >
145
+ {cleanId}
146
+ </Badge>
147
+ </Copy>
148
+ </div>
149
+ <div className="flex items-center gap-x-2">
150
+ {isFailed && (
151
+ <Button
152
+ variant="secondary"
153
+ size="small"
154
+ onClick={handleRetry}
155
+ isLoading={isRunning_}
156
+ >
157
+ <ArrowPathMini className="mr-1" />
158
+ {t("workflowExecutions.actions.retry")}
159
+ </Button>
160
+ )}
161
+ {isRunning && (
162
+ <Button
163
+ variant="danger"
164
+ size="small"
165
+ onClick={handleCancel}
166
+ isLoading={isCancelling}
167
+ >
168
+ <XMark className="mr-1" />
169
+ {t("workflowExecutions.actions.cancel")}
170
+ </Button>
171
+ )}
172
+ {hasWaitingSteps && (
173
+ <Button variant="secondary" size="small" asChild>
174
+ <Link to="complete-step">
175
+ {t("workflowExecutions.actions.completeStep")}
176
+ </Link>
177
+ </Button>
178
+ )}
179
+ <ActionMenu
180
+ groups={[
181
+ {
182
+ actions: [
183
+ ...(isFailed
184
+ ? [
185
+ {
186
+ label: t(
187
+ "workflowExecutions.actions.retryWithNewInput"
188
+ ),
189
+ icon: <ArrowPathMini />,
190
+ to: "rerun",
191
+ },
192
+ ]
193
+ : []),
194
+ {
195
+ label: t("workflowExecutions.actions.exportJson"),
196
+ icon: <EllipsisHorizontal />,
197
+ onClick: handleExportJson,
198
+ },
199
+ {
200
+ label: t("workflowExecutions.actions.exportCsv"),
201
+ icon: <EllipsisHorizontal />,
202
+ onClick: handleExportCsv,
203
+ },
204
+ ],
205
+ },
206
+ ]}
207
+ />
208
+ </div>
209
+ </div>
210
+ </Container>
211
+ )
212
+ }
@@ -0,0 +1 @@
1
+ export { WorkflowExecutionErrorCard } from "./workflow-execution-error-card"