mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 10:17:00 +00:00
feat(plugin-workflow): allow to configure auto delete execution in history (#2423)
* feat(plugin-workflow): allow to configure auto delete execution * fix(plugin-workflow): fix locale
This commit is contained in:
parent
9881d69def
commit
fa43d9c870
@ -342,7 +342,9 @@ export const useCreateAction = () => {
|
|||||||
field.data.loading = false;
|
field.data.loading = false;
|
||||||
refresh();
|
refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
field.data.loading=false;
|
if (field.data) {
|
||||||
|
field.data.loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ import { lang } from './locale';
|
|||||||
import { instructions } from './nodes';
|
import { instructions } from './nodes';
|
||||||
import { workflowSchema } from './schemas/workflows';
|
import { workflowSchema } from './schemas/workflows';
|
||||||
import { triggers } from './triggers';
|
import { triggers } from './triggers';
|
||||||
|
import { ExecutionStatusSelect } from './components/ExecutionStatusSelect';
|
||||||
|
|
||||||
// registerField(expressionField.group, 'expression', expressionField);
|
// registerField(expressionField.group, 'expression', expressionField);
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ function WorkflowPane() {
|
|||||||
ExecutionResourceProvider,
|
ExecutionResourceProvider,
|
||||||
ExecutionLink,
|
ExecutionLink,
|
||||||
OpenDrawer,
|
OpenDrawer,
|
||||||
|
ExecutionStatusSelect,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { Select, Tag } from 'antd';
|
||||||
|
|
||||||
|
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<HTMLSpanElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}, []);
|
||||||
|
const { color } = ExecutionStatusOptionsMap[props.value] ?? {};
|
||||||
|
return (
|
||||||
|
<Tag color={color} onMouseDown={onPreventMouseDown} closable={props.closable} onClose={props.onClose}>
|
||||||
|
{label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExecutionStatusOption(props) {
|
||||||
|
const compile = useCompile();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<LabelTag {...props} />
|
||||||
|
{props.description ? <span>{compile(props.description)}</span> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ExecutionStatusSelect({ ...props }) {
|
||||||
|
return (
|
||||||
|
<Select {...props} mode={props.multiple ? 'multiple' : null} optionLabelProp="label" tagRender={LabelTag}>
|
||||||
|
{ExecutionStatusOptions.filter((item) => Boolean(item.value) && item.value !== EXECUTION_STATUS.ABORTED).map(
|
||||||
|
(option) => (
|
||||||
|
<Select.Option key={option.value} {...option}>
|
||||||
|
<ExecutionStatusOption {...option} />
|
||||||
|
</Select.Option>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
@ -20,14 +20,54 @@ export const EXECUTION_STATUS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ExecutionStatusOptions = [
|
export const ExecutionStatusOptions = [
|
||||||
{ value: EXECUTION_STATUS.QUEUEING, label: `{{t("Queueing", { ns: "${NAMESPACE}" })}}`, color: 'blue' },
|
{
|
||||||
{ value: EXECUTION_STATUS.STARTED, label: `{{t("On going", { ns: "${NAMESPACE}" })}}`, color: 'gold' },
|
value: EXECUTION_STATUS.QUEUEING,
|
||||||
{ value: EXECUTION_STATUS.RESOLVED, label: `{{t("Resolved", { ns: "${NAMESPACE}" })}}`, color: 'green' },
|
label: `{{t("Queueing", { ns: "${NAMESPACE}" })}}`,
|
||||||
{ value: EXECUTION_STATUS.FAILED, label: `{{t("Failed", { ns: "${NAMESPACE}" })}}`, color: 'red' },
|
color: 'blue',
|
||||||
{ value: EXECUTION_STATUS.ERROR, label: `{{t("Error", { ns: "${NAMESPACE}" })}}`, color: 'red' },
|
description: `{{t("Triggered but still waiting in queue to execute.", { ns: "${NAMESPACE}" })}}`,
|
||||||
{ value: EXECUTION_STATUS.ABORTED, label: `{{t("Aborted", { ns: "${NAMESPACE}" })}}`, color: 'red' },
|
},
|
||||||
{ value: EXECUTION_STATUS.CANCELED, label: `{{t("Canceled", { ns: "${NAMESPACE}" })}}`, color: 'volcano' },
|
{
|
||||||
{ value: EXECUTION_STATUS.REJECTED, label: `{{t("Rejected", { ns: "${NAMESPACE}" })}}`, color: 'volcano' },
|
value: EXECUTION_STATUS.STARTED,
|
||||||
|
label: `{{t("On going", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
color: 'gold',
|
||||||
|
description: `{{t("Started and executing, maybe waiting for an async callback (manual, delay etc.).", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: EXECUTION_STATUS.RESOLVED,
|
||||||
|
label: `{{t("Resolved", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
color: 'green',
|
||||||
|
description: `{{t("Successfully finished.", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: EXECUTION_STATUS.FAILED,
|
||||||
|
label: `{{t("Failed", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
color: 'red',
|
||||||
|
description: `{{t("Failed to satisfy node configurations.", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: EXECUTION_STATUS.ERROR,
|
||||||
|
label: `{{t("Error", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
color: 'red',
|
||||||
|
description: `{{t("Some node meets error.", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: EXECUTION_STATUS.ABORTED,
|
||||||
|
label: `{{t("Aborted", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
color: 'red',
|
||||||
|
description: `{{t("Running of some node was aborted by program flow.", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: EXECUTION_STATUS.CANCELED,
|
||||||
|
label: `{{t("Canceled", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
color: 'volcano',
|
||||||
|
description: `{{t("Manually canceled whole execution when waiting.", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: EXECUTION_STATUS.REJECTED,
|
||||||
|
label: `{{t("Rejected", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
color: 'volcano',
|
||||||
|
description: `{{t("Rejected from a manual node.", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ExecutionStatusOptionsMap = ExecutionStatusOptions.reduce(
|
export const ExecutionStatusOptionsMap = ExecutionStatusOptions.reduce(
|
||||||
|
@ -74,9 +74,49 @@ const collection = {
|
|||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
} as ISchema,
|
} as ISchema,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
name: 'options',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const workflowFieldset = {
|
||||||
|
title: {
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: 'object',
|
||||||
|
'x-component': 'fieldset',
|
||||||
|
properties: {
|
||||||
|
useTransaction: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `{{ t("Use transaction", { ns: "${NAMESPACE}" }) }}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
},
|
||||||
|
deleteExecutionOnStatus: {
|
||||||
|
type: 'array',
|
||||||
|
title: `{{ t("Auto delete history when execution is on end status", { ns: "${NAMESPACE}" }) }}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ExecutionStatusSelect',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const workflowSchema: ISchema = {
|
export const workflowSchema: ISchema = {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
properties: {
|
properties: {
|
||||||
@ -132,18 +172,7 @@ export const workflowSchema: ISchema = {
|
|||||||
},
|
},
|
||||||
title: '{{t("Add new")}}',
|
title: '{{t("Add new")}}',
|
||||||
properties: {
|
properties: {
|
||||||
title: {
|
...workflowFieldset,
|
||||||
'x-component': 'CollectionField',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
'x-component': 'CollectionField',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
'x-component': 'CollectionField',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
},
|
|
||||||
footer: {
|
footer: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Action.Drawer.Footer',
|
'x-component': 'Action.Drawer.Footer',
|
||||||
@ -283,7 +312,7 @@ export const workflowSchema: ISchema = {
|
|||||||
split: '|',
|
split: '|',
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
config: {
|
view: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'WorkflowLink',
|
'x-component': 'WorkflowLink',
|
||||||
},
|
},
|
||||||
@ -304,18 +333,7 @@ export const workflowSchema: ISchema = {
|
|||||||
},
|
},
|
||||||
title: '{{ t("Edit") }}',
|
title: '{{ t("Edit") }}',
|
||||||
properties: {
|
properties: {
|
||||||
title: {
|
...workflowFieldset,
|
||||||
'x-component': 'CollectionField',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
},
|
|
||||||
enabled: {
|
|
||||||
'x-component': 'CollectionField',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
'x-component': 'CollectionField',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
},
|
|
||||||
footer: {
|
footer: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Action.Drawer.Footer',
|
'x-component': 'Action.Drawer.Footer',
|
||||||
@ -403,18 +421,18 @@ export const workflowSchema: ISchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// delete: {
|
delete: {
|
||||||
// type: 'void',
|
type: 'void',
|
||||||
// title: '{{ t("Delete") }}',
|
title: '{{ t("Delete") }}',
|
||||||
// 'x-component': 'Action.Link',
|
'x-component': 'Action.Link',
|
||||||
// 'x-component-props': {
|
'x-component-props': {
|
||||||
// confirm: {
|
confirm: {
|
||||||
// title: "{{t('Delete record')}}",
|
title: "{{t('Delete record')}}",
|
||||||
// content: "{{t('Are you sure you want to delete it?')}}",
|
content: "{{t('Are you sure you want to delete it?')}}",
|
||||||
// },
|
},
|
||||||
// useAction: '{{ cm.useDestroyActionAndRefreshCM }}',
|
useAction: '{{ cm.useDestroyActionAndRefreshCM }}',
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -65,7 +65,7 @@ const useStyles = createStyles(({ css, token }) => {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
time {
|
time {
|
||||||
width: 14em;
|
width: 12em;
|
||||||
color: ${token.colorText};
|
color: ${token.colorText};
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ export default {
|
|||||||
'Delete a main version will cause all other revisions to be deleted too.': '删除主版本将导致其他版本一并被删除。',
|
'Delete a main version will cause all other revisions to be deleted too.': '删除主版本将导致其他版本一并被删除。',
|
||||||
Loading: '加载中',
|
Loading: '加载中',
|
||||||
'Load failed': '加载失败',
|
'Load failed': '加载失败',
|
||||||
|
'Use transaction': '启用事务',
|
||||||
|
'Auto delete history when execution is on end status': '执行结束后自动删除对应状态的历史记录',
|
||||||
Trigger: '触发器',
|
Trigger: '触发器',
|
||||||
'Trigger variables': '触发器变量',
|
'Trigger variables': '触发器变量',
|
||||||
'Trigger data': '触发数据',
|
'Trigger data': '触发数据',
|
||||||
@ -103,6 +105,16 @@ export default {
|
|||||||
Canceled: '已取消',
|
Canceled: '已取消',
|
||||||
Rejected: '已拒绝',
|
Rejected: '已拒绝',
|
||||||
|
|
||||||
|
'Triggered but still waiting in queue to execute.': '已触发但仍在队列中等待执行。',
|
||||||
|
'Started and executing, maybe waiting for an async callback (manual, delay etc.).':
|
||||||
|
'已开始执行,可能在等待异步回调(人工、延时等)。',
|
||||||
|
'Successfully finished.': '成功完成。',
|
||||||
|
'Failed to satisfy node configurations.': '未满足节点配置造成的失败。',
|
||||||
|
'Some node meets error.': '某个节点出错。',
|
||||||
|
'Running of some node was aborted by program flow.': '某个节点被程序流程终止。',
|
||||||
|
'Manually canceled whole execution when waiting.': '等待时被手动取消整个执行。',
|
||||||
|
'Rejected from a manual node.': '被人工节点拒绝继续。',
|
||||||
|
|
||||||
'Continue the process': '继续流程',
|
'Continue the process': '继续流程',
|
||||||
'Terminate the process': '终止流程',
|
'Terminate the process': '终止流程',
|
||||||
'Save temporarily': '暂存',
|
'Save temporarily': '暂存',
|
||||||
|
@ -253,7 +253,6 @@ export default class WorkflowPlugin extends Plugin {
|
|||||||
context,
|
context,
|
||||||
key: workflow.key,
|
key: workflow.key,
|
||||||
status: EXECUTION_STATUS.QUEUEING,
|
status: EXECUTION_STATUS.QUEUEING,
|
||||||
useTransaction: workflow.useTransaction,
|
|
||||||
},
|
},
|
||||||
{ transaction },
|
{ transaction },
|
||||||
);
|
);
|
||||||
@ -350,6 +349,9 @@ export default class WorkflowPlugin extends Plugin {
|
|||||||
this.getLogger(execution.workflowId).info(
|
this.getLogger(execution.workflowId).info(
|
||||||
`execution (${execution.id}) finished with status: ${execution.status}`,
|
`execution (${execution.id}) finished with status: ${execution.status}`,
|
||||||
);
|
);
|
||||||
|
if (execution.status && execution.workflow.options?.deleteExecutionOnStatus?.includes(execution.status)) {
|
||||||
|
await execution.destroy();
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.getLogger(execution.workflowId).error(`execution (${execution.id}) error: ${err.message}`, err);
|
this.getLogger(execution.workflowId).error(`execution (${execution.id}) error: ${err.message}`, err);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,10 @@ export default class Processor {
|
|||||||
jobsMap = new Map<number, JobModel>();
|
jobsMap = new Map<number, JobModel>();
|
||||||
jobsMapByNodeId: { [key: number]: any } = {};
|
jobsMapByNodeId: { [key: number]: any } = {};
|
||||||
|
|
||||||
constructor(public execution: ExecutionModel, public options: ProcessorOptions) {
|
constructor(
|
||||||
|
public execution: ExecutionModel,
|
||||||
|
public options: ProcessorOptions,
|
||||||
|
) {
|
||||||
this.logger = options.plugin.getLogger(execution.workflowId);
|
this.logger = options.plugin.getLogger(execution.workflowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +66,7 @@ export default class Processor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getTransaction() {
|
private async getTransaction() {
|
||||||
if (!this.execution.useTransaction) {
|
if (!this.execution.workflow.options?.useTransaction) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,15 +79,15 @@ export default class Processor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async prepare() {
|
public async prepare() {
|
||||||
|
const { execution } = this;
|
||||||
|
if (!execution.workflow) {
|
||||||
|
execution.workflow = await execution.getWorkflow();
|
||||||
|
}
|
||||||
|
|
||||||
const transaction = await this.getTransaction();
|
const transaction = await this.getTransaction();
|
||||||
this.transaction = transaction;
|
this.transaction = transaction;
|
||||||
|
|
||||||
const { execution } = this;
|
const nodes = await execution.workflow.getNodes();
|
||||||
if (!execution.workflow) {
|
|
||||||
execution.workflow = await execution.getWorkflow({ transaction });
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodes = await execution.workflow.getNodes({ transaction });
|
|
||||||
|
|
||||||
this.makeNodes(nodes);
|
this.makeNodes(nodes);
|
||||||
|
|
||||||
|
@ -395,4 +395,96 @@ describe('workflow > Plugin', () => {
|
|||||||
expect(e2.status).toBe(EXECUTION_STATUS.QUEUEING);
|
expect(e2.status).toBe(EXECUTION_STATUS.QUEUEING);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('options.deleteExecutionOnStatus', () => {
|
||||||
|
it('no configured should not be deleted', async () => {
|
||||||
|
const w1 = await WorkflowModel.create({
|
||||||
|
enabled: true,
|
||||||
|
type: 'collection',
|
||||||
|
config: {
|
||||||
|
mode: 1,
|
||||||
|
collection: 'posts',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const p1 = await PostRepo.create({ values: { title: 't1' } });
|
||||||
|
|
||||||
|
await sleep(500);
|
||||||
|
|
||||||
|
const executions = await w1.getExecutions();
|
||||||
|
expect(executions.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('status on started should not be deleted', async () => {
|
||||||
|
const w1 = await WorkflowModel.create({
|
||||||
|
enabled: true,
|
||||||
|
options: {
|
||||||
|
deleteExecutionOnStatus: [EXECUTION_STATUS.STARTED],
|
||||||
|
},
|
||||||
|
type: 'collection',
|
||||||
|
config: {
|
||||||
|
mode: 1,
|
||||||
|
collection: 'posts',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await w1.createNode({
|
||||||
|
type: 'pending',
|
||||||
|
});
|
||||||
|
|
||||||
|
const p1 = await PostRepo.create({ values: { title: 't1' } });
|
||||||
|
|
||||||
|
await sleep(500);
|
||||||
|
|
||||||
|
const executions = await w1.getExecutions();
|
||||||
|
expect(executions.length).toBe(1);
|
||||||
|
expect(executions[0].status).toBe(EXECUTION_STATUS.STARTED);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('configured resolved status should be deleted', async () => {
|
||||||
|
const w1 = await WorkflowModel.create({
|
||||||
|
enabled: true,
|
||||||
|
options: {
|
||||||
|
deleteExecutionOnStatus: [EXECUTION_STATUS.RESOLVED],
|
||||||
|
},
|
||||||
|
type: 'collection',
|
||||||
|
config: {
|
||||||
|
mode: 1,
|
||||||
|
collection: 'posts',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const p1 = await PostRepo.create({ values: { title: 't1' } });
|
||||||
|
|
||||||
|
await sleep(500);
|
||||||
|
|
||||||
|
const executions = await w1.getExecutions();
|
||||||
|
expect(executions.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('configured error status should be deleted', async () => {
|
||||||
|
const w1 = await WorkflowModel.create({
|
||||||
|
enabled: true,
|
||||||
|
options: {
|
||||||
|
deleteExecutionOnStatus: [EXECUTION_STATUS.ERROR],
|
||||||
|
},
|
||||||
|
type: 'collection',
|
||||||
|
config: {
|
||||||
|
mode: 1,
|
||||||
|
collection: 'posts',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await w1.createNode({
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
|
||||||
|
const p1 = await PostRepo.create({ values: { title: 't1' } });
|
||||||
|
|
||||||
|
await sleep(500);
|
||||||
|
|
||||||
|
const executions = await w1.getExecutions();
|
||||||
|
expect(executions.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,7 @@ export async function update(context: Context, next) {
|
|||||||
const repository = utils.getRepositoryFromParams(context) as Repository;
|
const repository = utils.getRepositoryFromParams(context) as Repository;
|
||||||
const { filterByTk, values } = context.action.params;
|
const { filterByTk, values } = context.action.params;
|
||||||
context.action.mergeParams({
|
context.action.mergeParams({
|
||||||
whitelist: ['title', 'description', 'enabled', 'config'],
|
whitelist: ['title', 'description', 'enabled', 'config', 'options'],
|
||||||
});
|
});
|
||||||
// only enable/disable
|
// only enable/disable
|
||||||
if (Object.keys(values).includes('config')) {
|
if (Object.keys(values).includes('config')) {
|
||||||
|
@ -76,6 +76,11 @@ export default function () {
|
|||||||
constraints: false,
|
constraints: false,
|
||||||
onDelete: 'NO ACTION',
|
onDelete: 'NO ACTION',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'jsonb',
|
||||||
|
name: 'options',
|
||||||
|
defaultValue: {},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
// NOTE: use unique index for avoiding deadlock in mysql when setCurrent
|
// NOTE: use unique index for avoiding deadlock in mysql when setCurrent
|
||||||
indexes: [
|
indexes: [
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import { Migration } from '@nocobase/server';
|
||||||
|
|
||||||
|
export default class extends Migration {
|
||||||
|
async up() {
|
||||||
|
const match = await this.app.version.satisfies('<0.11.0-alpha.2');
|
||||||
|
if (!match) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { db } = this.context;
|
||||||
|
const WorkflowRepo = db.getRepository('flow_nodes');
|
||||||
|
await db.sequelize.transaction(async (transaction) => {
|
||||||
|
const workflows = await WorkflowRepo.find({
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
await workflows.reduce(
|
||||||
|
(promise, workflow) =>
|
||||||
|
promise.then(() => {
|
||||||
|
workflow.set('options', {
|
||||||
|
useTransaction: workflow.get('useTransaction'),
|
||||||
|
});
|
||||||
|
workflow.changed('options', true);
|
||||||
|
return workflow.save({
|
||||||
|
silent: true,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
Promise.resolve(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user