@gadmin2n/schematics 0.0.149 → 0.0.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/application/files/gadmin2-game-angle-demo/CODEBUDDY.md +125 -36
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +42 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/gitignore +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/.seed-timestamp +0 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +3 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/check-dev-env.js +218 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/page-manage.ts +78 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +37 -67
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/index.ts +0 -17
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.controller.ts +46 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.ts +60 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-callback.controller.ts +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/workflow-callback-url.controller.ts +9 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.ts +55 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.module.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/page-transfer.service.ts +491 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/perm-transfer.dto.ts +213 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/perm-transfer.module.ts +28 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/resource-transfer.service.ts +137 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/role-transfer.service.ts +248 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/transfer-token.service.ts +65 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/user-transfer.service.ts +146 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.ts +55 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.module.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.ts +55 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.module.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/providers/user-group.provider.spec.ts +46 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/providers/user-group.provider.ts +3 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +55 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.module.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-cancellation.spec.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +96 -14
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.ts +41 -41
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-database-function.spec.ts +36 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-database-function.ts +90 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-catalog.spec.ts +4 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-trigger.pg.spec.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution-idempotency.spec.ts +115 -63
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-outbox.service.spec.ts +166 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-outbox.service.ts +217 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-runtime.controller.spec.ts +33 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-runtime.controller.ts +57 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle.service.spec.ts +196 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle.service.ts +125 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-node-type.controller.ts +9 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +148 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.module.ts +12 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +14 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +75 -45
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/node-config-validator.service.spec.ts +25 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/TransferExportModal.tsx +115 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/TransferImportModal.tsx +257 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +0 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +40 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +15 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +40 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +15 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/plugins/devShellPlugin.ts +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/list.tsx +89 -65
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +51 -127
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/permissionReadme/index.tsx +53 -109
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +45 -41
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +51 -72
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +49 -40
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.test.tsx +16 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ReadonlyNodePanel.test.tsx +28 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.test.tsx +60 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +94 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-instance-show.css +13 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/schema-form/default-widgets.tsx +3 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/show.tsx +8 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DbExecuteConfigEditor.tsx +167 -94
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.tsx +95 -95
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerLifecycleConfigEditor.test.tsx +81 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerLifecycleConfigEditor.tsx +126 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +48 -52
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/forEachConnection.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/newNodeConfig.ts +1 -3
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/ac.config.ts +0 -24
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/SqlModal.tsx +0 -495
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
ShowButton,
|
|
9
9
|
useTable,
|
|
10
10
|
} from '@refinedev/antd';
|
|
11
|
-
import {
|
|
12
|
-
getDefaultFilter,
|
|
13
|
-
type HttpError,
|
|
14
|
-
useGetIdentity,
|
|
15
|
-
} from '@refinedev/core';
|
|
11
|
+
import { getDefaultFilter, type HttpError } from '@refinedev/core';
|
|
16
12
|
|
|
17
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
PlusOutlined,
|
|
15
|
+
ExportOutlined,
|
|
16
|
+
ImportOutlined,
|
|
17
|
+
} from '@ant-design/icons';
|
|
18
18
|
import { Button, Card, Input, Select, Space, Table, Tag, Tooltip } from 'antd';
|
|
19
19
|
|
|
20
20
|
import {
|
|
@@ -30,11 +30,10 @@ import { CreateModal } from './components/create-modal';
|
|
|
30
30
|
import { EditModal } from './components/edit-modal';
|
|
31
31
|
import { ShowDrawer } from './components/show-drawer';
|
|
32
32
|
import { LIST_CONTENT_PROPS } from '@/constants/layout';
|
|
33
|
-
import {
|
|
33
|
+
import { TransferExportModal } from '@/components/TransferExportModal';
|
|
34
|
+
import { TransferImportModal } from '@/components/TransferImportModal';
|
|
34
35
|
|
|
35
36
|
export const UserListPage: React.FC<PropsWithChildren> = ({ children }) => {
|
|
36
|
-
const { data: identity } = useGetIdentity<any>();
|
|
37
|
-
|
|
38
37
|
// Fetch roles from backend using custom hook
|
|
39
38
|
const { roleOptions, loading: rolesLoading } = useRoles();
|
|
40
39
|
|
|
@@ -59,7 +58,11 @@ export const UserListPage: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
59
58
|
});
|
|
60
59
|
};
|
|
61
60
|
|
|
62
|
-
const {
|
|
61
|
+
const {
|
|
62
|
+
tableProps,
|
|
63
|
+
filters,
|
|
64
|
+
tableQuery: tableQueryResult,
|
|
65
|
+
} = useTable<User, HttpError, Partial<User>>({
|
|
63
66
|
resource: 'user',
|
|
64
67
|
pagination: {
|
|
65
68
|
pageSize: 20,
|
|
@@ -105,20 +108,8 @@ export const UserListPage: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
105
108
|
id: null,
|
|
106
109
|
});
|
|
107
110
|
|
|
108
|
-
const [
|
|
109
|
-
|
|
110
|
-
title: string;
|
|
111
|
-
sql: string;
|
|
112
|
-
}>({ open: false, title: '', sql: '' });
|
|
113
|
-
|
|
114
|
-
const handleShowSql = (record: any) => {
|
|
115
|
-
const operator = {
|
|
116
|
-
authorId: identity?.userid,
|
|
117
|
-
authorName: identity?.username,
|
|
118
|
-
};
|
|
119
|
-
const sql = generateUserSql(record, operator);
|
|
120
|
-
setSqlModal({ open: true, title: `SQL — User: ${record.userid}`, sql });
|
|
121
|
-
};
|
|
111
|
+
const [exportEndpoint, setExportEndpoint] = useState<string | null>(null);
|
|
112
|
+
const [importOpen, setImportOpen] = useState(false);
|
|
122
113
|
|
|
123
114
|
// 处理modal关闭
|
|
124
115
|
const handleModalClose = () => {
|
|
@@ -132,13 +123,21 @@ export const UserListPage: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
132
123
|
contentProps={LIST_CONTENT_PROPS}
|
|
133
124
|
headerButtons={() => {
|
|
134
125
|
return (
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
126
|
+
<Space>
|
|
127
|
+
<Button
|
|
128
|
+
icon={<ImportOutlined />}
|
|
129
|
+
onClick={() => setImportOpen(true)}
|
|
130
|
+
>
|
|
131
|
+
导入
|
|
132
|
+
</Button>
|
|
133
|
+
<Button
|
|
134
|
+
type="primary"
|
|
135
|
+
icon={<PlusOutlined />}
|
|
136
|
+
onClick={() => setShowModal({ type: 'create', id: null })}
|
|
137
|
+
>
|
|
138
|
+
新增访问主体
|
|
139
|
+
</Button>
|
|
140
|
+
</Space>
|
|
142
141
|
);
|
|
143
142
|
}}
|
|
144
143
|
>
|
|
@@ -313,12 +312,16 @@ export const UserListPage: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
313
312
|
setShowModal({ type: 'edit', id: record.id })
|
|
314
313
|
}
|
|
315
314
|
/>
|
|
316
|
-
<Tooltip title="
|
|
315
|
+
<Tooltip title="导出">
|
|
317
316
|
<Button
|
|
318
317
|
size="small"
|
|
319
|
-
icon={<
|
|
318
|
+
icon={<ExportOutlined />}
|
|
320
319
|
style={{ backgroundColor: 'transparent' }}
|
|
321
|
-
onClick={() =>
|
|
320
|
+
onClick={() =>
|
|
321
|
+
setExportEndpoint(
|
|
322
|
+
`user/${encodeURIComponent(record.userid)}/export-token`,
|
|
323
|
+
)
|
|
324
|
+
}
|
|
322
325
|
/>
|
|
323
326
|
</Tooltip>
|
|
324
327
|
<DeleteButton
|
|
@@ -350,12 +353,18 @@ export const UserListPage: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
350
353
|
<CreateModal onClose={handleModalClose} />
|
|
351
354
|
)}
|
|
352
355
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
356
|
+
<TransferExportModal
|
|
357
|
+
open={!!exportEndpoint}
|
|
358
|
+
exportTokenEndpoint={exportEndpoint}
|
|
359
|
+
entity="user"
|
|
360
|
+
onClose={() => setExportEndpoint(null)}
|
|
361
|
+
/>
|
|
362
|
+
<TransferImportModal
|
|
363
|
+
open={importOpen}
|
|
364
|
+
onClose={() => setImportOpen(false)}
|
|
365
|
+
onSuccess={() => tableQueryResult?.refetch()}
|
|
366
|
+
previewEndpoint="user/import-preview"
|
|
367
|
+
confirmEndpoint="user/import-confirm"
|
|
359
368
|
/>
|
|
360
369
|
|
|
361
370
|
{children}
|
|
@@ -129,11 +129,18 @@ describe('NodePropertyPanel instance references', () => {
|
|
|
129
129
|
configSchema: {
|
|
130
130
|
type: 'object',
|
|
131
131
|
properties: {
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
functionRef: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
properties: {
|
|
135
|
+
schema: { type: 'string' },
|
|
136
|
+
name: { type: 'string' },
|
|
137
|
+
identityArguments: { type: 'string' },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
argumentBindings: { type: 'array' },
|
|
134
141
|
timeoutSeconds: { type: 'integer' },
|
|
135
142
|
},
|
|
136
|
-
required: ['
|
|
143
|
+
required: ['functionRef', 'argumentBindings'],
|
|
137
144
|
},
|
|
138
145
|
uiSchema: { widget: 'db-execute-config' },
|
|
139
146
|
} as unknown as WorkflowNodeType;
|
|
@@ -157,8 +164,12 @@ describe('NodePropertyPanel instance references', () => {
|
|
|
157
164
|
label: 'Database Operation',
|
|
158
165
|
position: { x: 0, y: 0 },
|
|
159
166
|
config: {
|
|
160
|
-
|
|
161
|
-
|
|
167
|
+
functionRef: {
|
|
168
|
+
schema: 'workflow_api',
|
|
169
|
+
name: 'refresh_summary',
|
|
170
|
+
identityArguments: '',
|
|
171
|
+
},
|
|
172
|
+
argumentBindings: [],
|
|
162
173
|
timeoutSeconds: 3,
|
|
163
174
|
},
|
|
164
175
|
})
|
|
@@ -9,28 +9,46 @@ const node: WorkflowNode = {
|
|
|
9
9
|
type: 'db_execute',
|
|
10
10
|
label: '查询待处理订单',
|
|
11
11
|
position: { x: 0, y: 0 },
|
|
12
|
-
config: {
|
|
12
|
+
config: {
|
|
13
|
+
functionRef: {
|
|
14
|
+
schema: 'workflow_api',
|
|
15
|
+
name: 'find_pending_orders',
|
|
16
|
+
identityArguments: '',
|
|
17
|
+
},
|
|
18
|
+
argumentBindings: [],
|
|
19
|
+
},
|
|
13
20
|
};
|
|
14
21
|
|
|
15
22
|
const nodeType = {
|
|
16
23
|
id: '1',
|
|
17
24
|
type: 'db_execute',
|
|
18
|
-
label: 'Database
|
|
19
|
-
description: '
|
|
25
|
+
label: 'Database Function',
|
|
26
|
+
description: 'Call a PostgreSQL Function',
|
|
20
27
|
category: 'ACTION',
|
|
21
28
|
configSchema: {
|
|
22
29
|
type: 'object',
|
|
23
30
|
properties: {
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
functionRef: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
title: 'Function',
|
|
34
|
+
properties: {
|
|
35
|
+
schema: { type: 'string', title: 'Schema' },
|
|
36
|
+
name: { type: 'string', title: 'Name' },
|
|
37
|
+
identityArguments: { type: 'string', title: 'Arguments' },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
argumentBindings: {
|
|
41
|
+
type: 'array',
|
|
42
|
+
title: 'Argument bindings',
|
|
43
|
+
items: { type: 'object' },
|
|
44
|
+
},
|
|
26
45
|
},
|
|
27
46
|
},
|
|
28
47
|
uiSchema: {},
|
|
29
48
|
outputSchema: {
|
|
30
49
|
type: 'object',
|
|
31
50
|
properties: {
|
|
32
|
-
|
|
33
|
-
rowCount: { type: 'number' },
|
|
51
|
+
result: {},
|
|
34
52
|
},
|
|
35
53
|
},
|
|
36
54
|
outputs: [],
|
|
@@ -62,9 +80,9 @@ describe('ReadonlyNodePanel', () => {
|
|
|
62
80
|
screen.getByRole('heading', { name: 'Node configuration' }),
|
|
63
81
|
).toBeInTheDocument();
|
|
64
82
|
expect(screen.getByDisplayValue('查询待处理订单')).toBeDisabled();
|
|
65
|
-
expect(screen.getByDisplayValue('
|
|
83
|
+
expect(screen.getByDisplayValue('find_pending_orders')).toBeDisabled();
|
|
66
84
|
expect(screen.getByText('No node template is used')).toBeInTheDocument();
|
|
67
|
-
expect(screen.getByText('input.
|
|
85
|
+
expect(screen.getByText('input.result')).toBeInTheDocument();
|
|
68
86
|
expect(screen.queryByText(/"position"/)).not.toBeInTheDocument();
|
|
69
87
|
expect(screen.getByText('Database Operation')).toBeInTheDocument();
|
|
70
88
|
expect(screen.queryByText('Perform an action')).not.toBeInTheDocument();
|
|
@@ -82,7 +100,7 @@ describe('ReadonlyNodePanel', () => {
|
|
|
82
100
|
fireEvent.click(screen.getByText('Advanced configuration'));
|
|
83
101
|
|
|
84
102
|
expect(screen.getByText(/"position"/)).toBeInTheDocument();
|
|
85
|
-
expect(screen.getByText(/
|
|
103
|
+
expect(screen.getByText(/find_pending_orders/)).toBeInTheDocument();
|
|
86
104
|
expect(
|
|
87
105
|
screen.getByRole('button', { name: 'Copy JSON' }),
|
|
88
106
|
).toBeInTheDocument();
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getDisplayNodeExecutions,
|
|
4
|
+
getErrorSummary,
|
|
5
|
+
getStepStatus,
|
|
6
|
+
getWorkflowFailureSummary,
|
|
7
|
+
shouldShowNodeError,
|
|
8
|
+
} from './instance-detail';
|
|
3
9
|
|
|
4
10
|
describe('Workflow instance cancellation display', () => {
|
|
5
11
|
it('does not present a cancelled node as an error', () => {
|
|
@@ -23,4 +29,57 @@ describe('Workflow instance cancellation display', () => {
|
|
|
23
29
|
).toBe(true);
|
|
24
30
|
expect(getStepStatus('FAILED')).toBe('error');
|
|
25
31
|
});
|
|
32
|
+
|
|
33
|
+
it('shows the root cause instead of the generic Activity wrapper', () => {
|
|
34
|
+
const error = {
|
|
35
|
+
message: 'database connection refused',
|
|
36
|
+
errorType: 'DatabaseConnectionError',
|
|
37
|
+
stack: 'DatabaseConnectionError: database connection refused',
|
|
38
|
+
causes: [
|
|
39
|
+
{ name: 'ActivityFailure', message: 'Activity task failed' },
|
|
40
|
+
{
|
|
41
|
+
name: 'ApplicationFailure',
|
|
42
|
+
message: 'database connection refused',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
expect(getErrorSummary(error)).toBe('database connection refused');
|
|
48
|
+
expect(
|
|
49
|
+
getWorkflowFailureSummary({ error, failedNodeId: 'database-node' }),
|
|
50
|
+
).toEqual({
|
|
51
|
+
message: 'database connection refused',
|
|
52
|
+
failedNodeId: 'database-node',
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('shows a completed trigger for legacy instances without a trigger execution record', () => {
|
|
57
|
+
const executions = getDisplayNodeExecutions({
|
|
58
|
+
context: { workflow: { requestId: 42 } },
|
|
59
|
+
startedAt: '2026-09-02T10:00:00.000Z',
|
|
60
|
+
nodeExecutions: [],
|
|
61
|
+
dsl: {
|
|
62
|
+
nodes: [
|
|
63
|
+
{
|
|
64
|
+
id: 'event-start',
|
|
65
|
+
type: 'event_trigger',
|
|
66
|
+
label: '业务事件触发',
|
|
67
|
+
config: {},
|
|
68
|
+
position: { x: 0, y: 0 },
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
edges: [],
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(executions).toEqual([
|
|
76
|
+
expect.objectContaining({
|
|
77
|
+
nodeId: 'event-start',
|
|
78
|
+
nodeType: 'event_trigger',
|
|
79
|
+
status: 'COMPLETED',
|
|
80
|
+
input: { requestId: 42 },
|
|
81
|
+
output: { requestId: 42 },
|
|
82
|
+
}),
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
26
85
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
Alert,
|
|
3
4
|
Button,
|
|
4
5
|
Card,
|
|
5
6
|
Descriptions,
|
|
@@ -93,6 +94,38 @@ interface InstanceDetail {
|
|
|
93
94
|
dsl?: WorkflowDSL;
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
export function getDisplayNodeExecutions(
|
|
98
|
+
instance: Pick<
|
|
99
|
+
InstanceDetail,
|
|
100
|
+
'context' | 'dsl' | 'nodeExecutions' | 'startedAt'
|
|
101
|
+
>,
|
|
102
|
+
): NodeExecution[] {
|
|
103
|
+
if (!instance.dsl || !instance.startedAt) return instance.nodeExecutions;
|
|
104
|
+
|
|
105
|
+
const recordedNodeIds = new Set(
|
|
106
|
+
instance.nodeExecutions.map((execution) => execution.nodeId),
|
|
107
|
+
);
|
|
108
|
+
const workflowInput = instance.context?.workflow ?? null;
|
|
109
|
+
const legacyTriggerExecutions = instance.dsl.nodes
|
|
110
|
+
.filter(
|
|
111
|
+
(node) => node.type.endsWith('_trigger') && !recordedNodeIds.has(node.id),
|
|
112
|
+
)
|
|
113
|
+
.map<NodeExecution>((node) => ({
|
|
114
|
+
id: `legacy-trigger-${node.id}`,
|
|
115
|
+
nodeId: node.id,
|
|
116
|
+
nodeType: node.type,
|
|
117
|
+
nodeLabel: node.label ?? null,
|
|
118
|
+
status: 'COMPLETED',
|
|
119
|
+
input: workflowInput,
|
|
120
|
+
output: workflowInput,
|
|
121
|
+
error: null,
|
|
122
|
+
startedAt: instance.startedAt,
|
|
123
|
+
finishedAt: instance.startedAt,
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
return [...legacyTriggerExecutions, ...instance.nodeExecutions];
|
|
127
|
+
}
|
|
128
|
+
|
|
96
129
|
function formatDuration(start: string | null, end: string | null): string {
|
|
97
130
|
if (!start) return '—';
|
|
98
131
|
const endTime = end ? new Date(end).getTime() : Date.now();
|
|
@@ -128,6 +161,35 @@ export function shouldShowNodeError(
|
|
|
128
161
|
return execution.status === 'FAILED' && Boolean(execution.error);
|
|
129
162
|
}
|
|
130
163
|
|
|
164
|
+
export function getErrorSummary(error: unknown): string {
|
|
165
|
+
if (typeof error === 'string') return error;
|
|
166
|
+
if (error && typeof error === 'object') {
|
|
167
|
+
const message = (error as Record<string, unknown>).message;
|
|
168
|
+
if (typeof message === 'string' && message.trim()) return message;
|
|
169
|
+
try {
|
|
170
|
+
return JSON.stringify(error);
|
|
171
|
+
} catch {
|
|
172
|
+
return String(error);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return String(error ?? 'Unknown error');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function getWorkflowFailureSummary(
|
|
179
|
+
result: unknown,
|
|
180
|
+
): { message: string; failedNodeId?: string } | null {
|
|
181
|
+
if (!result || typeof result !== 'object' || Array.isArray(result))
|
|
182
|
+
return null;
|
|
183
|
+
const record = result as Record<string, unknown>;
|
|
184
|
+
if (record.error === undefined || record.error === null) return null;
|
|
185
|
+
const failedNodeId =
|
|
186
|
+
typeof record.failedNodeId === 'string' ? record.failedNodeId : undefined;
|
|
187
|
+
return {
|
|
188
|
+
message: getErrorSummary(record.error),
|
|
189
|
+
...(failedNodeId && { failedNodeId }),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
131
193
|
export default function WorkflowInstanceDetailPage() {
|
|
132
194
|
const { t } = useTranslation('workflow');
|
|
133
195
|
const { instanceId } = useParams<{ instanceId: string }>();
|
|
@@ -251,9 +313,13 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
251
313
|
|
|
252
314
|
if (!data) return null;
|
|
253
315
|
|
|
316
|
+
const displayNodeExecutions = getDisplayNodeExecutions(data);
|
|
317
|
+
const instanceHasFinished = ['COMPLETED', 'FAILED', 'CANCELLED'].includes(
|
|
318
|
+
data.status,
|
|
319
|
+
);
|
|
254
320
|
const statusCfg = STATUS_TAG[data.status] || STATUS_TAG.PENDING;
|
|
255
321
|
const selectedExecution = selectedNodeId
|
|
256
|
-
?
|
|
322
|
+
? displayNodeExecutions.find((ne) => ne.nodeId === selectedNodeId)
|
|
257
323
|
: null;
|
|
258
324
|
const selectedDslNode =
|
|
259
325
|
selectedNodeId && data.dsl
|
|
@@ -268,6 +334,8 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
268
334
|
(attempt) => attempt.retryFromNodeId === selectedNodeId,
|
|
269
335
|
)
|
|
270
336
|
: undefined;
|
|
337
|
+
const workflowFailure =
|
|
338
|
+
data.status === 'FAILED' ? getWorkflowFailureSummary(data.result) : null;
|
|
271
339
|
|
|
272
340
|
return (
|
|
273
341
|
<div className="workflow-page-stack">
|
|
@@ -420,6 +488,19 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
420
488
|
</Descriptions.Item>
|
|
421
489
|
)}
|
|
422
490
|
</Descriptions>
|
|
491
|
+
{workflowFailure && (
|
|
492
|
+
<Alert
|
|
493
|
+
type="error"
|
|
494
|
+
showIcon
|
|
495
|
+
style={{ marginTop: 16 }}
|
|
496
|
+
message={workflowFailure.message}
|
|
497
|
+
description={
|
|
498
|
+
workflowFailure.failedNodeId
|
|
499
|
+
? `${t('instanceDetail.failedNode')}: ${workflowFailure.failedNodeId}`
|
|
500
|
+
: undefined
|
|
501
|
+
}
|
|
502
|
+
/>
|
|
503
|
+
)}
|
|
423
504
|
{data.result !== null && data.result !== undefined && (
|
|
424
505
|
<div style={{ marginTop: 16 }}>
|
|
425
506
|
<Text strong style={{ display: 'block', marginBottom: 8 }}>
|
|
@@ -474,14 +555,14 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
474
555
|
// Timeline View
|
|
475
556
|
<div style={{ display: 'flex', gap: 16 }}>
|
|
476
557
|
<div style={{ flex: 1 }}>
|
|
477
|
-
{
|
|
558
|
+
{displayNodeExecutions.length === 0 ? (
|
|
478
559
|
<Text type="secondary">No node executions recorded yet.</Text>
|
|
479
560
|
) : (
|
|
480
561
|
<Steps
|
|
481
562
|
direction="vertical"
|
|
482
563
|
size="small"
|
|
483
564
|
current={-1}
|
|
484
|
-
items={
|
|
565
|
+
items={displayNodeExecutions.map((ne) => {
|
|
485
566
|
const isApprovalPending =
|
|
486
567
|
ne.nodeType === 'approval' &&
|
|
487
568
|
ne.status === 'RUNNING' &&
|
|
@@ -590,10 +671,7 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
590
671
|
</span>
|
|
591
672
|
{shouldShowNodeError(ne) && (
|
|
592
673
|
<div style={{ color: '#ff4d4f', marginTop: 4 }}>
|
|
593
|
-
Error:{
|
|
594
|
-
{typeof ne.error === 'object'
|
|
595
|
-
? JSON.stringify(ne.error)
|
|
596
|
-
: String(ne.error)}
|
|
674
|
+
Error: {getErrorSummary(ne.error)}
|
|
597
675
|
</div>
|
|
598
676
|
)}
|
|
599
677
|
</div>
|
|
@@ -717,7 +795,7 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
717
795
|
borderRadius: 4,
|
|
718
796
|
fontSize: 11,
|
|
719
797
|
color: '#ff4d4f',
|
|
720
|
-
maxHeight:
|
|
798
|
+
maxHeight: 320,
|
|
721
799
|
overflow: 'auto',
|
|
722
800
|
}}
|
|
723
801
|
>
|
|
@@ -739,7 +817,7 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
739
817
|
<div style={{ flex: 1, height: 500, minHeight: 500 }}>
|
|
740
818
|
<EnhancedFlowRenderer
|
|
741
819
|
dsl={data.dsl}
|
|
742
|
-
nodeExecutions={
|
|
820
|
+
nodeExecutions={displayNodeExecutions}
|
|
743
821
|
showExecutionStatus={true}
|
|
744
822
|
selectedNodeId={selectedNodeId}
|
|
745
823
|
readonly={true}
|
|
@@ -875,7 +953,7 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
875
953
|
borderRadius: 4,
|
|
876
954
|
fontSize: 11,
|
|
877
955
|
color: '#ff4d4f',
|
|
878
|
-
maxHeight:
|
|
956
|
+
maxHeight: 320,
|
|
879
957
|
overflow: 'auto',
|
|
880
958
|
}}
|
|
881
959
|
>
|
|
@@ -974,7 +1052,9 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
974
1052
|
>
|
|
975
1053
|
{selectedNodeWasReused
|
|
976
1054
|
? t('instanceDetail.reusedResult')
|
|
977
|
-
:
|
|
1055
|
+
: instanceHasFinished
|
|
1056
|
+
? 'Not Executed / Skipped'
|
|
1057
|
+
: 'Not Executed'}
|
|
978
1058
|
</Tag>
|
|
979
1059
|
</div>
|
|
980
1060
|
<Descriptions column={1} size="small">
|
|
@@ -985,7 +1065,9 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
985
1065
|
<Text type="secondary">
|
|
986
1066
|
{selectedNodeWasReused
|
|
987
1067
|
? t('instanceDetail.reusedResultDescription')
|
|
988
|
-
:
|
|
1068
|
+
: instanceHasFinished
|
|
1069
|
+
? 'Not executed in this run'
|
|
1070
|
+
: 'Waiting for execution'}
|
|
989
1071
|
</Text>
|
|
990
1072
|
</Descriptions.Item>
|
|
991
1073
|
</Descriptions>
|
|
@@ -98,6 +98,19 @@
|
|
|
98
98
|
background: var(--ant-color-fill-quaternary);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
.node-instance-show__raw-configuration {
|
|
102
|
+
max-width: 960px;
|
|
103
|
+
margin: 0;
|
|
104
|
+
padding: 16px;
|
|
105
|
+
overflow: auto;
|
|
106
|
+
border: 1px solid var(--ant-color-border-secondary);
|
|
107
|
+
border-radius: 10px;
|
|
108
|
+
background: var(--ant-color-fill-quaternary);
|
|
109
|
+
font-family: var(--ant-font-family-code);
|
|
110
|
+
white-space: pre-wrap;
|
|
111
|
+
overflow-wrap: anywhere;
|
|
112
|
+
}
|
|
113
|
+
|
|
101
114
|
.node-instance-show__empty {
|
|
102
115
|
margin: 0;
|
|
103
116
|
padding: 28px 0;
|
|
@@ -5,7 +5,7 @@ import { CodeConfigEditor } from '../widgets/CodeConfigEditor';
|
|
|
5
5
|
import { CronTriggerConfigEditor } from '../widgets/CronTriggerConfigEditor';
|
|
6
6
|
import { DbExecuteConfigEditor } from '../widgets/DbExecuteConfigEditor';
|
|
7
7
|
import { DelayConfigEditor } from '../widgets/DelayConfigEditor';
|
|
8
|
-
import {
|
|
8
|
+
import { EventTriggerLifecycleConfigEditor } from '../widgets/EventTriggerLifecycleConfigEditor';
|
|
9
9
|
import { ExternalRequestConfigEditor } from '../widgets/ExternalRequestConfigEditor';
|
|
10
10
|
import { ExternalWaitConfigEditor } from '../widgets/ExternalWaitConfigEditor';
|
|
11
11
|
import { ForEachConfigEditor } from '../widgets/ForEachConfigEditor';
|
|
@@ -14,9 +14,9 @@ import { HumanApprovalConfigEditor } from '../widgets/HumanApprovalConfigEditor'
|
|
|
14
14
|
import { IfElseConfigEditor } from '../widgets/IfElseConfigEditor';
|
|
15
15
|
import { ManualTriggerConfigEditor } from '../widgets/ManualTriggerConfigEditor';
|
|
16
16
|
import { ParallelConfigEditor } from '../widgets/ParallelConfigEditor';
|
|
17
|
+
import { SubjectPicker } from '../widgets/SubjectPicker';
|
|
17
18
|
import { SubWorkflowConfigEditor } from '../widgets/SubWorkflowConfigEditor';
|
|
18
19
|
import { SwitchConfigEditor } from '../widgets/SwitchConfigEditor';
|
|
19
|
-
import { SubjectPicker } from '../widgets/SubjectPicker';
|
|
20
20
|
import {
|
|
21
21
|
widgetRegistry,
|
|
22
22
|
type WidgetComponent,
|
|
@@ -130,7 +130,7 @@ const DEFAULT_WIDGETS = {
|
|
|
130
130
|
'parallel-config': ParallelConfigEditor,
|
|
131
131
|
'sub-workflow-config': SubWorkflowConfigEditor,
|
|
132
132
|
'db-execute-config': DbExecuteConfigEditor,
|
|
133
|
-
'event-trigger-config':
|
|
133
|
+
'event-trigger-config': EventTriggerLifecycleConfigEditor,
|
|
134
134
|
'if-else-config': IfElseConfigEditor,
|
|
135
135
|
'manual-trigger-config': ManualTriggerConfigEditor,
|
|
136
136
|
'switch-config': SwitchConfigEditor,
|
|
@@ -308,7 +308,14 @@ export default function NodeInstanceShowPage() {
|
|
|
308
308
|
/>
|
|
309
309
|
</div>
|
|
310
310
|
</Form>
|
|
311
|
-
) :
|
|
311
|
+
) : (
|
|
312
|
+
<pre
|
|
313
|
+
className="node-instance-show__raw-configuration"
|
|
314
|
+
aria-label={t('nodeInstance.rawConfiguration')}
|
|
315
|
+
>
|
|
316
|
+
{JSON.stringify(record.config, null, 2)}
|
|
317
|
+
</pre>
|
|
318
|
+
)}
|
|
312
319
|
</Card>
|
|
313
320
|
|
|
314
321
|
<Card>
|