fix(ui): unsynchronized running status after WorkflowDetail subpage changed

This commit is contained in:
Fu Diwei
2025-07-26 13:10:18 +08:00
parent e20cf8fad3
commit 7d633135bc
7 changed files with 41 additions and 17 deletions
+4
View File
@@ -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({
+1 -1
View File
@@ -16,7 +16,7 @@ const Settings = () => {
["ssl-provider", "settings.sslprovider.tab", <IconPlugConnected size="1em" />],
["persistence", "settings.persistence.tab", <IconDatabaseCog size="1em" />],
] satisfies [string, string, React.ReactElement][];
const [menuKey, setMenuValue] = useState(menus[0][0]);
const [menuKey, setMenuValue] = useState<string>();
useEffect(() => {
const subpath = location.pathname.split("/")[2];
if (!subpath) {
+4 -2
View File
@@ -36,10 +36,10 @@ const WorkflowDetail = () => {
["design", "workflow.detail.design.tab", <IconRobot size="1em" />],
["runs", "workflow.detail.runs.tab", <IconHistory size="1em" />],
] satisfies [string, string, React.ReactElement][];
const [tabValue, setTabValue] = useState(tabs[0][0]);
const [tabValue, setTabValue] = useState<string>();
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 () => {
@@ -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<ReturnType<typeof subscribeWorkflow>> | 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]);
+3 -2
View File
@@ -108,8 +108,8 @@ const WorkflowList = () => {
} else {
return (
<Flex gap="small">
<WorkflowStatusIcon color={true} size="1.25em" status={lastRunStatus!} />
<Typography.Text>{lastRunTime ? dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss") : ""}</Typography.Text>
<WorkflowStatusIcon color={true} size="1.25em" status={lastRunStatus} />
<Typography.Text>{lastRunTime ? dayjs(lastRunTime).format("YYYY-MM-DD HH:mm:ss") : ""}</Typography.Text>
</Flex>
);
}
@@ -231,6 +231,7 @@ const WorkflowList = () => {
sort: sort,
page: page,
perPage: pageSize,
expand: true,
});
},
{
+3
View File
@@ -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<WorkflowModel>(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<WorkflowModel>(id, {
expand: "lastRunRef",
requestKey: null,
});
};
+15 -3
View File
@@ -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<WorkflowRunModel>(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<WorkflowRunModel>) => {
return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW_RUN).delete(record.id);
export const remove = async (record: MaybeModelRecordWithId<WorkflowRunModel> | MaybeModelRecordWithId<WorkflowRunModel>[]) => {
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<WorkflowRunModel>) => void) => {