mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-02 03:02:19 +08:00
feat(plugin-workflow): add workflow task panel in toolbar (#5858)
* feat(plugin-workflow): add workflow task panel in toolbar * fix(plugin-workflow): fix type * fix(plugin-workflow): fix manual task list filter * fix(plugin-workflow): add tooltip for task button
This commit is contained in:
parent
631cea1df9
commit
56c03f3fef
@ -7,21 +7,45 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ExtendCollectionsProvider, storePopupContext } from '@nocobase/client';
|
import { ExtendCollectionsProvider, storePopupContext, useRequest } from '@nocobase/client';
|
||||||
import React, { FC } from 'react';
|
import React, { createContext, FC, useContext } from 'react';
|
||||||
import { getWorkflowTodoViewActionSchema, nodeCollection, todoCollection, workflowCollection } from './WorkflowTodo';
|
import { getWorkflowTodoViewActionSchema, nodeCollection, todoCollection, workflowCollection } from './WorkflowTodo';
|
||||||
|
import { JOB_STATUS } from '@nocobase/plugin-workflow/client';
|
||||||
|
|
||||||
const collections = [nodeCollection, workflowCollection, todoCollection];
|
const collections = [nodeCollection, workflowCollection, todoCollection];
|
||||||
|
|
||||||
|
const ManualTaskCountRequestContext = createContext({});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. 扩展几个工作流相关的 collection,防止在区块中因找不到 collection 而报错;
|
* 1. 扩展几个工作流相关的 collection,防止在区块中因找不到 collection 而报错;
|
||||||
* @param props
|
* @param props
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const WorkflowManualProvider: FC = (props) => {
|
export const WorkflowManualProvider: FC = (props) => {
|
||||||
return <ExtendCollectionsProvider collections={collections}>{props.children}</ExtendCollectionsProvider>;
|
const request = useRequest<any>(
|
||||||
|
{
|
||||||
|
resource: 'users_jobs',
|
||||||
|
action: 'countMine',
|
||||||
|
params: {
|
||||||
|
filter: {
|
||||||
|
status: JOB_STATUS.PENDING,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ manual: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExtendCollectionsProvider collections={collections}>
|
||||||
|
<ManualTaskCountRequestContext.Provider value={request}>{props.children}</ManualTaskCountRequestContext.Provider>
|
||||||
|
</ExtendCollectionsProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function useCountRequest() {
|
||||||
|
return useContext(ManualTaskCountRequestContext);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 2. 将区块相关的按钮 Schema 缓存起来,这样就可以在弹窗中获取到 Schema,进而实现“弹窗 URL”的功能;
|
* 2. 将区块相关的按钮 Schema 缓存起来,这样就可以在弹窗中获取到 Schema,进而实现“弹窗 URL”的功能;
|
||||||
*/
|
*/
|
||||||
|
@ -14,11 +14,13 @@ import React, { createContext, useContext, useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
useCollection,
|
SchemaInitializerItem,
|
||||||
useCollectionRecordData,
|
useCollectionRecordData,
|
||||||
useCompile,
|
useCompile,
|
||||||
useOpenModeContext,
|
useOpenModeContext,
|
||||||
usePlugin,
|
usePlugin,
|
||||||
|
useSchemaInitializer,
|
||||||
|
useSchemaInitializerItem,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -44,6 +46,7 @@ import WorkflowPlugin, {
|
|||||||
import { NAMESPACE, useLang } from '../locale';
|
import { NAMESPACE, useLang } from '../locale';
|
||||||
import { FormBlockProvider } from './instruction/FormBlockProvider';
|
import { FormBlockProvider } from './instruction/FormBlockProvider';
|
||||||
import { ManualFormType, manualFormTypes } from './instruction/SchemaConfig';
|
import { ManualFormType, manualFormTypes } from './instruction/SchemaConfig';
|
||||||
|
import { TableOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
export const nodeCollection = {
|
export const nodeCollection = {
|
||||||
title: `{{t("Task", { ns: "${NAMESPACE}" })}}`,
|
title: `{{t("Task", { ns: "${NAMESPACE}" })}}`,
|
||||||
@ -232,9 +235,94 @@ function UserJobStatusColumn(props) {
|
|||||||
return props.children;
|
return props.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WorkflowTodo: React.FC & { Drawer: React.FC; Decorator: React.FC } = () => {
|
const tableColumns = {
|
||||||
|
workflow: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: null,
|
||||||
|
},
|
||||||
|
title: `{{t("Workflow", { ns: "workflow" })}}`,
|
||||||
|
properties: {
|
||||||
|
workflow: {
|
||||||
|
'x-component': 'WorkflowColumn',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: null,
|
||||||
|
},
|
||||||
|
title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
properties: {
|
||||||
|
node: {
|
||||||
|
'x-component': 'NodeColumn',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
title: `{{t("Status", { ns: "workflow" })}}`,
|
||||||
|
properties: {
|
||||||
|
status: {
|
||||||
|
type: 'number',
|
||||||
|
'x-decorator': 'UserJobStatusColumn',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: 140,
|
||||||
|
},
|
||||||
|
title: `{{t("Assignee", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
properties: {
|
||||||
|
user: {
|
||||||
|
'x-component': 'UserColumn',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
createdAt: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkflowTodo: React.FC<{ columns?: string[] }> & {
|
||||||
|
Initializer: React.FC;
|
||||||
|
Drawer: React.FC;
|
||||||
|
Decorator: React.FC;
|
||||||
|
TaskBlock: React.FC;
|
||||||
|
} = (props) => {
|
||||||
|
const { columns = Object.keys(tableColumns) } = props;
|
||||||
const { defaultOpenMode } = useOpenModeContext();
|
const { defaultOpenMode } = useOpenModeContext();
|
||||||
const collection = useCollection();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
@ -301,86 +389,13 @@ export const WorkflowTodo: React.FC & { Drawer: React.FC; Decorator: React.FC }
|
|||||||
},
|
},
|
||||||
title: '{{t("Actions")}}',
|
title: '{{t("Actions")}}',
|
||||||
properties: {
|
properties: {
|
||||||
view: getWorkflowTodoViewActionSchema({ defaultOpenMode, collectionName: collection.name }),
|
view: getWorkflowTodoViewActionSchema({ defaultOpenMode, collectionName: 'users_jobs' }),
|
||||||
},
|
|
||||||
},
|
|
||||||
node: {
|
|
||||||
type: 'void',
|
|
||||||
'x-decorator': 'TableV2.Column.Decorator',
|
|
||||||
'x-component': 'TableV2.Column',
|
|
||||||
'x-component-props': {
|
|
||||||
width: null,
|
|
||||||
},
|
|
||||||
title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`,
|
|
||||||
properties: {
|
|
||||||
node: {
|
|
||||||
'x-component': 'NodeColumn',
|
|
||||||
'x-read-pretty': true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
workflow: {
|
|
||||||
type: 'void',
|
|
||||||
'x-decorator': 'TableV2.Column.Decorator',
|
|
||||||
'x-component': 'TableV2.Column',
|
|
||||||
'x-component-props': {
|
|
||||||
width: null,
|
|
||||||
},
|
|
||||||
title: `{{t("Workflow", { ns: "workflow" })}}`,
|
|
||||||
properties: {
|
|
||||||
workflow: {
|
|
||||||
'x-component': 'WorkflowColumn',
|
|
||||||
'x-read-pretty': true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
type: 'void',
|
|
||||||
'x-decorator': 'TableV2.Column.Decorator',
|
|
||||||
'x-component': 'TableV2.Column',
|
|
||||||
'x-component-props': {
|
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
title: `{{t("Status", { ns: "workflow" })}}`,
|
|
||||||
properties: {
|
|
||||||
status: {
|
|
||||||
type: 'number',
|
|
||||||
'x-decorator': 'UserJobStatusColumn',
|
|
||||||
'x-component': 'CollectionField',
|
|
||||||
'x-read-pretty': true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
type: 'void',
|
|
||||||
'x-decorator': 'TableV2.Column.Decorator',
|
|
||||||
'x-component': 'TableV2.Column',
|
|
||||||
'x-component-props': {
|
|
||||||
width: 140,
|
|
||||||
},
|
|
||||||
title: `{{t("Assignee", { ns: "${NAMESPACE}" })}}`,
|
|
||||||
properties: {
|
|
||||||
user: {
|
|
||||||
'x-component': 'UserColumn',
|
|
||||||
'x-read-pretty': true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: 'void',
|
|
||||||
'x-decorator': 'TableV2.Column.Decorator',
|
|
||||||
'x-component': 'TableV2.Column',
|
|
||||||
'x-component-props': {
|
|
||||||
width: 160,
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
createdAt: {
|
|
||||||
type: 'string',
|
|
||||||
'x-component': 'CollectionField',
|
|
||||||
'x-read-pretty': true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
...columns.reduce((schema, key) => {
|
||||||
|
schema[key] = tableColumns[key];
|
||||||
|
return schema;
|
||||||
|
}, {}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -410,6 +425,7 @@ export function getWorkflowTodoViewActionSchema({ defaultOpenMode, collectionNam
|
|||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
drawer: {
|
drawer: {
|
||||||
|
type: 'void',
|
||||||
'x-component': WorkflowTodo.Drawer,
|
'x-component': WorkflowTodo.Drawer,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -671,7 +687,8 @@ function Drawer() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Decorator({ params = {}, children }) {
|
function Decorator(props) {
|
||||||
|
const { params = {}, children } = props;
|
||||||
const blockProps = {
|
const blockProps = {
|
||||||
collection: 'users_jobs',
|
collection: 'users_jobs',
|
||||||
resource: 'users_jobs',
|
resource: 'users_jobs',
|
||||||
@ -680,6 +697,9 @@ function Decorator({ params = {}, children }) {
|
|||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
sort: ['-createdAt'],
|
sort: ['-createdAt'],
|
||||||
...params,
|
...params,
|
||||||
|
filter: {
|
||||||
|
...params.filter,
|
||||||
|
},
|
||||||
appends: ['user', 'node', 'workflow', 'execution.status'],
|
appends: ['user', 'node', 'workflow', 'execution.status'],
|
||||||
except: ['node.config', 'workflow.config', 'workflow.options'],
|
except: ['node.config', 'workflow.config', 'workflow.options'],
|
||||||
},
|
},
|
||||||
@ -695,5 +715,79 @@ function Decorator({ params = {}, children }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Initializer() {
|
||||||
|
const itemConfig = useSchemaInitializerItem();
|
||||||
|
const { insert } = useSchemaInitializer();
|
||||||
|
return (
|
||||||
|
<SchemaInitializerItem
|
||||||
|
icon={<TableOutlined />}
|
||||||
|
{...itemConfig}
|
||||||
|
onClick={() => {
|
||||||
|
insert({
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'WorkflowTodo.Decorator',
|
||||||
|
'x-decorator-props': {},
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
|
'x-settings': 'blockSettings:table',
|
||||||
|
properties: {
|
||||||
|
todos: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'WorkflowTodo',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkflowTodo.Initializer = Initializer;
|
||||||
WorkflowTodo.Drawer = Drawer;
|
WorkflowTodo.Drawer = Drawer;
|
||||||
WorkflowTodo.Decorator = Decorator;
|
WorkflowTodo.Decorator = Decorator;
|
||||||
|
WorkflowTodo.TaskBlock = TaskBlock;
|
||||||
|
|
||||||
|
function TaskBlock() {
|
||||||
|
const { data: user } = useCurrentUserContext();
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
components={{
|
||||||
|
WorkflowTodo,
|
||||||
|
}}
|
||||||
|
schema={{
|
||||||
|
name: 'todos',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'WorkflowTodo.Decorator',
|
||||||
|
'x-decorator-props': {
|
||||||
|
params: {
|
||||||
|
filter: {
|
||||||
|
userId: user?.data?.id,
|
||||||
|
},
|
||||||
|
appends: [
|
||||||
|
'node.id',
|
||||||
|
'node.title',
|
||||||
|
'job.id',
|
||||||
|
'job.status',
|
||||||
|
'job.result',
|
||||||
|
'workflow.id',
|
||||||
|
'workflow.title',
|
||||||
|
'workflow.enabled',
|
||||||
|
'execution.id',
|
||||||
|
'execution.status',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
properties: {
|
||||||
|
todos: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'WorkflowTodo',
|
||||||
|
'x-component-props': {
|
||||||
|
columns: ['workflow', 'node', 'status', 'createdAt'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -13,9 +13,8 @@ import WorkflowPlugin from '@nocobase/plugin-workflow/client';
|
|||||||
import Manual from './instruction';
|
import Manual from './instruction';
|
||||||
|
|
||||||
import { NAMESPACE } from '../locale';
|
import { NAMESPACE } from '../locale';
|
||||||
import { WorkflowManualProvider } from './WorkflowManualProvider';
|
import { useCountRequest, WorkflowManualProvider } from './WorkflowManualProvider';
|
||||||
import { WorkflowTodo } from './WorkflowTodo';
|
import { WorkflowTodo } from './WorkflowTodo';
|
||||||
import { WorkflowTodoBlockInitializer } from './WorkflowTodoBlockInitializer';
|
|
||||||
import {
|
import {
|
||||||
addActionButton,
|
addActionButton,
|
||||||
addActionButton_deprecated,
|
addActionButton_deprecated,
|
||||||
@ -33,10 +32,17 @@ export default class extends Plugin {
|
|||||||
async load() {
|
async load() {
|
||||||
this.addComponents();
|
this.addComponents();
|
||||||
|
|
||||||
// this.app.addProvider(Provider);
|
this.app.addProvider(WorkflowManualProvider);
|
||||||
|
|
||||||
const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
|
const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
|
||||||
workflow.registerInstruction('manual', Manual);
|
workflow.registerInstruction('manual', Manual);
|
||||||
|
|
||||||
|
workflow.registerTaskType('manual', {
|
||||||
|
title: `{{t("My manual tasks", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
useCountRequest,
|
||||||
|
component: WorkflowTodo.TaskBlock,
|
||||||
|
});
|
||||||
|
|
||||||
this.app.schemaInitializerManager.add(addBlockButton_deprecated);
|
this.app.schemaInitializerManager.add(addBlockButton_deprecated);
|
||||||
this.app.schemaInitializerManager.add(addBlockButton);
|
this.app.schemaInitializerManager.add(addBlockButton);
|
||||||
this.app.schemaInitializerManager.add(addActionButton_deprecated);
|
this.app.schemaInitializerManager.add(addActionButton_deprecated);
|
||||||
@ -47,23 +53,20 @@ export default class extends Plugin {
|
|||||||
const blockInitializers = this.app.schemaInitializerManager.get('page:addBlock');
|
const blockInitializers = this.app.schemaInitializerManager.get('page:addBlock');
|
||||||
blockInitializers.add('otherBlocks.workflowTodos', {
|
blockInitializers.add('otherBlocks.workflowTodos', {
|
||||||
title: `{{t("Workflow todos", { ns: "${NAMESPACE}" })}}`,
|
title: `{{t("Workflow todos", { ns: "${NAMESPACE}" })}}`,
|
||||||
Component: 'WorkflowTodoBlockInitializer',
|
Component: 'WorkflowTodo.Initializer',
|
||||||
icon: 'CheckSquareOutlined',
|
icon: 'CheckSquareOutlined',
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.schemaInitializerManager.addItem('mobile:addBlock', 'otherBlocks.workflowTodos', {
|
this.app.schemaInitializerManager.addItem('mobile:addBlock', 'otherBlocks.workflowTodos', {
|
||||||
title: `{{t("Workflow todos", { ns: "${NAMESPACE}" })}}`,
|
title: `{{t("Workflow todos", { ns: "${NAMESPACE}" })}}`,
|
||||||
Component: 'WorkflowTodoBlockInitializer',
|
Component: 'WorkflowTodo.Initializer',
|
||||||
icon: 'CheckSquareOutlined',
|
icon: 'CheckSquareOutlined',
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.addProvider(WorkflowManualProvider);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addComponents() {
|
addComponents() {
|
||||||
this.app.addComponents({
|
this.app.addComponents({
|
||||||
WorkflowTodo,
|
WorkflowTodo,
|
||||||
WorkflowTodoBlockInitializer,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,5 +28,6 @@
|
|||||||
"Workflow todos": "工作流待办",
|
"Workflow todos": "工作流待办",
|
||||||
"Task node": "任务节点",
|
"Task node": "任务节点",
|
||||||
"Unprocessed": "未处理",
|
"Unprocessed": "未处理",
|
||||||
"Please check one of your update record form, and add at least one filter condition in form settings.": "请检查您的其中的更新数据表单,并在表单设置中至少添加一个筛选条件。"
|
"Please check one of your update record form, and add at least one filter condition in form settings.": "请检查您的其中的更新数据表单,并在表单设置中至少添加一个筛选条件。",
|
||||||
|
"My manual tasks": "我的人工任务"
|
||||||
}
|
}
|
||||||
|
@ -12,16 +12,13 @@ import actions from '@nocobase/actions';
|
|||||||
import { HandlerType } from '@nocobase/resourcer';
|
import { HandlerType } from '@nocobase/resourcer';
|
||||||
import WorkflowPlugin, { JOB_STATUS } from '@nocobase/plugin-workflow';
|
import WorkflowPlugin, { JOB_STATUS } from '@nocobase/plugin-workflow';
|
||||||
|
|
||||||
import path from 'path';
|
import * as jobActions from './actions';
|
||||||
import { submit } from './actions';
|
|
||||||
|
|
||||||
import ManualInstruction from './ManualInstruction';
|
import ManualInstruction from './ManualInstruction';
|
||||||
|
|
||||||
export default class extends Plugin {
|
export default class extends Plugin {
|
||||||
async load() {
|
async load() {
|
||||||
await this.importCollections(path.resolve(__dirname, 'collections'));
|
this.app.resourceManager.define({
|
||||||
|
|
||||||
this.app.resource({
|
|
||||||
name: 'users_jobs',
|
name: 'users_jobs',
|
||||||
actions: {
|
actions: {
|
||||||
list: {
|
list: {
|
||||||
@ -40,11 +37,11 @@ export default class extends Plugin {
|
|||||||
},
|
},
|
||||||
handler: actions.list as HandlerType,
|
handler: actions.list as HandlerType,
|
||||||
},
|
},
|
||||||
submit,
|
...jobActions,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.acl.allow('users_jobs', ['list', 'get', 'submit'], 'loggedIn');
|
this.app.acl.allow('users_jobs', ['list', 'get', 'submit', 'countMine'], 'loggedIn');
|
||||||
|
|
||||||
const workflowPlugin = this.app.pm.get(WorkflowPlugin) as WorkflowPlugin;
|
const workflowPlugin = this.app.pm.get(WorkflowPlugin) as WorkflowPlugin;
|
||||||
workflowPlugin.registerInstruction('manual', ManualInstruction);
|
workflowPlugin.registerInstruction('manual', ManualInstruction);
|
||||||
|
@ -112,3 +112,27 @@ export async function submit(context: Context, next) {
|
|||||||
|
|
||||||
plugin.resume(userJob.job);
|
plugin.resume(userJob.job);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function countMine(context: Context, next) {
|
||||||
|
const repository = utils.getRepositoryFromParams(context);
|
||||||
|
const { currentUser } = context.state;
|
||||||
|
|
||||||
|
const count = await repository.count({
|
||||||
|
filter: {
|
||||||
|
$and: [
|
||||||
|
{
|
||||||
|
'workflow.enabled': true,
|
||||||
|
},
|
||||||
|
context.action.params.filter ?? {},
|
||||||
|
{
|
||||||
|
userId: currentUser.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
|
context.body = count;
|
||||||
|
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
@ -0,0 +1,173 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
import React, { useEffect, useMemo } from 'react';
|
||||||
|
import { Link, Outlet, useNavigate, useParams } from 'react-router-dom';
|
||||||
|
import { Button, Layout, Menu, Spin, Badge, theme, Tooltip } from 'antd';
|
||||||
|
import { PageHeader } from '@ant-design/pro-layout';
|
||||||
|
import { CheckCircleOutlined } from '@ant-design/icons';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
PinnedPluginListProvider,
|
||||||
|
SchemaComponentContext,
|
||||||
|
SchemaComponentOptions,
|
||||||
|
useCompile,
|
||||||
|
usePlugin,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
|
||||||
|
import PluginWorkflowClient from '.';
|
||||||
|
import { lang } from './locale';
|
||||||
|
|
||||||
|
const sideClass = css`
|
||||||
|
height: calc(100vh - 46px);
|
||||||
|
|
||||||
|
.ant-layout-sider-children {
|
||||||
|
width: 200px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export interface TaskTypeOptions {
|
||||||
|
title: string;
|
||||||
|
useCountRequest?: Function;
|
||||||
|
component?: React.ComponentType;
|
||||||
|
children?: TaskTypeOptions[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function MenuLink({ type }: any) {
|
||||||
|
const workflowPlugin = usePlugin(PluginWorkflowClient);
|
||||||
|
const compile = useCompile();
|
||||||
|
const { title, useCountRequest } = workflowPlugin.taskTypes.get(type);
|
||||||
|
const { data, loading, run } = useCountRequest?.() || { loading: false };
|
||||||
|
useEffect(() => {
|
||||||
|
run?.();
|
||||||
|
}, [run]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={`/admin/workflow/tasks/${type}`}
|
||||||
|
className={css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<span>{compile(title)}</span>
|
||||||
|
{loading ? <Spin /> : <Badge count={data?.data || 0} />}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WorkflowTasks() {
|
||||||
|
const workflowPlugin = usePlugin(PluginWorkflowClient);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { taskType } = useParams();
|
||||||
|
const compile = useCompile();
|
||||||
|
const {
|
||||||
|
token: { colorBgContainer },
|
||||||
|
} = theme.useToken();
|
||||||
|
|
||||||
|
const items = useMemo(
|
||||||
|
() =>
|
||||||
|
Array.from(workflowPlugin.taskTypes.getKeys()).map((key: string) => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label: <MenuLink type={key} />,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
[workflowPlugin.taskTypes],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { title, component: Component } = useMemo<any>(
|
||||||
|
() => workflowPlugin.taskTypes.get(taskType ?? items[0]?.key) ?? {},
|
||||||
|
[items, taskType, workflowPlugin.taskTypes],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!taskType && items[0].key) {
|
||||||
|
navigate(`/admin/workflow/tasks/${items[0].key}`, { replace: true });
|
||||||
|
}
|
||||||
|
}, [items, navigate, taskType]);
|
||||||
|
|
||||||
|
const key = taskType ?? items[0].key;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Layout.Sider className={sideClass} theme="light">
|
||||||
|
<Menu mode="inline" selectedKeys={[key]} items={items} style={{ height: '100%' }} />
|
||||||
|
</Layout.Sider>
|
||||||
|
<Layout>
|
||||||
|
<PageHeader
|
||||||
|
className={classnames('pageHeaderCss', 'height0')}
|
||||||
|
style={{ background: colorBgContainer, padding: '12px 24px 0 24px' }}
|
||||||
|
title={compile(title)}
|
||||||
|
/>
|
||||||
|
<Layout.Content style={{ padding: '24px', minHeight: 280 }}>
|
||||||
|
<SchemaComponentContext.Provider value={{ designable: false }}>
|
||||||
|
{Component ? <Component /> : null}
|
||||||
|
<Outlet />
|
||||||
|
</SchemaComponentContext.Provider>
|
||||||
|
</Layout.Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function WorkflowTasksLink() {
|
||||||
|
const workflowPlugin = usePlugin(PluginWorkflowClient);
|
||||||
|
|
||||||
|
const types = Array.from(workflowPlugin.taskTypes.getKeys());
|
||||||
|
return types.length ? (
|
||||||
|
<Tooltip title={lang('Workflow todos')}>
|
||||||
|
<Button
|
||||||
|
className={css`
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
vertical-align: middle;
|
||||||
|
a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Link to="/admin/workflow/tasks">
|
||||||
|
<CheckCircleOutlined />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TasksProvider = (props: any) => {
|
||||||
|
return (
|
||||||
|
<PinnedPluginListProvider
|
||||||
|
items={{
|
||||||
|
todo: { component: 'WorkflowTasksLink', pin: true, snippet: '*' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SchemaComponentOptions
|
||||||
|
components={{
|
||||||
|
WorkflowTasksLink,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</SchemaComponentOptions>
|
||||||
|
</PinnedPluginListProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -11,7 +11,7 @@ import React from 'react';
|
|||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
import { isValid } from '@formily/shared';
|
import { isValid } from '@formily/shared';
|
||||||
|
|
||||||
import { Plugin, useCompile, WorkflowConfig } from '@nocobase/client';
|
import { PagePopups, Plugin, useCompile, WorkflowConfig } from '@nocobase/client';
|
||||||
import { Registry } from '@nocobase/utils/client';
|
import { Registry } from '@nocobase/utils/client';
|
||||||
|
|
||||||
// import { ExecutionPage } from './ExecutionPage';
|
// import { ExecutionPage } from './ExecutionPage';
|
||||||
@ -37,12 +37,15 @@ import { getWorkflowDetailPath, getWorkflowExecutionsPath } from './utils';
|
|||||||
import { lang, NAMESPACE } from './locale';
|
import { lang, NAMESPACE } from './locale';
|
||||||
import { customizeSubmitToWorkflowActionSettings } from './settings/customizeSubmitToWorkflowActionSettings';
|
import { customizeSubmitToWorkflowActionSettings } from './settings/customizeSubmitToWorkflowActionSettings';
|
||||||
import { VariableOption } from './variable';
|
import { VariableOption } from './variable';
|
||||||
|
import { WorkflowTasks, TasksProvider, TaskTypeOptions } from './WorkflowTasks';
|
||||||
|
|
||||||
export default class PluginWorkflowClient extends Plugin {
|
export default class PluginWorkflowClient extends Plugin {
|
||||||
triggers = new Registry<Trigger>();
|
triggers = new Registry<Trigger>();
|
||||||
instructions = new Registry<Instruction>();
|
instructions = new Registry<Instruction>();
|
||||||
systemVariables = new Registry<VariableOption>();
|
systemVariables = new Registry<VariableOption>();
|
||||||
|
|
||||||
|
taskTypes = new Registry<TaskTypeOptions>();
|
||||||
|
|
||||||
useTriggersOptions = () => {
|
useTriggersOptions = () => {
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
return Array.from(this.triggers.getEntities())
|
return Array.from(this.triggers.getEntities())
|
||||||
@ -83,15 +86,29 @@ export default class PluginWorkflowClient extends Plugin {
|
|||||||
this.systemVariables.register(option.key, option);
|
this.systemVariables.register(option.key, option);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerTaskType(key: string, option: TaskTypeOptions) {
|
||||||
|
this.taskTypes.register(key, option);
|
||||||
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.app.router.add('admin.workflow.workflows.id', {
|
this.router.add('admin.workflow.workflows.id', {
|
||||||
path: getWorkflowDetailPath(':id'),
|
path: getWorkflowDetailPath(':id'),
|
||||||
element: <WorkflowPage />,
|
Component: WorkflowPage,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.router.add('admin.workflow.executions.id', {
|
this.router.add('admin.workflow.executions.id', {
|
||||||
path: getWorkflowExecutionsPath(':id'),
|
path: getWorkflowExecutionsPath(':id'),
|
||||||
element: <ExecutionPage />,
|
Component: ExecutionPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.router.add('admin.workflow.tasks', {
|
||||||
|
path: '/admin/workflow/tasks/:taskType?',
|
||||||
|
Component: WorkflowTasks,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.router.add('admin.workflow.tasks.popup', {
|
||||||
|
path: '/admin/workflow/tasks/:taskType/popups/*',
|
||||||
|
Component: PagePopups,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.pluginSettingsManager.add(NAMESPACE, {
|
this.app.pluginSettingsManager.add(NAMESPACE, {
|
||||||
@ -101,6 +118,8 @@ export default class PluginWorkflowClient extends Plugin {
|
|||||||
aclSnippet: 'pm.workflow.workflows',
|
aclSnippet: 'pm.workflow.workflows',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.app.use(TasksProvider);
|
||||||
|
|
||||||
this.app.schemaSettingsManager.add(customizeSubmitToWorkflowActionSettings);
|
this.app.schemaSettingsManager.add(customizeSubmitToWorkflowActionSettings);
|
||||||
|
|
||||||
this.app.schemaSettingsManager.addItem('actionSettings:delete', 'workflowConfig', {
|
this.app.schemaSettingsManager.addItem('actionSettings:delete', 'workflowConfig', {
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Instruction } from '.';
|
||||||
|
import { NAMESPACE } from '../locale';
|
||||||
|
import { WorkflowVariableInput } from '../variable';
|
||||||
|
|
||||||
|
export default class extends Instruction {
|
||||||
|
title = `{{t("Assign output variable", { ns: "${NAMESPACE}" })}}`;
|
||||||
|
type = 'output';
|
||||||
|
group = 'control';
|
||||||
|
description = `{{t("Assign variables for workflow output, which could be used in other workflows as result of subflow.", { ns: "${NAMESPACE}" })}}`;
|
||||||
|
fieldset = {
|
||||||
|
result: {
|
||||||
|
type: 'object',
|
||||||
|
title: `{{t("Value", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'WorkflowVariableInput',
|
||||||
|
'x-component-props': {
|
||||||
|
useTypedConstant: true,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
components = {
|
||||||
|
WorkflowVariableInput,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Instruction } from '.';
|
||||||
|
import { NAMESPACE } from '../locale';
|
||||||
|
import { JOB_STATUS } from '../constants';
|
||||||
|
import { TriggerCollectionRecordSelect } from '../components/TriggerCollectionRecordSelect';
|
||||||
|
import { Fieldset } from '../components/Fieldset';
|
||||||
|
|
||||||
|
export default class extends Instruction {
|
||||||
|
title = `{{t("Call another workflow", { ns: "${NAMESPACE}" })}}`;
|
||||||
|
type = 'subflow';
|
||||||
|
group = 'control';
|
||||||
|
description = `{{t("Run another workflow and use its output as variables.", { ns: "${NAMESPACE}" })}}`;
|
||||||
|
fieldset = {
|
||||||
|
workflow: {
|
||||||
|
type: 'number',
|
||||||
|
title: `{{t("Workflow", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: [
|
||||||
|
{ label: `{{t("Post added", { ns: "${NAMESPACE}" })}}`, value: 1 },
|
||||||
|
{ label: `{{t("Some other action", { ns: "${NAMESPACE}" })}}`, value: 2 },
|
||||||
|
],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
type: 'object',
|
||||||
|
title: `{{t("Context", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Fieldset',
|
||||||
|
properties: {
|
||||||
|
data: {
|
||||||
|
type: 'object',
|
||||||
|
title: `{{t("Trigger data", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
description: `{{t("Choose a record of the collection to trigger.", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'TriggerCollectionRecordSelect',
|
||||||
|
default: null,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-reactions': [
|
||||||
|
{
|
||||||
|
dependencies: ['workflow'],
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: '{{workflow === 1}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
components = {
|
||||||
|
TriggerCollectionRecordSelect,
|
||||||
|
Fieldset,
|
||||||
|
};
|
||||||
|
}
|
@ -227,5 +227,7 @@
|
|||||||
"Add node": "添加节点",
|
"Add node": "添加节点",
|
||||||
"Move all downstream nodes to": "将所有下游节点移至",
|
"Move all downstream nodes to": "将所有下游节点移至",
|
||||||
"After end of branches": "分支结束后",
|
"After end of branches": "分支结束后",
|
||||||
"Inside of branch": "分支内"
|
"Inside of branch": "分支内",
|
||||||
|
|
||||||
|
"Workflow todos": "流程待办"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user