@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
@@ -2,6 +2,7 @@ import {
2
2
  DisconnectOutlined,
3
3
  MinusCircleOutlined,
4
4
  PlusOutlined,
5
+ SaveOutlined,
5
6
  SearchOutlined,
6
7
  SendOutlined,
7
8
  } from '@ant-design/icons';
@@ -9,11 +10,11 @@ import {
9
10
  Alert,
10
11
  Button,
11
12
  Card,
12
- Checkbox,
13
13
  Form,
14
14
  Input,
15
15
  InputNumber,
16
16
  message,
17
+ Modal,
17
18
  Segmented,
18
19
  Select,
19
20
  Space,
@@ -44,7 +45,6 @@ import type {
44
45
  WorkflowNodeType,
45
46
  } from '../types';
46
47
  import { NodeOutputSummary } from './NodeOutputSummary';
47
- import { CreateNodeInstanceDrawer } from './CreateNodeInstanceDrawer';
48
48
  import { WorkflowCodeEditor } from './WorkflowCodeEditor';
49
49
  import '../node-instances/node-config.css';
50
50
  import type { ReferencedConfigStatus } from '../hooks/useReferencedNodeInstanceConfigs';
@@ -186,12 +186,11 @@ function NodePropertyPanelImpl(
186
186
  const [instances, setInstances] = useState<WorkflowNodeInstance[]>([]);
187
187
  const [instanceLoading, setInstanceLoading] = useState(false);
188
188
  const [instanceLoadError, setInstanceLoadError] = useState(false);
189
- const [createInstanceOpen, setCreateInstanceOpen] = useState(false);
189
+ const [saveTemplateOpen, setSaveTemplateOpen] = useState(false);
190
190
  const [paletteQuery, setPaletteQuery] = useState('');
191
- const [newInstanceName, setNewInstanceName] = useState('');
192
- const [newInstanceDesc, setNewInstanceDesc] = useState('');
193
- const [saveAsLoading, setSaveAsLoading] = useState(false);
194
- const [alsoSaveAsInstance, setAlsoSaveAsInstance] = useState(false);
191
+ const [templateName, setTemplateName] = useState('');
192
+ const [templateDescription, setTemplateDescription] = useState('');
193
+ const [templateSaving, setTemplateSaving] = useState(false);
195
194
  const [configForm] = Form.useForm();
196
195
 
197
196
  // Find configSchema for current node type
@@ -326,7 +325,6 @@ function NodePropertyPanelImpl(
326
325
  ]);
327
326
  setDirty(false);
328
327
  setPrompt('');
329
- setAlsoSaveAsInstance(false);
330
328
  }
331
329
  }, [node?.id, currentNodeType]);
332
330
 
@@ -414,8 +412,7 @@ function NodePropertyPanelImpl(
414
412
 
415
413
  // Reset selection when node changes
416
414
  useEffect(() => {
417
- setAlsoSaveAsInstance(false);
418
- setCreateInstanceOpen(false);
415
+ setSaveTemplateOpen(false);
419
416
  }, [node?.id]);
420
417
 
421
418
  const buildCurrentUpdates = useCallback((): NodePropertyUpdates | null => {
@@ -590,57 +587,51 @@ function NodePropertyPanelImpl(
590
587
  }
591
588
  }
592
589
 
593
- async function handleSave() {
594
- if (!node) return;
590
+ function openSaveTemplate() {
591
+ if (!node || node.instanceRef) return;
592
+ setTemplateName(node.label.trim());
593
+ setTemplateDescription('');
594
+ setSaveTemplateOpen(true);
595
+ }
596
+
597
+ async function handleSaveAsTemplate() {
598
+ if (!node || node.instanceRef || !currentNodeType || !templateName.trim())
599
+ return;
595
600
  const updates = buildCurrentUpdates();
596
601
  if (!updates?.config) return;
597
- const cleanConfig = updates.config;
598
- if (
599
- !node.instanceRef &&
600
- alsoSaveAsInstance &&
601
- currentNodeType &&
602
- newInstanceName.trim()
603
- ) {
604
- // Save node AND create a new instance, then reference it
605
- setSaveAsLoading(true);
606
- try {
607
- const res = await customRequest<any>(
608
- 'workflowNodeInstance/createOne',
609
- 'POST',
610
- {
611
- nodeTypeId: Number(currentNodeType.id),
612
- name: newInstanceName.trim(),
613
- description: newInstanceDesc.trim() || null,
614
- config: cleanConfig,
615
- isEnabled: true,
616
- },
617
- );
618
- const created = res;
619
- const newId = String(created.id);
620
- onSave(node.id, {
621
- label,
622
- config: created.config,
623
- configOverride: null,
624
- instanceRef: {
625
- instanceId: newId,
626
- instanceCode: created.code,
627
- },
628
- });
629
- setInstances((prev) => [...prev, created]);
630
- setAlsoSaveAsInstance(false);
631
- setNewInstanceName('');
632
- setNewInstanceDesc('');
633
- message.success('已保存并创建新实例');
634
- } catch (e: any) {
635
- message.error(e?.message ?? '创建实例失败');
636
- } finally {
637
- setSaveAsLoading(false);
638
- }
639
- } else {
640
- onSave(node.id, updates);
641
- message.success('节点已保存');
602
+ setTemplateSaving(true);
603
+ try {
604
+ const created = await customRequest<WorkflowNodeInstance>(
605
+ 'workflowNodeInstance/createOne',
606
+ 'POST',
607
+ {
608
+ nodeTypeId: Number(currentNodeType.id),
609
+ name: templateName.trim(),
610
+ description: templateDescription.trim() || null,
611
+ config: updates.config,
612
+ isEnabled: true,
613
+ },
614
+ );
615
+ onSave(node.id, {
616
+ label,
617
+ config: created.config,
618
+ configOverride: null,
619
+ instanceRef: {
620
+ instanceId: String(created.id),
621
+ instanceCode: created.code,
622
+ },
623
+ });
624
+ setInstances((current) => [
625
+ created,
626
+ ...current.filter((item) => item.id !== created.id),
627
+ ]);
628
+ setSaveTemplateOpen(false);
629
+ message.success(t('editor.templateSavedAndApplied'));
630
+ } catch (error: any) {
631
+ message.error(error?.message ?? t('editor.templateSaveFailed'));
632
+ } finally {
633
+ setTemplateSaving(false);
642
634
  }
643
- setDirty(false);
644
635
  }
645
636
 
646
637
  function handleAiSubmit() {
@@ -1017,10 +1008,11 @@ function NodePropertyPanelImpl(
1017
1008
  <Button
1018
1009
  size="small"
1019
1010
  className="workflow-panel-action"
1020
- icon={<PlusOutlined />}
1021
- onClick={() => setCreateInstanceOpen(true)}
1011
+ icon={<SaveOutlined />}
1012
+ disabled={referencedConfigUnavailable}
1013
+ onClick={openSaveTemplate}
1022
1014
  >
1023
- {t('editor.createTemplate')}
1015
+ {t('editor.saveCurrentAsTemplate')}
1024
1016
  </Button>
1025
1017
  </div>
1026
1018
  <Select
@@ -1225,64 +1217,40 @@ function NodePropertyPanelImpl(
1225
1217
  </Card>
1226
1218
  )}
1227
1219
  </div>
1228
- {activeTab === 'config' && (
1229
- <footer className="workflow-node-panel__footer">
1230
- {!node.instanceRef && (
1231
- <div className="workflow-node-panel__save-template">
1232
- <Checkbox
1233
- checked={alsoSaveAsInstance}
1234
- onChange={(e) => setAlsoSaveAsInstance(e.target.checked)}
1235
- >
1236
- <Text>{t('editor.saveAsInstance')}</Text>
1237
- </Checkbox>
1238
- {alsoSaveAsInstance && (
1239
- <Space direction="vertical" size={6} style={{ width: '100%' }}>
1240
- <Input
1241
- value={newInstanceName}
1242
- onChange={(e) => setNewInstanceName(e.target.value)}
1243
- placeholder={t('editor.instanceNamePlaceholder')}
1244
- size="small"
1245
- />
1246
- <Input
1247
- value={newInstanceDesc}
1248
- onChange={(e) => setNewInstanceDesc(e.target.value)}
1249
- placeholder={t('common.optionalDescription')}
1250
- size="small"
1251
- />
1252
- </Space>
1253
- )}
1254
- </div>
1255
- )}
1256
- <Button
1257
- type="primary"
1258
- size="large"
1259
- block
1260
- loading={saveAsLoading}
1261
- disabled={
1262
- referencedConfigUnavailable ||
1263
- (!node.instanceRef &&
1264
- alsoSaveAsInstance &&
1265
- !newInstanceName.trim())
1266
- }
1267
- onClick={handleSave}
1268
- >
1269
- {t('editor.saveChanges')}
1270
- </Button>
1271
- </footer>
1272
- )}
1273
- <CreateNodeInstanceDrawer
1274
- open={createInstanceOpen}
1275
- nodeType={currentNodeType}
1276
- onCancel={() => setCreateInstanceOpen(false)}
1277
- onCreated={(instance) => {
1278
- setCreateInstanceOpen(false);
1279
- setInstances((current) => [
1280
- instance,
1281
- ...current.filter((item) => item.id !== instance.id),
1282
- ]);
1283
- referenceInstance(instance);
1284
- }}
1285
- />
1220
+ <Modal
1221
+ open={saveTemplateOpen}
1222
+ title={t('editor.saveCurrentAsTemplate')}
1223
+ okText={t('editor.saveAndUseTemplate')}
1224
+ cancelText={t('common.cancel')}
1225
+ confirmLoading={templateSaving}
1226
+ okButtonProps={{ disabled: !templateName.trim() }}
1227
+ onOk={() => void handleSaveAsTemplate()}
1228
+ onCancel={() => setSaveTemplateOpen(false)}
1229
+ destroyOnHidden
1230
+ >
1231
+ <Typography.Paragraph type="secondary">
1232
+ {t('editor.saveCurrentAsTemplateHint')}
1233
+ </Typography.Paragraph>
1234
+ <Form layout="vertical">
1235
+ <Form.Item label={t('editor.templateName')} required>
1236
+ <Input
1237
+ aria-label={t('editor.templateName')}
1238
+ value={templateName}
1239
+ onChange={(event) => setTemplateName(event.target.value)}
1240
+ autoFocus
1241
+ />
1242
+ </Form.Item>
1243
+ <Form.Item label={t('common.description')}>
1244
+ <Input.TextArea
1245
+ aria-label={t('common.description')}
1246
+ value={templateDescription}
1247
+ onChange={(event) => setTemplateDescription(event.target.value)}
1248
+ placeholder={t('common.optionalDescription')}
1249
+ rows={3}
1250
+ />
1251
+ </Form.Item>
1252
+ </Form>
1253
+ </Modal>
1286
1254
  </div>
1287
1255
  );
1288
1256
  }
@@ -5,14 +5,19 @@ import { WorkflowCodeEditor } from './WorkflowCodeEditor';
5
5
 
6
6
  vi.mock('@monaco-editor/react', () => ({
7
7
  default: ({
8
+ language,
8
9
  value,
9
10
  onChange,
11
+ options,
10
12
  }: {
13
+ language?: string;
11
14
  value?: string;
12
15
  onChange?(value: string): void;
16
+ options?: { ariaLabel?: string };
13
17
  }) => (
14
18
  <textarea
15
- aria-label="shared editor"
19
+ aria-label={options?.ariaLabel ?? 'shared editor'}
20
+ data-language={language}
16
21
  value={value}
17
22
  onChange={(event) => onChange?.(event.target.value)}
18
23
  />
@@ -39,4 +44,20 @@ describe('WorkflowCodeEditor', () => {
39
44
  });
40
45
  expect(onChange).toHaveBeenCalledWith('{"ok":true}');
41
46
  });
47
+
48
+ it('enables Monaco SQL mode and labels the status bar', () => {
49
+ render(
50
+ <WorkflowCodeEditor
51
+ language="sql"
52
+ value="SELECT * FROM users"
53
+ ariaLabel="SQL 语句"
54
+ />,
55
+ );
56
+
57
+ expect(screen.getByRole('textbox', { name: 'SQL 语句' })).toHaveAttribute(
58
+ 'data-language',
59
+ 'sql',
60
+ );
61
+ expect(screen.getByText('SQL')).toBeInTheDocument();
62
+ });
42
63
  });
@@ -2,7 +2,13 @@ import Editor from '@monaco-editor/react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import './workflow-code-editor.css';
4
4
 
5
- type WorkflowEditorLanguage = 'json' | 'javascript';
5
+ type WorkflowEditorLanguage = 'json' | 'javascript' | 'sql';
6
+
7
+ const LANGUAGE_LABELS: Record<WorkflowEditorLanguage, string> = {
8
+ json: 'JSON',
9
+ javascript: 'JavaScript',
10
+ sql: 'SQL',
11
+ };
6
12
 
7
13
  export function WorkflowCodeEditor({
8
14
  language,
@@ -67,7 +73,7 @@ export function WorkflowCodeEditor({
67
73
  }}
68
74
  />
69
75
  <div className="workflow-code-editor__status" aria-hidden="true">
70
- <span>{language === 'json' ? 'JSON' : 'JavaScript'}</span>
76
+ <span>{LANGUAGE_LABELS[language]}</span>
71
77
  <span>UTF-8</span>
72
78
  <span>{t('editor.indentSpaces')}: 2</span>
73
79
  </div>
@@ -1,53 +1,15 @@
1
- .workflow-connection-card {
2
- height: 100%;
3
- cursor: pointer;
4
- }
5
-
6
- .workflow-connection-card:focus-visible {
7
- outline: 2px solid var(--ant-color-primary);
8
- outline-offset: 2px;
9
- }
10
-
11
- .workflow-connection-card .ant-card-body {
12
- display: flex;
13
- flex-direction: column;
14
- height: 100%;
15
- }
16
-
17
- .workflow-connection-card__header,
18
- .workflow-connection-card__footer {
19
- display: flex;
20
- align-items: flex-start;
21
- justify-content: space-between;
22
- gap: 12px;
23
- }
24
-
25
- .workflow-connection-card__description {
26
- min-height: 40px;
27
- margin: 14px 0 8px;
28
- }
29
-
30
- .workflow-connection-card__url {
31
- margin: 0 0 14px;
32
- color: var(--ant-color-text);
1
+ .workflow-connection-table__code {
2
+ display: block;
3
+ margin-top: 2px;
33
4
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
34
5
  font-size: 12px;
35
6
  }
36
7
 
37
- .workflow-connection-card__footer {
38
- align-items: center;
39
- margin-top: auto;
40
- padding-top: 12px;
41
- border-top: 1px solid var(--ant-color-border-secondary);
42
- }
43
-
44
- .workflow-connection-card__action {
45
- display: inline-flex;
46
- flex: none;
47
- align-items: center;
48
- gap: 6px;
49
- color: var(--ant-color-primary);
50
- font-weight: 500;
8
+ .workflow-connection-table__url {
9
+ display: block;
10
+ max-width: 260px;
11
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
12
+ font-size: 12px;
51
13
  }
52
14
 
53
15
  .workflow-connection-form__section-title {
@@ -27,3 +27,10 @@ export function fetchConnectionSecrets(code: string) {
27
27
  'GET',
28
28
  );
29
29
  }
30
+
31
+ export function deleteConnection(code: string) {
32
+ return customRequest<{ code: string }>(
33
+ `integration-connections/${encodeURIComponent(code)}`,
34
+ 'DELETE',
35
+ );
36
+ }
@@ -1,8 +1,8 @@
1
1
  import { render, screen, waitFor } from '@testing-library/react';
2
2
  import userEvent from '@testing-library/user-event';
3
- import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
4
  import i18n from '../../../i18n';
5
- import { fetchConnections } from './connectionsApi';
5
+ import { deleteConnection, fetchConnections } from './connectionsApi';
6
6
  import ConnectionsPage from './index';
7
7
 
8
8
  vi.mock('components', () => ({
@@ -16,6 +16,7 @@ vi.mock('components', () => ({
16
16
 
17
17
  vi.mock('./connectionsApi', () => ({
18
18
  fetchConnections: vi.fn(),
19
+ deleteConnection: vi.fn(),
19
20
  }));
20
21
 
21
22
  vi.mock('./ConnectionModal', () => ({
@@ -42,6 +43,11 @@ Object.defineProperty(window, 'matchMedia', {
42
43
 
43
44
  describe('ConnectionsPage', () => {
44
45
  beforeEach(async () => {
46
+ vi.clearAllMocks();
47
+ const getComputedStyle = window.getComputedStyle.bind(window);
48
+ vi.spyOn(window, 'getComputedStyle').mockImplementation((element) =>
49
+ getComputedStyle(element),
50
+ );
45
51
  await i18n.changeLanguage('zh_CN');
46
52
  vi.mocked(fetchConnections).mockResolvedValue([
47
53
  {
@@ -57,9 +63,12 @@ describe('ConnectionsPage', () => {
57
63
  updatedAt: '2026-08-13T00:00:00.000Z',
58
64
  },
59
65
  ]);
66
+ vi.mocked(deleteConnection).mockResolvedValue({ code: 'ticket-system' });
60
67
  });
61
68
 
62
- it('opens the connection editor when the card is clicked', async () => {
69
+ afterEach(() => vi.restoreAllMocks());
70
+
71
+ it('opens the connection editor from the table row action', async () => {
63
72
  const user = userEvent.setup();
64
73
  render(<ConnectionsPage />);
65
74
 
@@ -72,4 +81,24 @@ describe('ConnectionsPage', () => {
72
81
  expect(screen.getByText('正在编辑:工单系统')).toBeInTheDocument(),
73
82
  );
74
83
  });
84
+
85
+ it('deletes the connection after confirmation', async () => {
86
+ const user = userEvent.setup();
87
+ render(<ConnectionsPage />);
88
+
89
+ await user.click(
90
+ await screen.findByRole('button', { name: '删除 工单系统' }),
91
+ );
92
+ await screen.findByText('删除连接 工单系统?');
93
+ const confirmButton = document.querySelector<HTMLButtonElement>(
94
+ '.ant-popconfirm-buttons .ant-btn-primary',
95
+ );
96
+ expect(confirmButton).not.toBeNull();
97
+ await user.click(confirmButton!);
98
+
99
+ await waitFor(() =>
100
+ expect(deleteConnection).toHaveBeenCalledWith('ticket-system'),
101
+ );
102
+ expect(screen.queryByText('ticket-system')).not.toBeInTheDocument();
103
+ });
75
104
  });