snow-flow 10.0.1-dev.488 → 10.0.1-dev.490
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/package.json
CHANGED
|
@@ -110,6 +110,26 @@ async function executeFlowPatchMutation(
|
|
|
110
110
|
return resp.data?.data?.global?.snFlowDesigner?.flow || resp.data;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Acquire the Flow Designer editing lock on a flow.
|
|
115
|
+
* The UI calls safeEdit(create: flowId) when opening the editor.
|
|
116
|
+
* This must be called before GraphQL mutations on existing flows.
|
|
117
|
+
*/
|
|
118
|
+
async function acquireFlowEditingLock(client: any, flowId: string): Promise<{ success: boolean; error?: string }> {
|
|
119
|
+
try {
|
|
120
|
+
var mutation = 'mutation { global { snFlowDesigner { safeEdit(safeEditInput: {create: "' + flowId + '"}) { createResult { canEdit id editingUserDisplayName __typename } __typename } __typename } __typename } }';
|
|
121
|
+
var resp = await client.post('/api/now/graphql', { variables: {}, query: mutation });
|
|
122
|
+
var result = resp.data?.data?.global?.snFlowDesigner?.safeEdit?.createResult;
|
|
123
|
+
if (result?.canEdit === true || result?.canEdit === 'true') {
|
|
124
|
+
return { success: true };
|
|
125
|
+
}
|
|
126
|
+
var editingUser = result?.editingUserDisplayName || 'another user';
|
|
127
|
+
return { success: false, error: 'Flow is locked by ' + editingUser };
|
|
128
|
+
} catch (e: any) {
|
|
129
|
+
return { success: false, error: e.message || 'unknown error' };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
113
133
|
/**
|
|
114
134
|
* Release the Flow Designer editing lock on a flow.
|
|
115
135
|
* The UI calls safeEdit(delete: flowId) when closing the editor.
|
|
@@ -221,7 +241,12 @@ async function buildActionInputsForInsert(
|
|
|
221
241
|
};
|
|
222
242
|
});
|
|
223
243
|
|
|
224
|
-
|
|
244
|
+
// Check for mandatory fields that are missing a value
|
|
245
|
+
var missingMandatory = inputs
|
|
246
|
+
.filter(function (inp: any) { return inp.parameter?.mandatory && !inp.value?.value; })
|
|
247
|
+
.map(function (inp: any) { return inp.name + ' (' + (inp.parameter?.label || inp.name) + ')'; });
|
|
248
|
+
|
|
249
|
+
return { inputs, resolvedInputs, actionParams, missingMandatory };
|
|
225
250
|
}
|
|
226
251
|
|
|
227
252
|
/**
|
|
@@ -353,7 +378,12 @@ async function buildFlowLogicInputsForInsert(
|
|
|
353
378
|
variables: '[]'
|
|
354
379
|
};
|
|
355
380
|
|
|
356
|
-
|
|
381
|
+
// Check for mandatory fields that are missing a value
|
|
382
|
+
var missingMandatory = inputs
|
|
383
|
+
.filter(function (inp: any) { return inp.parameter?.mandatory && !inp.value?.value; })
|
|
384
|
+
.map(function (inp: any) { return inp.name + ' (' + (inp.parameter?.label || inp.name) + ')'; });
|
|
385
|
+
|
|
386
|
+
return { inputs, flowLogicDefinition, resolvedInputs, inputQueryError: inputQueryError || undefined, defParamsCount: defParams.length, missingMandatory };
|
|
357
387
|
}
|
|
358
388
|
|
|
359
389
|
// Note: reordering of existing elements is NOT possible via Table API because
|
|
@@ -1107,6 +1137,12 @@ async function addActionViaGraphQL(
|
|
|
1107
1137
|
steps.available_inputs = inputResult.actionParams.map((p: any) => ({ element: p.element, label: p.label }));
|
|
1108
1138
|
steps.resolved_inputs = inputResult.resolvedInputs;
|
|
1109
1139
|
|
|
1140
|
+
// Validate mandatory fields
|
|
1141
|
+
if (inputResult.missingMandatory && inputResult.missingMandatory.length > 0) {
|
|
1142
|
+
steps.missing_mandatory = inputResult.missingMandatory;
|
|
1143
|
+
return { success: false, error: 'Missing required inputs for ' + actionType + ': ' + inputResult.missingMandatory.join(', ') + '. These fields are mandatory in Flow Designer.', steps };
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1110
1146
|
// Calculate insertion order
|
|
1111
1147
|
const resolvedOrder = await calculateInsertOrder(client, flowId, parentUiId, order);
|
|
1112
1148
|
steps.insert_order = resolvedOrder;
|
|
@@ -1997,6 +2033,12 @@ async function addFlowLogicViaGraphQL(
|
|
|
1997
2033
|
steps.resolved_inputs = inputResult.resolvedInputs;
|
|
1998
2034
|
steps.input_query_stats = { defParamsFound: inputResult.defParamsCount, inputsBuilt: inputResult.inputs.length, error: inputResult.inputQueryError };
|
|
1999
2035
|
|
|
2036
|
+
// Validate mandatory fields (e.g. condition for IF/ELSEIF)
|
|
2037
|
+
if (inputResult.missingMandatory && inputResult.missingMandatory.length > 0) {
|
|
2038
|
+
steps.missing_mandatory = inputResult.missingMandatory;
|
|
2039
|
+
return { success: false, error: 'Missing required inputs for ' + logicType + ': ' + inputResult.missingMandatory.join(', ') + '. These fields are mandatory in Flow Designer.', steps };
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2000
2042
|
// ── Detect condition that needs data pill transformation ────────────
|
|
2001
2043
|
// Flow Designer sets conditions via a SEPARATE UPDATE after the element is created.
|
|
2002
2044
|
// Three paths:
|
|
@@ -3727,22 +3769,27 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
3727
3769
|
}
|
|
3728
3770
|
|
|
3729
3771
|
// ────────────────────────────────────────────────────────────────
|
|
3730
|
-
// OPEN_FLOW — acquire Flow Designer editing lock (
|
|
3772
|
+
// OPEN_FLOW — acquire Flow Designer editing lock (safeEdit create)
|
|
3731
3773
|
// ────────────────────────────────────────────────────────────────
|
|
3732
3774
|
case 'open_flow': {
|
|
3733
3775
|
if (!args.flow_id) throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for open_flow');
|
|
3734
3776
|
var openFlowId = await resolveFlowId(client, args.flow_id);
|
|
3735
3777
|
var openSummary = summary();
|
|
3778
|
+
|
|
3779
|
+
// Step 1: Load flow data via processflow GET (same as UI)
|
|
3736
3780
|
try {
|
|
3737
|
-
// The processflow GET is what the Flow Designer UI calls when opening a flow for editing.
|
|
3738
|
-
// This acquires the editing lock so subsequent GraphQL mutations can work.
|
|
3739
3781
|
await client.get('/api/now/processflow/flow/' + openFlowId);
|
|
3740
|
-
|
|
3782
|
+
} catch (_) { /* best-effort — flow data load is not critical for lock acquisition */ }
|
|
3783
|
+
|
|
3784
|
+
// Step 2: Acquire editing lock via safeEdit create mutation (required for GraphQL mutations)
|
|
3785
|
+
var lockResult = await acquireFlowEditingLock(client, openFlowId);
|
|
3786
|
+
if (lockResult.success) {
|
|
3787
|
+
openSummary.success('Flow opened for editing (lock acquired)').field('Flow', openFlowId)
|
|
3741
3788
|
.line('You can now use add_action, add_flow_logic, etc. Call close_flow when done.');
|
|
3742
3789
|
return createSuccessResult({ action: 'open_flow', flow_id: openFlowId, editing_session: true }, {}, openSummary.build());
|
|
3743
|
-
}
|
|
3744
|
-
openSummary.error('
|
|
3745
|
-
return createErrorResult('
|
|
3790
|
+
} else {
|
|
3791
|
+
openSummary.error('Cannot open flow: ' + (lockResult.error || 'lock acquisition failed')).field('Flow', openFlowId);
|
|
3792
|
+
return createErrorResult('Cannot open flow for editing: ' + (lockResult.error || 'lock acquisition failed'));
|
|
3746
3793
|
}
|
|
3747
3794
|
}
|
|
3748
3795
|
|