diff --git a/packages/plugins/workflow/src/client/schemas/executions.tsx b/packages/plugins/workflow/src/client/schemas/executions.tsx index 1cc4130e1..5ad34d0fe 100644 --- a/packages/plugins/workflow/src/client/schemas/executions.tsx +++ b/packages/plugins/workflow/src/client/schemas/executions.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { ISchema } from '@formily/react'; import { Link } from 'react-router-dom'; -import { useActionContext } from '@nocobase/client'; +import { useActionContext, useRecord, useResourceActionContext, useResourceContext } from '@nocobase/client'; import { ExecutionStatusOptions } from '../constants'; import { NAMESPACE } from '../locale'; +import { useTranslation } from 'react-i18next'; +import { message } from 'antd'; export const executionCollection = { name: 'executions', @@ -87,10 +89,31 @@ export const executionSchema = { }, }, properties: { - // filter: { - // type: 'object', - // 'x-component': 'Filter', - // } + clear: { + type: 'void', + title: '{{t("Clear")}}', + 'x-component': 'Action', + 'x-component-props': { + useAction() { + const { t } = useTranslation(); + const { refresh, defaultRequest } = useResourceActionContext(); + const { resource } = useResourceContext(); + const { setVisible } = useActionContext(); + return { + async run() { + await resource.destroy({ filter: defaultRequest.params?.filter }); + message.success(t('Operation succeeded')); + refresh(); + setVisible(false); + }, + }; + }, + confirm: { + title: `{{t("Clear all executions", { ns: "${NAMESPACE}" })}}`, + content: `{{t("Clear executions will not reset executed count, and started executions will not be deleted, are you sure you want to delete them all?", { ns: "${NAMESPACE}" })}}`, + }, + }, + }, }, }, table: { @@ -152,7 +175,7 @@ export const executionSchema = { split: '|', }, properties: { - config: { + link: { type: 'void', title: `{{t("Details", { ns: "${NAMESPACE}" })}}`, 'x-component': 'ExecutionLink', diff --git a/packages/plugins/workflow/src/locale/zh-CN.ts b/packages/plugins/workflow/src/locale/zh-CN.ts index 4220f88b1..f7b426639 100644 --- a/packages/plugins/workflow/src/locale/zh-CN.ts +++ b/packages/plugins/workflow/src/locale/zh-CN.ts @@ -1,6 +1,9 @@ export default { Workflow: '工作流', 'Execution history': '执行历史', + 'Clear all executions': '清除所有执行记录', + 'Clear executions will not reset executed count, and started executions will not be deleted, are you sure you want to delete them all?': + '清空执行记录不会重置执行次数,且执行中的也不会被删除,确定要删除所有执行记录吗?', Executed: '已执行', Reload: '重载', 'Trigger type': '触发方式', diff --git a/packages/plugins/workflow/src/server/__tests__/actions/executions.test.ts b/packages/plugins/workflow/src/server/__tests__/actions/executions.test.ts new file mode 100644 index 000000000..bbc265236 --- /dev/null +++ b/packages/plugins/workflow/src/server/__tests__/actions/executions.test.ts @@ -0,0 +1,74 @@ +import { MockServer } from '@nocobase/test'; +import Database from '@nocobase/database'; +import { getApp, sleep } from '..'; +import { EXECUTION_STATUS } from '../../constants'; + +describe('workflow > actions > executions', () => { + let app: MockServer; + let agent; + let db: Database; + let PostRepo; + let WorkflowModel; + let workflow; + + beforeEach(async () => { + app = await getApp(); + agent = app.agent(); + db = app.db; + WorkflowModel = db.getCollection('workflows').model; + PostRepo = db.getCollection('posts').repository; + + workflow = await WorkflowModel.create({ + enabled: true, + type: 'collection', + config: { + mode: 1, + collection: 'posts', + }, + }); + }); + + afterEach(async () => await app.destroy()); + + describe('destroy', () => { + it('completed execution could be deleted', async () => { + const post = await PostRepo.create({ values: { title: 't1' } }); + await sleep(500); + + const e1 = await workflow.getExecutions(); + expect(e1.length).toBe(1); + expect(e1[0].get('status')).toBe(EXECUTION_STATUS.RESOLVED); + + await agent.resource('executions').destroy({ + filter: { + key: workflow.key, + }, + }); + + const e2 = await workflow.getExecutions(); + expect(e2.length).toBe(0); + }); + + it('started execution could not be deleted', async () => { + await workflow.createNode({ + type: 'pending', + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + await sleep(500); + + const e1 = await workflow.getExecutions(); + expect(e1.length).toBe(1); + expect(e1[0].get('status')).toBe(EXECUTION_STATUS.STARTED); + + await agent.resource('executions').destroy({ + filter: { + key: workflow.key, + }, + }); + + const e2 = await workflow.getExecutions(); + expect(e2.length).toBe(1); + }); + }); +}); diff --git a/packages/plugins/workflow/src/server/__tests__/index.ts b/packages/plugins/workflow/src/server/__tests__/index.ts index f40e43223..b15154e71 100644 --- a/packages/plugins/workflow/src/server/__tests__/index.ts +++ b/packages/plugins/workflow/src/server/__tests__/index.ts @@ -37,6 +37,14 @@ export async function getApp({ manual, ...options }: MockAppOptions = {}): Promi }, }, + pending: { + run(node, input, processor) { + return { + status: JOB_STATUS.PENDING, + }; + }, + }, + 'prompt->error': { run(node, input, processor) { return { diff --git a/packages/plugins/workflow/src/server/actions/executions.ts b/packages/plugins/workflow/src/server/actions/executions.ts new file mode 100644 index 000000000..a757f7874 --- /dev/null +++ b/packages/plugins/workflow/src/server/actions/executions.ts @@ -0,0 +1,15 @@ +import actions, { Context } from '@nocobase/actions'; +import { Op } from '@nocobase/database'; +import { EXECUTION_STATUS } from '../constants'; + +export async function destroy(context: Context, next) { + context.action.mergeParams({ + filter: { + status: { + [Op.ne]: EXECUTION_STATUS.STARTED, + }, + }, + }); + + await actions.destroy(context, next); +} diff --git a/packages/plugins/workflow/src/server/actions/index.ts b/packages/plugins/workflow/src/server/actions/index.ts index 5b67d63f0..df3c6b3f8 100644 --- a/packages/plugins/workflow/src/server/actions/index.ts +++ b/packages/plugins/workflow/src/server/actions/index.ts @@ -1,5 +1,6 @@ import * as workflows from './workflows'; import * as nodes from './nodes'; +import * as executions from './executions'; function make(name, mod) { return Object.keys(mod).reduce( @@ -21,5 +22,6 @@ export default function ({ app }) { ...make('flow_nodes', { update: nodes.update, }), + ...make('executions', executions), }); }