snow-flow 10.0.1-dev.477 → 10.0.1-dev.478
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
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Flow Designer
|
|
2
|
+
* Snow-Flow Flow Designer Tool
|
|
3
|
+
*
|
|
4
|
+
* DISCLAIMER:
|
|
5
|
+
* This tool uses both official and undocumented ServiceNow APIs to interact
|
|
6
|
+
* with Flow Designer. The GraphQL-based operations (snFlowDesigner) use
|
|
7
|
+
* internal ServiceNow APIs that are not officially documented and may change
|
|
8
|
+
* without notice. Use at your own risk.
|
|
9
|
+
*
|
|
10
|
+
* This tool is not affiliated with, endorsed by, or sponsored by ServiceNow, Inc.
|
|
11
|
+
* ServiceNow is a registered trademark of ServiceNow, Inc.
|
|
12
|
+
*
|
|
13
|
+
* A valid ServiceNow subscription and credentials are required to use this tool.
|
|
3
14
|
*/
|
|
4
15
|
|
|
5
16
|
export { toolDefinition as snow_manage_flow_def, execute as snow_manage_flow_exec } from './snow_manage_flow.js';
|
|
@@ -859,12 +859,21 @@ async function addTriggerViaGraphQL(
|
|
|
859
859
|
if (!trigDefId) return { success: false, error: 'Trigger definition not found for: ' + triggerType, steps };
|
|
860
860
|
|
|
861
861
|
// Build full trigger inputs and outputs from triggerpicker API (matching UI format)
|
|
862
|
-
|
|
862
|
+
// Pass empty table/condition — values are set via separate UPDATE (two-step, matching UI)
|
|
863
|
+
var triggerData = await buildTriggerInputsForInsert(client, trigDefId!, trigType, undefined, undefined);
|
|
863
864
|
steps.trigger_data = { inputCount: triggerData.inputs.length, outputCount: triggerData.outputs.length, source: triggerData.source, error: triggerData.error };
|
|
864
865
|
|
|
865
866
|
const triggerResponseFields = 'triggerInstances { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
|
|
866
867
|
try {
|
|
867
|
-
//
|
|
868
|
+
// Step 1: INSERT with empty table/condition values (matching UI behavior from trigger-query.txt)
|
|
869
|
+
// The UI always inserts the trigger with empty inputs first, then updates with actual values.
|
|
870
|
+
var insertInputs = triggerData.inputs.map(function (inp: any) {
|
|
871
|
+
if (inp.name === 'table' || inp.name === 'condition') {
|
|
872
|
+
return { ...inp, value: { value: '' }, displayValue: { value: '' } };
|
|
873
|
+
}
|
|
874
|
+
return inp;
|
|
875
|
+
});
|
|
876
|
+
|
|
868
877
|
const insertResult = await executeFlowPatchMutation(client, {
|
|
869
878
|
flowId: flowId,
|
|
870
879
|
triggerInstances: {
|
|
@@ -876,16 +885,61 @@ async function addTriggerViaGraphQL(
|
|
|
876
885
|
type: trigType,
|
|
877
886
|
hasDynamicOutputs: false,
|
|
878
887
|
metadata: '{"predicates":[]}',
|
|
879
|
-
inputs:
|
|
888
|
+
inputs: insertInputs,
|
|
880
889
|
outputs: triggerData.outputs
|
|
881
890
|
}]
|
|
882
891
|
}
|
|
883
892
|
}, triggerResponseFields);
|
|
884
893
|
|
|
885
894
|
const triggerId = insertResult?.triggerInstances?.inserts?.[0]?.sysId;
|
|
886
|
-
|
|
895
|
+
const triggerUiId = insertResult?.triggerInstances?.inserts?.[0]?.uiUniqueIdentifier;
|
|
896
|
+
steps.insert = { success: !!triggerId, triggerId, triggerUiId };
|
|
887
897
|
if (!triggerId) return { success: false, steps, error: 'GraphQL trigger INSERT returned no trigger ID' };
|
|
888
898
|
|
|
899
|
+
// Step 2: UPDATE with actual table and condition values (matching UI behavior from trigger-query-2.txt)
|
|
900
|
+
// The UI sends: table with displayField+displayValue+value, condition with just displayValue
|
|
901
|
+
if (table) {
|
|
902
|
+
try {
|
|
903
|
+
var tableDisplayName = '';
|
|
904
|
+
try {
|
|
905
|
+
var tblResp = await client.get('/api/now/table/sys_db_object', {
|
|
906
|
+
params: { sysparm_query: 'name=' + table, sysparm_fields: 'label', sysparm_display_value: 'true', sysparm_limit: 1 }
|
|
907
|
+
});
|
|
908
|
+
tableDisplayName = str(tblResp.data.result?.[0]?.label) || '';
|
|
909
|
+
} catch (_) {}
|
|
910
|
+
if (!tableDisplayName) {
|
|
911
|
+
tableDisplayName = table.charAt(0).toUpperCase() + table.slice(1).replace(/_/g, ' ');
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
var trigUpdateInputs: any[] = [
|
|
915
|
+
{
|
|
916
|
+
name: 'table',
|
|
917
|
+
displayField: 'number',
|
|
918
|
+
displayValue: { schemaless: false, schemalessValue: '', value: tableDisplayName },
|
|
919
|
+
value: { schemaless: false, schemalessValue: '', value: table }
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
name: 'condition',
|
|
923
|
+
displayValue: { schemaless: false, schemalessValue: '', value: condition || '^EQ' }
|
|
924
|
+
}
|
|
925
|
+
];
|
|
926
|
+
|
|
927
|
+
await executeFlowPatchMutation(client, {
|
|
928
|
+
flowId: flowId,
|
|
929
|
+
triggerInstances: {
|
|
930
|
+
update: [{
|
|
931
|
+
id: triggerId,
|
|
932
|
+
inputs: trigUpdateInputs
|
|
933
|
+
}]
|
|
934
|
+
}
|
|
935
|
+
}, triggerResponseFields);
|
|
936
|
+
steps.trigger_update = { success: true, table: table, tableDisplay: tableDisplayName };
|
|
937
|
+
} catch (updateErr: any) {
|
|
938
|
+
steps.trigger_update = { success: false, error: updateErr.message };
|
|
939
|
+
// Trigger was created — update failure is non-fatal
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
889
943
|
return { success: true, triggerId, steps };
|
|
890
944
|
} catch (e: any) {
|
|
891
945
|
steps.insert = { success: false, error: e.message };
|
|
@@ -1088,7 +1142,12 @@ async function addActionViaGraphQL(
|
|
|
1088
1142
|
var inp = recordActionResult.inputs[ri];
|
|
1089
1143
|
var val = inp.value?.value || '';
|
|
1090
1144
|
if (inp.name === 'record' && val.startsWith('{{')) {
|
|
1091
|
-
|
|
1145
|
+
// Both value and displayValue must be set (matching processflow XML format)
|
|
1146
|
+
updateInputs.push({
|
|
1147
|
+
name: 'record',
|
|
1148
|
+
displayValue: { schemaless: false, schemalessValue: '', value: val },
|
|
1149
|
+
value: { schemaless: false, schemalessValue: '', value: val }
|
|
1150
|
+
});
|
|
1092
1151
|
} else if (inp.name === 'table_name') {
|
|
1093
1152
|
// displayValue must use full schemaless format: {schemaless, schemalessValue, value}
|
|
1094
1153
|
updateInputs.push({
|
|
@@ -1119,11 +1178,7 @@ async function addActionViaGraphQL(
|
|
|
1119
1178
|
}]
|
|
1120
1179
|
}
|
|
1121
1180
|
};
|
|
1122
|
-
//
|
|
1123
|
-
if (recordActionResult.labelCacheUpdates.length > 0) {
|
|
1124
|
-
updatePatch.labelCache.update = recordActionResult.labelCacheUpdates;
|
|
1125
|
-
}
|
|
1126
|
-
// Field-level pills → labelCache INSERT (new entries)
|
|
1181
|
+
// All pills → labelCache INSERT (trigger created by our code, entries may not exist yet)
|
|
1127
1182
|
if (recordActionResult.labelCacheInserts.length > 0) {
|
|
1128
1183
|
updatePatch.labelCache.insert = recordActionResult.labelCacheInserts;
|
|
1129
1184
|
}
|
|
@@ -1441,14 +1496,12 @@ function transformConditionToDataPills(conditionValue: string, dataPillBase: str
|
|
|
1441
1496
|
/**
|
|
1442
1497
|
* Build labelCache entries for field-level data pills used in flow logic conditions.
|
|
1443
1498
|
*
|
|
1444
|
-
*
|
|
1445
|
-
*
|
|
1499
|
+
* Returns { inserts, updates }:
|
|
1500
|
+
* - inserts: record-level pill with full metadata (processflow XML format)
|
|
1501
|
+
* - updates: field-level pills with minimal fields (name + usedInstances, matching UI mutation)
|
|
1446
1502
|
*
|
|
1447
|
-
* The UI
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
1450
|
-
* Example for "category=inquiry" on incident table:
|
|
1451
|
-
* - { name: "Created or Updated_1.current.category", type: "choice", parent_table_name: "incident", column_name: "category" }
|
|
1503
|
+
* The UI uses labelCache.update for field-level condition pills (captured from network tab).
|
|
1504
|
+
* The record-level pill is INSERTed to ensure it exists when triggers are created programmatically.
|
|
1452
1505
|
*/
|
|
1453
1506
|
async function buildConditionLabelCache(
|
|
1454
1507
|
client: any,
|
|
@@ -1459,16 +1512,17 @@ async function buildConditionLabelCache(
|
|
|
1459
1512
|
tableLabel: string,
|
|
1460
1513
|
logicUiId: string,
|
|
1461
1514
|
explicitFields?: string[]
|
|
1462
|
-
): Promise<any[]> {
|
|
1463
|
-
|
|
1515
|
+
): Promise<{ inserts: any[]; updates: any[] }> {
|
|
1516
|
+
var empty = { inserts: [], updates: [] };
|
|
1517
|
+
if (!dataPillBase) return empty;
|
|
1464
1518
|
|
|
1465
1519
|
// Collect unique field names — either from explicit list or by parsing encoded query
|
|
1466
1520
|
if (!explicitFields) {
|
|
1467
1521
|
var clauses = parseEncodedQuery(conditionValue);
|
|
1468
|
-
if (clauses.length === 0) return
|
|
1522
|
+
if (clauses.length === 0) return empty;
|
|
1469
1523
|
explicitFields = clauses.map(function (c) { return c.field; }).filter(function (f) { return !!f; });
|
|
1470
1524
|
}
|
|
1471
|
-
if (explicitFields.length === 0) return
|
|
1525
|
+
if (explicitFields.length === 0) return empty;
|
|
1472
1526
|
|
|
1473
1527
|
// De-duplicate field names
|
|
1474
1528
|
var uniqueFields: string[] = [];
|
|
@@ -1505,30 +1559,39 @@ async function buildConditionLabelCache(
|
|
|
1505
1559
|
// Fallback: use "string" type and generated labels if dictionary lookup fails
|
|
1506
1560
|
}
|
|
1507
1561
|
|
|
1508
|
-
// Build
|
|
1509
|
-
|
|
1562
|
+
// Build labelCache entries for condition pills, split into INSERT and UPDATE:
|
|
1563
|
+
// - INSERT: record-level pill (must exist for actions; format from processflow XML)
|
|
1564
|
+
// - UPDATE: field-level pills (minimal: name + usedInstances — matching the UI's exact mutation format)
|
|
1565
|
+
//
|
|
1566
|
+
// The UI uses labelCache.update for condition pills (captured mutation shows only name + usedInstances).
|
|
1567
|
+
// The record-level pill is INSERTed to ensure it exists (our trigger is created via code, not UI).
|
|
1568
|
+
var inserts: any[] = [];
|
|
1569
|
+
var updates: any[] = [];
|
|
1570
|
+
|
|
1571
|
+
// Record-level pill entry → INSERT with full metadata (processflow XML format)
|
|
1572
|
+
inserts.push({
|
|
1573
|
+
name: dataPillBase,
|
|
1574
|
+
label: 'Trigger - Record ' + triggerName + '\u279b' + tableLabel + ' Record',
|
|
1575
|
+
reference: table,
|
|
1576
|
+
reference_display: tableLabel,
|
|
1577
|
+
type: 'reference',
|
|
1578
|
+
base_type: 'reference',
|
|
1579
|
+
attributes: {},
|
|
1580
|
+
usedInstances: []
|
|
1581
|
+
});
|
|
1582
|
+
|
|
1583
|
+
// Field-level pill entries → UPDATE with minimal fields (matching UI mutation)
|
|
1510
1584
|
for (var j = 0; j < uniqueFields.length; j++) {
|
|
1511
1585
|
var f = uniqueFields[j];
|
|
1512
|
-
var meta = fieldMeta[f];
|
|
1513
|
-
var fType = meta ? meta.type : 'string';
|
|
1514
|
-
var fLabel = meta ? meta.label : f.replace(/_/g, ' ').replace(/\b\w/g, function (c: string) { return c.toUpperCase(); });
|
|
1515
1586
|
var pillName = dataPillBase + '.' + f;
|
|
1516
1587
|
|
|
1517
|
-
|
|
1588
|
+
updates.push({
|
|
1518
1589
|
name: pillName,
|
|
1519
|
-
|
|
1520
|
-
reference: '',
|
|
1521
|
-
reference_display: fLabel,
|
|
1522
|
-
type: fType,
|
|
1523
|
-
base_type: fType,
|
|
1524
|
-
parent_table_name: table,
|
|
1525
|
-
column_name: f,
|
|
1526
|
-
usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }],
|
|
1527
|
-
choices: {}
|
|
1590
|
+
usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }]
|
|
1528
1591
|
});
|
|
1529
1592
|
}
|
|
1530
1593
|
|
|
1531
|
-
return
|
|
1594
|
+
return { inserts, updates };
|
|
1532
1595
|
}
|
|
1533
1596
|
|
|
1534
1597
|
// ── DATA PILL SUPPORT FOR RECORD ACTIONS (Update/Create Record) ──────
|
|
@@ -1595,10 +1658,15 @@ async function transformActionInputsForRecordAction(
|
|
|
1595
1658
|
|
|
1596
1659
|
if (isShorthand || !recordVal) {
|
|
1597
1660
|
// Auto-fill with trigger's current record data pill
|
|
1598
|
-
|
|
1661
|
+
// The processflow XML shows both value AND displayValue must be set to the pill reference
|
|
1662
|
+
var pillRef = '{{' + dataPillBase + '}}';
|
|
1663
|
+
recordInput.value = { schemaless: false, schemalessValue: '', value: pillRef };
|
|
1664
|
+
recordInput.displayValue = { schemaless: false, schemalessValue: '', value: pillRef };
|
|
1599
1665
|
usedInstances.push({ uiUniqueIdentifier: uuid, inputName: 'record' });
|
|
1600
|
-
steps.record_transform = { original: recordVal, pill:
|
|
1666
|
+
steps.record_transform = { original: recordVal, pill: pillRef };
|
|
1601
1667
|
} else if (isAlreadyPill) {
|
|
1668
|
+
// Also ensure displayValue is set for existing pill references
|
|
1669
|
+
recordInput.displayValue = { schemaless: false, schemalessValue: '', value: recordVal };
|
|
1602
1670
|
usedInstances.push({ uiUniqueIdentifier: uuid, inputName: 'record' });
|
|
1603
1671
|
}
|
|
1604
1672
|
}
|
|
@@ -1684,10 +1752,10 @@ async function transformActionInputsForRecordAction(
|
|
|
1684
1752
|
}
|
|
1685
1753
|
|
|
1686
1754
|
// ── 4. Build labelCache entries for data pills ────────────────────
|
|
1687
|
-
//
|
|
1688
|
-
//
|
|
1689
|
-
//
|
|
1690
|
-
//
|
|
1755
|
+
// Based on processflow XML analysis (labelCacheAsJsonString), the record-level pill
|
|
1756
|
+
// format is: { name, label, reference (table), reference_display (table label), type: "reference", base_type: "reference", attributes: {} }
|
|
1757
|
+
// Since our trigger is created via code (not UI), the labelCache entry may not exist yet.
|
|
1758
|
+
// We use INSERT for the record-level pill to ensure it exists.
|
|
1691
1759
|
var labelCacheUpdates: any[] = [];
|
|
1692
1760
|
var labelCacheInserts: any[] = [];
|
|
1693
1761
|
|
|
@@ -1695,9 +1763,16 @@ async function transformActionInputsForRecordAction(
|
|
|
1695
1763
|
var tableRef = triggerInfo.tableRef || triggerInfo.table || '';
|
|
1696
1764
|
var tblLabel = triggerInfo.tableLabel || '';
|
|
1697
1765
|
|
|
1698
|
-
// Record-level pill —
|
|
1699
|
-
|
|
1766
|
+
// Record-level pill — INSERT with full metadata matching processflow XML format:
|
|
1767
|
+
// { name: "Created or Updated_1.current", type: "reference", reference: "incident", reference_display: "Incident", ... }
|
|
1768
|
+
labelCacheInserts.push({
|
|
1700
1769
|
name: dataPillBase,
|
|
1770
|
+
label: 'Trigger - Record ' + triggerInfo.triggerName + '\u279b' + tblLabel + ' Record',
|
|
1771
|
+
reference: tableRef,
|
|
1772
|
+
reference_display: tblLabel,
|
|
1773
|
+
type: 'reference',
|
|
1774
|
+
base_type: 'reference',
|
|
1775
|
+
attributes: {},
|
|
1701
1776
|
usedInstances: usedInstances
|
|
1702
1777
|
});
|
|
1703
1778
|
|
|
@@ -1740,22 +1815,21 @@ async function transformActionInputsForRecordAction(
|
|
|
1740
1815
|
|
|
1741
1816
|
labelCacheInserts.push({
|
|
1742
1817
|
name: fullPillName,
|
|
1743
|
-
label: 'Trigger - Record ' + triggerInfo.triggerName + '\
|
|
1744
|
-
reference:
|
|
1818
|
+
label: 'Trigger - Record ' + triggerInfo.triggerName + '\u279b' + tblLabel + ' Record\u279b' + fMeta.label,
|
|
1819
|
+
reference: tableRef,
|
|
1745
1820
|
reference_display: fMeta.label,
|
|
1746
1821
|
type: fMeta.type,
|
|
1747
1822
|
base_type: fMeta.type,
|
|
1748
1823
|
parent_table_name: tableRef,
|
|
1749
1824
|
column_name: fieldCol,
|
|
1750
|
-
|
|
1751
|
-
|
|
1825
|
+
attributes: {},
|
|
1826
|
+
usedInstances: [{ uiUniqueIdentifier: uuid, inputName: fieldCol }]
|
|
1752
1827
|
});
|
|
1753
1828
|
}
|
|
1754
1829
|
}
|
|
1755
1830
|
}
|
|
1756
1831
|
|
|
1757
1832
|
steps.label_cache = {
|
|
1758
|
-
updates: labelCacheUpdates.map(function (e: any) { return e.name; }),
|
|
1759
1833
|
inserts: labelCacheInserts.map(function (e: any) { return e.name; }),
|
|
1760
1834
|
usedInstances: usedInstances.length
|
|
1761
1835
|
};
|
|
@@ -1940,7 +2014,6 @@ async function addFlowLogicViaGraphQL(
|
|
|
1940
2014
|
if (needsConditionUpdate && conditionTriggerInfo) {
|
|
1941
2015
|
var dataPillBase = conditionTriggerInfo.dataPillBase;
|
|
1942
2016
|
var transformedCondition: string;
|
|
1943
|
-
var labelCacheEntries: any[];
|
|
1944
2017
|
|
|
1945
2018
|
if (rawCondition.includes('{{')) {
|
|
1946
2019
|
// Condition already contains data pill references (after shorthand rewrite)
|
|
@@ -1954,7 +2027,7 @@ async function addFlowLogicViaGraphQL(
|
|
|
1954
2027
|
if (pParts.length > 2) pillFields.push(pParts[pParts.length - 1]);
|
|
1955
2028
|
}
|
|
1956
2029
|
// Build labelCache using extracted field names
|
|
1957
|
-
|
|
2030
|
+
var labelCacheResult = await buildConditionLabelCache(
|
|
1958
2031
|
client, rawCondition, dataPillBase, conditionTriggerInfo.triggerName,
|
|
1959
2032
|
conditionTriggerInfo.tableRef, conditionTriggerInfo.tableLabel, returnedUuid,
|
|
1960
2033
|
pillFields
|
|
@@ -1962,35 +2035,43 @@ async function addFlowLogicViaGraphQL(
|
|
|
1962
2035
|
} else {
|
|
1963
2036
|
// Plain encoded query — transform to data pill format
|
|
1964
2037
|
transformedCondition = transformConditionToDataPills(rawCondition, dataPillBase);
|
|
1965
|
-
|
|
2038
|
+
var labelCacheResult = await buildConditionLabelCache(
|
|
1966
2039
|
client, rawCondition, dataPillBase, conditionTriggerInfo.triggerName,
|
|
1967
2040
|
conditionTriggerInfo.tableRef, conditionTriggerInfo.tableLabel, returnedUuid
|
|
1968
2041
|
);
|
|
1969
2042
|
}
|
|
1970
2043
|
|
|
1971
2044
|
steps.condition_transform = { original: rawCondition, transformed: transformedCondition };
|
|
1972
|
-
steps.label_cache = {
|
|
2045
|
+
steps.label_cache = {
|
|
2046
|
+
inserts: labelCacheResult.inserts.map(function (e: any) { return e.name; }),
|
|
2047
|
+
updates: labelCacheResult.updates.map(function (e: any) { return e.name; })
|
|
2048
|
+
};
|
|
1973
2049
|
|
|
1974
2050
|
try {
|
|
2051
|
+
// Match the UI's exact format for condition UPDATE (from captured network tab mutation):
|
|
2052
|
+
// - condition input: only name + value (NO displayValue, NO flowLogicDefinition)
|
|
2053
|
+
// - labelCache.insert: record-level pill with full metadata (ensures it exists)
|
|
2054
|
+
// - labelCache.update: field-level pills with minimal name + usedInstances (matching UI)
|
|
1975
2055
|
var updatePatch: any = {
|
|
1976
2056
|
flowId: flowId,
|
|
1977
|
-
labelCache: {
|
|
2057
|
+
labelCache: {} as any,
|
|
1978
2058
|
flowLogics: {
|
|
1979
2059
|
update: [{
|
|
1980
2060
|
uiUniqueIdentifier: returnedUuid,
|
|
1981
2061
|
type: 'flowlogic',
|
|
1982
2062
|
inputs: [{
|
|
1983
2063
|
name: 'condition',
|
|
1984
|
-
displayValue: { value: '' },
|
|
1985
2064
|
value: { schemaless: false, schemalessValue: '', value: transformedCondition }
|
|
1986
|
-
}]
|
|
1987
|
-
flowLogicDefinition: {
|
|
1988
|
-
inputs: [{ name: 'condition_name', attributes: 'use_basic_input=true,' }],
|
|
1989
|
-
variables: 'undefined'
|
|
1990
|
-
}
|
|
2065
|
+
}]
|
|
1991
2066
|
}]
|
|
1992
2067
|
}
|
|
1993
2068
|
};
|
|
2069
|
+
if (labelCacheResult.inserts.length > 0) {
|
|
2070
|
+
updatePatch.labelCache.insert = labelCacheResult.inserts;
|
|
2071
|
+
}
|
|
2072
|
+
if (labelCacheResult.updates.length > 0) {
|
|
2073
|
+
updatePatch.labelCache.update = labelCacheResult.updates;
|
|
2074
|
+
}
|
|
1994
2075
|
// Log the exact GraphQL mutation for debugging
|
|
1995
2076
|
steps.condition_update_mutation = jsToGraphQL(updatePatch);
|
|
1996
2077
|
var updateResult = await executeFlowPatchMutation(client, updatePatch, logicResponseFields);
|