@gadmin2n/schematics 0.0.151 → 0.0.153
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/config/prisma/workflow.prisma +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/.seed-timestamp +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +23 -44
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +25 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +74 -64
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +0 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-cancellation.spec.ts +17 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +58 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.ts +66 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution-idempotency.spec.ts +61 -149
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-runtime.controller.spec.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-runtime.controller.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle.service.spec.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.spec.ts +38 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +18 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.dto.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +131 -242
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.test.ts +7 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +6 -36
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +0 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +29 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +29 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/index.tsx +20 -17
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/index.tsx +38 -35
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.tsx +6 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.tsx +24 -22
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/EnhancedFlowRenderer.tsx +0 -11
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/StatusCards.tsx +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.tsx +20 -18
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +7 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +28 -79
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instances.tsx +5 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/NodeInstanceForm.tsx +0 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DbExecuteConfigEditor.tsx +227 -97
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +62 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/settings/index.tsx +73 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +21 -46
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/newNodeConfig.ts +1 -0
- package/package.json +1 -1
|
@@ -40,6 +40,11 @@ const STATUS_TAG: Record<
|
|
|
40
40
|
> = {
|
|
41
41
|
PENDING: { color: 'gold', label: 'Pending', icon: <ClockCircleOutlined /> },
|
|
42
42
|
RUNNING: { color: 'blue', label: 'Running', icon: <LoadingOutlined /> },
|
|
43
|
+
WAITING_RETRY: {
|
|
44
|
+
color: 'orange',
|
|
45
|
+
label: 'Waiting to retry',
|
|
46
|
+
icon: <ClockCircleOutlined />,
|
|
47
|
+
},
|
|
43
48
|
COMPLETED: {
|
|
44
49
|
color: 'green',
|
|
45
50
|
label: 'Completed',
|
|
@@ -65,7 +70,6 @@ interface NodeExecution {
|
|
|
65
70
|
interface RetryAttempt {
|
|
66
71
|
id: string;
|
|
67
72
|
status: string;
|
|
68
|
-
retryFromNodeId: string | null;
|
|
69
73
|
createdAt: string;
|
|
70
74
|
}
|
|
71
75
|
|
|
@@ -88,9 +92,7 @@ interface InstanceDetail {
|
|
|
88
92
|
approvableNodeIds: string[];
|
|
89
93
|
retryableNodeIds: string[];
|
|
90
94
|
retryAttempts: RetryAttempt[];
|
|
91
|
-
reusedNodeIds: string[];
|
|
92
95
|
retryOfInstanceId: string | null;
|
|
93
|
-
retryFromNodeId: string | null;
|
|
94
96
|
dsl?: WorkflowDSL;
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -221,7 +223,11 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
221
223
|
|
|
222
224
|
// Auto-refresh for running instances
|
|
223
225
|
useEffect(() => {
|
|
224
|
-
if (
|
|
226
|
+
if (
|
|
227
|
+
data?.status === 'RUNNING' ||
|
|
228
|
+
data?.status === 'PENDING' ||
|
|
229
|
+
data?.status === 'WAITING_RETRY'
|
|
230
|
+
) {
|
|
225
231
|
const interval = setInterval(fetchData, 3000);
|
|
226
232
|
return () => clearInterval(interval);
|
|
227
233
|
}
|
|
@@ -263,16 +269,16 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
263
269
|
}
|
|
264
270
|
};
|
|
265
271
|
|
|
266
|
-
const
|
|
272
|
+
const handleRetryFailedNode = async (nodeId: string) => {
|
|
267
273
|
setActionLoading(true);
|
|
268
274
|
try {
|
|
269
|
-
|
|
270
|
-
`workflow/instance/${instanceId}/retry-
|
|
275
|
+
await customRequest(
|
|
276
|
+
`workflow/instance/${instanceId}/retry-failed-node`,
|
|
271
277
|
'POST',
|
|
272
278
|
{ nodeId },
|
|
273
279
|
);
|
|
274
280
|
message.success(t('instanceDetail.retryFromNodeStarted'));
|
|
275
|
-
|
|
281
|
+
await fetchData();
|
|
276
282
|
} catch (e: any) {
|
|
277
283
|
message.error(e?.message || t('instanceDetail.retryFromNodeFailed'));
|
|
278
284
|
} finally {
|
|
@@ -325,17 +331,10 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
325
331
|
selectedNodeId && data.dsl
|
|
326
332
|
? data.dsl.nodes.find((n) => n.id === selectedNodeId)
|
|
327
333
|
: null;
|
|
328
|
-
const selectedNodeWasReused = selectedNodeId
|
|
329
|
-
? data.reusedNodeIds.includes(selectedNodeId)
|
|
330
|
-
: false;
|
|
331
334
|
const latestRetryAttempt = data.retryAttempts[0];
|
|
332
|
-
const
|
|
333
|
-
? data.
|
|
334
|
-
|
|
335
|
-
)
|
|
336
|
-
: undefined;
|
|
337
|
-
const workflowFailure =
|
|
338
|
-
data.status === 'FAILED' ? getWorkflowFailureSummary(data.result) : null;
|
|
335
|
+
const workflowFailure = ['FAILED', 'WAITING_RETRY'].includes(data.status)
|
|
336
|
+
? getWorkflowFailureSummary(data.result)
|
|
337
|
+
: null;
|
|
339
338
|
|
|
340
339
|
return (
|
|
341
340
|
<div className="workflow-page-stack">
|
|
@@ -383,7 +382,7 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
383
382
|
{t('instanceDetail.openTemporal')}
|
|
384
383
|
</Button>
|
|
385
384
|
</Tooltip>
|
|
386
|
-
{
|
|
385
|
+
{['RUNNING', 'PENDING', 'WAITING_RETRY'].includes(data.status) && (
|
|
387
386
|
<Popconfirm
|
|
388
387
|
title="Cancel this workflow execution?"
|
|
389
388
|
onConfirm={handleCancel}
|
|
@@ -480,17 +479,10 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
480
479
|
</Button>
|
|
481
480
|
</Descriptions.Item>
|
|
482
481
|
)}
|
|
483
|
-
{data.reusedNodeIds.length > 0 && (
|
|
484
|
-
<Descriptions.Item label={t('instanceDetail.reusedResults')}>
|
|
485
|
-
{t('instanceDetail.reusedResultsCount', {
|
|
486
|
-
count: data.reusedNodeIds.length,
|
|
487
|
-
})}
|
|
488
|
-
</Descriptions.Item>
|
|
489
|
-
)}
|
|
490
482
|
</Descriptions>
|
|
491
483
|
{workflowFailure && (
|
|
492
484
|
<Alert
|
|
493
|
-
type=
|
|
485
|
+
type={data.status === 'WAITING_RETRY' ? 'warning' : 'error'}
|
|
494
486
|
showIcon
|
|
495
487
|
style={{ marginTop: 16 }}
|
|
496
488
|
message={workflowFailure.message}
|
|
@@ -571,9 +563,6 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
571
563
|
const canRetryFromNode =
|
|
572
564
|
ne.status === 'FAILED' &&
|
|
573
565
|
data.retryableNodeIds.includes(ne.nodeId);
|
|
574
|
-
const retryAttempt = data.retryAttempts.find(
|
|
575
|
-
(attempt) => attempt.retryFromNodeId === ne.nodeId,
|
|
576
|
-
);
|
|
577
566
|
return {
|
|
578
567
|
title: (
|
|
579
568
|
<Space
|
|
@@ -620,7 +609,7 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
620
609
|
description={t(
|
|
621
610
|
'instanceDetail.retryFromNodeDescription',
|
|
622
611
|
)}
|
|
623
|
-
onConfirm={() =>
|
|
612
|
+
onConfirm={() => handleRetryFailedNode(ne.nodeId)}
|
|
624
613
|
okText={t('instanceDetail.retryFromNode')}
|
|
625
614
|
cancelText={t('common.cancel')}
|
|
626
615
|
>
|
|
@@ -635,23 +624,6 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
635
624
|
</Button>
|
|
636
625
|
</Popconfirm>
|
|
637
626
|
)}
|
|
638
|
-
{retryAttempt && (
|
|
639
|
-
<Button
|
|
640
|
-
size="small"
|
|
641
|
-
type="link"
|
|
642
|
-
icon={<LinkOutlined />}
|
|
643
|
-
onClick={(event) => {
|
|
644
|
-
event.stopPropagation();
|
|
645
|
-
navigate(
|
|
646
|
-
`/admin/workflow/instance/show/${retryAttempt.id}`,
|
|
647
|
-
);
|
|
648
|
-
}}
|
|
649
|
-
>
|
|
650
|
-
{t('instanceDetail.viewRetryAttempt', {
|
|
651
|
-
id: retryAttempt.id,
|
|
652
|
-
})}
|
|
653
|
-
</Button>
|
|
654
|
-
)}
|
|
655
627
|
</Space>
|
|
656
628
|
),
|
|
657
629
|
description: (
|
|
@@ -821,7 +793,6 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
821
793
|
showExecutionStatus={true}
|
|
822
794
|
selectedNodeId={selectedNodeId}
|
|
823
795
|
readonly={true}
|
|
824
|
-
reusedNodeIds={data.reusedNodeIds}
|
|
825
796
|
onNodeClick={(nodeId) => setSelectedNodeId(nodeId)}
|
|
826
797
|
/>
|
|
827
798
|
</div>
|
|
@@ -982,7 +953,7 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
982
953
|
'instanceDetail.retryFromNodeDescription',
|
|
983
954
|
)}
|
|
984
955
|
onConfirm={() =>
|
|
985
|
-
|
|
956
|
+
handleRetryFailedNode(selectedExecution.nodeId)
|
|
986
957
|
}
|
|
987
958
|
okText={t('instanceDetail.retryFromNode')}
|
|
988
959
|
cancelText={t('common.cancel')}
|
|
@@ -997,22 +968,6 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
997
968
|
</Popconfirm>
|
|
998
969
|
)}
|
|
999
970
|
|
|
1000
|
-
{selectedExecution.status === 'FAILED' &&
|
|
1001
|
-
selectedRetryAttempt && (
|
|
1002
|
-
<Button
|
|
1003
|
-
icon={<LinkOutlined />}
|
|
1004
|
-
onClick={() =>
|
|
1005
|
-
navigate(
|
|
1006
|
-
`/admin/workflow/instance/show/${selectedRetryAttempt.id}`,
|
|
1007
|
-
)
|
|
1008
|
-
}
|
|
1009
|
-
>
|
|
1010
|
-
{t('instanceDetail.viewRetryAttempt', {
|
|
1011
|
-
id: selectedRetryAttempt.id,
|
|
1012
|
-
})}
|
|
1013
|
-
</Button>
|
|
1014
|
-
)}
|
|
1015
|
-
|
|
1016
971
|
{/* Approval actions */}
|
|
1017
972
|
{selectedExecution.nodeType === 'approval' &&
|
|
1018
973
|
selectedExecution.status === 'RUNNING' &&
|
|
@@ -1047,14 +1002,10 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
1047
1002
|
<div>
|
|
1048
1003
|
<div style={{ marginBottom: 16 }}>
|
|
1049
1004
|
<Title level={5}>{selectedDslNode.label}</Title>
|
|
1050
|
-
<Tag
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
? t('instanceDetail.reusedResult')
|
|
1055
|
-
: instanceHasFinished
|
|
1056
|
-
? 'Not Executed / Skipped'
|
|
1057
|
-
: 'Not Executed'}
|
|
1005
|
+
<Tag>
|
|
1006
|
+
{instanceHasFinished
|
|
1007
|
+
? 'Not Executed / Skipped'
|
|
1008
|
+
: 'Not Executed'}
|
|
1058
1009
|
</Tag>
|
|
1059
1010
|
</div>
|
|
1060
1011
|
<Descriptions column={1} size="small">
|
|
@@ -1063,11 +1014,9 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
1063
1014
|
</Descriptions.Item>
|
|
1064
1015
|
<Descriptions.Item label="Status">
|
|
1065
1016
|
<Text type="secondary">
|
|
1066
|
-
{
|
|
1067
|
-
?
|
|
1068
|
-
:
|
|
1069
|
-
? 'Not executed in this run'
|
|
1070
|
-
: 'Waiting for execution'}
|
|
1017
|
+
{instanceHasFinished
|
|
1018
|
+
? 'Not executed in this run'
|
|
1019
|
+
: 'Waiting for execution'}
|
|
1071
1020
|
</Text>
|
|
1072
1021
|
</Descriptions.Item>
|
|
1073
1022
|
</Descriptions>
|
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instances.tsx
CHANGED
|
@@ -13,6 +13,7 @@ const PAGE_SIZE = 20;
|
|
|
13
13
|
const STATUS_TAG: Record<string, { color: string; label: string }> = {
|
|
14
14
|
PENDING: { color: 'gold', label: 'Pending' },
|
|
15
15
|
RUNNING: { color: 'blue', label: 'Running' },
|
|
16
|
+
WAITING_RETRY: { color: 'orange', label: 'Waiting to retry' },
|
|
16
17
|
COMPLETED: { color: 'green', label: 'Completed' },
|
|
17
18
|
FAILED: { color: 'red', label: 'Failed' },
|
|
18
19
|
CANCELLED: { color: 'default', label: 'Cancelled' },
|
|
@@ -233,6 +234,10 @@ export default function WorkflowInstancesPage() {
|
|
|
233
234
|
options={[
|
|
234
235
|
{ value: 'PENDING', label: t('instances.statuses.pending') },
|
|
235
236
|
{ value: 'RUNNING', label: t('instances.statuses.running') },
|
|
237
|
+
{
|
|
238
|
+
value: 'WAITING_RETRY',
|
|
239
|
+
label: t('instances.statuses.waiting_retry'),
|
|
240
|
+
},
|
|
236
241
|
{ value: 'COMPLETED', label: t('instances.statuses.completed') },
|
|
237
242
|
{ value: 'FAILED', label: t('instances.statuses.failed') },
|
|
238
243
|
{ value: 'CANCELLED', label: t('instances.statuses.cancelled') },
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Alert,
|
|
3
|
+
Button,
|
|
4
|
+
InputNumber,
|
|
5
|
+
Segmented,
|
|
6
|
+
Select,
|
|
7
|
+
Space,
|
|
8
|
+
Switch,
|
|
9
|
+
Tag,
|
|
10
|
+
Typography,
|
|
11
|
+
} from 'antd';
|
|
2
12
|
import { customRequest } from 'helpers/http';
|
|
3
13
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
14
|
import { useTranslation } from 'react-i18next';
|
|
5
15
|
import { ParameterBindingEditor } from '../../components/ParameterBindingEditor';
|
|
16
|
+
import { WorkflowCodeEditor } from '../../components/WorkflowCodeEditor';
|
|
6
17
|
import type { ParameterBinding } from '../../types/parameter-binding';
|
|
7
18
|
import type { WidgetProps } from '../schema-form/widget-registry';
|
|
19
|
+
import { ParameterBindingListEditor } from './ParameterBindingListEditor';
|
|
8
20
|
|
|
9
21
|
export interface DatabaseFunctionArgument {
|
|
10
22
|
position: number;
|
|
@@ -26,11 +38,24 @@ type DatabaseFunctionRef = Pick<
|
|
|
26
38
|
>;
|
|
27
39
|
|
|
28
40
|
type DbExecuteConfig = {
|
|
41
|
+
mode?: 'FUNCTION' | 'SQL';
|
|
29
42
|
functionRef?: DatabaseFunctionRef;
|
|
30
43
|
argumentBindings?: ParameterBinding[];
|
|
44
|
+
allowWrite?: boolean;
|
|
45
|
+
sql?: string;
|
|
46
|
+
parameters?: ParameterBinding[];
|
|
31
47
|
timeoutSeconds?: number | null;
|
|
32
48
|
};
|
|
33
49
|
|
|
50
|
+
function executionMode(config: DbExecuteConfig): 'FUNCTION' | 'SQL' {
|
|
51
|
+
if (config.mode === 'FUNCTION' || config.mode === 'SQL') return config.mode;
|
|
52
|
+
return config.sql !== undefined ||
|
|
53
|
+
config.allowWrite !== undefined ||
|
|
54
|
+
config.parameters !== undefined
|
|
55
|
+
? 'SQL'
|
|
56
|
+
: 'FUNCTION';
|
|
57
|
+
}
|
|
58
|
+
|
|
34
59
|
function functionKey(value: DatabaseFunctionRef): string {
|
|
35
60
|
return `${value.schema}.${value.name}(${value.identityArguments})`;
|
|
36
61
|
}
|
|
@@ -50,10 +75,12 @@ export function DbExecuteConfigEditor({
|
|
|
50
75
|
}: WidgetProps<DbExecuteConfig | undefined>) {
|
|
51
76
|
const { t } = useTranslation('workflow');
|
|
52
77
|
const config = value ?? {};
|
|
78
|
+
const mode = executionMode(config);
|
|
79
|
+
const allowWrite = config.allowWrite === true;
|
|
53
80
|
const [functions, setFunctions] = useState<DatabaseFunctionOption[]>([]);
|
|
54
81
|
const [catalogStatus, setCatalogStatus] = useState<
|
|
55
|
-
'loading' | 'ready' | 'error'
|
|
56
|
-
>('
|
|
82
|
+
'idle' | 'loading' | 'ready' | 'error'
|
|
83
|
+
>('idle');
|
|
57
84
|
|
|
58
85
|
const loadFunctions = useCallback(async () => {
|
|
59
86
|
setCatalogStatus('loading');
|
|
@@ -70,8 +97,8 @@ export function DbExecuteConfigEditor({
|
|
|
70
97
|
}, []);
|
|
71
98
|
|
|
72
99
|
useEffect(() => {
|
|
73
|
-
void loadFunctions();
|
|
74
|
-
}, [loadFunctions]);
|
|
100
|
+
if (mode === 'FUNCTION') void loadFunctions();
|
|
101
|
+
}, [loadFunctions, mode]);
|
|
75
102
|
|
|
76
103
|
const selected = useMemo(
|
|
77
104
|
() =>
|
|
@@ -89,109 +116,212 @@ export function DbExecuteConfigEditor({
|
|
|
89
116
|
return (
|
|
90
117
|
<Space direction="vertical" size={14} style={{ width: '100%' }}>
|
|
91
118
|
<Space direction="vertical" size={6} style={{ width: '100%' }}>
|
|
92
|
-
<Typography.Text>{t('nodeEditors.database.
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
value={selectedKey}
|
|
107
|
-
disabled={disabled || catalogStatus !== 'ready'}
|
|
108
|
-
loading={catalogStatus === 'loading'}
|
|
109
|
-
placeholder={t('nodeEditors.database.selectFunction')}
|
|
110
|
-
showSearch
|
|
111
|
-
optionFilterProp="label"
|
|
112
|
-
options={functions.map((item) => ({
|
|
113
|
-
value: functionKey(item),
|
|
114
|
-
label: functionKey(item),
|
|
115
|
-
}))}
|
|
116
|
-
onChange={(key) => {
|
|
117
|
-
const databaseFunction = functions.find(
|
|
118
|
-
(item) => functionKey(item) === key,
|
|
119
|
-
);
|
|
120
|
-
if (!databaseFunction) return;
|
|
121
|
-
const keepExistingBindings = selectedKey === key;
|
|
119
|
+
<Typography.Text>{t('nodeEditors.database.mode')}</Typography.Text>
|
|
120
|
+
<Segmented
|
|
121
|
+
block
|
|
122
|
+
aria-label={t('nodeEditors.database.mode')}
|
|
123
|
+
value={mode}
|
|
124
|
+
disabled={disabled}
|
|
125
|
+
options={[
|
|
126
|
+
{
|
|
127
|
+
label: t('nodeEditors.database.functionMode'),
|
|
128
|
+
value: 'FUNCTION',
|
|
129
|
+
},
|
|
130
|
+
{ label: t('nodeEditors.database.sqlMode'), value: 'SQL' },
|
|
131
|
+
]}
|
|
132
|
+
onChange={(nextMode) =>
|
|
122
133
|
onChange({
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
name: databaseFunction.name,
|
|
126
|
-
identityArguments: databaseFunction.identityArguments,
|
|
127
|
-
},
|
|
128
|
-
argumentBindings: databaseFunction.arguments.map(
|
|
129
|
-
(argument, index) =>
|
|
130
|
-
(keepExistingBindings
|
|
131
|
-
? config.argumentBindings?.[index]
|
|
132
|
-
: undefined) ?? defaultBinding(argument),
|
|
133
|
-
),
|
|
134
|
+
...config,
|
|
135
|
+
mode: nextMode as 'FUNCTION' | 'SQL',
|
|
134
136
|
timeoutSeconds: config.timeoutSeconds ?? 30,
|
|
135
|
-
})
|
|
136
|
-
}
|
|
137
|
-
style={{ width: '100%' }}
|
|
137
|
+
})
|
|
138
|
+
}
|
|
138
139
|
/>
|
|
139
140
|
</Space>
|
|
140
141
|
|
|
141
|
-
{
|
|
142
|
-
<Alert
|
|
143
|
-
type="warning"
|
|
144
|
-
showIcon
|
|
145
|
-
message={t('nodeEditors.database.functionMissing')}
|
|
146
|
-
/>
|
|
147
|
-
)}
|
|
148
|
-
|
|
149
|
-
{selected && (
|
|
142
|
+
{mode === 'FUNCTION' ? (
|
|
150
143
|
<>
|
|
151
|
-
<
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
</Typography.Text>
|
|
156
|
-
{selected.arguments.length === 0 ? (
|
|
144
|
+
<Space direction="vertical" size={6} style={{ width: '100%' }}>
|
|
145
|
+
<Typography.Text>
|
|
146
|
+
{t('nodeEditors.database.function')}
|
|
147
|
+
</Typography.Text>
|
|
157
148
|
<Typography.Text type="secondary">
|
|
158
|
-
{t('nodeEditors.database.
|
|
149
|
+
{t('nodeEditors.database.functionHint')}
|
|
159
150
|
</Typography.Text>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
151
|
+
{catalogStatus === 'error' && (
|
|
152
|
+
<Typography.Text type="danger">
|
|
153
|
+
{t('nodeEditors.database.loadFailed')}{' '}
|
|
154
|
+
<Button type="link" size="small" onClick={loadFunctions}>
|
|
155
|
+
{t('common.retry')}
|
|
156
|
+
</Button>
|
|
157
|
+
</Typography.Text>
|
|
158
|
+
)}
|
|
159
|
+
<Select
|
|
160
|
+
aria-label={t('nodeEditors.database.function')}
|
|
161
|
+
value={selectedKey}
|
|
162
|
+
disabled={disabled || catalogStatus !== 'ready'}
|
|
163
|
+
loading={catalogStatus === 'loading'}
|
|
164
|
+
placeholder={t('nodeEditors.database.selectFunction')}
|
|
165
|
+
showSearch
|
|
166
|
+
optionFilterProp="label"
|
|
167
|
+
options={functions.map((item) => ({
|
|
168
|
+
value: functionKey(item),
|
|
169
|
+
label: functionKey(item),
|
|
170
|
+
}))}
|
|
171
|
+
onChange={(key) => {
|
|
172
|
+
const databaseFunction = functions.find(
|
|
173
|
+
(item) => functionKey(item) === key,
|
|
174
|
+
);
|
|
175
|
+
if (!databaseFunction) return;
|
|
176
|
+
const keepExistingBindings = selectedKey === key;
|
|
177
|
+
onChange({
|
|
178
|
+
...config,
|
|
179
|
+
mode: 'FUNCTION',
|
|
180
|
+
functionRef: {
|
|
181
|
+
schema: databaseFunction.schema,
|
|
182
|
+
name: databaseFunction.name,
|
|
183
|
+
identityArguments: databaseFunction.identityArguments,
|
|
184
|
+
},
|
|
185
|
+
argumentBindings: databaseFunction.arguments.map(
|
|
186
|
+
(argument, index) =>
|
|
187
|
+
(keepExistingBindings
|
|
188
|
+
? config.argumentBindings?.[index]
|
|
189
|
+
: undefined) ?? defaultBinding(argument),
|
|
190
|
+
),
|
|
191
|
+
timeoutSeconds: config.timeoutSeconds ?? 30,
|
|
192
|
+
});
|
|
193
|
+
}}
|
|
194
|
+
style={{ width: '100%' }}
|
|
195
|
+
/>
|
|
196
|
+
</Space>
|
|
197
|
+
|
|
198
|
+
{catalogStatus === 'ready' && config.functionRef && !selected && (
|
|
199
|
+
<Alert
|
|
200
|
+
type="warning"
|
|
201
|
+
showIcon
|
|
202
|
+
message={t('nodeEditors.database.functionMissing')}
|
|
203
|
+
/>
|
|
204
|
+
)}
|
|
205
|
+
|
|
206
|
+
{selected && (
|
|
207
|
+
<>
|
|
208
|
+
<Typography.Text type="secondary">
|
|
209
|
+
{t('nodeEditors.database.returnType', {
|
|
210
|
+
type: selected.returnType,
|
|
211
|
+
})}
|
|
212
|
+
</Typography.Text>
|
|
213
|
+
{selected.arguments.length === 0 ? (
|
|
214
|
+
<Typography.Text type="secondary">
|
|
215
|
+
{t('nodeEditors.database.noArguments')}
|
|
173
216
|
</Typography.Text>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
217
|
+
) : (
|
|
218
|
+
selected.arguments.map((argument, index) => (
|
|
219
|
+
<Space
|
|
220
|
+
key={`${argument.position}:${argument.name}`}
|
|
221
|
+
direction="vertical"
|
|
222
|
+
size={6}
|
|
223
|
+
style={{ width: '100%' }}
|
|
224
|
+
>
|
|
225
|
+
<Typography.Text>
|
|
226
|
+
{argument.name}{' '}
|
|
227
|
+
<Typography.Text type="secondary">
|
|
228
|
+
({argument.type})
|
|
229
|
+
</Typography.Text>
|
|
230
|
+
</Typography.Text>
|
|
231
|
+
<ParameterBindingEditor
|
|
232
|
+
name={argument.name}
|
|
233
|
+
defaultPath={defaultArgumentPath(argument.name)}
|
|
234
|
+
value={
|
|
235
|
+
config.argumentBindings?.[index] ??
|
|
236
|
+
defaultBinding(argument)
|
|
237
|
+
}
|
|
238
|
+
disabled={disabled}
|
|
239
|
+
onChange={(binding) => {
|
|
240
|
+
const argumentBindings = selected.arguments.map(
|
|
241
|
+
(item, bindingIndex) =>
|
|
242
|
+
config.argumentBindings?.[bindingIndex] ??
|
|
243
|
+
defaultBinding(item),
|
|
244
|
+
);
|
|
245
|
+
argumentBindings[index] = binding;
|
|
246
|
+
onChange({ ...config, argumentBindings });
|
|
247
|
+
}}
|
|
248
|
+
/>
|
|
249
|
+
</Space>
|
|
250
|
+
))
|
|
251
|
+
)}
|
|
252
|
+
</>
|
|
193
253
|
)}
|
|
194
254
|
</>
|
|
255
|
+
) : (
|
|
256
|
+
<>
|
|
257
|
+
<div className="db-execute-config__permission">
|
|
258
|
+
<div>
|
|
259
|
+
<Typography.Text strong>
|
|
260
|
+
{t('nodeEditors.database.allowWrite')}
|
|
261
|
+
</Typography.Text>
|
|
262
|
+
<Typography.Text
|
|
263
|
+
type="secondary"
|
|
264
|
+
className="db-execute-config__hint"
|
|
265
|
+
>
|
|
266
|
+
{t(
|
|
267
|
+
allowWrite
|
|
268
|
+
? 'nodeEditors.database.readWriteDescription'
|
|
269
|
+
: 'nodeEditors.database.readOnlyDescription',
|
|
270
|
+
)}
|
|
271
|
+
</Typography.Text>
|
|
272
|
+
</div>
|
|
273
|
+
<Switch
|
|
274
|
+
aria-label={t('nodeEditors.database.allowWrite')}
|
|
275
|
+
checked={allowWrite}
|
|
276
|
+
disabled={disabled}
|
|
277
|
+
onChange={(nextAllowWrite) =>
|
|
278
|
+
onChange({ ...config, allowWrite: nextAllowWrite })
|
|
279
|
+
}
|
|
280
|
+
/>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
<Space direction="vertical" size={6} style={{ width: '100%' }}>
|
|
284
|
+
<div className="db-execute-config__sql-head">
|
|
285
|
+
<Typography.Text strong>
|
|
286
|
+
{t('nodeEditors.database.sql')}
|
|
287
|
+
</Typography.Text>
|
|
288
|
+
<Tag bordered={false} color={allowWrite ? 'orange' : 'green'}>
|
|
289
|
+
{t(
|
|
290
|
+
allowWrite
|
|
291
|
+
? 'nodeEditors.database.writeEnabled'
|
|
292
|
+
: 'nodeEditors.database.readOnly',
|
|
293
|
+
)}
|
|
294
|
+
</Tag>
|
|
295
|
+
</div>
|
|
296
|
+
<WorkflowCodeEditor
|
|
297
|
+
language="sql"
|
|
298
|
+
value={config.sql ?? ''}
|
|
299
|
+
readOnly={disabled}
|
|
300
|
+
height={280}
|
|
301
|
+
wordWrap="off"
|
|
302
|
+
ariaLabel={t('nodeEditors.database.sql')}
|
|
303
|
+
onChange={(sql) => onChange({ ...config, sql })}
|
|
304
|
+
/>
|
|
305
|
+
</Space>
|
|
306
|
+
|
|
307
|
+
<Space direction="vertical" size={6} style={{ width: '100%' }}>
|
|
308
|
+
<Typography.Text>
|
|
309
|
+
{t('nodeEditors.database.parameters')}
|
|
310
|
+
</Typography.Text>
|
|
311
|
+
<Typography.Text
|
|
312
|
+
type="secondary"
|
|
313
|
+
className="db-execute-config__hint"
|
|
314
|
+
>
|
|
315
|
+
{t('nodeEditors.database.parametersHint')}
|
|
316
|
+
</Typography.Text>
|
|
317
|
+
<ParameterBindingListEditor
|
|
318
|
+
value={config.parameters}
|
|
319
|
+
disabled={disabled}
|
|
320
|
+
addLabel={t('nodeEditors.database.addParameter')}
|
|
321
|
+
onChange={(parameters) => onChange({ ...config, parameters })}
|
|
322
|
+
/>
|
|
323
|
+
</Space>
|
|
324
|
+
</>
|
|
195
325
|
)}
|
|
196
326
|
|
|
197
327
|
<Space direction="vertical" size={6} style={{ width: '100%' }}>
|