diff --git a/packages/core/client/src/workflow/ExecutionResourceProvider.tsx b/packages/core/client/src/workflow/ExecutionResourceProvider.tsx
index f2f08323a..97a5f8e2f 100644
--- a/packages/core/client/src/workflow/ExecutionResourceProvider.tsx
+++ b/packages/core/client/src/workflow/ExecutionResourceProvider.tsx
@@ -11,7 +11,7 @@ export const ExecutionResourceProvider = ({ request, ...others }) => {
...request?.params,
filter: {
...(request?.params?.filter),
- workflowId: workflow.id
+ key: workflow.key
}
}
},
diff --git a/packages/core/client/src/workflow/WorkflowCanvas.tsx b/packages/core/client/src/workflow/WorkflowCanvas.tsx
index 71caa158a..e2bfb8f65 100644
--- a/packages/core/client/src/workflow/WorkflowCanvas.tsx
+++ b/packages/core/client/src/workflow/WorkflowCanvas.tsx
@@ -8,7 +8,6 @@ import {
useAPIClient,
useCompile,
useDocumentTitle,
- useRecord,
useResourceActionContext,
useResourceContext
} from '..';
@@ -79,13 +78,13 @@ export function WorkflowCanvas() {
refresh();
}
- async function onDuplicate() {
- const { data: { data: duplicated } } = await resource.duplicate({
+ async function onRevision() {
+ const { data: { data: revision } } = await resource.revision({
filterByTk: workflow[targetKey]
});
message.success(t('Operation succeeded'));
- history.push(`${duplicated.id}`);
+ history.push(`${revision.id}`);
}
return (
@@ -134,7 +133,7 @@ export function WorkflowCanvas() {
/>
{workflow.executed && !revisions.find(item => !item.executed && new Date(item.createdAt) > new Date(workflow.createdAt))
? (
-
+
)
: null
}
diff --git a/packages/core/client/src/workflow/schemas/executions.ts b/packages/core/client/src/workflow/schemas/executions.ts
index ea8579cb4..acad4a1c3 100644
--- a/packages/core/client/src/workflow/schemas/executions.ts
+++ b/packages/core/client/src/workflow/schemas/executions.ts
@@ -16,6 +16,17 @@ const collection = {
'x-read-pretty': true,
} as ISchema,
},
+ {
+ interface: 'number',
+ type: 'number',
+ name: 'workflowId',
+ uiSchema: {
+ type: 'number',
+ title: '{{t("Version")}}',
+ 'x-component': 'InputNumber',
+ 'x-read-pretty': true,
+ } as ISchema,
+ },
{
type: 'number',
name: 'status',
@@ -95,6 +106,18 @@ export const executionSchema = {
},
}
},
+ workflowId: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ workflowId: {
+ type: 'number',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ }
+ },
status: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
diff --git a/packages/plugins/workflow/src/__tests__/workflow.test.ts b/packages/plugins/workflow/src/__tests__/workflow.test.ts
index 90f3c3360..bf4883b1a 100644
--- a/packages/plugins/workflow/src/__tests__/workflow.test.ts
+++ b/packages/plugins/workflow/src/__tests__/workflow.test.ts
@@ -1,12 +1,12 @@
-import { Application } from '@nocobase/server';
+import { MockServer } from '@nocobase/test';
import Database from '@nocobase/database';
import { getApp } from '.';
-import { EXECUTION_STATUS } from '../constants';
describe('workflow > workflow', () => {
- let app: Application;
+ let app: MockServer;
+ let agent;
let db: Database;
let PostModel;
let PostRepo;
@@ -14,7 +14,7 @@ describe('workflow > workflow', () => {
beforeEach(async () => {
app = await getApp();
-
+ agent = app.agent();
db = app.db;
WorkflowModel = db.getCollection('workflows').model;
PostModel = db.getCollection('posts').model;
@@ -41,4 +41,55 @@ describe('workflow > workflow', () => {
expect(executions.length).toBe(0);
});
});
+
+ describe('revisions', () => {
+ it('executed count', async () => {
+ const w1 = await WorkflowModel.create({
+ enabled: true,
+ type: 'collection',
+ config: {
+ mode: 1,
+ collection: 'posts'
+ }
+ });
+ expect(w1.current).toBe(true);
+
+ const { body, status } = await agent.resource(`workflows`).revision({
+ filterByTk: w1.id
+ });
+
+ expect(status).toBe(200);
+ const { data: w2 } = body;
+ expect(w2.config).toMatchObject(w1.config);
+ expect(w2.key).toBe(w1.key);
+
+ const p1 = await PostRepo.create({ values: { title: 't1' } });
+
+ await WorkflowModel.update({
+ enabled: true
+ }, {
+ where: {
+ id: w2.id
+ },
+ individualHooks: true
+ });
+
+ const [w1next, w2next] = await WorkflowModel.findAll({
+ order: [['id', 'ASC']]
+ });
+
+ expect(w1next.enabled).toBe(false);
+ expect(w1next.current).toBe(false);
+ expect(w2next.enabled).toBe(true);
+ expect(w2next.current).toBe(true);
+
+ const p2 = await PostRepo.create({ values: { title: 't2' } });
+
+ const [e1] = await w1next.getExecutions();
+ const [e2] = await w2next.getExecutions();
+ expect(e1.key).toBe(e2.key);
+ expect(e1.workflowId).toBe(w1.id);
+ expect(e2.workflowId).toBe(w2.id);
+ });
+ });
});
diff --git a/packages/plugins/workflow/src/actions/workflows.ts b/packages/plugins/workflow/src/actions/workflows.ts
index 4e1dc32ad..996143f6d 100644
--- a/packages/plugins/workflow/src/actions/workflows.ts
+++ b/packages/plugins/workflow/src/actions/workflows.ts
@@ -1,53 +1,7 @@
import { Context, utils } from '@nocobase/actions';
-import { Op } from '@nocobase/database';
-export async function update(context: Context, next) {
- const { db } = context;
- const repository = utils.getRepositoryFromParams(context);
- const { filterByTk, values, whitelist, blacklist, filter, updateAssociationValues } = context.action.params;
-
- context.body = await db.sequelize.transaction(async transaction => {
- const others: { enabled?: boolean, current?: boolean } = {};
-
- if (values.enabled) {
- values.current = true;
- others.enabled = false;
- }
-
- if (values.current) {
- others.current = false;
- await repository.update({
- filter: {
- key: values.key,
- id: {
- [Op.ne]: filterByTk
- }
- },
- values: others,
- context,
- transaction
- });
- }
-
- const instance = await repository.update({
- filterByTk,
- values,
- whitelist,
- blacklist,
- filter,
- updateAssociationValues,
- context,
- transaction
- });
-
- return instance;
- });
-
- await next();
-}
-
function typeOf(value) {
if (Array.isArray(value)) {
return 'array';
@@ -81,7 +35,7 @@ function migrateConfig(config, oldToNew) {
return migrate(config);
}
-export async function duplicate(context: Context, next) {
+export async function revision(context: Context, next) {
const { db } = context;
const repository = utils.getRepositoryFromParams(context);
const { filterByTk } = context.action.params;
diff --git a/packages/plugins/workflow/src/collections/executions.ts b/packages/plugins/workflow/src/collections/executions.ts
index 6df4a317f..dbf17dbea 100644
--- a/packages/plugins/workflow/src/collections/executions.ts
+++ b/packages/plugins/workflow/src/collections/executions.ts
@@ -3,18 +3,18 @@ import { CollectionOptions } from '@nocobase/database';
export default {
name: 'executions',
model: 'ExecutionModel',
- title: '执行流程',
fields: [
{
- interface: 'linkTo',
type: 'belongsTo',
- name: 'workflow',
- title: '所属工作流'
+ name: 'workflow'
+ },
+ {
+ type: 'uid',
+ name: 'key'
},
{
type: 'boolean',
name: 'useTransaction',
- title: '使用事务',
defaultValue: false
},
{
@@ -23,22 +23,16 @@ export default {
defaultValue: null
},
{
- interface: 'linkTo',
type: 'hasMany',
name: 'jobs',
- title: '流程记录'
},
{
- interface: 'json',
type: 'jsonb',
name: 'context',
- title: '上下文数据'
},
{
- interface: 'select',
type: 'integer',
name: 'status',
- title: '状态'
}
]
} as CollectionOptions;
diff --git a/packages/plugins/workflow/src/collections/workflows.ts b/packages/plugins/workflow/src/collections/workflows.ts
index 22a6b09c8..8b1f9f70e 100644
--- a/packages/plugins/workflow/src/collections/workflows.ts
+++ b/packages/plugins/workflow/src/collections/workflows.ts
@@ -3,7 +3,6 @@ import { CollectionOptions } from '@nocobase/database';
export default {
name: 'workflows',
model: 'WorkflowModel',
- title: '自动化',
fields: [
{
name: 'key',
@@ -12,56 +11,52 @@ export default {
{
type: 'string',
name: 'title',
- title: '工作流名称',
required: true
},
{
type: 'boolean',
name: 'enabled',
- title: '启用',
defaultValue: false
},
{
type: 'text',
- name: 'description',
- title: '描述'
+ name: 'description'
},
{
type: 'string',
- title: '触发方式',
name: 'type',
required: true
},
{
type: 'jsonb',
- title: '触发配置',
name: 'config',
required: true,
defaultValue: {}
},
{
type: 'boolean',
- title: '使用事务',
name: 'useTransaction',
defaultValue: true
},
{
type: 'hasMany',
name: 'nodes',
- target: 'flow_nodes',
- title: '流程节点'
+ target: 'flow_nodes'
},
{
type: 'hasMany',
- name: 'executions',
- target: 'executions',
- title: '触发执行'
+ name: 'executions'
},
{
type: 'integer',
name: 'executed',
defaultValue: 0
},
+ {
+ type: 'integer',
+ name: 'allExecuted',
+ defaultValue: 0
+ },
{
type: 'boolean',
name: 'current',
diff --git a/packages/plugins/workflow/src/models/Execution.ts b/packages/plugins/workflow/src/models/Execution.ts
index f28cbae35..017cba957 100644
--- a/packages/plugins/workflow/src/models/Execution.ts
+++ b/packages/plugins/workflow/src/models/Execution.ts
@@ -26,6 +26,8 @@ export default class ExecutionModel extends Model {
declare createdAt: Date;
declare updatedAt: Date;
+ declare key: string;
+
declare workflow?: WorkflowModel;
declare getWorkflow: BelongsToGetAssociationMixin;
diff --git a/packages/plugins/workflow/src/models/Workflow.ts b/packages/plugins/workflow/src/models/Workflow.ts
index 38e81e00f..845c77b8b 100644
--- a/packages/plugins/workflow/src/models/Workflow.ts
+++ b/packages/plugins/workflow/src/models/Workflow.ts
@@ -9,8 +9,10 @@ export default class WorkflowModel extends Model {
declare static database: Database;
declare id: number;
+ declare key: string;
declare title: string;
declare enabled: boolean;
+ declare current: boolean;
declare description?: string;
declare type: string;
declare config: any;
@@ -63,6 +65,7 @@ export default class WorkflowModel extends Model {
const execution = await this.createExecution({
context,
+ key: this.key,
status: EXECUTION_STATUS.STARTED,
useTransaction: this.useTransaction,
transaction: transaction.id
@@ -73,6 +76,21 @@ export default class WorkflowModel extends Model {
// NOTE: not to trigger afterUpdate hook here
await this.update({ executed }, { transaction, hooks: false });
+ const allExecuted = await (execution.constructor).count({
+ where: {
+ key: this.key
+ },
+ transaction
+ });
+ await (this.constructor).update({
+ allExecuted
+ }, {
+ where: {
+ key: this.key
+ },
+ transaction
+ });
+
execution.workflow = this;
await execution.start({ transaction });
diff --git a/packages/plugins/workflow/src/server.ts b/packages/plugins/workflow/src/server.ts
index a8dd52fbc..ac6481af9 100644
--- a/packages/plugins/workflow/src/server.ts
+++ b/packages/plugins/workflow/src/server.ts
@@ -1,6 +1,7 @@
import path from 'path';
import { Plugin } from '@nocobase/server';
+import { Op } from '@nocobase/database';
import WorkflowModel from './models/Workflow';
import ExecutionModel from './models/Execution';
@@ -33,6 +34,10 @@ export default class extends Plugin {
initTriggers(this);
+ db.on('workflows.beforeSave', this.setCurrent);
+ db.on('workflows.afterSave', (model: WorkflowModel) => this.toggle(model));
+ db.on('workflows.afterDestroy', (model: WorkflowModel) => this.toggle(model, false));
+
// [Life Cycle]:
// * load all workflows in db
// * add all hooks for enabled workflows
@@ -46,15 +51,34 @@ export default class extends Plugin {
workflows.forEach((workflow: WorkflowModel) => {
this.toggle(workflow);
});
-
- db.on('workflows.afterSave', (model: WorkflowModel) => this.toggle(model));
- db.on('workflows.afterDestroy', (model: WorkflowModel) => this.toggle(model, false));
});
-
// [Life Cycle]: initialize all necessary seed data
// this.app.on('db.init', async () => {});
}
+ setCurrent = async (workflow: WorkflowModel, options) => {
+ const others: { enabled?: boolean, current?: boolean } = {};
+
+ if (workflow.enabled) {
+ workflow.set('current', true);
+ others.enabled = false;
+ }
+
+ if (workflow.current) {
+ others.current = false;
+ await (workflow.constructor).update(others, {
+ where: {
+ key: workflow.key,
+ id: {
+ [Op.ne]: workflow.id
+ }
+ },
+ individualHooks: true,
+ transaction: options.transaction
+ });
+ }
+ }
+
toggle(workflow: WorkflowModel, enable?: boolean) {
const type = workflow.get('type');
const trigger = this.triggers.get(type);
diff --git a/packages/plugins/workflow/src/triggers/schedule.ts b/packages/plugins/workflow/src/triggers/schedule.ts
index e2ae26f33..2b1e712aa 100644
--- a/packages/plugins/workflow/src/triggers/schedule.ts
+++ b/packages/plugins/workflow/src/triggers/schedule.ts
@@ -320,7 +320,7 @@ function nextInCycle(this: ScheduleTrigger, workflow, now: Date): boolean {
export default class ScheduleTrigger implements Trigger {
static CacheRules = [
// ({ enabled }) => enabled,
- ({ config, executed }) => config.limit ? executed < config.limit : true,
+ ({ config, allExecuted }) => config.limit ? allExecuted < config.limit : true,
({ config }) => ['repeat', 'startsOn'].some(key => config[key]),
nextInCycle,
function(workflow, now) {
@@ -331,7 +331,7 @@ export default class ScheduleTrigger implements Trigger {
];
static TriggerRules = [
- ({ config, executed }) => config.limit ? executed < config.limit : true,
+ ({ config, allExecuted }) => config.limit ? allExecuted < config.limit : true,
({ config }) => ['repeat', 'startsOn'].some(key => config[key]),
function (workflow, now) {
const { repeat } = workflow.config;