@evenicanpm/admin-integrate 2.0.1 → 2.3.1
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/documents/Process/get-process-details.ts +2 -0
- package/documents/ProcessConfig/fragments.ts +2 -2
- package/documents/ProcessExport/read.ts +2 -0
- package/documents/ProcessTemplate/fragments.ts +8 -0
- package/package.json +2 -2
- package/src/components/integration-view/nodes/group-node.tsx +1 -1
- package/src/components/templatesV2/inputs/FormikPageBody.tsx +1 -0
- package/src/components/templatesV2/inputs/primitives/TextWithQueryInput.tsx +20 -1
- package/src/components/templatesV2/inputs/renderers/renderFormControl.tsx +4 -0
- package/src/components/templatesV2/types.tsx +2 -2
- package/src/components/templatesV2/utils/parseTemplateValuesFromConfig.ts +311 -0
- package/src/components/templatesV2/wizard.tsx +61 -12
- package/src/pages/integration-create/integration-create.tsx +41 -33
- package/src/pages/integration-create/template-list-step.tsx +8 -13
- package/src/pages/integration-create/template-options-step.tsx +30 -10
- package/src/pages/integration-create/wizard-step.tsx +15 -1
- package/src/pages/integration-details/integration-details.tsx +77 -30
- package/src/pages/integration-details/task-drawer/add-task.tsx +1 -0
- package/src/pages/integration-details/task-drawer/edit-task.tsx +25 -6
- package/src/pages/integration-details/task-drawer/task-reducer.ts +6 -0
|
@@ -63,8 +63,6 @@ export const ENDPOINT_EXPORT_FRAGMENT = gql`
|
|
|
63
63
|
Enabled
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
TemplateConfig
|
|
67
|
-
ProcessTemplateId
|
|
68
66
|
}
|
|
69
67
|
`;
|
|
70
68
|
|
|
@@ -109,6 +107,8 @@ export const PROCESS_EXPORT_FRAGMENT = gql`
|
|
|
109
107
|
Sort
|
|
110
108
|
Params
|
|
111
109
|
ParallelProcessingGroup
|
|
110
|
+
DirectionTypeId
|
|
111
|
+
ProcessTemplateId
|
|
112
112
|
}
|
|
113
113
|
ProcessIcon
|
|
114
114
|
IsTriggered
|
|
@@ -7,6 +7,7 @@ export const PROCESS_TEMPLATE_FRAGMENT = gql`
|
|
|
7
7
|
isActive
|
|
8
8
|
created
|
|
9
9
|
modified
|
|
10
|
+
directionTypeId
|
|
10
11
|
pages {
|
|
11
12
|
id
|
|
12
13
|
title
|
|
@@ -47,6 +48,13 @@ export const PROCESS_TEMPLATE_FRAGMENT = gql`
|
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
mappings {
|
|
52
|
+
fieldName
|
|
53
|
+
jsonPath
|
|
54
|
+
jsonValuePath
|
|
55
|
+
format
|
|
56
|
+
inputTypeCode
|
|
57
|
+
}
|
|
50
58
|
}
|
|
51
59
|
`;
|
|
52
60
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evenicanpm/admin-integrate",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "e4Integrate Admin Panel",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -21,5 +21,5 @@
|
|
|
21
21
|
"elkjs": "^0.10.0",
|
|
22
22
|
"lodash": "^4.17.21"
|
|
23
23
|
},
|
|
24
|
-
"gitHead": "
|
|
24
|
+
"gitHead": "7a5a6a8c3c3dd9efe96fbbb884b21e7681d32550"
|
|
25
25
|
}
|
|
@@ -38,7 +38,7 @@ export function GroupNode(props: NodeProps): JSX.Element {
|
|
|
38
38
|
paddingRight: "48px",
|
|
39
39
|
}}
|
|
40
40
|
>
|
|
41
|
-
{props.data.variant === "process" &&
|
|
41
|
+
{props.data.variant === "process" && props?.data?.canEdit ? (
|
|
42
42
|
<AddCircleOutlineIcon
|
|
43
43
|
sx={{
|
|
44
44
|
color: "grey.600",
|
|
@@ -66,6 +66,8 @@ interface DisplayStateInput {
|
|
|
66
66
|
helpText?: string | null;
|
|
67
67
|
validating: boolean;
|
|
68
68
|
readOnly?: boolean;
|
|
69
|
+
isEditableAfterCreate?: boolean;
|
|
70
|
+
isCreateMode?: boolean;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
function getInputDisplayState({
|
|
@@ -75,11 +77,14 @@ function getInputDisplayState({
|
|
|
75
77
|
helpText,
|
|
76
78
|
validating,
|
|
77
79
|
readOnly,
|
|
80
|
+
isEditableAfterCreate,
|
|
81
|
+
isCreateMode,
|
|
78
82
|
}: DisplayStateInput): {
|
|
79
83
|
displayError: string | undefined;
|
|
80
84
|
displayHelpText: string;
|
|
81
85
|
formHelperTextSx: SxProps<Theme>;
|
|
82
86
|
isDisabled: boolean;
|
|
87
|
+
isCreateMode: boolean;
|
|
83
88
|
} {
|
|
84
89
|
const showError = Boolean(error);
|
|
85
90
|
const effectiveValidation = getEffectiveValidation(
|
|
@@ -94,7 +99,11 @@ function getInputDisplayState({
|
|
|
94
99
|
const isError = showError || effectiveValidation?.isValid === false;
|
|
95
100
|
const displayError = getDisplayError(validating, isError, helperMessage);
|
|
96
101
|
const displayHelpText = validating ? "Checking…" : helperMessage;
|
|
97
|
-
const isDisabled = Boolean(
|
|
102
|
+
const isDisabled = Boolean(
|
|
103
|
+
readOnly ||
|
|
104
|
+
validating ||
|
|
105
|
+
(isEditableAfterCreate === false && !isCreateMode),
|
|
106
|
+
);
|
|
98
107
|
const formHelperTextSx: SxProps<Theme> = {
|
|
99
108
|
...(validating && { color: "text.secondary" }),
|
|
100
109
|
...(isSuccess && !validating && { color: "success.main" }),
|
|
@@ -105,6 +114,7 @@ function getInputDisplayState({
|
|
|
105
114
|
displayHelpText,
|
|
106
115
|
formHelperTextSx,
|
|
107
116
|
isDisabled,
|
|
117
|
+
isCreateMode: isCreateMode || false,
|
|
108
118
|
};
|
|
109
119
|
}
|
|
110
120
|
|
|
@@ -128,6 +138,8 @@ export interface TextWithQueryInputProps {
|
|
|
128
138
|
options?: SelectorOption[];
|
|
129
139
|
/** Result from validateFieldOnSubmit (on Next click); shown below field. */
|
|
130
140
|
submitValidationResult?: SubmitValidationResult | null;
|
|
141
|
+
isEditableAfterCreate?: boolean;
|
|
142
|
+
isCreateMode?: boolean;
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
export function TextWithQueryInput({
|
|
@@ -147,6 +159,8 @@ export function TextWithQueryInput({
|
|
|
147
159
|
placeholder,
|
|
148
160
|
options = [],
|
|
149
161
|
submitValidationResult,
|
|
162
|
+
isEditableAfterCreate,
|
|
163
|
+
isCreateMode,
|
|
150
164
|
}: Readonly<TextWithQueryInputProps>) {
|
|
151
165
|
const [validation, setValidation] = React.useState<{
|
|
152
166
|
isValid: boolean;
|
|
@@ -156,6 +170,7 @@ export function TextWithQueryInput({
|
|
|
156
170
|
|
|
157
171
|
const handleBlur = React.useCallback(async () => {
|
|
158
172
|
onBlur?.();
|
|
173
|
+
if (isEditableAfterCreate === false && !isCreateMode) return;
|
|
159
174
|
const strValue = valueToString(value);
|
|
160
175
|
if (!strValue && inputType !== InputType.CHECKBOX) {
|
|
161
176
|
setValidation(null);
|
|
@@ -195,6 +210,8 @@ export function TextWithQueryInput({
|
|
|
195
210
|
onBlur,
|
|
196
211
|
validateFormikBeforeApi,
|
|
197
212
|
setFieldValidationInProgress,
|
|
213
|
+
isEditableAfterCreate,
|
|
214
|
+
isCreateMode,
|
|
198
215
|
]);
|
|
199
216
|
|
|
200
217
|
if (readOnly) {
|
|
@@ -216,6 +233,8 @@ export function TextWithQueryInput({
|
|
|
216
233
|
helpText,
|
|
217
234
|
validating,
|
|
218
235
|
readOnly,
|
|
236
|
+
isEditableAfterCreate,
|
|
237
|
+
isCreateMode,
|
|
219
238
|
});
|
|
220
239
|
|
|
221
240
|
const clearValidationAndSet = (v: TextWithQueryValue) => {
|
|
@@ -200,6 +200,7 @@ export function renderFormControl({
|
|
|
200
200
|
disabledFields,
|
|
201
201
|
setFieldValidationInProgress,
|
|
202
202
|
submitValidationResults,
|
|
203
|
+
isCreateMode,
|
|
203
204
|
}: {
|
|
204
205
|
input: ProcessTemplateInput;
|
|
205
206
|
templateId: string;
|
|
@@ -221,6 +222,7 @@ export function renderFormControl({
|
|
|
221
222
|
string,
|
|
222
223
|
{ isValid: boolean; message: string }
|
|
223
224
|
>;
|
|
225
|
+
isCreateMode?: boolean;
|
|
224
226
|
}) {
|
|
225
227
|
const k = templateFieldKey(templateId, input.fieldName ?? "");
|
|
226
228
|
const err = errors[k] ?? null;
|
|
@@ -263,6 +265,8 @@ export function renderFormControl({
|
|
|
263
265
|
setFieldValidationInProgress={setFieldValidationInProgress}
|
|
264
266
|
options={getSelectorOptionsFromInput(input)}
|
|
265
267
|
submitValidationResult={submitValidationResult}
|
|
268
|
+
isEditableAfterCreate={input.isEditableAfterCreate ?? true}
|
|
269
|
+
isCreateMode={isCreateMode}
|
|
266
270
|
/>
|
|
267
271
|
)}
|
|
268
272
|
</Field>
|
|
@@ -19,8 +19,8 @@ export type ProcessTemplateInputSchema = ArrayElement<
|
|
|
19
19
|
export type Data = { processTemplate: ProcessTemplate[] };
|
|
20
20
|
|
|
21
21
|
export type ProcessTemplateFilter = {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
sourceTemplateId?: string | null;
|
|
23
|
+
targetTemplateId?: string | null;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export type FieldValue =
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
export interface Mapping {
|
|
2
|
+
fieldName: string;
|
|
3
|
+
jsonPath: string;
|
|
4
|
+
jsonValuePath: string;
|
|
5
|
+
format: string;
|
|
6
|
+
inputTypeCode?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function parseWithMappings(
|
|
10
|
+
input: { json: Record<string, unknown> },
|
|
11
|
+
mappings: Mapping[],
|
|
12
|
+
): Record<string, unknown> {
|
|
13
|
+
const result: Record<string, unknown> = {};
|
|
14
|
+
|
|
15
|
+
for (const mapping of mappings) {
|
|
16
|
+
const { fieldName, jsonPath, format } = mapping;
|
|
17
|
+
|
|
18
|
+
const pathValue = resolvePath(input.json, jsonPath);
|
|
19
|
+
if (pathValue === undefined || pathValue === null) {
|
|
20
|
+
result[fieldName] = mapping.inputTypeCode === "input-table" ? [] : null;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const parsed = parseValueFromFormat(format, fieldName, pathValue);
|
|
25
|
+
const isEmptyForInputTable =
|
|
26
|
+
mapping.inputTypeCode === "input-table" &&
|
|
27
|
+
(parsed === "" ||
|
|
28
|
+
(typeof parsed === "object" &&
|
|
29
|
+
parsed !== null &&
|
|
30
|
+
!Array.isArray(parsed) &&
|
|
31
|
+
Object.keys(parsed).length === 0));
|
|
32
|
+
result[fieldName] = isEmptyForInputTable ? [] : parsed;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function stripPathPrefix(path: string): string {
|
|
39
|
+
if (path.startsWith("$.")) return path.slice(2);
|
|
40
|
+
if (path.startsWith("$")) return path.slice(1);
|
|
41
|
+
return path;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function walkSegment(current: unknown, segment: string | number): unknown {
|
|
45
|
+
if (typeof segment === "number") {
|
|
46
|
+
if (!Array.isArray(current)) return undefined;
|
|
47
|
+
return current[segment];
|
|
48
|
+
}
|
|
49
|
+
if (typeof current !== "object" || Array.isArray(current)) return undefined;
|
|
50
|
+
return (current as Record<string, unknown>)[segment];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Walks an object/array using a simple JSONPath expression (dot + bracket notation). */
|
|
54
|
+
function resolvePath(obj: unknown, path: string): unknown {
|
|
55
|
+
if (path === "$") return obj;
|
|
56
|
+
|
|
57
|
+
const stripped = stripPathPrefix(path);
|
|
58
|
+
if (!stripped) return obj;
|
|
59
|
+
|
|
60
|
+
let current: unknown = obj;
|
|
61
|
+
for (const segment of parsePathSegments(stripped)) {
|
|
62
|
+
if (current === null || current === undefined) return undefined;
|
|
63
|
+
current = walkSegment(current, segment);
|
|
64
|
+
}
|
|
65
|
+
return current;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Splits a stripped JSONPath string into typed segments, e.g. "Processes[0].ProcessTasks[1].Name" */
|
|
69
|
+
function parsePathSegments(path: string): (string | number)[] {
|
|
70
|
+
const segments: (string | number)[] = [];
|
|
71
|
+
const regex = /([^.[\]]+)|\[(\d+)\]/g;
|
|
72
|
+
let match: RegExpExecArray | null;
|
|
73
|
+
while ((match = regex.exec(path)) !== null) {
|
|
74
|
+
if (match[1] !== undefined) segments.push(match[1]);
|
|
75
|
+
else if (match[2] !== undefined)
|
|
76
|
+
segments.push(Number.parseInt(match[2], 10));
|
|
77
|
+
}
|
|
78
|
+
return segments;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Applies the format string to the extracted value.
|
|
83
|
+
* - If format is exactly `{0}` or `{fieldName}`, the raw value is returned as-is.
|
|
84
|
+
* - Otherwise `{0}` / `{fieldName}` tokens are replaced with the stringified value.
|
|
85
|
+
* If the result is valid JSON it is parsed back to an object.
|
|
86
|
+
*/
|
|
87
|
+
function toValueString(value: unknown): string {
|
|
88
|
+
if (value == null) return "";
|
|
89
|
+
if (typeof value === "string") return value;
|
|
90
|
+
if (
|
|
91
|
+
typeof value === "number" ||
|
|
92
|
+
typeof value === "boolean" ||
|
|
93
|
+
typeof value === "bigint"
|
|
94
|
+
)
|
|
95
|
+
return String(value);
|
|
96
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
97
|
+
return "";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function escapeRegex(str: string): string {
|
|
101
|
+
return str.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function findPlaceholderInArray(
|
|
105
|
+
arr: unknown[],
|
|
106
|
+
placeholder: string,
|
|
107
|
+
): string[] | null {
|
|
108
|
+
for (let i = 0; i < arr.length; i++) {
|
|
109
|
+
const path = findPlaceholderPath(arr[i], placeholder);
|
|
110
|
+
if (path !== null) return [String(i), ...path];
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function findPlaceholderInObject(
|
|
116
|
+
obj: Record<string, unknown>,
|
|
117
|
+
placeholder: string,
|
|
118
|
+
): string[] | null {
|
|
119
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
120
|
+
const path = findPlaceholderPath(val, placeholder);
|
|
121
|
+
if (path !== null) return [key, ...path];
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Recursively searches an object/array for the first occurrence of `placeholder`
|
|
128
|
+
* as a string value, returning the key path to it, or `null` if not found.
|
|
129
|
+
*/
|
|
130
|
+
function findPlaceholderPath(
|
|
131
|
+
obj: unknown,
|
|
132
|
+
placeholder: string,
|
|
133
|
+
): string[] | null {
|
|
134
|
+
if (typeof obj === "string") return obj === placeholder ? [] : null;
|
|
135
|
+
if (Array.isArray(obj)) return findPlaceholderInArray(obj, placeholder);
|
|
136
|
+
if (typeof obj === "object" && obj !== null) {
|
|
137
|
+
return findPlaceholderInObject(obj as Record<string, unknown>, placeholder);
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function navigatePath(obj: unknown, path: string[]): unknown {
|
|
143
|
+
let current: unknown = obj;
|
|
144
|
+
for (const key of path) {
|
|
145
|
+
if (current === null || current === undefined) return undefined;
|
|
146
|
+
if (Array.isArray(current)) {
|
|
147
|
+
current = current[Number(key)];
|
|
148
|
+
} else if (typeof current === "object") {
|
|
149
|
+
current = (current as Record<string, unknown>)[key];
|
|
150
|
+
} else {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return current;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Like findPlaceholderPath but matches any string leaf that CONTAINS the placeholder
|
|
159
|
+
* as a substring rather than being exactly equal to it.
|
|
160
|
+
*/
|
|
161
|
+
function findContainingInArray(
|
|
162
|
+
arr: unknown[],
|
|
163
|
+
placeholder: string,
|
|
164
|
+
): string[] | null {
|
|
165
|
+
for (let i = 0; i < arr.length; i++) {
|
|
166
|
+
const path = findContainingPath(arr[i], placeholder);
|
|
167
|
+
if (path !== null) return [String(i), ...path];
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function findContainingInObject(
|
|
173
|
+
obj: Record<string, unknown>,
|
|
174
|
+
placeholder: string,
|
|
175
|
+
): string[] | null {
|
|
176
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
177
|
+
const path = findContainingPath(val, placeholder);
|
|
178
|
+
if (path !== null) return [key, ...path];
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function findContainingPath(
|
|
184
|
+
obj: unknown,
|
|
185
|
+
placeholder: string,
|
|
186
|
+
): string[] | null {
|
|
187
|
+
if (typeof obj === "string") return obj.includes(placeholder) ? [] : null;
|
|
188
|
+
if (Array.isArray(obj)) return findContainingInArray(obj, placeholder);
|
|
189
|
+
if (typeof obj === "object" && obj !== null)
|
|
190
|
+
return findContainingInObject(obj as Record<string, unknown>, placeholder);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Reverse of `applyFormat`: given a format string containing `{0}` and a value
|
|
196
|
+
* that was produced by that format, extracts and returns the original `{0}` value.
|
|
197
|
+
*
|
|
198
|
+
* For JSON formats (e.g. `{ "ConnectionName": "{0}" }`): parses both the format and
|
|
199
|
+
* the value as JSON, locates the key path of `{0}` in the format structure, then
|
|
200
|
+
* extracts the value at that same path from the actual value. This correctly handles
|
|
201
|
+
* values with extra keys not present in the format template.
|
|
202
|
+
*
|
|
203
|
+
* When the placeholder is embedded within a format leaf string
|
|
204
|
+
* (e.g. `{ "Sql": "exec {0}" }`), navigates to that leaf in the actual value
|
|
205
|
+
* and applies regex capture within that field.
|
|
206
|
+
*
|
|
207
|
+
* For non-JSON formats (e.g. `"{0}_entity"`): falls back to regex capture.
|
|
208
|
+
*
|
|
209
|
+
* Returns `null` when no match is found.
|
|
210
|
+
*/
|
|
211
|
+
function extractViaJsonFormat(
|
|
212
|
+
format: string,
|
|
213
|
+
placeholder0: string,
|
|
214
|
+
placeholderField: string,
|
|
215
|
+
formattedValue: unknown,
|
|
216
|
+
): unknown {
|
|
217
|
+
let parsedFormat: unknown;
|
|
218
|
+
try {
|
|
219
|
+
parsedFormat = JSON.parse(format);
|
|
220
|
+
} catch {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let parsedValue: unknown = formattedValue;
|
|
225
|
+
if (typeof formattedValue === "string") {
|
|
226
|
+
try {
|
|
227
|
+
parsedValue = JSON.parse(formattedValue);
|
|
228
|
+
} catch {
|
|
229
|
+
/* use as-is */
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Exact match: the format leaf IS the placeholder
|
|
234
|
+
const exactPath =
|
|
235
|
+
findPlaceholderPath(parsedFormat, placeholder0) ??
|
|
236
|
+
findPlaceholderPath(parsedFormat, placeholderField);
|
|
237
|
+
if (exactPath !== null) {
|
|
238
|
+
return navigatePath(parsedValue, exactPath);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Embedded match: the placeholder appears inside a format leaf string
|
|
242
|
+
const containingPath =
|
|
243
|
+
findContainingPath(parsedFormat, placeholder0) ??
|
|
244
|
+
findContainingPath(parsedFormat, placeholderField);
|
|
245
|
+
if (containingPath === null) return undefined;
|
|
246
|
+
|
|
247
|
+
const formatLeaf = navigatePath(parsedFormat, containingPath);
|
|
248
|
+
if (typeof formatLeaf !== "string") return undefined;
|
|
249
|
+
|
|
250
|
+
return extractViaRegex(
|
|
251
|
+
formatLeaf,
|
|
252
|
+
placeholder0,
|
|
253
|
+
placeholderField,
|
|
254
|
+
navigatePath(parsedValue, containingPath),
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function extractViaRegex(
|
|
259
|
+
format: string,
|
|
260
|
+
placeholder0: string,
|
|
261
|
+
placeholderField: string,
|
|
262
|
+
formattedValue: unknown,
|
|
263
|
+
): unknown {
|
|
264
|
+
const segments = format.includes(placeholder0)
|
|
265
|
+
? format.split(placeholder0)
|
|
266
|
+
: format.split(placeholderField);
|
|
267
|
+
|
|
268
|
+
if (segments.length === 1) return null;
|
|
269
|
+
|
|
270
|
+
const captureGroup = String.raw`([\s\S]*?)`;
|
|
271
|
+
const regexSource = segments.map(escapeRegex).join(captureGroup);
|
|
272
|
+
const match = new RegExp(`^${regexSource}$`).exec(
|
|
273
|
+
toValueString(formattedValue).trim(),
|
|
274
|
+
);
|
|
275
|
+
if (!match) return null;
|
|
276
|
+
|
|
277
|
+
const captured = match[1];
|
|
278
|
+
try {
|
|
279
|
+
return JSON.parse(captured);
|
|
280
|
+
} catch {
|
|
281
|
+
return captured;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function parseValueFromFormat(
|
|
286
|
+
format: string,
|
|
287
|
+
fieldName: string,
|
|
288
|
+
formattedValue: unknown,
|
|
289
|
+
): unknown {
|
|
290
|
+
if (format === "{0}" || format === `{${fieldName}}`) {
|
|
291
|
+
return formattedValue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const placeholder0 = "{0}";
|
|
295
|
+
const placeholderField = `{${fieldName}}`;
|
|
296
|
+
|
|
297
|
+
const jsonResult = extractViaJsonFormat(
|
|
298
|
+
format,
|
|
299
|
+
placeholder0,
|
|
300
|
+
placeholderField,
|
|
301
|
+
formattedValue,
|
|
302
|
+
);
|
|
303
|
+
if (jsonResult !== undefined) return jsonResult;
|
|
304
|
+
|
|
305
|
+
return extractViaRegex(
|
|
306
|
+
format,
|
|
307
|
+
placeholder0,
|
|
308
|
+
placeholderField,
|
|
309
|
+
formattedValue,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
22
22
|
import React, { useEffect, useRef, useState } from "react";
|
|
23
23
|
import { applyTemplateConfig, InputType } from "./inputs";
|
|
24
|
+
import {
|
|
25
|
+
parseWithMappings,
|
|
26
|
+
type Mapping,
|
|
27
|
+
} from "./utils/parseTemplateValuesFromConfig";
|
|
24
28
|
import PageForm from "./pageForm";
|
|
25
29
|
import {
|
|
26
30
|
type FieldValue,
|
|
@@ -277,7 +281,8 @@ function PageNav({
|
|
|
277
281
|
|
|
278
282
|
interface Props {
|
|
279
283
|
readonly metadata: Metadata;
|
|
280
|
-
readonly
|
|
284
|
+
readonly sourceTemplateId?: string;
|
|
285
|
+
readonly targetTemplateId?: string;
|
|
281
286
|
readonly processTemplateId?: string;
|
|
282
287
|
readonly handleBack?: (event?: React.MouseEventHandler) => void;
|
|
283
288
|
readonly handleFinish?: (event?: React.MouseEventHandler) => void;
|
|
@@ -290,6 +295,7 @@ interface Props {
|
|
|
290
295
|
readonly isCreateMode?: boolean;
|
|
291
296
|
readonly readOnly?: boolean;
|
|
292
297
|
readonly sharedCount?: (count: number) => void;
|
|
298
|
+
readonly config: Record<string, unknown>;
|
|
293
299
|
}
|
|
294
300
|
|
|
295
301
|
export default function Wizard({ metadata, sharedCount, ...props }: Props) {
|
|
@@ -304,11 +310,54 @@ export default function Wizard({ metadata, sharedCount, ...props }: Props) {
|
|
|
304
310
|
|
|
305
311
|
const { data, isLoading } = getProcessTemplate.useData({
|
|
306
312
|
input: {
|
|
307
|
-
|
|
308
|
-
|
|
313
|
+
sourceTemplateId: props.sourceTemplateId || null,
|
|
314
|
+
targetTemplateId: props.targetTemplateId || null,
|
|
309
315
|
},
|
|
310
316
|
});
|
|
311
317
|
|
|
318
|
+
const parsedTemplateConfigs = React.useMemo((): Array<
|
|
319
|
+
Record<string, unknown>
|
|
320
|
+
> => {
|
|
321
|
+
if (props.config == null) return [];
|
|
322
|
+
type TemplateEntry = {
|
|
323
|
+
id?: unknown;
|
|
324
|
+
processTemplateId?: unknown;
|
|
325
|
+
mappings?: unknown[];
|
|
326
|
+
};
|
|
327
|
+
const allTemplates =
|
|
328
|
+
(data as { processTemplate?: TemplateEntry[] })?.processTemplate ?? [];
|
|
329
|
+
return allTemplates.flatMap((template) => {
|
|
330
|
+
const mappings = (template?.mappings ?? []) as Mapping[];
|
|
331
|
+
if (!mappings.length) return [];
|
|
332
|
+
const processTemplateId = template?.processTemplateId ?? template?.id;
|
|
333
|
+
return [
|
|
334
|
+
{
|
|
335
|
+
...parseWithMappings(
|
|
336
|
+
props.config as { json: Record<string, unknown> },
|
|
337
|
+
mappings,
|
|
338
|
+
),
|
|
339
|
+
processTemplateId,
|
|
340
|
+
},
|
|
341
|
+
];
|
|
342
|
+
});
|
|
343
|
+
}, [data, props.config]);
|
|
344
|
+
|
|
345
|
+
const parsedTemplateConfigString = React.useMemo(
|
|
346
|
+
() =>
|
|
347
|
+
parsedTemplateConfigs.length > 0
|
|
348
|
+
? JSON.stringify(parsedTemplateConfigs[0])
|
|
349
|
+
: null,
|
|
350
|
+
[parsedTemplateConfigs],
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
const parsedAllTemplateConfigsString = React.useMemo(
|
|
354
|
+
() =>
|
|
355
|
+
parsedTemplateConfigs.length > 0
|
|
356
|
+
? JSON.stringify(parsedTemplateConfigs)
|
|
357
|
+
: null,
|
|
358
|
+
[parsedTemplateConfigs],
|
|
359
|
+
);
|
|
360
|
+
|
|
312
361
|
const [initialValues, setInitialValues] = React.useState<FormState>({});
|
|
313
362
|
// aggregated form values across pages
|
|
314
363
|
const [aggregatedValues, setAggregatedValues] =
|
|
@@ -340,7 +389,6 @@ export default function Wizard({ metadata, sharedCount, ...props }: Props) {
|
|
|
340
389
|
const typedData = data as GetProcessTemplateQuery;
|
|
341
390
|
if (typedData?.processTemplate) {
|
|
342
391
|
const templates = typedData.processTemplate as ProcessTemplate[];
|
|
343
|
-
|
|
344
392
|
setTemplates(templates);
|
|
345
393
|
setTemplatesSorted(
|
|
346
394
|
templates.map((t) => ({
|
|
@@ -425,12 +473,12 @@ export default function Wizard({ metadata, sharedCount, ...props }: Props) {
|
|
|
425
473
|
);
|
|
426
474
|
|
|
427
475
|
useEffect(() => {
|
|
428
|
-
if (
|
|
429
|
-
|
|
476
|
+
if (!parsedAllTemplateConfigsString) return;
|
|
477
|
+
for (const config of parsedTemplateConfigs) {
|
|
430
478
|
applyTemplateConfig(
|
|
431
479
|
{
|
|
432
|
-
name:
|
|
433
|
-
templateConfig:
|
|
480
|
+
name: "",
|
|
481
|
+
templateConfig: JSON.stringify(config),
|
|
434
482
|
} as SearchOption,
|
|
435
483
|
(templateId: string, fieldName: string, value: FieldValue) => {
|
|
436
484
|
const key = templateFieldKey(templateId, fieldName);
|
|
@@ -438,7 +486,7 @@ export default function Wizard({ metadata, sharedCount, ...props }: Props) {
|
|
|
438
486
|
},
|
|
439
487
|
);
|
|
440
488
|
}
|
|
441
|
-
}, [
|
|
489
|
+
}, [parsedAllTemplateConfigsString]);
|
|
442
490
|
|
|
443
491
|
const clearTemplateFields = (templateId: string) => {
|
|
444
492
|
setAggregatedValues((prev) => {
|
|
@@ -600,8 +648,9 @@ export default function Wizard({ metadata, sharedCount, ...props }: Props) {
|
|
|
600
648
|
isLoading &&
|
|
601
649
|
!currentFlat &&
|
|
602
650
|
!currentTemplate &&
|
|
603
|
-
!
|
|
604
|
-
|
|
651
|
+
!parsedTemplateConfigString &&
|
|
652
|
+
!props.sourceTemplateId &&
|
|
653
|
+
!props.targetTemplateId;
|
|
605
654
|
if (showLoading) {
|
|
606
655
|
return (
|
|
607
656
|
<Box sx={{ padding: theme.spacing(10, 3) }}>
|
|
@@ -676,7 +725,7 @@ export default function Wizard({ metadata, sharedCount, ...props }: Props) {
|
|
|
676
725
|
isCreateMode={props.isCreateMode}
|
|
677
726
|
disabledFields={disabledFields}
|
|
678
727
|
setFieldsDisabled={setFieldsDisabled}
|
|
679
|
-
templateConfig={
|
|
728
|
+
templateConfig={parsedTemplateConfigString ?? ""}
|
|
680
729
|
setFieldValidationInProgress={setIsFieldValidationInProgress}
|
|
681
730
|
submitValidationResults={submitValidationResults ?? undefined}
|
|
682
731
|
/>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ConfigExport,
|
|
3
3
|
ConfigImport,
|
|
4
|
-
|
|
4
|
+
ConfigMerge,
|
|
5
|
+
InputMaybe,
|
|
5
6
|
} from "@evenicanpm/admin-core/api/e4/graphqlRequestSdk";
|
|
6
7
|
import {
|
|
7
8
|
processConfigImport,
|
|
@@ -26,6 +27,7 @@ import SelectStep from "./select-step";
|
|
|
26
27
|
import TemplateListStep, { DirectionType } from "./template-list-step";
|
|
27
28
|
import TemplateOptionsStep from "./template-options-step";
|
|
28
29
|
import WizardStep from "./wizard-step";
|
|
30
|
+
import { TemplateOption } from "./template-options-step";
|
|
29
31
|
|
|
30
32
|
export default function IntegrationCreate() {
|
|
31
33
|
const t = useTranslations("Integrate");
|
|
@@ -59,7 +61,17 @@ export default function IntegrationCreate() {
|
|
|
59
61
|
const [dualSourceThenTarget, setDualSourceThenTarget] = useState(false);
|
|
60
62
|
|
|
61
63
|
useEffect(() => {
|
|
62
|
-
|
|
64
|
+
const templateOption = searchParams.get("option");
|
|
65
|
+
|
|
66
|
+
if (
|
|
67
|
+
(templateOption === TemplateOption.Source &&
|
|
68
|
+
searchParams.get(DirectionType.Incoming)) ||
|
|
69
|
+
(templateOption === TemplateOption.Target &&
|
|
70
|
+
searchParams.get(DirectionType.Outgoing)) ||
|
|
71
|
+
(templateOption === TemplateOption.SourceAndTarget &&
|
|
72
|
+
searchParams.get(DirectionType.Incoming) &&
|
|
73
|
+
searchParams.get(DirectionType.Outgoing))
|
|
74
|
+
) {
|
|
63
75
|
setStep(INTEGRATION_CREATE_STEPS.wizard);
|
|
64
76
|
}
|
|
65
77
|
}, [searchParams]);
|
|
@@ -93,32 +105,31 @@ export default function IntegrationCreate() {
|
|
|
93
105
|
c.code = code;
|
|
94
106
|
}
|
|
95
107
|
|
|
96
|
-
|
|
108
|
+
const templateOption = searchParams.get("option");
|
|
97
109
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
110
|
+
let sourceConfig: InputMaybe<ConfigMerge> | undefined = null;
|
|
111
|
+
let targetConfig: InputMaybe<ConfigMerge> | undefined = null;
|
|
112
|
+
let targetSourcesConfig: InputMaybe<ConfigMerge> | undefined = null;
|
|
101
113
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
);
|
|
114
|
+
const mergedInput = removeTemplateConfig(newProcessConfigMerge?.input) as
|
|
115
|
+
| { config: Array<Record<string, unknown>> }
|
|
116
|
+
| undefined;
|
|
117
|
+
|
|
118
|
+
if (templateOption === TemplateOption.SourceAndTarget) {
|
|
119
|
+
sourceConfig = (mergedInput?.config?.[0] ?? null) as ConfigMerge | null;
|
|
120
|
+
targetConfig = (mergedInput?.config?.[1] ?? null) as ConfigMerge | null;
|
|
121
|
+
} else if (templateOption === TemplateOption.Source) {
|
|
122
|
+
sourceConfig = (mergedInput?.config?.[0] ?? null) as ConfigMerge | null;
|
|
123
|
+
} else if (templateOption === TemplateOption.Target) {
|
|
124
|
+
targetSourcesConfig = (mergedInput?.config?.[0] ??
|
|
125
|
+
null) as ConfigMerge | null;
|
|
126
|
+
targetConfig = (mergedInput?.config?.[1] ?? null) as ConfigMerge | null;
|
|
115
127
|
}
|
|
116
128
|
|
|
117
129
|
processConfigMergeMutation.mutate(
|
|
118
130
|
{
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
({} as ConfigurationMergeMutationVariables),
|
|
131
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
132
|
+
input: { sourceConfig, targetConfig, targetSourcesConfig } as any,
|
|
122
133
|
},
|
|
123
134
|
{
|
|
124
135
|
onSuccess: ({ configurationMerge }) => {
|
|
@@ -236,23 +247,20 @@ export default function IntegrationCreate() {
|
|
|
236
247
|
<TemplateListStep
|
|
237
248
|
listStepTitle={t("Templates.SelectSource")}
|
|
238
249
|
directionType={DirectionType.Incoming}
|
|
239
|
-
onSelectTemplate={
|
|
240
|
-
dualSourceThenTarget
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
p.delete(`${t("Templates.group")}`);
|
|
245
|
-
router.replace(`${pathname}?${p}`);
|
|
246
|
-
setStep(INTEGRATION_CREATE_STEPS.targetTemplateList);
|
|
247
|
-
}
|
|
248
|
-
: undefined
|
|
249
|
-
}
|
|
250
|
+
onSelectTemplate={() => {
|
|
251
|
+
if (dualSourceThenTarget)
|
|
252
|
+
setStep(INTEGRATION_CREATE_STEPS.targetTemplateList);
|
|
253
|
+
//else setStep(INTEGRATION_CREATE_STEPS.wizard)
|
|
254
|
+
}}
|
|
250
255
|
/>
|
|
251
256
|
)}
|
|
252
257
|
{step === INTEGRATION_CREATE_STEPS.targetTemplateList && (
|
|
253
258
|
<TemplateListStep
|
|
254
259
|
listStepTitle={t("Templates.SelectTarget")}
|
|
255
260
|
directionType={DirectionType.Outgoing}
|
|
261
|
+
// onSelectTemplate={
|
|
262
|
+
// ()=>setStep(INTEGRATION_CREATE_STEPS.wizard)
|
|
263
|
+
// }
|
|
256
264
|
/>
|
|
257
265
|
)}
|
|
258
266
|
{/* </Card> */}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
FilterQueryInput,
|
|
3
|
-
|
|
3
|
+
GetProcessTemplatesQuery,
|
|
4
4
|
} from "@evenicanpm/admin-core/api/e4/graphqlRequestSdk";
|
|
5
|
-
import {
|
|
5
|
+
import { getProcessTemplates } from "@evenicanpm/admin-integrate/api/templates/queries";
|
|
6
6
|
import type { Head } from "@evenicanpm/admin-integrate/components/data-table/table-header";
|
|
7
7
|
import TemplatesList from "@evenicanpm/admin-integrate/components/templatesV2/templates-list";
|
|
8
8
|
import { Box, Typography, useTheme } from "@mui/material";
|
|
@@ -17,12 +17,12 @@ export enum DirectionType {
|
|
|
17
17
|
|
|
18
18
|
interface Props {
|
|
19
19
|
listStepTitle?: string;
|
|
20
|
-
directionType
|
|
20
|
+
directionType: DirectionType;
|
|
21
21
|
/** When set, skips default `group` URL handoff (e.g. source pick in dual flow). */
|
|
22
22
|
onSelectTemplate?: (id: string) => void;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export default function TemplateListStep(props
|
|
25
|
+
export default function TemplateListStep(props: Props) {
|
|
26
26
|
const t = useTranslations("Integrate.Templates");
|
|
27
27
|
|
|
28
28
|
const router = useRouter();
|
|
@@ -62,18 +62,15 @@ export default function TemplateListStep(props?: Props) {
|
|
|
62
62
|
skip: 0,
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
const { data, isLoading } =
|
|
65
|
+
const { data, isLoading } = getProcessTemplates.useData({
|
|
66
66
|
input: input,
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
const handleRedirect = (id: string) => {
|
|
70
|
-
if (props?.onSelectTemplate) {
|
|
71
|
-
props.onSelectTemplate(id);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
70
|
const currentParams = new URLSearchParams(searchParams.toString());
|
|
75
|
-
currentParams.set(
|
|
71
|
+
currentParams.set(props.directionType, id);
|
|
76
72
|
router.replace(`${pathname}?${currentParams.toString()}`);
|
|
73
|
+
if (props?.onSelectTemplate) props.onSelectTemplate(id);
|
|
77
74
|
};
|
|
78
75
|
|
|
79
76
|
return (
|
|
@@ -89,13 +86,11 @@ export default function TemplateListStep(props?: Props) {
|
|
|
89
86
|
</Box>
|
|
90
87
|
<TemplatesList
|
|
91
88
|
heading={heading}
|
|
92
|
-
data={data as
|
|
89
|
+
data={data as GetProcessTemplatesQuery}
|
|
93
90
|
loading={isLoading}
|
|
94
|
-
// input={{ ...input, directionType: props?.directionType }}
|
|
95
91
|
input={input}
|
|
96
92
|
setInput={setInput}
|
|
97
93
|
handleRedirect={handleRedirect}
|
|
98
|
-
isTemplateGroupsList
|
|
99
94
|
/>
|
|
100
95
|
</Box>
|
|
101
96
|
);
|
|
@@ -12,6 +12,13 @@ import {
|
|
|
12
12
|
import { useTranslations } from "next-intl";
|
|
13
13
|
import type React from "react";
|
|
14
14
|
import { INTEGRATION_CREATE_STEPS } from "./integration-create-steps";
|
|
15
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
16
|
+
|
|
17
|
+
export enum TemplateOption {
|
|
18
|
+
Source = "source",
|
|
19
|
+
Target = "target",
|
|
20
|
+
SourceAndTarget = "both",
|
|
21
|
+
}
|
|
15
22
|
|
|
16
23
|
interface Props {
|
|
17
24
|
setStep: React.Dispatch<React.SetStateAction<number>>;
|
|
@@ -24,14 +31,27 @@ export default function TemplateOptionsStep(props: Props) {
|
|
|
24
31
|
const theme = useTheme();
|
|
25
32
|
const styles = getStyles(theme);
|
|
26
33
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
const router = useRouter();
|
|
35
|
+
const pathname = usePathname();
|
|
36
|
+
const searchParams = useSearchParams();
|
|
37
|
+
|
|
38
|
+
const handleClickOption = (option: TemplateOption) => {
|
|
39
|
+
switch (option) {
|
|
40
|
+
case TemplateOption.SourceAndTarget:
|
|
41
|
+
props.onSourceAndTarget?.();
|
|
42
|
+
props.setStep(INTEGRATION_CREATE_STEPS.sourceTemplateList);
|
|
43
|
+
break;
|
|
44
|
+
case TemplateOption.Source:
|
|
45
|
+
props.setStep(INTEGRATION_CREATE_STEPS.sourceTemplateList);
|
|
46
|
+
break;
|
|
47
|
+
case TemplateOption.Target:
|
|
48
|
+
props.setStep(INTEGRATION_CREATE_STEPS.targetTemplateList);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const currentParams = new URLSearchParams(searchParams.toString());
|
|
52
|
+
currentParams.set("option", option);
|
|
53
|
+
router.replace(`${pathname}?${currentParams.toString()}`);
|
|
30
54
|
};
|
|
31
|
-
const handleClickOption2 = () =>
|
|
32
|
-
props.setStep(INTEGRATION_CREATE_STEPS.sourceTemplateList);
|
|
33
|
-
const handleClickOption3 = () =>
|
|
34
|
-
props.setStep(INTEGRATION_CREATE_STEPS.targetTemplateList);
|
|
35
55
|
|
|
36
56
|
return (
|
|
37
57
|
<Box sx={styles.main}>
|
|
@@ -45,7 +65,7 @@ export default function TemplateOptionsStep(props: Props) {
|
|
|
45
65
|
<LinkCard
|
|
46
66
|
title={t("templateOption1")}
|
|
47
67
|
description={t("templateOption1Description")}
|
|
48
|
-
onClick={
|
|
68
|
+
onClick={() => handleClickOption(TemplateOption.SourceAndTarget)}
|
|
49
69
|
>
|
|
50
70
|
<Box
|
|
51
71
|
sx={{
|
|
@@ -62,7 +82,7 @@ export default function TemplateOptionsStep(props: Props) {
|
|
|
62
82
|
<LinkCard
|
|
63
83
|
title={t("templateOption2")}
|
|
64
84
|
description={t("templateOption2Description")}
|
|
65
|
-
onClick={
|
|
85
|
+
onClick={() => handleClickOption(TemplateOption.Source)}
|
|
66
86
|
>
|
|
67
87
|
<Box
|
|
68
88
|
sx={{
|
|
@@ -79,7 +99,7 @@ export default function TemplateOptionsStep(props: Props) {
|
|
|
79
99
|
<LinkCard
|
|
80
100
|
title={t("templateOption3")}
|
|
81
101
|
description={t("templateOption3Description")}
|
|
82
|
-
onClick={
|
|
102
|
+
onClick={() => handleClickOption(TemplateOption.Target)}
|
|
83
103
|
>
|
|
84
104
|
<Box
|
|
85
105
|
sx={{
|
|
@@ -8,6 +8,8 @@ import { Box } from "@mui/material";
|
|
|
8
8
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
9
9
|
import { INTEGRATION_CREATE_STEPS } from "./integration-create-steps";
|
|
10
10
|
|
|
11
|
+
import { DirectionType } from "./template-list-step";
|
|
12
|
+
|
|
11
13
|
const metadata = {
|
|
12
14
|
name: "",
|
|
13
15
|
code: "",
|
|
@@ -37,14 +39,26 @@ export default function WizardStep(props: Readonly<Props>) {
|
|
|
37
39
|
router.replace(pathname);
|
|
38
40
|
};
|
|
39
41
|
|
|
42
|
+
console.log(
|
|
43
|
+
"SEARHCPARAMS",
|
|
44
|
+
searchParams.get(DirectionType.Incoming)?.toString(),
|
|
45
|
+
);
|
|
46
|
+
console.log(
|
|
47
|
+
"SEARHCPARAMS",
|
|
48
|
+
searchParams.get(DirectionType.Outgoing)?.toString(),
|
|
49
|
+
);
|
|
50
|
+
|
|
40
51
|
return (
|
|
41
52
|
<Box>
|
|
42
53
|
<Wizard
|
|
43
54
|
metadata={metadata}
|
|
44
|
-
processTemplateGroupId={
|
|
55
|
+
// processTemplateGroupId={}
|
|
56
|
+
sourceTemplateId={searchParams.get(DirectionType.Incoming)?.toString()}
|
|
57
|
+
targetTemplateId={searchParams.get(DirectionType.Outgoing)?.toString()}
|
|
45
58
|
handleBack={handleBack}
|
|
46
59
|
handleFinish={handleFinish}
|
|
47
60
|
setProcessConfigMerge={props.setProcessConfigMerge}
|
|
61
|
+
config={{}}
|
|
48
62
|
isCreateMode
|
|
49
63
|
/>
|
|
50
64
|
</Box>
|
|
@@ -37,22 +37,22 @@ import {
|
|
|
37
37
|
taskReducer,
|
|
38
38
|
} from "./task-drawer/task-reducer";
|
|
39
39
|
|
|
40
|
-
function mergeByKey(
|
|
40
|
+
function mergeByKey<T>(
|
|
41
41
|
keyFields: string | string[],
|
|
42
|
-
arr1:
|
|
43
|
-
arr2:
|
|
44
|
-
) {
|
|
42
|
+
arr1: T[] = [],
|
|
43
|
+
arr2: T[] = [],
|
|
44
|
+
): T[] {
|
|
45
45
|
const keys = Array.isArray(keyFields) ? keyFields : [keyFields];
|
|
46
46
|
|
|
47
47
|
// Build lookup maps for each key -> value => baseItem (from arr1)
|
|
48
|
-
const lookupByKey: Record<string, Map<
|
|
48
|
+
const lookupByKey: Record<string, Map<unknown, T>> = {};
|
|
49
49
|
for (const k of keys) {
|
|
50
50
|
lookupByKey[k] = new Map();
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
for (const item of arr1) {
|
|
54
54
|
for (const key of keys) {
|
|
55
|
-
const val = item?.[key];
|
|
55
|
+
const val = (item as Record<string, unknown> | null | undefined)?.[key];
|
|
56
56
|
if (val !== undefined && val !== null) {
|
|
57
57
|
lookupByKey[key].set(val, item);
|
|
58
58
|
}
|
|
@@ -60,28 +60,29 @@ function mergeByKey(
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// Use a Set to preserve insertion order and ensure uniqueness
|
|
63
|
-
const resultSet = new Set<
|
|
63
|
+
const resultSet = new Set<T>(arr1);
|
|
64
64
|
|
|
65
65
|
for (const item of arr2) {
|
|
66
|
-
let matchedBase:
|
|
66
|
+
let matchedBase: T | undefined;
|
|
67
67
|
|
|
68
68
|
// Try keys in order of priority
|
|
69
69
|
for (const key of keys) {
|
|
70
|
-
const val = item?.[key];
|
|
70
|
+
const val = (item as Record<string, unknown> | null | undefined)?.[key];
|
|
71
71
|
if (val !== undefined && val !== null) {
|
|
72
72
|
const candidate = lookupByKey[key].get(val);
|
|
73
|
-
if (candidate) {
|
|
73
|
+
if (candidate !== undefined) {
|
|
74
74
|
matchedBase = candidate;
|
|
75
75
|
break;
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
if (matchedBase) {
|
|
80
|
+
if (matchedBase !== undefined) {
|
|
81
81
|
// Merge properties from item into matched base (respecting exclusions)
|
|
82
|
-
|
|
82
|
+
const itemRecord = item as Record<string, unknown> | null | undefined;
|
|
83
|
+
for (const prop of Object.keys(itemRecord ?? {})) {
|
|
83
84
|
if (prop !== "Sort" && prop !== "ParallelProcessingGroup") {
|
|
84
|
-
matchedBase[prop] =
|
|
85
|
+
(matchedBase as Record<string, unknown>)[prop] = itemRecord?.[prop];
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
} else {
|
|
@@ -89,7 +90,7 @@ function mergeByKey(
|
|
|
89
90
|
resultSet.add(item);
|
|
90
91
|
// Register the newly added item in lookups so subsequent arr2 items can match it
|
|
91
92
|
for (const key of keys) {
|
|
92
|
-
const val = item?.[key];
|
|
93
|
+
const val = (item as Record<string, unknown> | null | undefined)?.[key];
|
|
93
94
|
if (val !== undefined && val !== null) {
|
|
94
95
|
lookupByKey[key].set(val, item);
|
|
95
96
|
}
|
|
@@ -175,7 +176,9 @@ export default function IntegrationDetails() {
|
|
|
175
176
|
//setConfig(newConfig);
|
|
176
177
|
|
|
177
178
|
const configData: ConfigExport =
|
|
178
|
-
config ??
|
|
179
|
+
config ??
|
|
180
|
+
((configFromApi as ConfigurationExportQuery)
|
|
181
|
+
?.configurationExport as ConfigExport);
|
|
179
182
|
|
|
180
183
|
configData.DepotGroups = mergeByKey(
|
|
181
184
|
"DepotGroup",
|
|
@@ -287,7 +290,10 @@ export default function IntegrationDetails() {
|
|
|
287
290
|
}
|
|
288
291
|
}, [scheduleLoading]);
|
|
289
292
|
|
|
290
|
-
const onAdd = (data:
|
|
293
|
+
const onAdd = (data: {
|
|
294
|
+
sort?: number;
|
|
295
|
+
paralellelProcessingGroup?: string;
|
|
296
|
+
}) => {
|
|
291
297
|
dispatchTaskState({
|
|
292
298
|
type: TaskActionType.OPEN_ADD,
|
|
293
299
|
sort: data.sort,
|
|
@@ -314,14 +320,21 @@ export default function IntegrationDetails() {
|
|
|
314
320
|
<IntegrateBreadcrumbs breadcrumbs={breadcrumbs} />
|
|
315
321
|
<IntegrationDetailsHeader
|
|
316
322
|
name={
|
|
317
|
-
(
|
|
318
|
-
|
|
323
|
+
(
|
|
324
|
+
config ||
|
|
325
|
+
((configFromApi as ConfigurationExportQuery)
|
|
326
|
+
?.configurationExport as ConfigExport)
|
|
327
|
+
)?.Processes?.[0]?.Name || "Untitled Integration"
|
|
319
328
|
}
|
|
320
329
|
icon={details?.processById?.processIcon ?? ""}
|
|
321
330
|
setName={(name) => {
|
|
322
331
|
const configData =
|
|
323
|
-
config ||
|
|
324
|
-
|
|
332
|
+
config ||
|
|
333
|
+
((configFromApi as ConfigurationExportQuery)
|
|
334
|
+
?.configurationExport as ConfigExport);
|
|
335
|
+
if (configData?.Processes?.[0]) {
|
|
336
|
+
configData.Processes[0].Name = name as string;
|
|
337
|
+
}
|
|
325
338
|
setConfig(configData);
|
|
326
339
|
}}
|
|
327
340
|
onImportClick={() => {
|
|
@@ -399,24 +412,54 @@ export default function IntegrationDetails() {
|
|
|
399
412
|
(ep) => !!ep && ep.Name === processTask.endpoint,
|
|
400
413
|
);
|
|
401
414
|
|
|
402
|
-
let
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
415
|
+
let filteredConfig: ConfigExport | null =
|
|
416
|
+
config ||
|
|
417
|
+
(configFromApi as ConfigurationExportQuery)?.configurationExport
|
|
418
|
+
? JSON.parse(
|
|
419
|
+
JSON.stringify(
|
|
420
|
+
config ||
|
|
421
|
+
(configFromApi as ConfigurationExportQuery)
|
|
422
|
+
?.configurationExport,
|
|
423
|
+
),
|
|
424
|
+
)
|
|
425
|
+
: null;
|
|
426
|
+
|
|
427
|
+
if (filteredConfig?.Processes?.[0]) {
|
|
428
|
+
filteredConfig.Processes[0].ProcessTasks =
|
|
429
|
+
filteredConfig.Processes[0].ProcessTasks?.filter(
|
|
430
|
+
(task) =>
|
|
431
|
+
!!task &&
|
|
432
|
+
Number.parseInt(task.ProcessTaskId ?? "") ===
|
|
433
|
+
Number.parseInt(processTask.id),
|
|
434
|
+
) || [];
|
|
435
|
+
|
|
436
|
+
filteredConfig.Endpoints =
|
|
437
|
+
filteredConfig.Endpoints?.filter(
|
|
438
|
+
(ep) => !!ep && ep.Name === processTask.endpoint,
|
|
439
|
+
) || [];
|
|
440
|
+
|
|
441
|
+
if ((filteredConfig.Endpoints?.length ?? 0) > 0 && endpoint) {
|
|
442
|
+
filteredConfig.Entities =
|
|
443
|
+
filteredConfig.Entities?.filter(
|
|
444
|
+
(entity) =>
|
|
445
|
+
!!entity &&
|
|
446
|
+
entity.Name === filteredConfig?.Endpoints?.[0]?.Entity,
|
|
447
|
+
) || [];
|
|
409
448
|
}
|
|
410
449
|
}
|
|
411
450
|
|
|
451
|
+
console.log("filtered down Config", filteredConfig);
|
|
452
|
+
|
|
412
453
|
dispatchTaskState({
|
|
413
454
|
type: TaskActionType.OPEN_EDIT,
|
|
414
455
|
processTaskId: processTask.id,
|
|
415
|
-
templateConfig: templateConfig,
|
|
456
|
+
//templateConfig: templateConfig,
|
|
457
|
+
config: filteredConfig,
|
|
416
458
|
sort: processTask.sort,
|
|
417
459
|
parallelProcessingGroup: processTask.parallelProcessingGroup,
|
|
418
|
-
processTemplateId:
|
|
460
|
+
processTemplateId: processTask.processTemplateId,
|
|
419
461
|
name: processTask.name,
|
|
462
|
+
directionTypeId: processTask.directionTypeId,
|
|
420
463
|
} as EditAction);
|
|
421
464
|
}}
|
|
422
465
|
drawerOpen={taskState.open}
|
|
@@ -437,7 +480,11 @@ export default function IntegrationDetails() {
|
|
|
437
480
|
renderKey={taskState.processTaskId ?? 0}
|
|
438
481
|
dispatch={dispatchTaskState}
|
|
439
482
|
handleUpdateConfig={handleUpdateConfig}
|
|
440
|
-
config={
|
|
483
|
+
config={
|
|
484
|
+
config ??
|
|
485
|
+
((configFromApi as ConfigurationExportQuery)
|
|
486
|
+
?.configurationExport as ConfigExport)
|
|
487
|
+
}
|
|
441
488
|
/>
|
|
442
489
|
</Box>
|
|
443
490
|
<ScheduleDrawer
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ConfigExport,
|
|
3
3
|
ConfigurationMergeMutation,
|
|
4
|
-
ConfigurationMergeMutationVariables,
|
|
5
4
|
} from "@evenicanpm/admin-core/api/e4/graphqlRequestSdk";
|
|
6
5
|
import { processConfigMerge } from "@evenicanpm/admin-integrate/api/process-config/mutation";
|
|
7
6
|
import { TitleEdit } from "@evenicanpm/admin-integrate/components/core";
|
|
@@ -63,12 +62,21 @@ export default function EditTask(props: Readonly<Props>) {
|
|
|
63
62
|
|
|
64
63
|
useEffect(() => {
|
|
65
64
|
if (processConfigMergeInput) {
|
|
66
|
-
const input =
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
const input = (removeTemplateConfig(processConfigMergeInput?.input) ||
|
|
66
|
+
{}) as {
|
|
67
|
+
config: Array<Record<string, unknown>>;
|
|
68
|
+
};
|
|
69
69
|
processConfigMergeMutation.mutate(
|
|
70
70
|
{
|
|
71
|
-
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
+
input: {
|
|
73
|
+
sourceConfig:
|
|
74
|
+
props.taskState.directionTypeId === 0 ? input?.config[0] : null,
|
|
75
|
+
targetConfig:
|
|
76
|
+
props.taskState.directionTypeId === 1 ? input?.config[1] : null,
|
|
77
|
+
targetSourcesConfig:
|
|
78
|
+
props.taskState.directionTypeId === 1 ? input?.config[0] : null,
|
|
79
|
+
} as any,
|
|
72
80
|
},
|
|
73
81
|
{
|
|
74
82
|
onSuccess: async (data: ConfigurationMergeMutation) => {
|
|
@@ -143,13 +151,24 @@ export default function EditTask(props: Readonly<Props>) {
|
|
|
143
151
|
setProcessConfigMerge={setProcessConfigMergeInput}
|
|
144
152
|
handleBack={handleBack}
|
|
145
153
|
handleFinish={handleFinish}
|
|
146
|
-
templateConfig={props.taskState.templateConfig}
|
|
154
|
+
//templateConfig={props.taskState.templateConfig}
|
|
147
155
|
processTemplateId={props.taskState.processTemplateId as string}
|
|
148
156
|
isEditMode
|
|
149
157
|
isCreateMode={false}
|
|
150
158
|
disableBackToTemplates
|
|
151
159
|
readOnly={!props.editable}
|
|
152
160
|
sharedCount={(count: number) => setSharedCount(count)}
|
|
161
|
+
sourceTemplateId={
|
|
162
|
+
(props.taskState.directionTypeId as number) == 0
|
|
163
|
+
? props.taskState.processTemplateId
|
|
164
|
+
: undefined
|
|
165
|
+
}
|
|
166
|
+
targetTemplateId={
|
|
167
|
+
(props.taskState.directionTypeId as number) == 1
|
|
168
|
+
? props.taskState.processTemplateId
|
|
169
|
+
: undefined
|
|
170
|
+
}
|
|
171
|
+
config={{ json: props.taskState.config as Record<string, unknown> }}
|
|
153
172
|
/>
|
|
154
173
|
)}
|
|
155
174
|
{props.taskState.step === 1 && (
|
|
@@ -23,6 +23,8 @@ export interface EditAction extends TaskAction {
|
|
|
23
23
|
parallelProcessingGroup?: number;
|
|
24
24
|
processTemplateId?: string;
|
|
25
25
|
name?: string;
|
|
26
|
+
directionTypeId?: number;
|
|
27
|
+
config?: object;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export interface TaskState {
|
|
@@ -35,6 +37,8 @@ export interface TaskState {
|
|
|
35
37
|
parallelProcessingGroup?: number;
|
|
36
38
|
processTemplateId?: string;
|
|
37
39
|
name?: string;
|
|
40
|
+
directionTypeId?: number;
|
|
41
|
+
config?: object | null;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
export function taskReducer(state: TaskState, action: TaskAction): TaskState {
|
|
@@ -69,6 +73,8 @@ export function taskReducer(state: TaskState, action: TaskAction): TaskState {
|
|
|
69
73
|
parallelProcessingGroup: (action as EditAction).parallelProcessingGroup,
|
|
70
74
|
processTemplateId: (action as EditAction).processTemplateId,
|
|
71
75
|
name: (action as EditAction).name,
|
|
76
|
+
directionTypeId: (action as EditAction).directionTypeId,
|
|
77
|
+
config: (action as EditAction).config,
|
|
72
78
|
};
|
|
73
79
|
}
|
|
74
80
|
case TaskActionType.INCREMENT_STEP: {
|