diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index 64066f01..4f18594a 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -45,7 +45,7 @@ type WorkflowNode struct { Type WorkflowNodeType `json:"type"` Name string `json:"name"` - Config map[string]any `json:"config"` + Config map[string]any `json:"data"` Inputs []WorkflowNodeIO `json:"inputs"` Outputs []WorkflowNodeIO `json:"outputs"` diff --git a/ui/src/i18n/locales/en/nls.workflow.json b/ui/src/i18n/locales/en/nls.workflow.json index da529db7..79036a81 100644 --- a/ui/src/i18n/locales/en/nls.workflow.json +++ b/ui/src/i18n/locales/en/nls.workflow.json @@ -42,6 +42,7 @@ "workflow.detail.baseinfo.form.description.label": "Description", "workflow.detail.baseinfo.form.description.placeholder": "Please enter workflow description", "workflow.detail.orchestration.tab": "Orchestration", + "workflow.detail.orchestration.draft.alert": "The orchestration is not released yet.", "workflow.detail.orchestration.action.discard": "Discard changes", "workflow.detail.orchestration.action.discard.confirm": "Are you sure to discard your changes?", "workflow.detail.orchestration.action.release": "Release", diff --git a/ui/src/i18n/locales/zh/nls.workflow.json b/ui/src/i18n/locales/zh/nls.workflow.json index 7019bda7..5bf54898 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.json +++ b/ui/src/i18n/locales/zh/nls.workflow.json @@ -42,6 +42,7 @@ "workflow.detail.baseinfo.form.description.label": "描述", "workflow.detail.baseinfo.form.description.placeholder": "请输入工作流描述", "workflow.detail.orchestration.tab": "流程编排", + "workflow.detail.orchestration.draft.alert": "当前编排有未发布的更改。", "workflow.detail.orchestration.action.discard": "撤销更改", "workflow.detail.orchestration.action.discard.confirm": "确定要撤销更改并回退到最近一次发布的版本吗?", "workflow.detail.orchestration.action.release": "发布更改", diff --git a/ui/src/pages/workflows/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx index f0369a50..0948f62b 100644 --- a/ui/src/pages/workflows/WorkflowDetail.tsx +++ b/ui/src/pages/workflows/WorkflowDetail.tsx @@ -12,7 +12,7 @@ import { } from "@ant-design/icons"; import { PageHeader } from "@ant-design/pro-components"; import { useDeepCompareEffect } from "ahooks"; -import { Button, Card, Dropdown, Form, Input, Modal, Space, Tabs, Typography, message, notification } from "antd"; +import { Alert, Button, Card, Dropdown, Form, Input, Modal, Space, Tabs, Typography, message, notification } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { ClientResponseError } from "pocketbase"; import { isEqual } from "radash"; @@ -39,15 +39,15 @@ const WorkflowDetail = () => { const [notificationApi, NotificationContextHolder] = notification.useNotification(); const { id: workflowId } = useParams(); - const { workflow, initialized, init, save, destroy, setBaseInfo, switchEnable } = useWorkflowStore( - useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "save", "setBaseInfo", "switchEnable"]) + const { workflow, initialized, ...workflowState } = useWorkflowStore( + useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setBaseInfo", "setEnabled", "release", "discard"]) ); useEffect(() => { // TODO: loading & error - init(workflowId!); + workflowState.init(workflowId!); return () => { - destroy(); + workflowState.destroy(); }; }, [workflowId]); @@ -68,7 +68,7 @@ const WorkflowDetail = () => { const handleBaseInfoFormFinish = async (values: Pick) => { try { - await setBaseInfo(values.name!, values.description!); + await workflowState.setBaseInfo(values.name!, values.description!); } catch (err) { console.error(err); notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); @@ -83,7 +83,7 @@ const WorkflowDetail = () => { } try { - await switchEnable(); + await workflowState.setEnabled(!workflow.enabled); } catch (err) { console.error(err); notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); @@ -112,8 +112,15 @@ const WorkflowDetail = () => { modalApi.confirm({ title: t("workflow.detail.orchestration.action.discard"), content: t("workflow.detail.orchestration.action.discard.confirm"), - onOk: () => { - alert("TODO"); + onOk: async () => { + try { + await workflowState.discard(); + + messageApi.success(t("common.text.operation_succeeded")); + } catch (err) { + console.error(err); + notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + } }, }); }; @@ -129,7 +136,7 @@ const WorkflowDetail = () => { content: t("workflow.detail.orchestration.action.release.confirm"), onOk: async () => { try { - await save(); + await workflowState.release(); messageApi.success(t("common.text.operation_succeeded")); } catch (err) { @@ -242,38 +249,45 @@ const WorkflowDetail = () => {
-
- -
-
- - - - - - , - onClick: handleDiscardClick, - }, - ], - }} - trigger={["click"]} - > - + + , + onClick: handleDiscardClick, + }, + ], + }} + trigger={["click"]} + > +
+
+
+
diff --git a/ui/src/repository/workflow.ts b/ui/src/repository/workflow.ts index b4596d17..a1ae7bd3 100644 --- a/ui/src/repository/workflow.ts +++ b/ui/src/repository/workflow.ts @@ -35,7 +35,7 @@ export const get = async (id: string) => { }); }; -export const save = async (record: Record) => { +export const save = async (record: MaybeModelRecord) => { if (record.id) { return await getPocketBase() .collection(COLLECTION_NAME) diff --git a/ui/src/stores/workflow/index.ts b/ui/src/stores/workflow/index.ts index 5656da77..4e1559ff 100644 --- a/ui/src/stores/workflow/index.ts +++ b/ui/src/stores/workflow/index.ts @@ -1,11 +1,12 @@ +import { produce } from "immer"; import { create } from "zustand"; import { type WorkflowModel, type WorkflowNode, + type WorkflowNodeConfigForStart, addBranch, addNode, - getExecuteMethod, getWorkflowOutputBeforeId, removeBranch, removeNode, @@ -16,17 +17,22 @@ import { get as getWorkflow, save as saveWorkflow } from "@/repository/workflow" export type WorkflowState = { workflow: WorkflowModel; initialized: boolean; - updateNode: (node: WorkflowNode) => void; - addNode: (node: WorkflowNode, preId: string) => void; - addBranch: (branchId: string) => void; - removeNode: (nodeId: string) => void; - removeBranch: (branchId: string, index: number) => void; - getWorkflowOuptutBeforeId: (id: string, type: string) => WorkflowNode[]; - switchEnable(): void; - save(): void; - setBaseInfo: (name: string, description: string) => void; + init(id: string): void; + setBaseInfo: (name: string, description: string) => void; + setEnabled(enabled: boolean): void; + release(): void; + discard(): void; destroy(): void; + + addNode: (node: WorkflowNode, preId: string) => void; + updateNode: (node: WorkflowNode) => void; + removeNode: (nodeId: string) => void; + + addBranch: (branchId: string) => void; + removeBranch: (branchId: string, index: number) => void; + + getWorkflowOuptutBeforeId: (id: string, type: string) => WorkflowNode[]; }; export const useWorkflowStore = create((set, get) => ({ @@ -42,171 +48,197 @@ export const useWorkflowStore = create((set, get) => ({ }); }, + destroy: () => { + set({ + workflow: {} as WorkflowModel, + initialized: false, + }); + }, + setBaseInfo: async (name: string, description: string) => { - const data: Record = { - id: (get().workflow.id as string) ?? "", + if (!get().initialized) throw "Workflow not initialized yet"; + + const resp = await saveWorkflow({ + id: get().workflow.id!, name: name || "", description: description || "", - }; - const resp = await saveWorkflow(data); - set((state: WorkflowState) => { - return { - workflow: { - ...state.workflow, - name, - description, - id: resp.id, - }, - }; - }); - }, - - switchEnable: async () => { - const root = get().workflow.content as WorkflowNode; - const executeMethod = getExecuteMethod(root); - const resp = await saveWorkflow({ - id: (get().workflow.id as string) ?? "", - content: root, - enabled: !get().workflow.enabled, - trigger: executeMethod.trigger, - triggerCron: executeMethod.triggerCron, }); set((state: WorkflowState) => { return { - workflow: { - ...state.workflow, - id: resp.id, - content: resp.content, - enabled: resp.enabled, - trigger: resp.trigger, - triggerCron: resp.triggerCron, - }, + workflow: produce(state.workflow, (draft) => { + draft.name = resp.name; + draft.description = resp.description; + }), }; }); }, - save: async () => { - const root = get().workflow.draft as WorkflowNode; - const executeMethod = getExecuteMethod(root); + setEnabled: async (enabled: boolean) => { + if (!get().initialized) throw "Workflow not initialized yet"; + const resp = await saveWorkflow({ - id: (get().workflow.id as string) ?? "", + id: get().workflow.id!, + enabled: enabled, + }); + + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.enabled = resp.enabled; + }), + }; + }); + }, + + release: async () => { + if (!get().initialized) throw "Workflow not initialized yet"; + + const root = get().workflow.draft!; + const startConfig = root.config as WorkflowNodeConfigForStart; + const resp = await saveWorkflow({ + id: get().workflow.id!, + trigger: startConfig.trigger, + triggerCron: startConfig.triggerCron, content: root, hasDraft: false, - trigger: executeMethod.trigger, - triggerCron: executeMethod.triggerCron, }); set((state: WorkflowState) => { return { - workflow: { - ...state.workflow, - id: resp.id, - content: resp.content, - hasDraft: false, - trigger: resp.trigger, - triggerCron: resp.triggerCron, - }, + workflow: produce(state.workflow, (draft) => { + draft.trigger = resp.trigger; + draft.triggerCron = resp.triggerCron; + draft.content = resp.content; + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), }; }); }, - updateNode: async (node: WorkflowNode) => { - const newRoot = updateNode(get().workflow.draft as WorkflowNode, node); + discard: async () => { + if (!get().initialized) throw "Workflow not initialized yet"; + + const root = get().workflow.content!; + const startConfig = root.config as WorkflowNodeConfigForStart; const resp = await saveWorkflow({ - id: (get().workflow.id as string) ?? "", - draft: newRoot, - hasDraft: true, + id: get().workflow.id!, + draft: root, + hasDraft: false, + trigger: startConfig.trigger, + triggerCron: startConfig.triggerCron, }); set((state: WorkflowState) => { return { - workflow: { - ...state.workflow, - draft: newRoot, - id: resp.id, - hasDraft: true, - }, + workflow: produce(state.workflow, (draft) => { + draft.trigger = resp.trigger; + draft.triggerCron = resp.triggerCron; + draft.content = resp.content; + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), }; }); }, addNode: async (node: WorkflowNode, preId: string) => { - const newRoot = addNode(get().workflow.draft as WorkflowNode, preId, node); + if (!get().initialized) throw "Workflow not initialized yet"; + + const root = addNode(get().workflow.draft!, preId, node); const resp = await saveWorkflow({ - id: (get().workflow.id as string) ?? "", - draft: newRoot, + id: get().workflow.id!, + draft: root, hasDraft: true, }); set((state: WorkflowState) => { return { - workflow: { - ...state.workflow, - draft: newRoot, - id: resp.id, - hasDraft: true, - }, + workflow: produce(state.workflow, (draft) => { + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), }; }); }, - addBranch: async (branchId: string) => { - const newRoot = addBranch(get().workflow.draft as WorkflowNode, branchId); + updateNode: async (node: WorkflowNode) => { + if (!get().initialized) throw "Workflow not initialized yet"; + + const root = updateNode(get().workflow.draft!, node); const resp = await saveWorkflow({ - id: (get().workflow.id as string) ?? "", - draft: newRoot, + id: get().workflow.id!, + draft: root, hasDraft: true, }); set((state: WorkflowState) => { return { - workflow: { - ...state.workflow, - draft: newRoot, - id: resp.id, - hasDraft: true, - }, - }; - }); - }, - - removeBranch: async (branchId: string, index: number) => { - const newRoot = removeBranch(get().workflow.draft as WorkflowNode, branchId, index); - const resp = await saveWorkflow({ - id: (get().workflow.id as string) ?? "", - draft: newRoot, - hasDraft: true, - }); - - set((state: WorkflowState) => { - return { - workflow: { - ...state.workflow, - draft: newRoot, - id: resp.id, - hasDraft: true, - }, + workflow: produce(state.workflow, (draft) => { + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), }; }); }, removeNode: async (nodeId: string) => { - const newRoot = removeNode(get().workflow.draft as WorkflowNode, nodeId); + if (!get().initialized) throw "Workflow not initialized yet"; + + const root = removeNode(get().workflow.draft!, nodeId); const resp = await saveWorkflow({ - id: (get().workflow.id as string) ?? "", - draft: newRoot, + id: get().workflow.id!, + draft: root, hasDraft: true, }); set((state: WorkflowState) => { return { - workflow: { - ...state.workflow, - draft: newRoot, - id: resp.id, - hasDraft: true, - }, + workflow: produce(state.workflow, (draft) => { + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), + }; + }); + }, + + addBranch: async (branchId: string) => { + if (!get().initialized) throw "Workflow not initialized yet"; + + const root = addBranch(get().workflow.draft!, branchId); + const resp = await saveWorkflow({ + id: get().workflow.id!, + draft: root, + hasDraft: true, + }); + + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), + }; + }); + }, + + removeBranch: async (branchId: string, index: number) => { + if (!get().initialized) throw "Workflow not initialized yet"; + + const root = removeBranch(get().workflow.draft!, branchId, index); + const resp = await saveWorkflow({ + id: get().workflow.id!, + draft: root, + hasDraft: true, + }); + + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), }; }); }, @@ -214,11 +246,4 @@ export const useWorkflowStore = create((set, get) => ({ getWorkflowOuptutBeforeId: (id: string, type: string) => { return getWorkflowOutputBeforeId(get().workflow.draft as WorkflowNode, id, type); }, - - destroy: () => { - set({ - workflow: {} as WorkflowModel, - initialized: false, - }); - }, }));