) => {
+ event.preventDefault();
+ event.stopPropagation();
+ }, []);
+ const { color } = ExecutionStatusOptionsMap[props.value] ?? {};
+ return (
+
+ {label}
+
+ );
+}
+
+function ExecutionStatusOption(props) {
+ const compile = useCompile();
+ return (
+ <>
+
+ {props.description ? {compile(props.description)} : null}
+ >
+ );
+}
+
+export function ExecutionStatusSelect({ ...props }) {
+ const mode = props.multiple ? 'multiple' : null;
+
+ return (
+
+ );
+}
+
+export function ExecutionStatusColumn(props) {
+ const { t } = useTranslation();
+ const { refresh } = useResourceActionContext();
+ const { resource } = useResourceContext();
+ const record = useRecord();
+ const onCancel = useCallback(() => {
+ Modal.confirm({
+ title: lang('Cancel the execution'),
+ icon: ,
+ content: lang('Are you sure you want to cancel the execution?'),
+ onOk: () => {
+ resource
+ .cancel({
+ filterByTk: record.id,
+ })
+ .then(() => {
+ message.success(t('Operation succeeded'));
+ refresh();
+ })
+ .catch((response) => {
+ console.error(response.data.error);
+ });
+ },
+ });
+ }, [record]);
+ return (
+
+ {props.children}
+ {record.status ? null : (
+
+ } />
+
+ )}
+
+ );
+}
diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/components/ExecutionStatusSelect.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/components/ExecutionStatusSelect.tsx
deleted file mode 100644
index 4eacd9717c..0000000000
--- a/packages/plugins/@nocobase/plugin-workflow/src/client/components/ExecutionStatusSelect.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import { Select, Tag } from 'antd';
-import React, { useCallback } from 'react';
-
-import { useCompile } from '@nocobase/client';
-import { EXECUTION_STATUS, ExecutionStatusOptions, ExecutionStatusOptionsMap } from '../constants';
-
-function LabelTag(props) {
- const compile = useCompile();
- const label = compile(props.label);
- const onPreventMouseDown = useCallback((event: React.MouseEvent) => {
- event.preventDefault();
- event.stopPropagation();
- }, []);
- const { color } = ExecutionStatusOptionsMap[props.value] ?? {};
- return (
-
- {label}
-
- );
-}
-
-function ExecutionStatusOption(props) {
- const compile = useCompile();
- return (
- <>
-
- {props.description ? {compile(props.description)} : null}
- >
- );
-}
-
-export function ExecutionStatusSelect({ ...props }) {
- const mode = props.multiple ? 'multiple' : null;
-
- return (
-
- );
-}
diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/schemas/executions.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/schemas/executions.tsx
index 06d8ae50d6..6b15e0a47b 100644
--- a/packages/plugins/@nocobase/plugin-workflow/src/client/schemas/executions.tsx
+++ b/packages/plugins/@nocobase/plugin-workflow/src/client/schemas/executions.tsx
@@ -11,10 +11,21 @@ import { getWorkflowDetailPath } from '../constant';
export const executionCollection = {
name: 'executions',
fields: [
+ {
+ interface: 'id',
+ type: 'bigInt',
+ name: 'id',
+ uiSchema: {
+ type: 'number',
+ title: '{{t("ID")}}',
+ 'x-component': 'Input',
+ 'x-component-props': {},
+ 'x-read-pretty': true,
+ } as ISchema,
+ },
{
interface: 'createdAt',
type: 'datetime',
- // field: 'createdAt',
name: 'createdAt',
uiSchema: {
type: 'datetime',
@@ -120,6 +131,18 @@ export const executionSchema = {
useDataSource: '{{ cm.useDataSourceFromRAC }}',
},
properties: {
+ id: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ id: {
+ type: 'number',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
createdAt: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
@@ -151,9 +174,11 @@ export const executionSchema = {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
+ title: `{{t("Status", { ns: "${NAMESPACE}" })}}`,
properties: {
status: {
type: 'number',
+ 'x-decorator': 'ExecutionStatusColumn',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
@@ -173,7 +198,6 @@ export const executionSchema = {
properties: {
link: {
type: 'void',
- title: `{{t("Details", { ns: "${NAMESPACE}" })}}`,
'x-component': 'ExecutionLink',
},
},
diff --git a/packages/plugins/@nocobase/plugin-workflow/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-workflow/src/locale/zh-CN.json
index e09f17598a..446247507b 100644
--- a/packages/plugins/@nocobase/plugin-workflow/src/locale/zh-CN.json
+++ b/packages/plugins/@nocobase/plugin-workflow/src/locale/zh-CN.json
@@ -116,6 +116,9 @@
"Rejected from a manual node.": "被人工节点拒绝继续。",
"General failed but should do another try.": "执行失败,需重试。",
+ "Cancel the execution": "取消执行",
+ "Are you sure you want to cancel the execution?": "确定要取消该执行吗?",
+
"Operations": "操作",
"This node contains branches, deleting will also be preformed to them, are you sure?":
"节点包含分支,将同时删除其所有分支下的子节点,确定继续?",
diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/actions/executions.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/actions/executions.ts
index a757f7874e..9ed81cb200 100644
--- a/packages/plugins/@nocobase/plugin-workflow/src/server/actions/executions.ts
+++ b/packages/plugins/@nocobase/plugin-workflow/src/server/actions/executions.ts
@@ -1,6 +1,6 @@
import actions, { Context } from '@nocobase/actions';
import { Op } from '@nocobase/database';
-import { EXECUTION_STATUS } from '../constants';
+import { EXECUTION_STATUS, JOB_STATUS } from '../constants';
export async function destroy(context: Context, next) {
context.action.mergeParams({
@@ -13,3 +13,43 @@ export async function destroy(context: Context, next) {
await actions.destroy(context, next);
}
+
+export async function cancel(context: Context, next) {
+ const { filterByTk } = context.action.params;
+ const ExecutionRepo = context.db.getRepository('executions');
+ const JobRepo = context.db.getRepository('jobs');
+ const execution = await ExecutionRepo.findOne({
+ filterByTk,
+ appends: ['jobs'],
+ });
+ if (!execution) {
+ return context.throw(404);
+ }
+ if (execution.status) {
+ return context.throw(400);
+ }
+
+ await context.db.sequelize.transaction(async (transaction) => {
+ await execution.update(
+ {
+ status: EXECUTION_STATUS.CANCELED,
+ },
+ { transaction },
+ );
+
+ const pendingJobs = execution.jobs.filter((job) => job.status === JOB_STATUS.PENDING);
+ await JobRepo.update({
+ values: {
+ status: JOB_STATUS.CANCELED,
+ },
+ filter: {
+ id: pendingJobs.map((job) => job.id),
+ },
+ individualHooks: false,
+ transaction,
+ });
+ });
+
+ context.body = execution;
+ await next();
+}