snow-flow 10.0.1-dev.461 → 10.0.1-dev.463
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
|
@@ -365,6 +365,417 @@ async function calculateInsertOrder(
|
|
|
365
365
|
return 1;
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
+
/**
|
|
369
|
+
* Flatten an attributes object { key: "val" } into comma-separated "key=val," string (matching UI format).
|
|
370
|
+
* If already a string, returns as-is.
|
|
371
|
+
*/
|
|
372
|
+
function flattenAttributes(attrs: any): string {
|
|
373
|
+
if (!attrs || typeof attrs === 'string') return attrs || '';
|
|
374
|
+
return Object.entries(attrs).map(([k, v]) => k + '=' + v).join(',') + ',';
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Build full trigger input and output objects for the INSERT mutation by fetching from the
|
|
379
|
+
* triggerpicker API (/api/now/hub/triggerpicker/{id}) — the same endpoint Flow Designer UI uses.
|
|
380
|
+
*
|
|
381
|
+
* The UI sends ALL inputs with full parameter definitions (choices, defaults, attributes) and
|
|
382
|
+
* ALL outputs in a single INSERT mutation. This function replicates that format exactly.
|
|
383
|
+
*
|
|
384
|
+
* Fallback: if the triggerpicker API fails, queries sys_hub_trigger_input / sys_hub_trigger_output
|
|
385
|
+
* via the Table API (same approach as buildActionInputsForInsert / buildFlowLogicInputsForInsert).
|
|
386
|
+
*/
|
|
387
|
+
/**
|
|
388
|
+
* Parse XML string from triggerpicker API to extract input/output elements.
|
|
389
|
+
* The triggerpicker endpoint may return XML instead of JSON on some instances.
|
|
390
|
+
*/
|
|
391
|
+
function parseTriggerpickerXml(xmlStr: string): { inputs: any[]; outputs: any[] } {
|
|
392
|
+
var inputs: any[] = [];
|
|
393
|
+
var outputs: any[] = [];
|
|
394
|
+
|
|
395
|
+
// Helper: extract text content of an XML element by tag name
|
|
396
|
+
var getTag = function (xml: string, tag: string): string {
|
|
397
|
+
var m = xml.match(new RegExp('<' + tag + '>([\\s\\S]*?)</' + tag + '>'));
|
|
398
|
+
return m ? m[1].trim() : '';
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// Helper: extract all occurrences of a repeated element
|
|
402
|
+
var getAll = function (xml: string, tag: string): string[] {
|
|
403
|
+
var re = new RegExp('<' + tag + '>([\\s\\S]*?)</' + tag + '>', 'g');
|
|
404
|
+
var results: string[] = [];
|
|
405
|
+
var m;
|
|
406
|
+
while ((m = re.exec(xml)) !== null) results.push(m[1]);
|
|
407
|
+
return results;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
// Try to find input elements — XML may wrap them in <inputs><element>...</element></inputs>
|
|
411
|
+
// or <trigger_inputs><input>...</input></trigger_inputs> etc.
|
|
412
|
+
var inputsSection = getTag(xmlStr, 'inputs') || getTag(xmlStr, 'trigger_inputs') || xmlStr;
|
|
413
|
+
var inputElements = getAll(inputsSection, 'element');
|
|
414
|
+
if (inputElements.length === 0) inputElements = getAll(inputsSection, 'input');
|
|
415
|
+
if (inputElements.length === 0) inputElements = getAll(inputsSection, 'trigger_input');
|
|
416
|
+
|
|
417
|
+
for (var ii = 0; ii < inputElements.length; ii++) {
|
|
418
|
+
var el = inputElements[ii];
|
|
419
|
+
var name = getTag(el, 'name') || getTag(el, 'element');
|
|
420
|
+
if (!name) continue;
|
|
421
|
+
inputs.push({
|
|
422
|
+
id: getTag(el, 'sys_id') || getTag(el, 'id'),
|
|
423
|
+
name: name,
|
|
424
|
+
label: getTag(el, 'label') || name,
|
|
425
|
+
type: getTag(el, 'type') || getTag(el, 'internal_type') || 'string',
|
|
426
|
+
type_label: getTag(el, 'type_label') || '',
|
|
427
|
+
mandatory: getTag(el, 'mandatory') === 'true',
|
|
428
|
+
order: parseInt(getTag(el, 'order') || '0', 10),
|
|
429
|
+
maxsize: parseInt(getTag(el, 'maxsize') || getTag(el, 'max_length') || '4000', 10),
|
|
430
|
+
hint: getTag(el, 'hint'),
|
|
431
|
+
defaultValue: getTag(el, 'defaultValue') || getTag(el, 'default_value'),
|
|
432
|
+
defaultDisplayValue: getTag(el, 'defaultDisplayValue') || getTag(el, 'default_display_value'),
|
|
433
|
+
choiceOption: getTag(el, 'choiceOption') || getTag(el, 'choice_option'),
|
|
434
|
+
reference: getTag(el, 'reference'),
|
|
435
|
+
reference_display: getTag(el, 'reference_display'),
|
|
436
|
+
use_dependent: getTag(el, 'use_dependent') === 'true',
|
|
437
|
+
dependent_on: getTag(el, 'dependent_on'),
|
|
438
|
+
internal_link: getTag(el, 'internal_link'),
|
|
439
|
+
attributes: getTag(el, 'attributes')
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
var outputsSection = getTag(xmlStr, 'outputs') || getTag(xmlStr, 'trigger_outputs') || '';
|
|
444
|
+
var outputElements = getAll(outputsSection, 'element');
|
|
445
|
+
if (outputElements.length === 0) outputElements = getAll(outputsSection, 'output');
|
|
446
|
+
if (outputElements.length === 0) outputElements = getAll(outputsSection, 'trigger_output');
|
|
447
|
+
|
|
448
|
+
for (var oi = 0; oi < outputElements.length; oi++) {
|
|
449
|
+
var oel = outputElements[oi];
|
|
450
|
+
var oname = getTag(oel, 'name') || getTag(oel, 'element');
|
|
451
|
+
if (!oname) continue;
|
|
452
|
+
outputs.push({
|
|
453
|
+
id: getTag(oel, 'sys_id') || getTag(oel, 'id'),
|
|
454
|
+
name: oname,
|
|
455
|
+
label: getTag(oel, 'label') || oname,
|
|
456
|
+
type: getTag(oel, 'type') || getTag(oel, 'internal_type') || 'string',
|
|
457
|
+
type_label: getTag(oel, 'type_label') || '',
|
|
458
|
+
mandatory: getTag(oel, 'mandatory') === 'true',
|
|
459
|
+
order: parseInt(getTag(oel, 'order') || '0', 10),
|
|
460
|
+
maxsize: parseInt(getTag(oel, 'maxsize') || getTag(oel, 'max_length') || '200', 10),
|
|
461
|
+
hint: getTag(oel, 'hint'),
|
|
462
|
+
reference: getTag(oel, 'reference'),
|
|
463
|
+
reference_display: getTag(oel, 'reference_display'),
|
|
464
|
+
use_dependent: getTag(oel, 'use_dependent') === 'true',
|
|
465
|
+
dependent_on: getTag(oel, 'dependent_on'),
|
|
466
|
+
internal_link: getTag(oel, 'internal_link'),
|
|
467
|
+
attributes: getTag(oel, 'attributes')
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return { inputs, outputs };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Build a single trigger input object in GraphQL mutation format.
|
|
476
|
+
* Used by buildTriggerInputsForInsert and the hardcoded fallback.
|
|
477
|
+
*/
|
|
478
|
+
function buildTriggerInputObj(inp: any, userTable?: string, userCondition?: string): any {
|
|
479
|
+
var paramType = inp.type || 'string';
|
|
480
|
+
var name = inp.name || '';
|
|
481
|
+
var label = inp.label || name;
|
|
482
|
+
var attrs = typeof inp.attributes === 'object' ? flattenAttributes(inp.attributes) : (inp.attributes || '');
|
|
483
|
+
|
|
484
|
+
// Determine value: user-provided > default
|
|
485
|
+
var value = '';
|
|
486
|
+
if (name === 'table' && userTable) value = userTable;
|
|
487
|
+
else if (name === 'condition') value = userCondition || '^EQ';
|
|
488
|
+
else if (inp.defaultValue) value = inp.defaultValue;
|
|
489
|
+
|
|
490
|
+
var parameter: any = {
|
|
491
|
+
id: inp.id || '', label: label, name: name, type: paramType,
|
|
492
|
+
type_label: inp.type_label || TYPE_LABELS[paramType] || paramType,
|
|
493
|
+
order: inp.order || 0, extended: inp.extended || false,
|
|
494
|
+
mandatory: inp.mandatory || false, readonly: inp.readonly || false,
|
|
495
|
+
maxsize: inp.maxsize || 4000, data_structure: '',
|
|
496
|
+
reference: inp.reference || '', reference_display: inp.reference_display || '',
|
|
497
|
+
ref_qual: inp.ref_qual || '', choiceOption: inp.choiceOption || '',
|
|
498
|
+
table: '', columnName: '', defaultValue: inp.defaultValue || '',
|
|
499
|
+
use_dependent: inp.use_dependent || false, dependent_on: inp.dependent_on || '',
|
|
500
|
+
internal_link: inp.internal_link || '', show_ref_finder: inp.show_ref_finder || false,
|
|
501
|
+
local: inp.local || false, attributes: attrs, sys_class_name: '', children: []
|
|
502
|
+
};
|
|
503
|
+
if (inp.hint) parameter.hint = inp.hint;
|
|
504
|
+
if (inp.defaultDisplayValue) parameter.defaultDisplayValue = inp.defaultDisplayValue;
|
|
505
|
+
if (inp.choices) parameter.choices = inp.choices;
|
|
506
|
+
if (inp.defaultChoices) parameter.defaultChoices = inp.defaultChoices;
|
|
507
|
+
|
|
508
|
+
var inputObj: any = {
|
|
509
|
+
name: name, label: label, internalType: paramType,
|
|
510
|
+
mandatory: inp.mandatory || false, order: inp.order || 0,
|
|
511
|
+
valueSysId: '', field_name: name, type: paramType, children: [],
|
|
512
|
+
displayValue: { value: '' },
|
|
513
|
+
value: value ? { schemaless: false, schemalessValue: '', value: value } : { value: '' },
|
|
514
|
+
parameter: parameter
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
if (inp.choices && Array.isArray(inp.choices)) {
|
|
518
|
+
inputObj.choiceList = inp.choices.map(function (c: any) {
|
|
519
|
+
return { label: c.label, value: c.value };
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return inputObj;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Build a single trigger output object in GraphQL mutation format.
|
|
528
|
+
*/
|
|
529
|
+
function buildTriggerOutputObj(out: any): any {
|
|
530
|
+
var paramType = out.type || 'string';
|
|
531
|
+
var name = out.name || '';
|
|
532
|
+
var label = out.label || name;
|
|
533
|
+
var attrs = typeof out.attributes === 'object' ? flattenAttributes(out.attributes) : (out.attributes || '');
|
|
534
|
+
|
|
535
|
+
var parameter: any = {
|
|
536
|
+
id: out.id || '', label: label, name: name, type: paramType,
|
|
537
|
+
type_label: out.type_label || TYPE_LABELS[paramType] || paramType,
|
|
538
|
+
hint: out.hint || '', order: out.order || 0, extended: out.extended || false,
|
|
539
|
+
mandatory: out.mandatory || false, readonly: out.readonly || false,
|
|
540
|
+
maxsize: out.maxsize || 200, data_structure: '',
|
|
541
|
+
reference: out.reference || '', reference_display: out.reference_display || '',
|
|
542
|
+
ref_qual: '', choiceOption: '', table: '', columnName: '', defaultValue: '',
|
|
543
|
+
use_dependent: out.use_dependent || false, dependent_on: out.dependent_on || '',
|
|
544
|
+
internal_link: out.internal_link || '', show_ref_finder: false, local: false,
|
|
545
|
+
attributes: attrs, sys_class_name: ''
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
var children: any[] = [];
|
|
549
|
+
var paramChildren: any[] = [];
|
|
550
|
+
if (out.children && Array.isArray(out.children)) {
|
|
551
|
+
children = out.children.map(function (child: any) {
|
|
552
|
+
return { id: '', name: child.name || '', scriptActive: false, children: [], value: { value: '' }, script: null };
|
|
553
|
+
});
|
|
554
|
+
paramChildren = out.children.map(function (child: any) {
|
|
555
|
+
return {
|
|
556
|
+
id: '', label: child.label || child.name || '', name: child.name || '',
|
|
557
|
+
type: child.type || 'string', type_label: child.type_label || TYPE_LABELS[child.type || 'string'] || 'String',
|
|
558
|
+
hint: '', order: child.order || 0, extended: false, mandatory: false, readonly: false, maxsize: 0,
|
|
559
|
+
data_structure: '', reference: '', reference_display: '', ref_qual: '', choiceOption: '',
|
|
560
|
+
table: '', columnName: '', defaultValue: '', defaultDisplayValue: '',
|
|
561
|
+
use_dependent: false, dependent_on: false, show_ref_finder: false, local: false,
|
|
562
|
+
attributes: '', sys_class_name: '',
|
|
563
|
+
uiDisplayType: child.uiDisplayType || child.type || 'string',
|
|
564
|
+
uiDisplayTypeLabel: child.type_label || 'String',
|
|
565
|
+
internal_link: '', value: '', display_value: '', scriptActive: false,
|
|
566
|
+
parent: out.id || '',
|
|
567
|
+
fieldFacetMap: 'uiTypeLabel=' + (child.type_label || 'String') + ',',
|
|
568
|
+
children: [], script: null
|
|
569
|
+
};
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
parameter.children = paramChildren;
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
name: name, value: '', displayValue: '', type: paramType,
|
|
576
|
+
order: out.order || 0, label: label, children: children, parameter: parameter
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Hardcoded record trigger inputs — used as ultimate fallback when API and Table lookups fail.
|
|
582
|
+
* These definitions match the exact format captured from the Flow Designer UI for record-based triggers
|
|
583
|
+
* (record_create, record_update, record_create_or_update). Field names and types are consistent across instances.
|
|
584
|
+
*/
|
|
585
|
+
function getRecordTriggerFallbackInputs(): any[] {
|
|
586
|
+
return [
|
|
587
|
+
{ name: 'table', label: 'Table', type: 'table_name', type_label: 'Table Name', mandatory: true, order: 1, maxsize: 80, attributes: 'filter_table_source=RECORD_WATCHER_RESTRICTED,' },
|
|
588
|
+
{ name: 'condition', label: 'Condition', type: 'conditions', type_label: 'Conditions', mandatory: false, order: 100, maxsize: 4000, use_dependent: true, dependent_on: 'table', attributes: 'extended_operators=VALCHANGES;CHANGESFROM;CHANGESTO,wants_to_add_conditions=true,modelDependent=trigger_inputs,' },
|
|
589
|
+
{ name: 'run_on_extended', label: 'run_on_extended', type: 'choice', type_label: 'Choice', mandatory: false, order: 100, maxsize: 40, defaultValue: 'false', defaultDisplayValue: 'Run only on current table', choiceOption: '3', attributes: 'advanced=true,', choices: [{ label: 'Run only on current table', value: 'false', order: 0 }, { label: 'Run on current and extended tables', value: 'true', order: 1 }], defaultChoices: [{ label: 'Run only on current table', value: 'false', order: 1 }, { label: 'Run on current and extended tables', value: 'true', order: 2 }] },
|
|
590
|
+
{ name: 'run_flow_in', label: 'run_flow_in', type: 'choice', type_label: 'Choice', mandatory: false, order: 100, maxsize: 40, defaultValue: 'any', defaultDisplayValue: 'any', choiceOption: '3', attributes: 'advanced=true,', choices: [{ label: 'Run flow in background (default)', value: 'background', order: 0 }, { label: 'Run flow in foreground', value: 'foreground', order: 1 }], defaultChoices: [{ label: 'Run flow in background (default)', value: 'background', order: 1 }, { label: 'Run flow in foreground', value: 'foreground', order: 2 }] },
|
|
591
|
+
{ name: 'run_when_user_list', label: 'run_when_user_list', type: 'glide_list', type_label: 'List', mandatory: false, order: 100, maxsize: 4000, reference: 'sys_user', reference_display: 'User', attributes: 'advanced=true,' },
|
|
592
|
+
{ name: 'run_when_setting', label: 'run_when_setting', type: 'choice', type_label: 'Choice', mandatory: false, order: 100, maxsize: 40, defaultValue: 'both', defaultDisplayValue: 'Run for Both Interactive and Non-Interactive Sessions', choiceOption: '3', attributes: 'advanced=true,', choices: [{ label: 'Only Run for Non-Interactive Session', value: 'non_interactive', order: 0 }, { label: 'Only Run for User Interactive Session', value: 'interactive', order: 1 }, { label: 'Run for Both Interactive and Non-Interactive Sessions', value: 'both', order: 2 }], defaultChoices: [{ label: 'Only Run for Non-Interactive Session', value: 'non_interactive', order: 1 }, { label: 'Only Run for User Interactive Session', value: 'interactive', order: 2 }, { label: 'Run for Both Interactive and Non-Interactive Sessions', value: 'both', order: 3 }] },
|
|
593
|
+
{ name: 'run_when_user_setting', label: 'run_when_user_setting', type: 'choice', type_label: 'Choice', mandatory: false, order: 100, maxsize: 40, defaultValue: 'any', defaultDisplayValue: 'Run for any user', choiceOption: '3', attributes: 'advanced=true,', choices: [{ label: 'Do not run if triggered by the following users', value: 'not_one_of', order: 0 }, { label: 'Only Run if triggered by the following users', value: 'one_of', order: 1 }, { label: 'Run for any user', value: 'any', order: 2 }], defaultChoices: [{ label: 'Do not run if triggered by the following users', value: 'not_one_of', order: 1 }, { label: 'Only Run if triggered by the following users', value: 'one_of', order: 2 }, { label: 'Run for any user', value: 'any', order: 3 }] },
|
|
594
|
+
{ name: 'trigger_strategy', label: 'Run Trigger', type: 'choice', type_label: 'Choice', mandatory: false, order: 200, maxsize: 40, defaultValue: 'once', defaultDisplayValue: 'Once', choiceOption: '3', hint: 'Run Trigger every time the condition matches, or only the first time.', choices: [{ label: 'Once', value: 'once', order: 0 }, { label: 'For each unique change', value: 'unique_changes', order: 1 }, { label: 'Only if not currently running', value: 'always', order: 2 }, { label: 'For every update', value: 'every', order: 3 }], defaultChoices: [{ label: 'Once', value: 'once', order: 1 }, { label: 'For each unique change', value: 'unique_changes', order: 2 }, { label: 'Only if not currently running', value: 'always', order: 3 }, { label: 'For every update', value: 'every', order: 4 }] }
|
|
595
|
+
];
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function getRecordTriggerFallbackOutputs(): any[] {
|
|
599
|
+
return [
|
|
600
|
+
{ name: 'current', label: 'Record', type: 'document_id', type_label: 'Document ID', mandatory: true, order: 100, maxsize: 200, use_dependent: true, dependent_on: 'table_name', internal_link: 'table' },
|
|
601
|
+
{ name: 'changed_fields', label: 'Changed Fields', type: 'array.object', type_label: 'Array.Object', mandatory: false, order: 100, maxsize: 4000, attributes: 'uiTypeLabel=Array.Object,co_type_name=FDCollection,child_label=FDChangeDetails,child_type_label=Object,element_mapping_provider=com.glide.flow_design.action.data.FlowDesignVariableMapper,pwd2droppable=true,uiType=array.object,child_type=object,child_name=FDChangeDetails,', children: [{ name: 'field_name', label: 'Field Name', type: 'string', type_label: 'String', order: 1 }, { name: 'previous_value', label: 'Previous Value', type: 'string', type_label: 'String', order: 2 }, { name: 'current_value', label: 'Current Value', type: 'string', type_label: 'String', order: 3 }, { name: 'previous_display_value', label: 'Previous Display Value', type: 'string', type_label: 'String', order: 4 }, { name: 'current_display_value', label: 'Current Display Value', type: 'string', type_label: 'String', order: 5 }] },
|
|
602
|
+
{ name: 'table_name', label: 'Table Name', type: 'table_name', type_label: 'Table Name', mandatory: false, order: 101, maxsize: 200, internal_link: 'table', attributes: 'test_input_hidden=true,' },
|
|
603
|
+
{ name: 'run_start_time', label: 'Run Start Time UTC', type: 'glide_date_time', type_label: 'Date/Time', mandatory: false, order: 110, maxsize: 200, attributes: 'test_input_hidden=true,' },
|
|
604
|
+
{ name: 'run_start_date_time', label: 'Run Start Date/Time', type: 'glide_date_time', type_label: 'Date/Time', mandatory: false, order: 110, maxsize: 200, attributes: 'test_input_hidden=true,' }
|
|
605
|
+
];
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async function buildTriggerInputsForInsert(
|
|
609
|
+
client: any,
|
|
610
|
+
trigDefId: string,
|
|
611
|
+
trigType: string,
|
|
612
|
+
userTable?: string,
|
|
613
|
+
userCondition?: string
|
|
614
|
+
): Promise<{ inputs: any[]; outputs: any[]; source: string; error?: string }> {
|
|
615
|
+
var apiInputs: any[] = [];
|
|
616
|
+
var apiOutputs: any[] = [];
|
|
617
|
+
var fetchError = '';
|
|
618
|
+
var source = '';
|
|
619
|
+
|
|
620
|
+
// Strategy 1: triggerpicker API (primary — same as Flow Designer UI)
|
|
621
|
+
try {
|
|
622
|
+
var tpResp = await client.get('/api/now/hub/triggerpicker/' + trigDefId, {
|
|
623
|
+
params: { sysparm_transaction_scope: 'global' },
|
|
624
|
+
headers: { Accept: 'application/json' }
|
|
625
|
+
});
|
|
626
|
+
var tpRaw = tpResp.data;
|
|
627
|
+
var tpData = tpRaw?.result || tpRaw;
|
|
628
|
+
|
|
629
|
+
// Handle JSON object response
|
|
630
|
+
if (tpData && typeof tpData === 'object' && !Array.isArray(tpData)) {
|
|
631
|
+
// Try common field name variations
|
|
632
|
+
var foundInputs = Array.isArray(tpData.inputs) ? tpData.inputs
|
|
633
|
+
: Array.isArray(tpData.trigger_inputs) ? tpData.trigger_inputs
|
|
634
|
+
: Array.isArray(tpData.input) ? tpData.input : null;
|
|
635
|
+
var foundOutputs = Array.isArray(tpData.outputs) ? tpData.outputs
|
|
636
|
+
: Array.isArray(tpData.trigger_outputs) ? tpData.trigger_outputs
|
|
637
|
+
: Array.isArray(tpData.output) ? tpData.output : null;
|
|
638
|
+
if (foundInputs) { apiInputs = foundInputs; source = 'triggerpicker_json'; }
|
|
639
|
+
if (foundOutputs) apiOutputs = foundOutputs;
|
|
640
|
+
|
|
641
|
+
// If no arrays found, try to explore nested structure
|
|
642
|
+
if (!foundInputs && !foundOutputs) {
|
|
643
|
+
for (var key of Object.keys(tpData)) {
|
|
644
|
+
var val = tpData[key];
|
|
645
|
+
if (val && typeof val === 'object' && !Array.isArray(val)) {
|
|
646
|
+
if (Array.isArray(val.inputs)) { apiInputs = val.inputs; source = 'triggerpicker_json.' + key; }
|
|
647
|
+
if (Array.isArray(val.outputs)) apiOutputs = val.outputs;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Handle XML string response
|
|
654
|
+
if (apiInputs.length === 0 && typeof tpData === 'string' && tpData.includes('<')) {
|
|
655
|
+
var xmlResult = parseTriggerpickerXml(tpData);
|
|
656
|
+
if (xmlResult.inputs.length > 0) {
|
|
657
|
+
apiInputs = xmlResult.inputs;
|
|
658
|
+
apiOutputs = xmlResult.outputs;
|
|
659
|
+
source = 'triggerpicker_xml';
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// Also check if the raw response itself is XML (not wrapped in result)
|
|
663
|
+
if (apiInputs.length === 0 && typeof tpRaw === 'string' && tpRaw.includes('<')) {
|
|
664
|
+
var xmlResult2 = parseTriggerpickerXml(tpRaw);
|
|
665
|
+
if (xmlResult2.inputs.length > 0) {
|
|
666
|
+
apiInputs = xmlResult2.inputs;
|
|
667
|
+
apiOutputs = xmlResult2.outputs;
|
|
668
|
+
source = 'triggerpicker_xml_raw';
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
} catch (tpErr: any) {
|
|
672
|
+
fetchError = 'triggerpicker: ' + (tpErr.message || 'unknown');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Strategy 2: Table API fallback (query sys_hub_trigger_input / sys_hub_trigger_output)
|
|
676
|
+
if (apiInputs.length === 0) {
|
|
677
|
+
try {
|
|
678
|
+
var tiResp = await client.get('/api/now/table/sys_hub_trigger_input', {
|
|
679
|
+
params: {
|
|
680
|
+
sysparm_query: 'model=' + trigDefId,
|
|
681
|
+
sysparm_fields: 'sys_id,element,label,internal_type,mandatory,default_value,order,max_length,hint,read_only,attributes,reference,reference_display,choice,dependent_on_field,use_dependent_field',
|
|
682
|
+
sysparm_display_value: 'false',
|
|
683
|
+
sysparm_limit: 50
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
var tableInputs = tiResp.data.result || [];
|
|
687
|
+
if (tableInputs.length > 0) {
|
|
688
|
+
apiInputs = tableInputs.map(function (rec: any) {
|
|
689
|
+
return {
|
|
690
|
+
id: str(rec.sys_id), name: str(rec.element), label: str(rec.label) || str(rec.element),
|
|
691
|
+
type: str(rec.internal_type) || 'string',
|
|
692
|
+
type_label: TYPE_LABELS[str(rec.internal_type) || 'string'] || str(rec.internal_type),
|
|
693
|
+
mandatory: str(rec.mandatory) === 'true',
|
|
694
|
+
order: parseInt(str(rec.order) || '0', 10),
|
|
695
|
+
maxsize: parseInt(str(rec.max_length) || '4000', 10),
|
|
696
|
+
hint: str(rec.hint), defaultValue: str(rec.default_value),
|
|
697
|
+
reference: str(rec.reference), reference_display: str(rec.reference_display),
|
|
698
|
+
use_dependent: str(rec.use_dependent_field) === 'true',
|
|
699
|
+
dependent_on: str(rec.dependent_on_field),
|
|
700
|
+
attributes: str(rec.attributes)
|
|
701
|
+
};
|
|
702
|
+
});
|
|
703
|
+
source = 'table_api';
|
|
704
|
+
fetchError = '';
|
|
705
|
+
}
|
|
706
|
+
} catch (tiErr: any) {
|
|
707
|
+
fetchError += '; table_api_inputs: ' + (tiErr.message || 'unknown');
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (apiOutputs.length === 0) {
|
|
711
|
+
try {
|
|
712
|
+
var toResp = await client.get('/api/now/table/sys_hub_trigger_output', {
|
|
713
|
+
params: {
|
|
714
|
+
sysparm_query: 'model=' + trigDefId,
|
|
715
|
+
sysparm_fields: 'sys_id,element,label,internal_type,mandatory,order,max_length,hint,attributes,reference,reference_display,use_dependent_field,dependent_on_field',
|
|
716
|
+
sysparm_display_value: 'false',
|
|
717
|
+
sysparm_limit: 50
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
var tableOutputs = toResp.data.result || [];
|
|
721
|
+
if (tableOutputs.length > 0) {
|
|
722
|
+
apiOutputs = tableOutputs.map(function (rec: any) {
|
|
723
|
+
return {
|
|
724
|
+
id: str(rec.sys_id), name: str(rec.element), label: str(rec.label) || str(rec.element),
|
|
725
|
+
type: str(rec.internal_type) || 'string',
|
|
726
|
+
type_label: TYPE_LABELS[str(rec.internal_type) || 'string'] || str(rec.internal_type),
|
|
727
|
+
mandatory: str(rec.mandatory) === 'true',
|
|
728
|
+
order: parseInt(str(rec.order) || '0', 10),
|
|
729
|
+
maxsize: parseInt(str(rec.max_length) || '200', 10),
|
|
730
|
+
hint: str(rec.hint), reference: str(rec.reference), reference_display: str(rec.reference_display),
|
|
731
|
+
use_dependent: str(rec.use_dependent_field) === 'true',
|
|
732
|
+
dependent_on: str(rec.dependent_on_field),
|
|
733
|
+
attributes: str(rec.attributes)
|
|
734
|
+
};
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
} catch (_) {}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Strategy 3: Hardcoded fallback for record-based triggers (ultimate safety net)
|
|
741
|
+
// Uses exact definitions captured from the Flow Designer UI
|
|
742
|
+
var isRecordTrigger = /record/.test(trigType.toLowerCase());
|
|
743
|
+
if (apiInputs.length === 0 && isRecordTrigger) {
|
|
744
|
+
apiInputs = getRecordTriggerFallbackInputs();
|
|
745
|
+
source = 'hardcoded_fallback';
|
|
746
|
+
}
|
|
747
|
+
if (apiOutputs.length === 0 && isRecordTrigger) {
|
|
748
|
+
apiOutputs = getRecordTriggerFallbackOutputs();
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Transform to GraphQL mutation format
|
|
752
|
+
var inputs = apiInputs.map(function (inp: any) { return buildTriggerInputObj(inp, userTable, userCondition); });
|
|
753
|
+
var outputs = apiOutputs.map(function (out: any) { return buildTriggerOutputObj(out); });
|
|
754
|
+
|
|
755
|
+
// Final safety net: ensure table and condition inputs are ALWAYS present for record triggers
|
|
756
|
+
if (isRecordTrigger) {
|
|
757
|
+
var hasTable = inputs.some(function (i: any) { return i.name === 'table'; });
|
|
758
|
+
var hasCondition = inputs.some(function (i: any) { return i.name === 'condition'; });
|
|
759
|
+
if (!hasTable) {
|
|
760
|
+
inputs.unshift(buildTriggerInputObj(
|
|
761
|
+
{ name: 'table', label: 'Table', type: 'table_name', type_label: 'Table Name', mandatory: true, order: 1, maxsize: 80, attributes: 'filter_table_source=RECORD_WATCHER_RESTRICTED,' },
|
|
762
|
+
userTable, userCondition
|
|
763
|
+
));
|
|
764
|
+
source += '+table_injected';
|
|
765
|
+
}
|
|
766
|
+
if (!hasCondition) {
|
|
767
|
+
var condIdx = inputs.findIndex(function (i: any) { return i.name === 'table'; });
|
|
768
|
+
inputs.splice(condIdx + 1, 0, buildTriggerInputObj(
|
|
769
|
+
{ name: 'condition', label: 'Condition', type: 'conditions', type_label: 'Conditions', mandatory: false, order: 100, maxsize: 4000, use_dependent: true, dependent_on: 'table', attributes: 'extended_operators=VALCHANGES;CHANGESFROM;CHANGESTO,wants_to_add_conditions=true,modelDependent=trigger_inputs,' },
|
|
770
|
+
userTable, userCondition
|
|
771
|
+
));
|
|
772
|
+
source += '+condition_injected';
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return { inputs, outputs, source: source || 'none', error: fetchError || undefined };
|
|
777
|
+
}
|
|
778
|
+
|
|
368
779
|
async function addTriggerViaGraphQL(
|
|
369
780
|
client: any,
|
|
370
781
|
flowId: string,
|
|
@@ -432,8 +843,13 @@ async function addTriggerViaGraphQL(
|
|
|
432
843
|
}
|
|
433
844
|
if (!trigDefId) return { success: false, error: 'Trigger definition not found for: ' + triggerType, steps };
|
|
434
845
|
|
|
846
|
+
// Build full trigger inputs and outputs from triggerpicker API (matching UI format)
|
|
847
|
+
var triggerData = await buildTriggerInputsForInsert(client, trigDefId!, trigType, table, condition);
|
|
848
|
+
steps.trigger_data = { inputCount: triggerData.inputs.length, outputCount: triggerData.outputs.length, source: triggerData.source, error: triggerData.error };
|
|
849
|
+
|
|
435
850
|
const triggerResponseFields = 'triggerInstances { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
|
|
436
851
|
try {
|
|
852
|
+
// Single INSERT with full inputs and outputs (matching UI behavior — no separate UPDATE needed)
|
|
437
853
|
const insertResult = await executeFlowPatchMutation(client, {
|
|
438
854
|
flowId: flowId,
|
|
439
855
|
triggerInstances: {
|
|
@@ -445,8 +861,8 @@ async function addTriggerViaGraphQL(
|
|
|
445
861
|
type: trigType,
|
|
446
862
|
hasDynamicOutputs: false,
|
|
447
863
|
metadata: '{"predicates":[]}',
|
|
448
|
-
inputs:
|
|
449
|
-
outputs:
|
|
864
|
+
inputs: triggerData.inputs,
|
|
865
|
+
outputs: triggerData.outputs
|
|
450
866
|
}]
|
|
451
867
|
}
|
|
452
868
|
}, triggerResponseFields);
|
|
@@ -455,31 +871,6 @@ async function addTriggerViaGraphQL(
|
|
|
455
871
|
steps.insert = { success: !!triggerId, triggerId };
|
|
456
872
|
if (!triggerId) return { success: false, steps, error: 'GraphQL trigger INSERT returned no trigger ID' };
|
|
457
873
|
|
|
458
|
-
if (table) {
|
|
459
|
-
const updateInputs: any[] = [
|
|
460
|
-
{
|
|
461
|
-
name: 'table',
|
|
462
|
-
displayField: 'number',
|
|
463
|
-
displayValue: { schemaless: false, schemalessValue: '', value: table.charAt(0).toUpperCase() + table.slice(1) },
|
|
464
|
-
value: { schemaless: false, schemalessValue: '', value: table }
|
|
465
|
-
},
|
|
466
|
-
{
|
|
467
|
-
name: 'condition',
|
|
468
|
-
displayValue: { schemaless: false, schemalessValue: '', value: condition || '^EQ' },
|
|
469
|
-
value: { schemaless: false, schemalessValue: '', value: condition || '^EQ' }
|
|
470
|
-
}
|
|
471
|
-
];
|
|
472
|
-
try {
|
|
473
|
-
await executeFlowPatchMutation(client, {
|
|
474
|
-
flowId: flowId,
|
|
475
|
-
triggerInstances: { update: [{ id: triggerId, inputs: updateInputs }] }
|
|
476
|
-
}, triggerResponseFields);
|
|
477
|
-
steps.update = { success: true, table, condition: condition || '^EQ' };
|
|
478
|
-
} catch (e: any) {
|
|
479
|
-
steps.update = { success: false, error: e.message };
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
874
|
return { success: true, triggerId, steps };
|
|
484
875
|
} catch (e: any) {
|
|
485
876
|
steps.insert = { success: false, error: e.message };
|
|
@@ -487,6 +878,18 @@ async function addTriggerViaGraphQL(
|
|
|
487
878
|
}
|
|
488
879
|
}
|
|
489
880
|
|
|
881
|
+
// Common short-name aliases for action types — maps user-friendly names to ServiceNow internal names
|
|
882
|
+
const ACTION_TYPE_ALIASES: Record<string, string[]> = {
|
|
883
|
+
script: ['script_step', 'run_script', 'Run Script'],
|
|
884
|
+
log: ['log_message', 'Log Message', 'Log'],
|
|
885
|
+
create_record: ['Create Record'],
|
|
886
|
+
update_record: ['Update Record'],
|
|
887
|
+
notification: ['send_notification', 'send_email', 'Send Notification', 'Send Email'],
|
|
888
|
+
field_update: ['set_field_values', 'Set Field Values'],
|
|
889
|
+
wait: ['wait_for', 'Wait For Duration', 'Wait'],
|
|
890
|
+
approval: ['ask_for_approval', 'create_approval', 'Ask for Approval'],
|
|
891
|
+
};
|
|
892
|
+
|
|
490
893
|
async function addActionViaGraphQL(
|
|
491
894
|
client: any,
|
|
492
895
|
flowId: string,
|
|
@@ -499,7 +902,7 @@ async function addActionViaGraphQL(
|
|
|
499
902
|
): Promise<{ success: boolean; actionId?: string; steps?: any; error?: string }> {
|
|
500
903
|
const steps: any = {};
|
|
501
904
|
|
|
502
|
-
// Dynamically look up action definition in sys_hub_action_type_snapshot
|
|
905
|
+
// Dynamically look up action definition in sys_hub_action_type_snapshot and sys_hub_action_type_definition
|
|
503
906
|
// Prefer global/core actions over spoke-specific ones (e.g. core "Update Record" vs spoke-specific "Update Record")
|
|
504
907
|
const snapshotFields = 'sys_id,internal_name,name,sys_scope,sys_package';
|
|
505
908
|
let actionDefId: string | null = null;
|
|
@@ -527,41 +930,66 @@ async function addActionViaGraphQL(
|
|
|
527
930
|
return candidates[0];
|
|
528
931
|
};
|
|
529
932
|
|
|
530
|
-
for
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
933
|
+
// Helper: search a table for action definitions by exact match and LIKE
|
|
934
|
+
const searchTable = async (tableName: string, searchTerms: string[]): Promise<void> => {
|
|
935
|
+
for (var si = 0; si < searchTerms.length && !actionDefId; si++) {
|
|
936
|
+
var term = searchTerms[si];
|
|
937
|
+
// Exact match on internal_name and name
|
|
938
|
+
for (const field of ['internal_name', 'name']) {
|
|
939
|
+
if (actionDefId) break;
|
|
940
|
+
try {
|
|
941
|
+
const resp = await client.get('/api/now/table/' + tableName, {
|
|
942
|
+
params: { sysparm_query: field + '=' + term, sysparm_fields: snapshotFields, sysparm_limit: 10 }
|
|
943
|
+
});
|
|
944
|
+
const results = resp.data.result || [];
|
|
945
|
+
if (results.length > 1) {
|
|
946
|
+
steps.def_lookup_candidates = results.map((r: any) => ({ sys_id: r.sys_id, internal_name: str(r.internal_name), name: str(r.name), scope: str(r.sys_scope), package: str(r.sys_package) }));
|
|
947
|
+
}
|
|
948
|
+
const found = pickBest(results);
|
|
949
|
+
if (found?.sys_id) {
|
|
950
|
+
actionDefId = found.sys_id;
|
|
951
|
+
steps.def_lookup = { id: found.sys_id, internal_name: str(found.internal_name), name: str(found.name), scope: str(found.sys_scope), package: str(found.sys_package), matched: tableName + ':' + field + '=' + term };
|
|
952
|
+
}
|
|
953
|
+
} catch (_) {}
|
|
539
954
|
}
|
|
540
|
-
|
|
541
|
-
if (
|
|
542
|
-
|
|
543
|
-
|
|
955
|
+
// LIKE search
|
|
956
|
+
if (!actionDefId) {
|
|
957
|
+
try {
|
|
958
|
+
const resp = await client.get('/api/now/table/' + tableName, {
|
|
959
|
+
params: {
|
|
960
|
+
sysparm_query: 'internal_nameLIKE' + term + '^ORnameLIKE' + term,
|
|
961
|
+
sysparm_fields: snapshotFields, sysparm_limit: 10
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
const results = resp.data.result || [];
|
|
965
|
+
if (results.length > 0 && !steps.def_lookup_fallback_candidates) {
|
|
966
|
+
steps.def_lookup_fallback_candidates = results.map((r: any) => ({ sys_id: r.sys_id, internal_name: str(r.internal_name), name: str(r.name), scope: str(r.sys_scope), package: str(r.sys_package) }));
|
|
967
|
+
}
|
|
968
|
+
const found = pickBest(results);
|
|
969
|
+
if (found?.sys_id) {
|
|
970
|
+
actionDefId = found.sys_id;
|
|
971
|
+
steps.def_lookup = { id: found.sys_id, internal_name: str(found.internal_name), name: str(found.name), scope: str(found.sys_scope), package: str(found.sys_package), matched: tableName + ':LIKE ' + term };
|
|
972
|
+
}
|
|
973
|
+
} catch (_) {}
|
|
544
974
|
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
// Build search terms: original actionType + any alias variations
|
|
979
|
+
var searchTerms = [actionType];
|
|
980
|
+
var aliases = ACTION_TYPE_ALIASES[actionType.toLowerCase()];
|
|
981
|
+
if (aliases) searchTerms = searchTerms.concat(aliases);
|
|
982
|
+
|
|
983
|
+
// Search 1: sys_hub_action_type_snapshot (published action snapshots)
|
|
984
|
+
await searchTable('sys_hub_action_type_snapshot', searchTerms);
|
|
985
|
+
|
|
986
|
+
// Search 2: sys_hub_action_type_definition (action definitions — includes built-in/native actions)
|
|
547
987
|
if (!actionDefId) {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
params: {
|
|
551
|
-
sysparm_query: 'internal_nameLIKE' + actionType + '^ORnameLIKE' + actionType,
|
|
552
|
-
sysparm_fields: snapshotFields, sysparm_limit: 10
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
const results = resp.data.result || [];
|
|
556
|
-
steps.def_lookup_fallback_candidates = results.map((r: any) => ({ sys_id: r.sys_id, internal_name: str(r.internal_name), name: str(r.name), scope: str(r.sys_scope), package: str(r.sys_package) }));
|
|
557
|
-
const found = pickBest(results);
|
|
558
|
-
if (found?.sys_id) {
|
|
559
|
-
actionDefId = found.sys_id;
|
|
560
|
-
steps.def_lookup = { id: found.sys_id, internal_name: str(found.internal_name), name: str(found.name), scope: str(found.sys_scope), package: str(found.sys_package), matched: 'LIKE ' + actionType };
|
|
561
|
-
}
|
|
562
|
-
} catch (_) {}
|
|
988
|
+
steps.snapshot_not_found = true;
|
|
989
|
+
await searchTable('sys_hub_action_type_definition', searchTerms);
|
|
563
990
|
}
|
|
564
|
-
|
|
991
|
+
|
|
992
|
+
if (!actionDefId) return { success: false, error: 'Action definition not found for: ' + actionType + ' (searched snapshot + definition tables with terms: ' + searchTerms.join(', ') + ')', steps };
|
|
565
993
|
|
|
566
994
|
// Build full input objects with parameter definitions (matching UI format)
|
|
567
995
|
const inputResult = await buildActionInputsForInsert(client, actionDefId, inputs);
|
|
@@ -1151,8 +1579,7 @@ export const toolDefinition: MCPToolDefinition = {
|
|
|
1151
1579
|
},
|
|
1152
1580
|
action_type: {
|
|
1153
1581
|
type: 'string',
|
|
1154
|
-
|
|
1155
|
-
description: 'Action type to add (for add_action)',
|
|
1582
|
+
description: 'Action type to add (for add_action). Looked up dynamically by internal_name or name in sys_hub_action_type_snapshot and sys_hub_action_type_definition. Common short names: log, script, create_record, update_record, notification, field_update, wait, approval. You can also use the exact ServiceNow internal name (e.g. "sn_fd.script_step", "global.update_record") or display name (e.g. "Run Script", "Update Record").',
|
|
1156
1583
|
default: 'log'
|
|
1157
1584
|
},
|
|
1158
1585
|
action_name: {
|