@gadmin2n/schematics 0.0.147 → 0.0.148

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.
Files changed (92) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +1 -0
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +67 -15
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +17 -4
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/index.ts +5 -0
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/structured-all-exceptions.filter.spec.ts +24 -0
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/structured-all-exceptions.filter.ts +15 -0
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-runtime.controller.spec.ts +2 -6
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.spec.ts +28 -29
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.ts +16 -34
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.dto.ts +3 -3
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.service.spec.ts +61 -6
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.controller.spec.ts +2 -2
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/index.ts +2 -0
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.controller.ts +11 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.spec.ts +126 -0
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.ts +136 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.controller.ts +24 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.dto.ts +8 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.service.spec.ts +62 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.service.ts +94 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.types.ts +6 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staffDirectory.module.ts +10 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/providers/user.provider.ts +6 -2
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +20 -12
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +21 -19
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +11 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +97 -0
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.ts +150 -2
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.controller.ts +13 -1
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.dto.ts +13 -1
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.errors.ts +1 -0
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.service.spec.ts +125 -0
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.service.ts +118 -5
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-cancellation.spec.ts +20 -3
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +6 -9
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-runtime.facade.ts +2 -0
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.spec.ts +4 -1
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +5 -1
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +44 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +15 -1
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/node-config-validator.service.spec.ts +2 -3
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.spec.ts +33 -22
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.ts +10 -23
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/StaffSelect.test.tsx +69 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/StaffSelect.tsx +145 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/index.ts +2 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/staff-directory.api.ts +16 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +1 -0
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.test.ts +10 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +20 -2
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +6 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +49 -11
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +49 -11
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/PeopleGroupModal.tsx +181 -0
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/index.tsx +203 -0
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/people-groups.api.ts +145 -0
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/PersonModal.tsx +112 -0
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/index.tsx +214 -0
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/person-directory.api.ts +79 -0
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +7 -11
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +7 -7
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeOutputSummary.test.tsx +20 -0
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.test.tsx +97 -28
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +87 -119
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowCodeEditor.test.tsx +22 -1
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowCodeEditor.tsx +8 -2
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/connections.css +8 -46
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/connectionsApi.ts +7 -0
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.test.tsx +32 -3
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.tsx +163 -106
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +8 -1
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.test.tsx +26 -0
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +34 -6
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-config.css +13 -16
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-instance-show.css +185 -0
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/show.tsx +224 -105
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/CodeConfigEditor.test.tsx +1 -1
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DbExecuteConfigEditor.tsx +21 -12
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.tsx +1 -2
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.test.tsx +160 -8
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.tsx +125 -55
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +47 -3
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubjectPicker.test.tsx +33 -1
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubjectPicker.tsx +28 -46
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/human-approval-config.css +16 -0
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/subject-provider.api.ts +0 -6
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.test.tsx +24 -0
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +14 -17
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +1 -0
  90. package/package.json +1 -1
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CreateNodeInstanceDrawer.tsx +0 -162
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/UserGroupCreateModal.tsx +0 -72
@@ -4,28 +4,31 @@ import {
4
4
  Card,
5
5
  Empty,
6
6
  Input,
7
+ Popconfirm,
7
8
  Space,
8
- Spin,
9
+ Table,
9
10
  Tag,
11
+ Tooltip,
10
12
  Typography,
11
13
  message,
14
+ type TableColumnsType,
12
15
  } from 'antd';
13
16
  import {
14
- ApiOutlined,
17
+ DeleteOutlined,
15
18
  EditOutlined,
16
19
  PlusOutlined,
17
20
  ReloadOutlined,
18
21
  } from '@ant-design/icons';
19
22
  import { PageHeader } from 'components';
20
- import {
21
- NESTED_CARD_PROPS,
22
- PAGE_WRAPPER_STYLE,
23
- SEARCH_CARD_PROPS,
24
- } from 'constants/layout';
23
+ import { PAGE_WRAPPER_STYLE, SEARCH_CARD_PROPS } from 'constants/layout';
25
24
  import { ConnectionModal } from './ConnectionModal';
26
25
  import type { IntegrationConnection } from './connection.types';
27
- import { fetchConnections } from './connectionsApi';
26
+ import { deleteConnection, fetchConnections } from './connectionsApi';
28
27
  import { useTranslation } from 'react-i18next';
28
+ import {
29
+ formatWorkflowUtcDate,
30
+ formatWorkflowUtcDateTime,
31
+ } from '../utils/workflowDateTime';
29
32
  import '../workflow-ui.css';
30
33
  import './connections.css';
31
34
 
@@ -51,6 +54,137 @@ export default function ConnectionsPage() {
51
54
 
52
55
  useEffect(() => void load(), [load]);
53
56
 
57
+ async function removeConnection(connection: IntegrationConnection) {
58
+ try {
59
+ await deleteConnection(connection.code);
60
+ setConnections((current) =>
61
+ current.filter((item) => item.code !== connection.code),
62
+ );
63
+ message.success(t('connection.deleteSuccess'));
64
+ } catch (error: any) {
65
+ message.error(error?.message ?? t('connection.deleteFailed'));
66
+ }
67
+ }
68
+
69
+ const columns: TableColumnsType<IntegrationConnection> = [
70
+ {
71
+ title: t('common.name'),
72
+ dataIndex: 'name',
73
+ key: 'name',
74
+ width: 220,
75
+ render: (name: string, connection) => (
76
+ <div>
77
+ <Typography.Link onClick={() => setEditing(connection)}>
78
+ {name}
79
+ </Typography.Link>
80
+ <Typography.Text
81
+ type="secondary"
82
+ className="workflow-connection-table__code"
83
+ >
84
+ {connection.code}
85
+ </Typography.Text>
86
+ </div>
87
+ ),
88
+ },
89
+ {
90
+ title: t('common.description'),
91
+ dataIndex: 'description',
92
+ key: 'description',
93
+ width: 240,
94
+ render: (description?: string | null) => (
95
+ <Typography.Text
96
+ type="secondary"
97
+ ellipsis={{ tooltip: description || t('connection.noDescription') }}
98
+ >
99
+ {description || t('connection.noDescription')}
100
+ </Typography.Text>
101
+ ),
102
+ },
103
+ {
104
+ title: t('connection.baseUrl'),
105
+ dataIndex: 'baseUrl',
106
+ key: 'baseUrl',
107
+ width: 280,
108
+ render: (baseUrl: string) => (
109
+ <Typography.Text
110
+ ellipsis={{ tooltip: baseUrl }}
111
+ className="workflow-connection-table__url"
112
+ >
113
+ {baseUrl}
114
+ </Typography.Text>
115
+ ),
116
+ },
117
+ {
118
+ title: t('connection.authentication'),
119
+ key: 'authentication',
120
+ width: 210,
121
+ render: (_, connection) => (
122
+ <Space wrap size={[4, 4]}>
123
+ <Tag>{connectionAuthLabel(connection, t)}</Tag>
124
+ {connection.hasSecret && (
125
+ <Tag color="green">{t('connection.secretStored')}</Tag>
126
+ )}
127
+ </Space>
128
+ ),
129
+ },
130
+ {
131
+ title: t('list.status'),
132
+ dataIndex: 'isEnabled',
133
+ key: 'isEnabled',
134
+ width: 100,
135
+ render: (isEnabled: boolean) => (
136
+ <Tag color={isEnabled ? 'blue' : 'default'}>
137
+ {isEnabled ? t('common.enabled') : t('connection.disabled')}
138
+ </Tag>
139
+ ),
140
+ },
141
+ {
142
+ title: t('list.updatedAt'),
143
+ dataIndex: 'updatedAt',
144
+ key: 'updatedAt',
145
+ width: 150,
146
+ render: (updatedAt: string) => (
147
+ <Tooltip title={formatWorkflowUtcDateTime(updatedAt)}>
148
+ <span>{formatWorkflowUtcDate(updatedAt)}</span>
149
+ </Tooltip>
150
+ ),
151
+ },
152
+ {
153
+ title: t('list.actions'),
154
+ key: 'actions',
155
+ width: 120,
156
+ render: (_, connection) => (
157
+ <Space>
158
+ <Tooltip title={t('connection.openAndEdit')}>
159
+ <Button
160
+ size="small"
161
+ icon={<EditOutlined />}
162
+ aria-label={t('connection.openAria', { name: connection.name })}
163
+ onClick={() => setEditing(connection)}
164
+ />
165
+ </Tooltip>
166
+ <Popconfirm
167
+ title={t('connection.deleteConfirm', { name: connection.name })}
168
+ description={t('connection.deleteDescription')}
169
+ okText={t('common.delete')}
170
+ cancelText={t('common.cancel')}
171
+ okButtonProps={{ danger: true }}
172
+ onConfirm={() => removeConnection(connection)}
173
+ >
174
+ <Button
175
+ size="small"
176
+ danger
177
+ icon={<DeleteOutlined />}
178
+ aria-label={t('connection.deleteAria', {
179
+ name: connection.name,
180
+ })}
181
+ />
182
+ </Popconfirm>
183
+ </Space>
184
+ ),
185
+ },
186
+ ];
187
+
54
188
  return (
55
189
  <div className="workflow-page-stack" style={PAGE_WRAPPER_STYLE}>
56
190
  <PageHeader
@@ -84,105 +218,28 @@ export default function ConnectionsPage() {
84
218
 
85
219
  {/* Content Card */}
86
220
  <Card>
87
- {loading ? (
88
- <Spin
89
- size="large"
90
- style={{ display: 'block', margin: '48px auto' }}
91
- />
92
- ) : connections.length === 0 ? (
93
- <Empty description={t('connection.empty')}>
94
- <Button
95
- type="primary"
96
- icon={<PlusOutlined />}
97
- onClick={() => setEditing(null)}
98
- >
99
- {t('connection.new')}
100
- </Button>
101
- </Empty>
102
- ) : (
103
- <div
104
- style={{
105
- display: 'grid',
106
- gridTemplateColumns: 'repeat(auto-fill, minmax(330px, 1fr))',
107
- gap: 16,
108
- }}
109
- >
110
- {connections.map((connection) => (
111
- <Card
112
- key={connection.code}
113
- size="small"
114
- hoverable
115
- {...NESTED_CARD_PROPS}
116
- className="workflow-connection-card"
117
- role="button"
118
- tabIndex={0}
119
- aria-label={t('connection.openAria', {
120
- name: connection.name,
121
- })}
122
- onClick={() => setEditing(connection)}
123
- onKeyDown={(event) => {
124
- if (event.key === 'Enter' || event.key === ' ') {
125
- event.preventDefault();
126
- setEditing(connection);
127
- }
128
- }}
129
- >
130
- <div className="workflow-connection-card__header">
131
- <Space align="start">
132
- <ApiOutlined
133
- style={{
134
- color: 'var(--ant-color-primary)',
135
- fontSize: 20,
136
- marginTop: 3,
137
- }}
138
- />
139
- <div>
140
- <Typography.Text strong>
141
- {connection.name}
142
- </Typography.Text>
143
- <Typography.Text
144
- type="secondary"
145
- style={{ display: 'block', fontSize: 12 }}
146
- >
147
- {connection.code}
148
- </Typography.Text>
149
- </div>
150
- </Space>
151
- <Tag color={connection.isEnabled ? 'blue' : 'default'}>
152
- {connection.isEnabled
153
- ? t('common.enabled')
154
- : t('connection.disabled')}
155
- </Tag>
156
- </div>
157
- <Typography.Paragraph
158
- type="secondary"
159
- ellipsis={{ rows: 2 }}
160
- className="workflow-connection-card__description"
221
+ <Table
222
+ rowKey="code"
223
+ loading={loading}
224
+ columns={columns}
225
+ dataSource={connections}
226
+ pagination={false}
227
+ scroll={{ x: 'max-content' }}
228
+ size="small"
229
+ locale={{
230
+ emptyText: (
231
+ <Empty description={t('connection.empty')}>
232
+ <Button
233
+ type="primary"
234
+ icon={<PlusOutlined />}
235
+ onClick={() => setEditing(null)}
161
236
  >
162
- {connection.description || t('connection.noDescription')}
163
- </Typography.Paragraph>
164
- <Typography.Paragraph
165
- ellipsis={{ rows: 1, tooltip: connection.baseUrl }}
166
- className="workflow-connection-card__url"
167
- >
168
- {connection.baseUrl}
169
- </Typography.Paragraph>
170
- <div className="workflow-connection-card__footer">
171
- <Space wrap size={[4, 4]}>
172
- <Tag>{connectionAuthLabel(connection, t)}</Tag>
173
- {connection.hasSecret && (
174
- <Tag color="green">{t('connection.secretStored')}</Tag>
175
- )}
176
- </Space>
177
- <span className="workflow-connection-card__action">
178
- <EditOutlined />
179
- {t('connection.openAndEdit')}
180
- </span>
181
- </div>
182
- </Card>
183
- ))}
184
- </div>
185
- )}
237
+ {t('connection.new')}
238
+ </Button>
239
+ </Empty>
240
+ ),
241
+ }}
242
+ />
186
243
  </Card>
187
244
  <ConnectionModal
188
245
  open={editing !== undefined}
@@ -283,13 +283,14 @@ export default function WorkflowListPage() {
283
283
  {
284
284
  title: t('list.runs'),
285
285
  key: 'runs',
286
- width: 160,
286
+ width: 190,
287
287
  render: (_: any, record: Workflow) => {
288
288
  const runs = record.runs || {
289
289
  pending: 0,
290
290
  running: 0,
291
291
  completed: 0,
292
292
  failed: 0,
293
+ cancelled: 0,
293
294
  };
294
295
  const go = (status: string) =>
295
296
  navigate(
@@ -321,6 +322,12 @@ export default function WorkflowListPage() {
321
322
  label={t('list.failed')}
322
323
  onClick={() => go('FAILED')}
323
324
  />
325
+ <RunDot
326
+ count={runs.cancelled}
327
+ color="#8c8c8c"
328
+ label={t('list.cancelled')}
329
+ onClick={() => go('CANCELLED')}
330
+ />
324
331
  </Space>
325
332
  );
326
333
  },
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getStepStatus, shouldShowNodeError } from './instance-detail';
3
+
4
+ describe('Workflow instance cancellation display', () => {
5
+ it('does not present a cancelled node as an error', () => {
6
+ const cancelledError = {
7
+ message: 'Workflow cancelled',
8
+ stack: 'CancelledFailure: Workflow cancelled',
9
+ };
10
+
11
+ expect(
12
+ shouldShowNodeError({ status: 'CANCELLED', error: cancelledError }),
13
+ ).toBe(false);
14
+ expect(getStepStatus('CANCELLED')).toBe('wait');
15
+ });
16
+
17
+ it('keeps failure details visible for failed nodes', () => {
18
+ expect(
19
+ shouldShowNodeError({
20
+ status: 'FAILED',
21
+ error: { message: 'Request failed' },
22
+ }),
23
+ ).toBe(true);
24
+ expect(getStepStatus('FAILED')).toBe('error');
25
+ });
26
+ });
@@ -75,6 +75,9 @@ interface InstanceDetail {
75
75
  context: any;
76
76
  result: any;
77
77
  temporalRunId: string | null;
78
+ cancellationRequestedAt: string | null;
79
+ cancellationRequestedBy: string | null;
80
+ cancellationReason: string | null;
78
81
  startedAt: string | null;
79
82
  finishedAt: string | null;
80
83
  creator: string;
@@ -102,7 +105,7 @@ function formatDuration(start: string | null, end: string | null): string {
102
105
  return rem > 0 ? `${m}m ${rem}s` : `${m}m`;
103
106
  }
104
107
 
105
- function getStepStatus(
108
+ export function getStepStatus(
106
109
  status: string,
107
110
  ): 'wait' | 'process' | 'finish' | 'error' {
108
111
  switch (status) {
@@ -113,12 +116,18 @@ function getStepStatus(
113
116
  case 'FAILED':
114
117
  return 'error';
115
118
  case 'CANCELLED':
116
- return 'error';
119
+ return 'wait';
117
120
  default:
118
121
  return 'wait';
119
122
  }
120
123
  }
121
124
 
125
+ export function shouldShowNodeError(
126
+ execution: Pick<NodeExecution, 'status' | 'error'>,
127
+ ): boolean {
128
+ return execution.status === 'FAILED' && Boolean(execution.error);
129
+ }
130
+
122
131
  export default function WorkflowInstanceDetailPage() {
123
132
  const { t } = useTranslation('workflow');
124
133
  const { instanceId } = useParams<{ instanceId: string }>();
@@ -370,6 +379,23 @@ export default function WorkflowInstanceDetailPage() {
370
379
  <Descriptions.Item label="Duration">
371
380
  {formatDuration(data.startedAt, data.finishedAt)}
372
381
  </Descriptions.Item>
382
+ {data.cancellationRequestedAt && (
383
+ <>
384
+ <Descriptions.Item
385
+ label={t('instanceDetail.cancellationRequestedBy')}
386
+ >
387
+ {data.cancellationRequestedBy || '—'}
388
+ </Descriptions.Item>
389
+ <Descriptions.Item
390
+ label={t('instanceDetail.cancellationRequestedAt')}
391
+ >
392
+ {formatWorkflowUtcDateTime(data.cancellationRequestedAt)}
393
+ </Descriptions.Item>
394
+ <Descriptions.Item label={t('instanceDetail.cancellationReason')}>
395
+ {data.cancellationReason || '—'}
396
+ </Descriptions.Item>
397
+ </>
398
+ )}
373
399
  {data.retryOfInstanceId && (
374
400
  <Descriptions.Item label={t('instanceDetail.retrySource')}>
375
401
  <Button
@@ -562,7 +588,7 @@ export default function WorkflowInstanceDetailPage() {
562
588
  Duration:{' '}
563
589
  {formatDuration(ne.startedAt, ne.finishedAt)}
564
590
  </span>
565
- {ne.error && (
591
+ {shouldShowNodeError(ne) && (
566
592
  <div style={{ color: '#ff4d4f', marginTop: 4 }}>
567
593
  Error:{' '}
568
594
  {typeof ne.error === 'object'
@@ -679,7 +705,7 @@ export default function WorkflowInstanceDetailPage() {
679
705
  </div>
680
706
  )}
681
707
 
682
- {selectedExecution.error && (
708
+ {shouldShowNodeError(selectedExecution) && (
683
709
  <div style={{ marginBottom: 12 }}>
684
710
  <Text strong style={{ fontSize: 12, color: '#ff4d4f' }}>
685
711
  Error:
@@ -747,7 +773,9 @@ export default function WorkflowInstanceDetailPage() {
747
773
  ? 'blue'
748
774
  : selectedExecution.status === 'FAILED'
749
775
  ? 'red'
750
- : 'gold'
776
+ : selectedExecution.status === 'CANCELLED'
777
+ ? 'default'
778
+ : 'gold'
751
779
  }
752
780
  >
753
781
  {selectedExecution.status}
@@ -832,7 +860,7 @@ export default function WorkflowInstanceDetailPage() {
832
860
  </div>
833
861
  )}
834
862
 
835
- {selectedExecution.error && (
863
+ {shouldShowNodeError(selectedExecution) && (
836
864
  <div style={{ marginBottom: 12 }}>
837
865
  <Text
838
866
  strong
@@ -321,6 +321,19 @@
321
321
  line-height: 1.5;
322
322
  }
323
323
 
324
+ .db-execute-config__sql-head {
325
+ min-height: 24px;
326
+ display: flex;
327
+ align-items: center;
328
+ justify-content: space-between;
329
+ gap: 12px;
330
+ }
331
+
332
+ .db-execute-config__sql-head .ant-tag {
333
+ margin-inline-end: 0;
334
+ font-size: 11px;
335
+ }
336
+
324
337
  .callable-input-parameters__row {
325
338
  display: grid;
326
339
  min-width: 0;
@@ -865,22 +878,6 @@
865
878
  margin-top: -8px;
866
879
  }
867
880
 
868
- .workflow-node-panel__footer {
869
- display: flex;
870
- flex-direction: column;
871
- gap: 8px;
872
- padding: 12px 16px;
873
- border-top: 1px solid var(--ant-color-border-secondary);
874
- background: var(--ant-color-bg-container);
875
- box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.04);
876
- }
877
-
878
- .workflow-node-panel__save-template {
879
- display: flex;
880
- flex-direction: column;
881
- gap: 8px;
882
- }
883
-
884
881
  @media (max-width: 900px) {
885
882
  .workflow-node-panel--editor {
886
883
  width: min(420px, 52vw) !important;
@@ -0,0 +1,185 @@
1
+ .node-instance-show__header,
2
+ .node-instance-show__header-main,
3
+ .node-instance-show__title-row,
4
+ .node-instance-show__section-heading {
5
+ display: flex;
6
+ align-items: center;
7
+ }
8
+
9
+ .node-instance-show__header {
10
+ justify-content: space-between;
11
+ flex-wrap: wrap;
12
+ gap: 16px;
13
+ }
14
+
15
+ .node-instance-show__header-main {
16
+ min-width: 0;
17
+ gap: 8px;
18
+ }
19
+
20
+ .node-instance-show__heading {
21
+ min-width: 0;
22
+ }
23
+
24
+ .node-instance-show__title-row {
25
+ flex-wrap: wrap;
26
+ gap: 8px;
27
+ }
28
+
29
+ .node-instance-show__title-row .ant-typography {
30
+ margin: 0;
31
+ }
32
+
33
+ .node-instance-show__section-heading {
34
+ justify-content: space-between;
35
+ gap: 16px;
36
+ margin-bottom: 20px;
37
+ }
38
+
39
+ .node-instance-show__section-heading .ant-typography {
40
+ margin: 0;
41
+ }
42
+
43
+ .node-instance-show__section-heading h5.ant-typography {
44
+ font-size: var(--workflow-content-title-size, 18px);
45
+ line-height: 28px;
46
+ }
47
+
48
+ .node-instance-show__summary-grid {
49
+ display: grid;
50
+ grid-template-columns: repeat(3, minmax(0, 1fr));
51
+ overflow: hidden;
52
+ border: 1px solid var(--ant-color-border-secondary);
53
+ border-radius: 10px;
54
+ background: var(--ant-color-fill-quaternary);
55
+ }
56
+
57
+ .node-instance-show__summary-item {
58
+ min-width: 0;
59
+ padding: 16px 18px;
60
+ border-right: 1px solid var(--ant-color-border-secondary);
61
+ border-bottom: 1px solid var(--ant-color-border-secondary);
62
+ }
63
+
64
+ .node-instance-show__summary-item:nth-child(3n) {
65
+ border-right: 0;
66
+ }
67
+
68
+ .node-instance-show__summary-item:nth-last-child(-n + 3) {
69
+ border-bottom: 0;
70
+ }
71
+
72
+ .node-instance-show__summary-item > .ant-typography:first-child {
73
+ display: block;
74
+ margin-bottom: 6px;
75
+ font-size: 12px;
76
+ }
77
+
78
+ .node-instance-show__summary-item > div {
79
+ min-height: 24px;
80
+ overflow-wrap: anywhere;
81
+ }
82
+
83
+ .node-instance-show__description {
84
+ display: grid;
85
+ grid-template-columns: 120px minmax(0, 1fr);
86
+ gap: 16px;
87
+ margin-top: 16px;
88
+ padding: 16px 18px;
89
+ border: 1px solid var(--ant-color-border-secondary);
90
+ border-radius: 10px;
91
+ }
92
+
93
+ .node-instance-show__configuration {
94
+ max-width: 960px;
95
+ padding: 20px;
96
+ border: 1px solid var(--ant-color-border-secondary);
97
+ border-radius: 10px;
98
+ background: var(--ant-color-fill-quaternary);
99
+ }
100
+
101
+ .node-instance-show__empty {
102
+ margin: 0;
103
+ padding: 28px 0;
104
+ border: 1px dashed var(--ant-color-border-secondary);
105
+ border-radius: 10px;
106
+ background: var(--ant-color-fill-quaternary);
107
+ }
108
+
109
+ .node-instance-show__references-loading {
110
+ display: flex;
111
+ min-height: 112px;
112
+ align-items: center;
113
+ justify-content: center;
114
+ }
115
+
116
+ .node-instance-show__workflow-link.ant-btn {
117
+ height: auto;
118
+ padding: 0;
119
+ white-space: normal;
120
+ text-align: left;
121
+ }
122
+
123
+ .node-instance-show__loading,
124
+ .node-instance-show__not-found {
125
+ min-height: calc(100vh - 160px);
126
+ display: flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ }
130
+
131
+ .node-instance-show__not-found {
132
+ flex-direction: column;
133
+ gap: 16px;
134
+ }
135
+
136
+ @media (max-width: 900px) {
137
+ .node-instance-show__summary-grid {
138
+ grid-template-columns: repeat(2, minmax(0, 1fr));
139
+ }
140
+
141
+ .node-instance-show__summary-item,
142
+ .node-instance-show__summary-item:nth-child(3n) {
143
+ border-right: 1px solid var(--ant-color-border-secondary);
144
+ border-bottom: 1px solid var(--ant-color-border-secondary);
145
+ }
146
+
147
+ .node-instance-show__summary-item:nth-child(2n) {
148
+ border-right: 0;
149
+ }
150
+
151
+ .node-instance-show__summary-item:nth-last-child(-n + 2) {
152
+ border-bottom: 0;
153
+ }
154
+ }
155
+
156
+ @media (max-width: 600px) {
157
+ .node-instance-show__header,
158
+ .node-instance-show__section-heading {
159
+ align-items: flex-start;
160
+ }
161
+
162
+ .node-instance-show__summary-grid {
163
+ grid-template-columns: 1fr;
164
+ }
165
+
166
+ .node-instance-show__summary-item,
167
+ .node-instance-show__summary-item:nth-child(2n),
168
+ .node-instance-show__summary-item:nth-child(3n) {
169
+ border-right: 0;
170
+ border-bottom: 1px solid var(--ant-color-border-secondary);
171
+ }
172
+
173
+ .node-instance-show__summary-item:last-child {
174
+ border-bottom: 0;
175
+ }
176
+
177
+ .node-instance-show__description {
178
+ grid-template-columns: 1fr;
179
+ gap: 6px;
180
+ }
181
+
182
+ .node-instance-show__configuration {
183
+ padding: 16px;
184
+ }
185
+ }