From 7d633135bcd6a0db0d6d1b0e6d222b7931e322ee Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 26 Jul 2025 13:10:18 +0800 Subject: [PATCH] fix(ui): unsynchronized running status after WorkflowDetail subpage changed --- ui/src/domain/workflow.ts | 4 ++++ ui/src/pages/settings/Settings.tsx | 2 +- ui/src/pages/workflows/WorkflowDetail.tsx | 6 ++++-- .../pages/workflows/WorkflowDetailDesign.tsx | 20 ++++++++++--------- ui/src/pages/workflows/WorkflowList.tsx | 5 +++-- ui/src/repository/workflow.ts | 3 +++ ui/src/repository/workflowRun.ts | 18 ++++++++++++++--- 7 files changed, 41 insertions(+), 17 deletions(-) diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts index ac2e2b19d..c4a73f0e6 100644 --- a/ui/src/domain/workflow.ts +++ b/ui/src/domain/workflow.ts @@ -3,6 +3,7 @@ import { Immer, produce } from "immer"; import { nanoid } from "nanoid"; import i18n from "@/i18n"; +import { type WorkflowRunModel } from "./workflowRun"; export interface WorkflowModel extends BaseModel { name: string; @@ -16,6 +17,9 @@ export interface WorkflowModel extends BaseModel { lastRunRef?: string; lastRunStatus?: string; lastRunTime?: string; + expand?: { + lastRunRef?: WorkflowRunModel; + }; } export const WORKFLOW_TRIGGERS = Object.freeze({ diff --git a/ui/src/pages/settings/Settings.tsx b/ui/src/pages/settings/Settings.tsx index 82fc6115b..6de4be1ed 100644 --- a/ui/src/pages/settings/Settings.tsx +++ b/ui/src/pages/settings/Settings.tsx @@ -16,7 +16,7 @@ const Settings = () => { ["ssl-provider", "settings.sslprovider.tab", ], ["persistence", "settings.persistence.tab", ], ] satisfies [string, string, React.ReactElement][]; - const [menuKey, setMenuValue] = useState(menus[0][0]); + const [menuKey, setMenuValue] = useState(); useEffect(() => { const subpath = location.pathname.split("/")[2]; if (!subpath) { diff --git a/ui/src/pages/workflows/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx index aad56f363..07b2d120a 100644 --- a/ui/src/pages/workflows/WorkflowDetail.tsx +++ b/ui/src/pages/workflows/WorkflowDetail.tsx @@ -36,10 +36,10 @@ const WorkflowDetail = () => { ["design", "workflow.detail.design.tab", ], ["runs", "workflow.detail.runs.tab", ], ] satisfies [string, string, React.ReactElement][]; - const [tabValue, setTabValue] = useState(tabs[0][0]); + const [tabValue, setTabValue] = useState(); useEffect(() => { const subpath = location.pathname.split("/")[3]; - if (!tabs.map((tab) => tab[0]).includes(subpath)) { + if (!subpath) { navigate(`/workflows/${workflowId}/${tabs[0][0]}`, { replace: true }); return; } @@ -50,6 +50,8 @@ const WorkflowDetail = () => { const handleTabChange = (value: string) => { setTabValue(value); navigate(`/workflows/${workflowId}/${value}`); + + workflowState.init(workflow.id); // reload state }; const handleEnableClick = async () => { diff --git a/ui/src/pages/workflows/WorkflowDetailDesign.tsx b/ui/src/pages/workflows/WorkflowDetailDesign.tsx index 1876a2ba5..5bd694e74 100644 --- a/ui/src/pages/workflows/WorkflowDetailDesign.tsx +++ b/ui/src/pages/workflows/WorkflowDetailDesign.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { IconArrowBackUp, IconDots, IconPlayerPlay } from "@tabler/icons-react"; import { Alert, App, Button, Card, Dropdown, Space } from "antd"; @@ -10,7 +10,7 @@ import WorkflowElementsContainer from "@/components/workflow/WorkflowElementsCon import { isAllNodesValidated } from "@/domain/workflow"; import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun"; import { useZustandShallowSelector } from "@/hooks"; -import { subscribe as subscribeWorkflow, unsubscribe as unsubscribeWorkflow } from "@/repository/workflow"; +import { subscribe as subscribeWorkflow } from "@/repository/workflow"; import { useWorkflowStore } from "@/stores/workflow"; import { getErrMsg } from "@/utils/error"; @@ -19,30 +19,32 @@ const WorkflowDetailDesign = () => { const { message, modal, notification } = App.useApp(); - const { workflow, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "publish", "rollback"])); + const { workflow, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "init", "publish", "rollback"])); const [isPendingOrRunning, setIsPendingOrRunning] = useState(false); - const lastRunStatus = useMemo(() => workflow.lastRunStatus, [workflow]); - const [allowRollback, setAllowRollback] = useState(false); const [allowPublish, setAllowPublish] = useState(false); const [allowRun, setAllowRun] = useState(false); useEffect(() => { - setIsPendingOrRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.PENDING || lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING); - }, [lastRunStatus]); + const pending = workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.PENDING || workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.RUNNING; + setIsPendingOrRunning(pending); + }, [workflow]); useEffect(() => { if (isPendingOrRunning) { + let unsubscribeFn: Awaited> | undefined = undefined; subscribeWorkflow(workflow.id, (cb) => { if (cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) { setIsPendingOrRunning(false); - unsubscribeWorkflow(workflow.id); + unsubscribeFn?.(); } + }).then((res) => { + unsubscribeFn = res; }); return () => { - unsubscribeWorkflow(workflow.id); + unsubscribeFn?.(); }; } }, [workflow.id, isPendingOrRunning]); diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx index 97373a875..88641169a 100644 --- a/ui/src/pages/workflows/WorkflowList.tsx +++ b/ui/src/pages/workflows/WorkflowList.tsx @@ -108,8 +108,8 @@ const WorkflowList = () => { } else { return ( - - {lastRunTime ? dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss") : ""} + + {lastRunTime ? dayjs(lastRunTime).format("YYYY-MM-DD HH:mm:ss") : ""} ); } @@ -231,6 +231,7 @@ const WorkflowList = () => { sort: sort, page: page, perPage: pageSize, + expand: true, }); }, { diff --git a/ui/src/repository/workflow.ts b/ui/src/repository/workflow.ts index 9ce71f26e..60ccc557e 100644 --- a/ui/src/repository/workflow.ts +++ b/ui/src/repository/workflow.ts @@ -9,6 +9,7 @@ export type ListRequest = { sort?: string; page?: number; perPage?: number; + expand?: boolean; }; export const list = async (request: ListRequest) => { @@ -28,6 +29,7 @@ export const list = async (request: ListRequest) => { const perPage = request.perPage || 10; return await pb.collection(COLLECTION_NAME_WORKFLOW).getList(page, perPage, { + expand: request.expand ? "lastRunRef" : undefined, filter: filters.join(" && "), sort: sort, requestKey: null, @@ -36,6 +38,7 @@ export const list = async (request: ListRequest) => { export const get = async (id: string) => { return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW).getOne(id, { + expand: "lastRunRef", requestKey: null, }); }; diff --git a/ui/src/repository/workflowRun.ts b/ui/src/repository/workflowRun.ts index 2fdc011f2..4f2082f16 100644 --- a/ui/src/repository/workflowRun.ts +++ b/ui/src/repository/workflowRun.ts @@ -22,15 +22,27 @@ export const list = async (request: ListRequest) => { const page = request.page || 1; const perPage = request.perPage || 10; return await pb.collection(COLLECTION_NAME_WORKFLOW_RUN).getList(page, perPage, { + expand: request.expand ? "workflowRef" : undefined, filter: filters.join(" && "), sort: "-created", requestKey: null, - expand: request.expand ? "workflowRef" : undefined, }); }; -export const remove = async (record: MaybeModelRecordWithId) => { - return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW_RUN).delete(record.id); +export const remove = async (record: MaybeModelRecordWithId | MaybeModelRecordWithId[]) => { + const pb = getPocketBase(); + + if (Array.isArray(record)) { + const batch = pb.createBatch(); + for (const item of record) { + batch.collection(COLLECTION_NAME_WORKFLOW_RUN).delete(item.id); + } + const res = await batch.send(); + return res.every((e) => e.status === 200); + } else { + await pb.collection(COLLECTION_NAME_WORKFLOW_RUN).delete(record.id!); + return true; + } }; export const subscribe = async (id: string, cb: (e: RecordSubscription) => void) => {