Fix(plugin workflow) (#346)

* fix(plugin-workflow): try fix i18n in tooltips

* fix(plugin-workflow): fix i18n contents and change model event to collection event
This commit is contained in:
Junyi 2022-04-30 15:44:03 +08:00 committed by GitHub
parent c1128bf150
commit b7ea6b0a5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 122 additions and 79 deletions

View File

@ -280,5 +280,8 @@ export default {
"Add card": "Add card",
"edit title": "Edit Title",
"Turn the page": "Turn the page",
"Others": "Others"
"Others": "Others",
// workflows
'Execution History': 'History',
}

View File

@ -318,6 +318,7 @@ export default {
'Skip required validation': '跳过必填校验',
'Form values': '表单值',
'Fields values': '字段值',
'When submitting the following fields, the saved values are': '提交以下字段时,保存的值为',
'After successful submission': '提交成功后',
'Then': '然后',
@ -407,25 +408,24 @@ export default {
// plugins/workflow
'Workflow': '工作流',
'Configure workflow': '流程配置',
'Executions': '执行历史',
'Execution History': '执行历史',
'Trigger type': '触发方式',
'Description': '描述',
'Status': '状态',
'Enabled': '启用',
'Disabled': '禁用',
'Trigger configuration': '触发器配置',
'Load failed': '加载失败',
'Model event': '数据表事件',
'Trigger': '触发器',
'Collection event': '数据表事件',
'Trigger on': '触发时机',
'After record added': '新增数据后',
'After record updated': '更新数据后',
'After record added or updated': '新增或更新数据后',
'After record deleted': '删除数据后',
'Changed fields': '发生变动的字段',
'Select the fields which changed will trigger the event only': '只有被选中的某个字段发生变动时才会触发。如果不选择,则表示任何字段变动时都会触发。新增或删除数据时,任意字段都被认为发生变动。',
'Match condition': '满足条件',
'Triggered only if one of the selected fields changes. If unselected, it means that it will be triggered when any field changes. When record is added or deleted, any field is considered to have been changed.': '只有被选中的某个字段发生变动时才会触发。如果不选择,则表示任何字段变动时都会触发。新增或删除数据时,任意字段都被认为发生变动。',
'Only triggers when match conditions': '满足以下条件才触发',
'End': '结束',
@ -445,7 +445,6 @@ export default {
'Failed': '失败',
'Canceled': '已取消',
'Node configuration': '配置节点',
'This node contains branches, deleting will also be preformed to them, are you sure?': '节点包含分支,将同时删除其所有分支下的子节点,确定继续?',
'Control': '流程控制',
@ -456,8 +455,28 @@ export default {
'Calculation': '运算',
'Configure calculation': '配置运算',
'Calculation result': '运算结果',
'True': '真',
'False': '假',
'Condition': '条件判断',
'Mode': '模式',
'Reject on false': '通行模式',
'Branch results': '分支模式',
'Continue when result is "Yes"': '判断为“是”时继续',
'Make 2 branches for "Yes" and "No"': '判断结果分为“是”和“否”两个分支,分别继续',
'Conditions': '条件配置',
'Parallel': '并行',
'All succeeded': '全部成功',
'Any succeeded': '任意成功',
'Continue after all branches succeeded': '全部分支都成功后才能继续',
'Continue after any branch succeeded': '任意分支成功后就继续',
'Create record': '新增数据',
'Update record': '更新数据',
'Query record': '查询数据',
'Multiple records': '多条数据',
'Please select collection first': '请先选择数据表',
'Only update records matching conditions': '只更新满足条件的数据',
'Fields that are not assigned a value will be set to the default value, and those that do not have a default value are set to null.': '未被赋值的字段将被设置为默认值,没有默认值的设置为空值。'
}

View File

@ -9,6 +9,6 @@ export const WorkflowLink = () => {
const { id } = useRecord();
const { setVisible } = useActionContext();
return (
<Link to={`/admin/plugins/workflows/${id}`} onClick={() => setVisible(false)}>{t('Configure workflow')}</Link>
<Link to={`/admin/plugins/workflows/${id}`} onClick={() => setVisible(false)}>{t('Configure')}</Link>
);
}

View File

@ -10,6 +10,7 @@ import { useFlowContext } from "./WorkflowCanvas";
import { triggers } from "./triggers";
import { SchemaComponent, useCollectionManager, useCompile } from "..";
import { useTranslation } from "react-i18next";
import { t } from "i18next";
function NullRender() {
return null;
@ -107,10 +108,11 @@ const ConstantTypes = {
title: '{{t("Boolean")}}',
value: 'boolean',
component({ onChange, type, options, value }) {
const { t } = useTranslation();
return (
<Select value={value} onChange={v => onChange({ value: v, type, options })}>
<Select.Option value={true}></Select.Option>
<Select.Option value={false}></Select.Option>
<Select.Option value={true}>{t('True')}</Select.Option>
<Select.Option value={false}>{t('False')}</Select.Option>
</Select>
);
},
@ -260,7 +262,7 @@ export function Operand({
const { component, appendTypeValue } = Types[type] || {};
const VariableComponent = typeof component === 'function' ? component(operand) : NullRender;
console.log(Types);
return (
<div className={css`
display: flex;
@ -283,7 +285,7 @@ export function Operand({
return {
label: compile(item.title),
value: item.value,
children: options,
children: compile(options),
disabled: options && !options.length,
isLeaf: !options
};
@ -305,7 +307,7 @@ export function Operand({
}
export function Calculation({ calculator, operands = [], onChange }) {
const { t } = useTranslation();
const compile = useCompile();
return (
<VariableTypesContext.Provider value={VariableTypes}>
<div className={css`
@ -324,9 +326,9 @@ export function Calculation({ calculator, operands = [], onChange }) {
<>
<Select value={calculator} onChange={v => onChange({ operands, calculator: v })}>
{calculators.map(group => (
<Select.OptGroup key={group.value} label={t(group.title)}>
<Select.OptGroup key={group.value} label={compile(group.title)}>
{group.children.map(item => (
<Select.Option key={item.value} value={t(item.value)}>{item.name}</Select.Option>
<Select.Option key={item.value} value={item.value}>{compile(item.name)}</Select.Option>
))}
</Select.OptGroup>
))}
@ -469,7 +471,7 @@ export const CollectionFieldset = observer(({ value, onChange }: any) => {
}
</>
)
: <p>{t('Select collection')}</p>
: <p>{t('Please select collection first')}</p>
}
</fieldset>
);

View File

@ -2,12 +2,13 @@ import React from "react";
import { css, cx } from "@emotion/css";
import { Button, Select, Tooltip } from "antd";
import { CloseCircleOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { Trans } from "react-i18next";
import { Trans, useTranslation } from "react-i18next";
import { NodeDefaultView } from ".";
import { Branch, useFlowContext } from "../WorkflowCanvas";
import { branchBlockClass, nodeSubtreeClass } from "../style";
import { Calculation } from "../calculators";
import { i18n } from "../../i18n";
// import { SchemaComponent } from "../../schema-component";
function CalculationItem({ value, onChange, onRemove }) {
@ -38,6 +39,7 @@ function CalculationItem({ value, onChange, onRemove }) {
}
function CalculationGroup({ value, onChange }) {
const { t } = useTranslation();
const { type = 'and', calculations = [] } = value;
function onAddSingle() {
@ -113,8 +115,8 @@ function CalculationGroup({ value, onChange }) {
margin-right: 1em;
}
`} >
<a onClick={onAddSingle}></a>
<a onClick={onAddGroup}></a>
<a onClick={onAddSingle}>{t('Add condition')}</a>
<a onClick={onAddGroup}>{t('Add condition group')}</a>
</div>
</div>
);
@ -129,15 +131,17 @@ function CalculationConfig({ value, onChange }) {
);
}
console.log(i18n.t('Collection name'));
export default {
title: '条件判断',
title: '{{t("Condition")}}',
type: 'condition',
group: 'control',
fieldset: {
'config.rejectOnFalse': {
type: 'boolean',
name: 'config.rejectOnFalse',
title: '模式',
title: '{{t("Mode")}}',
'x-decorator': 'FormItem',
'x-component': 'Radio.Group',
'x-component-props': {
@ -148,10 +152,10 @@ export default {
value: true,
label: (
<Tooltip
title="判断为“是”时继续"
title={i18n.t('Continue when result is "Yes"')}
placement="bottom"
>
<QuestionCircleOutlined style={{ color: '#999' }} />
{i18n.t('Reject on false')} <QuestionCircleOutlined style={{ color: '#999' }} />
</Tooltip>
)
},
@ -159,10 +163,10 @@ export default {
value: false,
label: (
<Tooltip
title="判断结果分为“是”和“否”两个分支,分别继续"
title={i18n.t('Make 2 branches for "Yes" and "No"')}
placement="bottom"
>
<QuestionCircleOutlined style={{ color: '#999' }} />
{i18n.t('Branch results')} <QuestionCircleOutlined style={{ color: '#999' }} />
</Tooltip>
)
}
@ -171,7 +175,7 @@ export default {
'config.calculation': {
type: 'string',
name: 'config.calculation',
title: '条件配置',
title: '{{t("Conditions")}}',
'x-decorator': 'FormItem',
'x-component': 'CalculationConfig',
}
@ -180,8 +184,8 @@ export default {
},
options: [
{ label: '通行模式', key: 'rejectOnFalse', value: { rejectOnFalse: true } },
{ label: '分支模式', key: 'branch', value: { rejectOnFalse: false } }
{ label: i18n.t('Reject on false'), key: 'rejectOnFalse', value: { rejectOnFalse: true } },
{ label: i18n.t('Branch results'), key: 'branch', value: { rejectOnFalse: false } }
],
render(data) {
const { id, config: { rejectOnFalse } } = data;
@ -210,7 +214,7 @@ export default {
height: 2em;
overflow: visible;
:before,:after{
> span{
position: absolute;
top: calc(1.5em - 1px);
line-height: 1em;
@ -218,18 +222,11 @@ export default {
background-color: #f0f2f5;
padding: 1px;
}
:before{
content: "否";
right: 4em;
}
:after{
content: "是";
left: 4em;
}
`}
/>
>
<span className={css`right: 4em;`}>{i18n.t('No')}</span>
<span className={css`left: 4em;`}>{i18n.t('Yes')}</span>
</div>
</div>
)}
</NodeDefaultView>

View File

@ -6,7 +6,7 @@ import { Button, Modal, Tag } from 'antd';
import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaComponent, useActionContext, useAPIClient, useCollection, useCompile, useRequest, useResourceActionContext } from '../..';
import { nodeBlockClass, nodeCardClass, nodeClass, nodeHeaderClass, nodeTitleClass } from '../style';
import { nodeBlockClass, nodeCardClass, nodeClass, nodeHeaderClass, nodeMetaClass, nodeTitleClass } from '../style';
import { AddButton, useFlowContext } from '../WorkflowCanvas';
import calculation from './calculation';
@ -156,8 +156,10 @@ export function NodeDefaultView(props) {
<div className={cx(nodeClass, `workflow-node-type-${data.type}`)}>
<div className={cx(nodeCardClass)}>
<div className={cx(nodeHeaderClass)}>
<h4 className={cx(nodeTitleClass)}>
<div className={cx(nodeMetaClass)}>
<Tag>{compile(instruction.title)}</Tag>
</div>
<h4 className={cx(nodeTitleClass)}>
<strong>{data.title}</strong>
<span className="workflow-node-id">#{data.id}</span>
</h4>
@ -172,7 +174,7 @@ export function NodeDefaultView(props) {
view: instruction.view,
config: {
type: 'void',
title: '{{t("Node configuration")}}',
title: '{{t("Configure")}}',
'x-component': 'Action.Link',
'x-component-props': {
type: 'primary',
@ -180,7 +182,7 @@ export function NodeDefaultView(props) {
properties: {
drawer: {
type: 'void',
title: '{{t("Node configuration")}}',
title: '{{t("Configure")}}',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {

View File

@ -6,17 +6,19 @@ import { NodeDefaultView } from ".";
import { Branch, useFlowContext } from "../WorkflowCanvas";
import { branchBlockClass, nodeSubtreeClass } from "../style";
import { Button, Tooltip } from "antd";
import { i18n } from "../../i18n";
import { useTranslation } from "react-i18next";
// import { SchemaComponent } from "../../schema-component";
export default {
title: '并行',
title: '{{t("Parallel")}}',
type: 'parallel',
group: 'control',
fieldset: {
'config.mode': {
type: 'string',
name: 'config.mode',
title: '模式',
title: '{{t("Mode")}}',
'x-decorator': 'FormItem',
'x-component': 'Radio.Group',
'x-component-props': {
@ -26,10 +28,10 @@ export default {
value: 'all',
label: (
<Tooltip
title="全部分支都成功后才能继续"
title={i18n.t('Continue after all branches succeeded')}
placement="bottom"
>
<QuestionCircleOutlined style={{ color: '#999' }} />
{i18n.t('All succeeded')} <QuestionCircleOutlined style={{ color: '#999' }} />
</Tooltip>
)
},
@ -37,10 +39,10 @@ export default {
value: 'any',
label: (
<Tooltip
title="任意分支成功后就继续"
title="Continue after any branch succeeded"
placement="bottom"
>
<QuestionCircleOutlined style={{ color: '#999' }} />
{i18n.t('Any succeeded')} <QuestionCircleOutlined style={{ color: '#999' }} />
</Tooltip>
)
},
@ -54,6 +56,7 @@ export default {
},
render(data) {
const { id, config: { mode } } = data;
const { t } = useTranslation();
const { nodes } = useFlowContext();
const branches = nodes.reduce((result, node) => {
if (node.upstreamId === id && node.branchIndex != null) {
@ -108,7 +111,7 @@ export default {
height: 2em;
`}
>
<Tooltip title="添加分支">
<Tooltip title={t('Add branch')}>
<Button
icon={<PlusOutlined />}
className={css`

View File

@ -16,7 +16,7 @@ export default {
'config.collection': collection,
'config.multiple': {
type: 'boolean',
title: '多条数据',
title: '{{t("Multiple records")}}',
name: 'config.multiple',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',

View File

@ -14,7 +14,10 @@ export default {
title: '',
'x-decorator': 'FormItem',
properties: {
filter,
filter: {
...filter,
title: '{{t("Only update records matching conditions")}}',
},
values
}
}

View File

@ -15,7 +15,7 @@ export const collection = {
export const values = {
type: 'object',
title: '{{t("Form values")}}',
title: '{{t("Fields values")}}',
name: 'config.params.values',
'x-decorator': 'FormItem',
'x-decorator-props': {
@ -24,7 +24,8 @@ export const values = {
flex-direction: column;
`
},
'x-component': 'CollectionFieldset'
'x-component': 'CollectionFieldset',
description: '{{t("Fields that are not assigned a value will be set to the default value, and those that do not have a default value are set to null.")}}',
};
export const filter = {

View File

@ -225,7 +225,7 @@ export const workflowSchema: ISchema = {
},
executions: {
type: 'void',
title: '{{t("Executions")}}',
title: '{{t("Execution History")}}',
'x-component': 'Action.Link',
'x-component-props': {
type: 'primary',
@ -233,7 +233,7 @@ export const workflowSchema: ISchema = {
properties: {
drawer: {
type: 'void',
title: '{{t("Executions")}}',
title: '{{t("Execution History")}}',
'x-component': 'Action.Drawer',
properties: executionSchema
}

View File

@ -133,6 +133,10 @@ export const nodeHeaderClass = css`
position: relative;
`;
export const nodeMetaClass = css`
margin-bottom: .5em;
`;
export const nodeTitleClass = css`
font-weight: normal;

View File

@ -33,8 +33,8 @@ function useCollectionFieldsDataSource() {
}
export default {
title: '{{t("Model event")}}',
type: 'model',
title: '{{t("Collection event")}}',
type: 'collection',
fieldset: {
'config.collection': collection,
'config.mode': {
@ -50,13 +50,14 @@ export default {
{ value: 3, label: '{{t("After record added or updated")}}' },
{ value: 4, label: '{{t("After record deleted")}}' }
]
}
},
required: true
},
'config.changed': {
type: 'array',
name: 'changed',
title: '{{t("Changed fields")}}',
description: '{{t("Select the fields which changed will trigger the event only")}}',
description: '{{t("Triggered only if one of the selected fields changes. If unselected, it means that it will be triggered when any field changes. When record is added or deleted, any field is considered to have been changed.")}}',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
@ -69,7 +70,7 @@ export default {
'config.condition': {
...filter,
name: 'config.condition',
title: '{{t("Match condition")}}',
title: '{{t("Only triggers when match conditions")}}'
}
},
scope: {

View File

@ -4,8 +4,10 @@ import { cx } from "@emotion/css";
import { Registry } from "@nocobase/utils";
import { SchemaComponent, useActionContext, useAPIClient, useCompile, useRecord, useRequest, useResourceActionContext } from '../../';
import model from './model';
import { nodeCardClass } from "../style";
import collection from './collection';
import { nodeCardClass, nodeMetaClass } from "../style";
import { useTranslation } from "react-i18next";
import { Tag } from "antd";
function useUpdateConfigAction() {
@ -42,9 +44,10 @@ export interface Trigger {
export const triggers = new Registry<Trigger>();
triggers.register(model.type, model);
triggers.register(collection.type, collection);
export const TriggerConfig = () => {
const { t } = useTranslation();
const compile = useCompile();
const { data } = useResourceActionContext();
if (!data) {
@ -54,17 +57,20 @@ export const TriggerConfig = () => {
const { title, fieldset, scope } = triggers.get(type);
return (
<div className={cx(nodeCardClass)}>
<div className={cx(nodeMetaClass)}>
<Tag color="gold">{t('Trigger')}</Tag>
</div>
<h4>{compile(title)}</h4>
<SchemaComponent
schema={{
type: 'void',
title: '{{t("Trigger configuration")}}',
title: '{{t("Configure")}}',
'x-component': 'Action.Link',
name: 'drawer',
properties: {
drawer: {
type: 'void',
title: '{{t("Trigger configuration")}}',
title: '{{t("Configure")}}',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {

View File

@ -22,7 +22,7 @@ describe('execution', () => {
workflow = await WorkflowModel.create({
title: 'test workflow',
enabled: true,
type: 'model',
type: 'collection',
config: {
mode: 1,
collection: 'posts'

View File

@ -21,7 +21,7 @@ describe('workflow > instructions > calculation', () => {
workflow = await WorkflowModel.create({
title: 'test workflow',
enabled: true,
type: 'model',
type: 'collection',
config: {
mode: 1,
collection: 'posts'

View File

@ -22,7 +22,7 @@ describe('workflow > instructions > condition', () => {
workflow = await WorkflowModel.create({
title: 'test workflow',
enabled: true,
type: 'model',
type: 'collection',
config: {
mode: 1,
collection: 'posts'

View File

@ -21,7 +21,7 @@ describe('workflow > instructions > create', () => {
workflow = await WorkflowModel.create({
title: 'test workflow',
enabled: true,
type: 'model',
type: 'collection',
config: {
mode: 1,
collection: 'posts'

View File

@ -21,7 +21,7 @@ describe('workflow > instructions > destroy', () => {
workflow = await WorkflowModel.create({
title: 'test workflow',
enabled: true,
type: 'model',
type: 'collection',
config: {
mode: 1,
collection: 'posts'

View File

@ -21,7 +21,7 @@ describe('workflow > instructions > query', () => {
workflow = await WorkflowModel.create({
title: 'test workflow',
enabled: true,
type: 'model',
type: 'collection',
config: {
mode: 1,
collection: 'posts'

View File

@ -21,7 +21,7 @@ describe('workflow > instructions > update', () => {
workflow = await WorkflowModel.create({
title: 'test workflow',
enabled: true,
type: 'model',
type: 'collection',
config: {
mode: 1,
collection: 'posts'

View File

@ -37,7 +37,7 @@ function bindHandler(this: WorkflowModel, callback: Function) {
}
export default {
name: 'model',
name: 'collection',
on(this: WorkflowModel, callback: Function) {
const { database } = <typeof WorkflowModel>this.constructor;
const { collection, mode } = this.config;
@ -47,7 +47,9 @@ export default {
}
// TODO: duplication when mode change should be considered
for (let [key, event] of MODE_BITMAP_EVENTS.entries()) {
if (mode & key) {
if (mode & key
&& !Collection.model.options.hooks[event]?.find(item => item.name && item.name === this.getHookId())
) {
Collection.model.addHook(event, this.getHookId(), bindHandler.call(this, callback));
}
}

View File

@ -1,6 +1,6 @@
import { Registry } from '@nocobase/utils';
import WorkflowModel from '../models/Workflow';
import modelTrigger from './model';
import collectionlTrigger from './collection';
export interface Trigger {
name: string;
@ -12,4 +12,4 @@ export const triggers = new Registry<Trigger>();
export default triggers;
triggers.register(modelTrigger.name, modelTrigger);
triggers.register(collectionlTrigger.name, collectionlTrigger);