snow-flow 1.1.43 โ 1.1.45
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/README.md +97 -0
- package/dist/mcp/servicenow-intelligent-mcp.js +4 -0
- package/dist/mcp/servicenow-intelligent-mcp.js.map +1 -1
- package/dist/mcp/servicenow-operations-mcp.js +894 -0
- package/dist/mcp/servicenow-operations-mcp.js.map +1 -1
- package/dist/version.d.ts +16 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +80 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -482,6 +482,175 @@ class ServiceNowOperationsMCP {
|
|
|
482
482
|
},
|
|
483
483
|
required: ['action']
|
|
484
484
|
}
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: 'snow_catalog_item_search',
|
|
488
|
+
description: 'Search for catalog items with intelligent matching and discovery',
|
|
489
|
+
inputSchema: {
|
|
490
|
+
type: 'object',
|
|
491
|
+
properties: {
|
|
492
|
+
query: {
|
|
493
|
+
type: 'string',
|
|
494
|
+
description: 'Search query (e.g., "iPhone", "laptop", "mobile device")'
|
|
495
|
+
},
|
|
496
|
+
include_inactive: {
|
|
497
|
+
type: 'boolean',
|
|
498
|
+
description: 'Include inactive catalog items',
|
|
499
|
+
default: false
|
|
500
|
+
},
|
|
501
|
+
fuzzy_match: {
|
|
502
|
+
type: 'boolean',
|
|
503
|
+
description: 'Enable fuzzy matching for similar items',
|
|
504
|
+
default: true
|
|
505
|
+
},
|
|
506
|
+
category_filter: {
|
|
507
|
+
type: 'string',
|
|
508
|
+
description: 'Filter by category name or sys_id'
|
|
509
|
+
},
|
|
510
|
+
limit: {
|
|
511
|
+
type: 'number',
|
|
512
|
+
description: 'Maximum results to return',
|
|
513
|
+
default: 50
|
|
514
|
+
},
|
|
515
|
+
include_variables: {
|
|
516
|
+
type: 'boolean',
|
|
517
|
+
description: 'Include catalog item variables in results',
|
|
518
|
+
default: false
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
required: ['query']
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
name: 'snow_test_flow_with_mock',
|
|
526
|
+
description: 'Test flows with mock data - create test users, mock catalog items, and simulate flow execution',
|
|
527
|
+
inputSchema: {
|
|
528
|
+
type: 'object',
|
|
529
|
+
properties: {
|
|
530
|
+
flow_id: {
|
|
531
|
+
type: 'string',
|
|
532
|
+
description: 'Flow sys_id or name to test'
|
|
533
|
+
},
|
|
534
|
+
create_test_user: {
|
|
535
|
+
type: 'boolean',
|
|
536
|
+
description: 'Create a test user for the flow',
|
|
537
|
+
default: true
|
|
538
|
+
},
|
|
539
|
+
test_user_data: {
|
|
540
|
+
type: 'object',
|
|
541
|
+
description: 'Test user details',
|
|
542
|
+
properties: {
|
|
543
|
+
user_name: { type: 'string' },
|
|
544
|
+
first_name: { type: 'string' },
|
|
545
|
+
last_name: { type: 'string' },
|
|
546
|
+
email: { type: 'string' },
|
|
547
|
+
department: { type: 'string' },
|
|
548
|
+
manager: { type: 'string' }
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
mock_catalog_items: {
|
|
552
|
+
type: 'boolean',
|
|
553
|
+
description: 'Create mock catalog items for testing',
|
|
554
|
+
default: true
|
|
555
|
+
},
|
|
556
|
+
mock_catalog_data: {
|
|
557
|
+
type: 'array',
|
|
558
|
+
description: 'Mock catalog items to create',
|
|
559
|
+
items: {
|
|
560
|
+
type: 'object',
|
|
561
|
+
properties: {
|
|
562
|
+
name: { type: 'string' },
|
|
563
|
+
short_description: { type: 'string' },
|
|
564
|
+
price: { type: 'string' },
|
|
565
|
+
variables: { type: 'array' }
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
test_inputs: {
|
|
570
|
+
type: 'object',
|
|
571
|
+
description: 'Input values to test the flow with'
|
|
572
|
+
},
|
|
573
|
+
simulate_approvals: {
|
|
574
|
+
type: 'boolean',
|
|
575
|
+
description: 'Automatically approve any approval requests',
|
|
576
|
+
default: true
|
|
577
|
+
},
|
|
578
|
+
cleanup_after_test: {
|
|
579
|
+
type: 'boolean',
|
|
580
|
+
description: 'Remove test data after testing',
|
|
581
|
+
default: true
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
required: ['flow_id']
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
name: 'snow_link_catalog_to_flow',
|
|
589
|
+
description: 'Link catalog items directly to flows - configure flow as fulfillment process',
|
|
590
|
+
inputSchema: {
|
|
591
|
+
type: 'object',
|
|
592
|
+
properties: {
|
|
593
|
+
catalog_item_id: {
|
|
594
|
+
type: 'string',
|
|
595
|
+
description: 'Catalog item sys_id or name'
|
|
596
|
+
},
|
|
597
|
+
flow_id: {
|
|
598
|
+
type: 'string',
|
|
599
|
+
description: 'Flow sys_id or name to use for fulfillment'
|
|
600
|
+
},
|
|
601
|
+
link_type: {
|
|
602
|
+
type: 'string',
|
|
603
|
+
description: 'Type of link to create',
|
|
604
|
+
enum: ['workflow', 'flow_catalog_process', 'process_engine'],
|
|
605
|
+
default: 'flow_catalog_process'
|
|
606
|
+
},
|
|
607
|
+
variable_mapping: {
|
|
608
|
+
type: 'array',
|
|
609
|
+
description: 'Map catalog variables to flow inputs',
|
|
610
|
+
items: {
|
|
611
|
+
type: 'object',
|
|
612
|
+
properties: {
|
|
613
|
+
catalog_variable: { type: 'string', description: 'Catalog variable name' },
|
|
614
|
+
flow_input: { type: 'string', description: 'Flow input name' },
|
|
615
|
+
transform: { type: 'string', description: 'Optional transformation script' }
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
trigger_condition: {
|
|
620
|
+
type: 'string',
|
|
621
|
+
description: 'Condition for when to trigger the flow',
|
|
622
|
+
default: 'current.stage == "request_approved"'
|
|
623
|
+
},
|
|
624
|
+
execution_options: {
|
|
625
|
+
type: 'object',
|
|
626
|
+
description: 'Flow execution options',
|
|
627
|
+
properties: {
|
|
628
|
+
run_as: {
|
|
629
|
+
type: 'string',
|
|
630
|
+
description: 'User context for flow execution',
|
|
631
|
+
enum: ['requester', 'system', 'fulfiller'],
|
|
632
|
+
default: 'system'
|
|
633
|
+
},
|
|
634
|
+
wait_for_completion: {
|
|
635
|
+
type: 'boolean',
|
|
636
|
+
description: 'Wait for flow to complete before closing request',
|
|
637
|
+
default: true
|
|
638
|
+
},
|
|
639
|
+
update_request_on_progress: {
|
|
640
|
+
type: 'boolean',
|
|
641
|
+
description: 'Update request item with flow progress',
|
|
642
|
+
default: true
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
test_link: {
|
|
647
|
+
type: 'boolean',
|
|
648
|
+
description: 'Test the link by creating a sample request',
|
|
649
|
+
default: false
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
required: ['catalog_item_id', 'flow_id']
|
|
653
|
+
}
|
|
485
654
|
}
|
|
486
655
|
],
|
|
487
656
|
};
|
|
@@ -515,6 +684,12 @@ class ServiceNowOperationsMCP {
|
|
|
515
684
|
return await this.handlePredictiveAnalysis(args);
|
|
516
685
|
case 'snow_catalog_item_manager':
|
|
517
686
|
return await this.handleCatalogItemManager(args);
|
|
687
|
+
case 'snow_catalog_item_search':
|
|
688
|
+
return await this.handleCatalogItemSearch(args);
|
|
689
|
+
case 'snow_test_flow_with_mock':
|
|
690
|
+
return await this.handleTestFlowWithMock(args);
|
|
691
|
+
case 'snow_link_catalog_to_flow':
|
|
692
|
+
return await this.handleLinkCatalogToFlow(args);
|
|
518
693
|
default:
|
|
519
694
|
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Tool ${name} not found`);
|
|
520
695
|
}
|
|
@@ -1597,6 +1772,725 @@ class ServiceNowOperationsMCP {
|
|
|
1597
1772
|
};
|
|
1598
1773
|
return typeMap[type] || '6'; // Default to single line text
|
|
1599
1774
|
}
|
|
1775
|
+
async handleCatalogItemSearch(args) {
|
|
1776
|
+
const { query, include_inactive = false, fuzzy_match = true, category_filter, limit = 50, include_variables = false } = args;
|
|
1777
|
+
logger_js_1.logger.info(`Searching catalog items for: ${query}`, { fuzzy_match, category_filter, limit });
|
|
1778
|
+
try {
|
|
1779
|
+
// Build search queries
|
|
1780
|
+
const searchQueries = [];
|
|
1781
|
+
// Exact match
|
|
1782
|
+
searchQueries.push(`name=${query}`);
|
|
1783
|
+
// Contains match
|
|
1784
|
+
searchQueries.push(`nameLIKE${query}`);
|
|
1785
|
+
searchQueries.push(`short_descriptionLIKE${query}`);
|
|
1786
|
+
// Fuzzy matching for similar items
|
|
1787
|
+
if (fuzzy_match) {
|
|
1788
|
+
// Handle common variations
|
|
1789
|
+
const variations = this.generateSearchVariations(query);
|
|
1790
|
+
variations.forEach(variation => {
|
|
1791
|
+
searchQueries.push(`nameLIKE${variation}`);
|
|
1792
|
+
searchQueries.push(`short_descriptionLIKE${variation}`);
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
// Add active filter
|
|
1796
|
+
const activeFilter = include_inactive ? '' : '^active=true';
|
|
1797
|
+
// Add category filter if provided
|
|
1798
|
+
let categoryFilter = '';
|
|
1799
|
+
if (category_filter) {
|
|
1800
|
+
// First try to find the category
|
|
1801
|
+
const categoryResult = await this.client.searchRecords('sc_category', `name=${category_filter}^ORsys_id=${category_filter}`, 1);
|
|
1802
|
+
if (categoryResult.success && categoryResult.data?.result?.length > 0) {
|
|
1803
|
+
categoryFilter = `^sc_categories=${categoryResult.data.result[0].sys_id}`;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
// Execute searches
|
|
1807
|
+
const allResults = new Map();
|
|
1808
|
+
for (const searchQuery of searchQueries) {
|
|
1809
|
+
const fullQuery = searchQuery + activeFilter + categoryFilter;
|
|
1810
|
+
const result = await this.client.searchRecords('sc_cat_item', fullQuery, limit);
|
|
1811
|
+
if (result.success && result.data?.result) {
|
|
1812
|
+
result.data.result.forEach((item) => {
|
|
1813
|
+
if (!allResults.has(item.sys_id)) {
|
|
1814
|
+
allResults.set(item.sys_id, {
|
|
1815
|
+
sys_id: item.sys_id,
|
|
1816
|
+
name: item.name,
|
|
1817
|
+
short_description: item.short_description,
|
|
1818
|
+
price: item.price,
|
|
1819
|
+
recurring_price: item.recurring_price,
|
|
1820
|
+
active: item.active,
|
|
1821
|
+
category: item.sc_categories?.display_value,
|
|
1822
|
+
catalog: item.sc_catalogs?.display_value,
|
|
1823
|
+
model_number: item.model,
|
|
1824
|
+
manufacturer: item.vendor,
|
|
1825
|
+
variables: []
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
// Get variables if requested
|
|
1832
|
+
if (include_variables && allResults.size > 0) {
|
|
1833
|
+
for (const [sys_id, item] of allResults) {
|
|
1834
|
+
const variablesResult = await this.client.searchRecords('item_option_new', `cat_item=${sys_id}`, 50);
|
|
1835
|
+
if (variablesResult.success && variablesResult.data?.result) {
|
|
1836
|
+
item.variables = variablesResult.data.result.map((v) => ({
|
|
1837
|
+
name: v.name,
|
|
1838
|
+
label: v.question_text,
|
|
1839
|
+
type: v.type,
|
|
1840
|
+
mandatory: v.mandatory,
|
|
1841
|
+
default_value: v.default_value
|
|
1842
|
+
}));
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
const resultsArray = Array.from(allResults.values());
|
|
1847
|
+
// Sort by relevance (exact matches first)
|
|
1848
|
+
resultsArray.sort((a, b) => {
|
|
1849
|
+
const aExact = a.name.toLowerCase() === query.toLowerCase();
|
|
1850
|
+
const bExact = b.name.toLowerCase() === query.toLowerCase();
|
|
1851
|
+
if (aExact && !bExact)
|
|
1852
|
+
return -1;
|
|
1853
|
+
if (!aExact && bExact)
|
|
1854
|
+
return 1;
|
|
1855
|
+
return 0;
|
|
1856
|
+
});
|
|
1857
|
+
if (resultsArray.length === 0) {
|
|
1858
|
+
// Provide suggestions if no results found
|
|
1859
|
+
const suggestions = this.generateSearchSuggestions(query);
|
|
1860
|
+
return {
|
|
1861
|
+
content: [{
|
|
1862
|
+
type: 'text',
|
|
1863
|
+
text: `โ No catalog items found for "${query}"\n\n` +
|
|
1864
|
+
`๐ก Suggestions:\n` +
|
|
1865
|
+
suggestions.map(s => `- Try searching for "${s}"`).join('\n') +
|
|
1866
|
+
`\n\n๐ก Tips:\n` +
|
|
1867
|
+
`- Check if the item name is spelled correctly\n` +
|
|
1868
|
+
`- Try searching with partial names (e.g., "iPhone" instead of "iPhone 6S")\n` +
|
|
1869
|
+
`- Use category filter to narrow down results\n` +
|
|
1870
|
+
`- Enable include_inactive if looking for retired items`
|
|
1871
|
+
}]
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
// Format results
|
|
1875
|
+
let resultText = `๐ Found ${resultsArray.length} catalog items matching "${query}":\n\n`;
|
|
1876
|
+
resultsArray.forEach((item, index) => {
|
|
1877
|
+
resultText += `${index + 1}. **${item.name}** (${item.sys_id})\n`;
|
|
1878
|
+
resultText += ` ๐ ${item.short_description || 'No description'}\n`;
|
|
1879
|
+
resultText += ` ๐ฐ Price: $${item.price || '0'} ${item.recurring_price ? `(+ $${item.recurring_price}/month)` : ''}\n`;
|
|
1880
|
+
resultText += ` ๐ Category: ${item.category || 'Uncategorized'}\n`;
|
|
1881
|
+
resultText += ` ๐ Catalog: ${item.catalog || 'Default'}\n`;
|
|
1882
|
+
if (item.model_number) {
|
|
1883
|
+
resultText += ` ๐ข Model: ${item.model_number}\n`;
|
|
1884
|
+
}
|
|
1885
|
+
if (item.manufacturer) {
|
|
1886
|
+
resultText += ` ๐ญ Manufacturer: ${item.manufacturer}\n`;
|
|
1887
|
+
}
|
|
1888
|
+
resultText += ` โ
Status: ${item.active ? 'Active' : 'Inactive'}\n`;
|
|
1889
|
+
if (item.variables && item.variables.length > 0) {
|
|
1890
|
+
resultText += ` ๐ Variables (${item.variables.length}):\n`;
|
|
1891
|
+
item.variables.forEach((v) => {
|
|
1892
|
+
resultText += ` - ${v.label} (${v.name}) - ${v.type}${v.mandatory ? ' *Required' : ''}\n`;
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
resultText += '\n';
|
|
1896
|
+
});
|
|
1897
|
+
resultText += `\n๐ก Use snow_catalog_item_manager to create or update catalog items`;
|
|
1898
|
+
return {
|
|
1899
|
+
content: [{
|
|
1900
|
+
type: 'text',
|
|
1901
|
+
text: resultText
|
|
1902
|
+
}]
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
catch (error) {
|
|
1906
|
+
logger_js_1.logger.error('Error searching catalog items:', error);
|
|
1907
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Catalog search error: ${error}`);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
generateSearchVariations(query) {
|
|
1911
|
+
const variations = [];
|
|
1912
|
+
// Handle common product variations
|
|
1913
|
+
if (query.toLowerCase().includes('iphone')) {
|
|
1914
|
+
// Add variations without spaces, with different numbers
|
|
1915
|
+
const baseModel = query.replace(/iphone\s*/i, 'iPhone ');
|
|
1916
|
+
variations.push(baseModel);
|
|
1917
|
+
variations.push(baseModel.replace(' ', ''));
|
|
1918
|
+
// Try different model numbers
|
|
1919
|
+
const modelMatch = query.match(/\d+/);
|
|
1920
|
+
if (modelMatch) {
|
|
1921
|
+
const modelNum = parseInt(modelMatch[0]);
|
|
1922
|
+
variations.push(query.replace(modelNum.toString(), (modelNum + 1).toString()));
|
|
1923
|
+
variations.push(query.replace(modelNum.toString(), (modelNum - 1).toString()));
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
// Handle laptop/computer variations
|
|
1927
|
+
if (query.toLowerCase().includes('laptop') || query.toLowerCase().includes('computer')) {
|
|
1928
|
+
variations.push('notebook', 'macbook', 'thinkpad', 'dell', 'hp');
|
|
1929
|
+
}
|
|
1930
|
+
// Handle phone variations
|
|
1931
|
+
if (query.toLowerCase().includes('phone') || query.toLowerCase().includes('mobile')) {
|
|
1932
|
+
variations.push('smartphone', 'android', 'samsung', 'pixel');
|
|
1933
|
+
}
|
|
1934
|
+
return variations;
|
|
1935
|
+
}
|
|
1936
|
+
generateSearchSuggestions(query) {
|
|
1937
|
+
const suggestions = [];
|
|
1938
|
+
if (query.toLowerCase().includes('iphone')) {
|
|
1939
|
+
suggestions.push('iPhone', 'Apple iPhone', 'iOS device', 'Apple mobile');
|
|
1940
|
+
}
|
|
1941
|
+
else if (query.toLowerCase().includes('laptop')) {
|
|
1942
|
+
suggestions.push('notebook', 'computer', 'workstation', 'MacBook', 'ThinkPad');
|
|
1943
|
+
}
|
|
1944
|
+
else if (query.toLowerCase().includes('software')) {
|
|
1945
|
+
suggestions.push('license', 'application', 'subscription', 'SaaS');
|
|
1946
|
+
}
|
|
1947
|
+
else {
|
|
1948
|
+
// Generic suggestions
|
|
1949
|
+
suggestions.push(query.split(' ')[0], // First word only
|
|
1950
|
+
query.substring(0, Math.floor(query.length / 2)), // First half
|
|
1951
|
+
'hardware', 'software', 'equipment', 'device');
|
|
1952
|
+
}
|
|
1953
|
+
return [...new Set(suggestions)].filter(s => s && s !== query);
|
|
1954
|
+
}
|
|
1955
|
+
async handleTestFlowWithMock(args) {
|
|
1956
|
+
const { flow_id, create_test_user = true, test_user_data, mock_catalog_items = true, mock_catalog_data, test_inputs = {}, simulate_approvals = true, cleanup_after_test = true } = args;
|
|
1957
|
+
logger_js_1.logger.info(`Testing flow ${flow_id} with mock data`, {
|
|
1958
|
+
create_test_user,
|
|
1959
|
+
mock_catalog_items,
|
|
1960
|
+
simulate_approvals,
|
|
1961
|
+
cleanup_after_test
|
|
1962
|
+
});
|
|
1963
|
+
const testResults = {
|
|
1964
|
+
flow_id,
|
|
1965
|
+
test_run_id: `test_${Date.now()}`,
|
|
1966
|
+
created_data: {
|
|
1967
|
+
test_user: null,
|
|
1968
|
+
catalog_items: [],
|
|
1969
|
+
test_requests: []
|
|
1970
|
+
},
|
|
1971
|
+
execution_results: {
|
|
1972
|
+
status: 'pending',
|
|
1973
|
+
start_time: new Date().toISOString(),
|
|
1974
|
+
end_time: null,
|
|
1975
|
+
duration_ms: 0,
|
|
1976
|
+
execution_id: null,
|
|
1977
|
+
steps_executed: [],
|
|
1978
|
+
errors: [],
|
|
1979
|
+
approvals_simulated: []
|
|
1980
|
+
},
|
|
1981
|
+
cleanup_status: null
|
|
1982
|
+
};
|
|
1983
|
+
try {
|
|
1984
|
+
// Step 1: Find the flow
|
|
1985
|
+
const flowResult = await this.client.searchRecords('sys_hub_flow', `sys_id=${flow_id}^ORname=${flow_id}`, 1);
|
|
1986
|
+
if (!flowResult.success || !flowResult.data?.result?.length) {
|
|
1987
|
+
throw new Error(`Flow not found: ${flow_id}`);
|
|
1988
|
+
}
|
|
1989
|
+
const flow = flowResult.data.result[0];
|
|
1990
|
+
testResults.flow_id = flow.sys_id;
|
|
1991
|
+
// Step 2: Create test user if requested
|
|
1992
|
+
if (create_test_user) {
|
|
1993
|
+
const userData = test_user_data || {
|
|
1994
|
+
user_name: `test_user_${Date.now()}`,
|
|
1995
|
+
first_name: 'Test',
|
|
1996
|
+
last_name: 'User',
|
|
1997
|
+
email: `test_${Date.now()}@example.com`,
|
|
1998
|
+
department: 'IT',
|
|
1999
|
+
active: true
|
|
2000
|
+
};
|
|
2001
|
+
const userResult = await this.client.createRecord('sys_user', userData);
|
|
2002
|
+
if (userResult.success && userResult.data?.result) {
|
|
2003
|
+
testResults.created_data.test_user = {
|
|
2004
|
+
sys_id: userResult.data.result.sys_id,
|
|
2005
|
+
user_name: userData.user_name,
|
|
2006
|
+
email: userData.email
|
|
2007
|
+
};
|
|
2008
|
+
logger_js_1.logger.info('Created test user:', testResults.created_data.test_user);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
// Step 3: Create mock catalog items if requested
|
|
2012
|
+
if (mock_catalog_items) {
|
|
2013
|
+
const catalogData = mock_catalog_data || [
|
|
2014
|
+
{
|
|
2015
|
+
name: 'Test iPhone 6S',
|
|
2016
|
+
short_description: 'Test mobile device for flow testing',
|
|
2017
|
+
price: '699.00',
|
|
2018
|
+
active: true,
|
|
2019
|
+
billable: true
|
|
2020
|
+
},
|
|
2021
|
+
{
|
|
2022
|
+
name: 'Test Laptop',
|
|
2023
|
+
short_description: 'Test laptop for equipment provisioning',
|
|
2024
|
+
price: '1299.00',
|
|
2025
|
+
active: true,
|
|
2026
|
+
billable: true
|
|
2027
|
+
}
|
|
2028
|
+
];
|
|
2029
|
+
for (const item of catalogData) {
|
|
2030
|
+
const catalogResult = await this.client.createRecord('sc_cat_item', item);
|
|
2031
|
+
if (catalogResult.success && catalogResult.data?.result) {
|
|
2032
|
+
testResults.created_data.catalog_items.push({
|
|
2033
|
+
sys_id: catalogResult.data.result.sys_id,
|
|
2034
|
+
name: item.name,
|
|
2035
|
+
price: item.price
|
|
2036
|
+
});
|
|
2037
|
+
logger_js_1.logger.info('Created mock catalog item:', item.name);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
// Step 4: Execute the flow with test data
|
|
2042
|
+
try {
|
|
2043
|
+
// Prepare test inputs
|
|
2044
|
+
const flowInputs = {
|
|
2045
|
+
...test_inputs,
|
|
2046
|
+
test_mode: true,
|
|
2047
|
+
test_run_id: testResults.test_run_id
|
|
2048
|
+
};
|
|
2049
|
+
// If we created a test user, use them as the requested_for
|
|
2050
|
+
if (testResults.created_data.test_user) {
|
|
2051
|
+
flowInputs.requested_for = testResults.created_data.test_user.sys_id;
|
|
2052
|
+
flowInputs.opened_by = testResults.created_data.test_user.sys_id;
|
|
2053
|
+
}
|
|
2054
|
+
// If we created catalog items, use the first one
|
|
2055
|
+
if (testResults.created_data.catalog_items.length > 0) {
|
|
2056
|
+
flowInputs.cat_item = testResults.created_data.catalog_items[0].sys_id;
|
|
2057
|
+
}
|
|
2058
|
+
// Execute flow via REST API
|
|
2059
|
+
const executeResult = await this.executeFlowViaAPI(flow.sys_id, flowInputs);
|
|
2060
|
+
if (executeResult.success) {
|
|
2061
|
+
testResults.execution_results.status = 'running';
|
|
2062
|
+
testResults.execution_results.execution_id = executeResult.execution_id;
|
|
2063
|
+
// Monitor flow execution
|
|
2064
|
+
const executionStatus = await this.monitorFlowExecution(executeResult.execution_id, simulate_approvals);
|
|
2065
|
+
testResults.execution_results = {
|
|
2066
|
+
...testResults.execution_results,
|
|
2067
|
+
...executionStatus
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
else {
|
|
2071
|
+
testResults.execution_results.status = 'failed';
|
|
2072
|
+
testResults.execution_results.errors.push(executeResult.error || 'Unknown execution error');
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
catch (execError) {
|
|
2076
|
+
testResults.execution_results.status = 'error';
|
|
2077
|
+
testResults.execution_results.errors.push(execError.message);
|
|
2078
|
+
logger_js_1.logger.error('Flow execution error:', execError);
|
|
2079
|
+
}
|
|
2080
|
+
// Step 5: Cleanup if requested
|
|
2081
|
+
if (cleanup_after_test) {
|
|
2082
|
+
const cleanupResults = {
|
|
2083
|
+
user_deleted: false,
|
|
2084
|
+
catalog_items_deleted: 0,
|
|
2085
|
+
requests_cancelled: 0
|
|
2086
|
+
};
|
|
2087
|
+
// Delete test user
|
|
2088
|
+
if (testResults.created_data.test_user) {
|
|
2089
|
+
const deleteUserResult = await this.client.deleteRecord('sys_user', testResults.created_data.test_user.sys_id);
|
|
2090
|
+
cleanupResults.user_deleted = deleteUserResult.success;
|
|
2091
|
+
}
|
|
2092
|
+
// Delete catalog items
|
|
2093
|
+
for (const item of testResults.created_data.catalog_items) {
|
|
2094
|
+
const deleteItemResult = await this.client.deleteRecord('sc_cat_item', item.sys_id);
|
|
2095
|
+
if (deleteItemResult.success) {
|
|
2096
|
+
cleanupResults.catalog_items_deleted++;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
testResults.cleanup_status = cleanupResults;
|
|
2100
|
+
}
|
|
2101
|
+
// Format results
|
|
2102
|
+
const resultText = this.formatTestResults(testResults);
|
|
2103
|
+
return {
|
|
2104
|
+
content: [{
|
|
2105
|
+
type: 'text',
|
|
2106
|
+
text: resultText
|
|
2107
|
+
}]
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
catch (error) {
|
|
2111
|
+
logger_js_1.logger.error('Error testing flow with mock data:', error);
|
|
2112
|
+
testResults.execution_results.status = 'error';
|
|
2113
|
+
testResults.execution_results.errors.push(error.message);
|
|
2114
|
+
return {
|
|
2115
|
+
content: [{
|
|
2116
|
+
type: 'text',
|
|
2117
|
+
text: `โ Flow test failed\n\nError: ${error.message}\n\nTest Results:\n${JSON.stringify(testResults, null, 2)}`
|
|
2118
|
+
}]
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
async executeFlowViaAPI(flowId, inputs) {
|
|
2123
|
+
try {
|
|
2124
|
+
// Use the ServiceNow Flow API to execute the flow
|
|
2125
|
+
const result = await this.client.createRecord('sys_flow_context', {
|
|
2126
|
+
flow: flowId,
|
|
2127
|
+
inputs: JSON.stringify(inputs),
|
|
2128
|
+
state: 'executing'
|
|
2129
|
+
});
|
|
2130
|
+
if (result.success && result.data?.result) {
|
|
2131
|
+
return {
|
|
2132
|
+
success: true,
|
|
2133
|
+
execution_id: result.data.result.sys_id
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
return {
|
|
2137
|
+
success: false,
|
|
2138
|
+
error: 'Failed to start flow execution'
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
catch (error) {
|
|
2142
|
+
logger_js_1.logger.error('Error executing flow via API:', error);
|
|
2143
|
+
return {
|
|
2144
|
+
success: false,
|
|
2145
|
+
error: error.message
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
async monitorFlowExecution(executionId, simulateApprovals) {
|
|
2150
|
+
const maxWaitTime = 60000; // 60 seconds
|
|
2151
|
+
const pollInterval = 2000; // 2 seconds
|
|
2152
|
+
const startTime = Date.now();
|
|
2153
|
+
const executionResults = {
|
|
2154
|
+
status: 'running',
|
|
2155
|
+
steps_executed: [],
|
|
2156
|
+
approvals_simulated: [],
|
|
2157
|
+
errors: [],
|
|
2158
|
+
end_time: null,
|
|
2159
|
+
duration_ms: 0
|
|
2160
|
+
};
|
|
2161
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
2162
|
+
try {
|
|
2163
|
+
// Check flow context status
|
|
2164
|
+
const contextResult = await this.client.getRecord('sys_flow_context', executionId);
|
|
2165
|
+
if (!contextResult.success || !contextResult.data?.result) {
|
|
2166
|
+
executionResults.errors.push('Failed to get flow context');
|
|
2167
|
+
break;
|
|
2168
|
+
}
|
|
2169
|
+
const context = contextResult.data.result;
|
|
2170
|
+
executionResults.status = context.state;
|
|
2171
|
+
// Check for completed state
|
|
2172
|
+
if (context.state === 'complete' || context.state === 'cancelled' || context.state === 'error') {
|
|
2173
|
+
executionResults.end_time = context.sys_updated_on;
|
|
2174
|
+
executionResults.duration_ms = Date.now() - startTime;
|
|
2175
|
+
break;
|
|
2176
|
+
}
|
|
2177
|
+
// Check for approvals if simulating
|
|
2178
|
+
if (simulateApprovals) {
|
|
2179
|
+
const approvalResult = await this.client.searchRecords('sysapproval_approver', `source_table=sys_flow_context^document_id=${executionId}^state=requested`, 10);
|
|
2180
|
+
if (approvalResult.success && approvalResult.data?.result?.length > 0) {
|
|
2181
|
+
for (const approval of approvalResult.data.result) {
|
|
2182
|
+
// Auto-approve
|
|
2183
|
+
const approveResult = await this.client.updateRecord('sysapproval_approver', approval.sys_id, {
|
|
2184
|
+
state: 'approved',
|
|
2185
|
+
comments: 'Auto-approved by test framework'
|
|
2186
|
+
});
|
|
2187
|
+
if (approveResult.success) {
|
|
2188
|
+
executionResults.approvals_simulated.push({
|
|
2189
|
+
sys_id: approval.sys_id,
|
|
2190
|
+
approver: approval.approver.display_value,
|
|
2191
|
+
approved_at: new Date().toISOString()
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
// Wait before next poll
|
|
2198
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
2199
|
+
}
|
|
2200
|
+
catch (error) {
|
|
2201
|
+
executionResults.errors.push(error.message);
|
|
2202
|
+
executionResults.status = 'error';
|
|
2203
|
+
break;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
if (executionResults.status === 'running') {
|
|
2207
|
+
executionResults.status = 'timeout';
|
|
2208
|
+
executionResults.errors.push('Flow execution timed out after 60 seconds');
|
|
2209
|
+
}
|
|
2210
|
+
return executionResults;
|
|
2211
|
+
}
|
|
2212
|
+
formatTestResults(testResults) {
|
|
2213
|
+
let text = `๐งช Flow Test Results\n${'='.repeat(50)}\n\n`;
|
|
2214
|
+
text += `**Flow ID:** ${testResults.flow_id}\n`;
|
|
2215
|
+
text += `**Test Run ID:** ${testResults.test_run_id}\n\n`;
|
|
2216
|
+
// Test Data Created
|
|
2217
|
+
text += `๐ **Test Data Created:**\n`;
|
|
2218
|
+
if (testResults.created_data.test_user) {
|
|
2219
|
+
text += ` โ
Test User: ${testResults.created_data.test_user.user_name} (${testResults.created_data.test_user.sys_id})\n`;
|
|
2220
|
+
}
|
|
2221
|
+
if (testResults.created_data.catalog_items.length > 0) {
|
|
2222
|
+
text += ` โ
Catalog Items: ${testResults.created_data.catalog_items.length} created\n`;
|
|
2223
|
+
testResults.created_data.catalog_items.forEach((item) => {
|
|
2224
|
+
text += ` - ${item.name} ($${item.price})\n`;
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
// Execution Results
|
|
2228
|
+
text += `\n๐ **Execution Results:**\n`;
|
|
2229
|
+
text += ` Status: ${testResults.execution_results.status === 'complete' ? 'โ
' : 'โ'} ${testResults.execution_results.status}\n`;
|
|
2230
|
+
if (testResults.execution_results.start_time) {
|
|
2231
|
+
text += ` Start Time: ${testResults.execution_results.start_time}\n`;
|
|
2232
|
+
}
|
|
2233
|
+
if (testResults.execution_results.end_time) {
|
|
2234
|
+
text += ` End Time: ${testResults.execution_results.end_time}\n`;
|
|
2235
|
+
}
|
|
2236
|
+
if (testResults.execution_results.duration_ms) {
|
|
2237
|
+
text += ` Duration: ${(testResults.execution_results.duration_ms / 1000).toFixed(2)}s\n`;
|
|
2238
|
+
}
|
|
2239
|
+
if (testResults.execution_results.approvals_simulated.length > 0) {
|
|
2240
|
+
text += `\n โ
Approvals Simulated: ${testResults.execution_results.approvals_simulated.length}\n`;
|
|
2241
|
+
testResults.execution_results.approvals_simulated.forEach((approval) => {
|
|
2242
|
+
text += ` - ${approval.approver} at ${approval.approved_at}\n`;
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
if (testResults.execution_results.errors.length > 0) {
|
|
2246
|
+
text += `\n โ Errors:\n`;
|
|
2247
|
+
testResults.execution_results.errors.forEach((error) => {
|
|
2248
|
+
text += ` - ${error}\n`;
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
// Cleanup Status
|
|
2252
|
+
if (testResults.cleanup_status) {
|
|
2253
|
+
text += `\n๐งน **Cleanup Status:**\n`;
|
|
2254
|
+
text += ` User Deleted: ${testResults.cleanup_status.user_deleted ? 'โ
' : 'โ'}\n`;
|
|
2255
|
+
text += ` Catalog Items Deleted: ${testResults.cleanup_status.catalog_items_deleted}\n`;
|
|
2256
|
+
text += ` Requests Cancelled: ${testResults.cleanup_status.requests_cancelled}\n`;
|
|
2257
|
+
}
|
|
2258
|
+
// Recommendations
|
|
2259
|
+
text += `\n๐ก **Recommendations:**\n`;
|
|
2260
|
+
if (testResults.execution_results.status === 'complete') {
|
|
2261
|
+
text += ` โ
Flow executed successfully! Ready for production use.\n`;
|
|
2262
|
+
}
|
|
2263
|
+
else if (testResults.execution_results.status === 'timeout') {
|
|
2264
|
+
text += ` โ ๏ธ Flow execution timed out. Consider:\n`;
|
|
2265
|
+
text += ` - Checking for infinite loops\n`;
|
|
2266
|
+
text += ` - Reviewing wait conditions\n`;
|
|
2267
|
+
text += ` - Optimizing flow performance\n`;
|
|
2268
|
+
}
|
|
2269
|
+
else {
|
|
2270
|
+
text += ` โ Flow execution failed. Please:\n`;
|
|
2271
|
+
text += ` - Review error messages above\n`;
|
|
2272
|
+
text += ` - Check flow configuration\n`;
|
|
2273
|
+
text += ` - Verify all dependencies exist\n`;
|
|
2274
|
+
}
|
|
2275
|
+
return text;
|
|
2276
|
+
}
|
|
2277
|
+
async handleLinkCatalogToFlow(args) {
|
|
2278
|
+
const { catalog_item_id, flow_id, link_type = 'flow_catalog_process', variable_mapping = [], trigger_condition = 'current.stage == "request_approved"', execution_options = {}, test_link = false } = args;
|
|
2279
|
+
logger_js_1.logger.info(`Linking catalog item ${catalog_item_id} to flow ${flow_id}`, {
|
|
2280
|
+
link_type,
|
|
2281
|
+
variable_mapping_count: variable_mapping.length
|
|
2282
|
+
});
|
|
2283
|
+
const linkResults = {
|
|
2284
|
+
catalog_item: null,
|
|
2285
|
+
flow: null,
|
|
2286
|
+
link_created: false,
|
|
2287
|
+
link_details: null,
|
|
2288
|
+
variable_mappings: [],
|
|
2289
|
+
test_results: null,
|
|
2290
|
+
errors: []
|
|
2291
|
+
};
|
|
2292
|
+
try {
|
|
2293
|
+
// Step 1: Find the catalog item
|
|
2294
|
+
const catalogResult = await this.client.searchRecords('sc_cat_item', `sys_id=${catalog_item_id}^ORname=${catalog_item_id}`, 1);
|
|
2295
|
+
if (!catalogResult.success || !catalogResult.data?.result?.length) {
|
|
2296
|
+
throw new Error(`Catalog item not found: ${catalog_item_id}`);
|
|
2297
|
+
}
|
|
2298
|
+
linkResults.catalog_item = {
|
|
2299
|
+
sys_id: catalogResult.data.result[0].sys_id,
|
|
2300
|
+
name: catalogResult.data.result[0].name,
|
|
2301
|
+
category: catalogResult.data.result[0].sc_categories?.display_value
|
|
2302
|
+
};
|
|
2303
|
+
// Step 2: Find the flow
|
|
2304
|
+
const flowResult = await this.client.searchRecords('sys_hub_flow', `sys_id=${flow_id}^ORname=${flow_id}`, 1);
|
|
2305
|
+
if (!flowResult.success || !flowResult.data?.result?.length) {
|
|
2306
|
+
throw new Error(`Flow not found: ${flow_id}`);
|
|
2307
|
+
}
|
|
2308
|
+
linkResults.flow = {
|
|
2309
|
+
sys_id: flowResult.data.result[0].sys_id,
|
|
2310
|
+
name: flowResult.data.result[0].name,
|
|
2311
|
+
type: flowResult.data.result[0].type
|
|
2312
|
+
};
|
|
2313
|
+
// Step 3: Create the link based on link_type
|
|
2314
|
+
switch (link_type) {
|
|
2315
|
+
case 'flow_catalog_process': {
|
|
2316
|
+
// Modern approach: Use Flow Designer catalog process
|
|
2317
|
+
const processData = {
|
|
2318
|
+
catalog_item: linkResults.catalog_item.sys_id,
|
|
2319
|
+
flow: linkResults.flow.sys_id,
|
|
2320
|
+
active: true,
|
|
2321
|
+
condition: trigger_condition,
|
|
2322
|
+
run_as: execution_options.run_as || 'system',
|
|
2323
|
+
wait_for_completion: execution_options.wait_for_completion !== false
|
|
2324
|
+
};
|
|
2325
|
+
const processResult = await this.client.createRecord('sc_cat_item_producer', processData);
|
|
2326
|
+
if (processResult.success && processResult.data?.result) {
|
|
2327
|
+
linkResults.link_created = true;
|
|
2328
|
+
linkResults.link_details = {
|
|
2329
|
+
type: 'flow_catalog_process',
|
|
2330
|
+
sys_id: processResult.data.result.sys_id,
|
|
2331
|
+
table: 'sc_cat_item_producer'
|
|
2332
|
+
};
|
|
2333
|
+
// Create variable mappings
|
|
2334
|
+
for (const mapping of variable_mapping) {
|
|
2335
|
+
const mappingData = {
|
|
2336
|
+
producer: processResult.data.result.sys_id,
|
|
2337
|
+
catalog_variable: mapping.catalog_variable,
|
|
2338
|
+
flow_input: mapping.flow_input,
|
|
2339
|
+
transform_script: mapping.transform || ''
|
|
2340
|
+
};
|
|
2341
|
+
const mappingResult = await this.client.createRecord('sc_cat_item_producer_mapping', mappingData);
|
|
2342
|
+
if (mappingResult.success) {
|
|
2343
|
+
linkResults.variable_mappings.push({
|
|
2344
|
+
catalog_variable: mapping.catalog_variable,
|
|
2345
|
+
flow_input: mapping.flow_input,
|
|
2346
|
+
mapping_sys_id: mappingResult.data?.result?.sys_id
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
break;
|
|
2352
|
+
}
|
|
2353
|
+
case 'workflow': {
|
|
2354
|
+
// Legacy approach: Direct workflow assignment
|
|
2355
|
+
const updateData = {
|
|
2356
|
+
workflow: linkResults.flow.sys_id,
|
|
2357
|
+
no_order: false,
|
|
2358
|
+
no_proceed_checkout: false,
|
|
2359
|
+
no_quantity: false,
|
|
2360
|
+
no_delivery_time: false
|
|
2361
|
+
};
|
|
2362
|
+
const updateResult = await this.client.updateRecord('sc_cat_item', linkResults.catalog_item.sys_id, updateData);
|
|
2363
|
+
if (updateResult.success) {
|
|
2364
|
+
linkResults.link_created = true;
|
|
2365
|
+
linkResults.link_details = {
|
|
2366
|
+
type: 'workflow',
|
|
2367
|
+
field: 'workflow',
|
|
2368
|
+
value: linkResults.flow.sys_id
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
break;
|
|
2372
|
+
}
|
|
2373
|
+
case 'process_engine': {
|
|
2374
|
+
// Process Engine approach
|
|
2375
|
+
const engineData = {
|
|
2376
|
+
catalog_item: linkResults.catalog_item.sys_id,
|
|
2377
|
+
engine_type: 'flow',
|
|
2378
|
+
engine_id: linkResults.flow.sys_id,
|
|
2379
|
+
active: true,
|
|
2380
|
+
order: 100,
|
|
2381
|
+
condition: trigger_condition
|
|
2382
|
+
};
|
|
2383
|
+
const engineResult = await this.client.createRecord('sc_process_flow', engineData);
|
|
2384
|
+
if (engineResult.success && engineResult.data?.result) {
|
|
2385
|
+
linkResults.link_created = true;
|
|
2386
|
+
linkResults.link_details = {
|
|
2387
|
+
type: 'process_engine',
|
|
2388
|
+
sys_id: engineResult.data.result.sys_id,
|
|
2389
|
+
table: 'sc_process_flow'
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
break;
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
// Step 4: Test the link if requested
|
|
2396
|
+
if (test_link && linkResults.link_created) {
|
|
2397
|
+
logger_js_1.logger.info('Testing catalog-flow link with sample request');
|
|
2398
|
+
const testRequestData = {
|
|
2399
|
+
cat_item: linkResults.catalog_item.sys_id,
|
|
2400
|
+
requested_for: 'admin', // Default to admin user
|
|
2401
|
+
quantity: 1,
|
|
2402
|
+
comments: `Test request for catalog-flow link verification [${new Date().toISOString()}]`
|
|
2403
|
+
};
|
|
2404
|
+
// Add any mapped variables with test values
|
|
2405
|
+
for (const mapping of linkResults.variable_mappings) {
|
|
2406
|
+
testRequestData[`variables.${mapping.catalog_variable}`] = 'Test Value';
|
|
2407
|
+
}
|
|
2408
|
+
const testResult = await this.client.createRecord('sc_request', testRequestData);
|
|
2409
|
+
if (testResult.success && testResult.data?.result) {
|
|
2410
|
+
linkResults.test_results = {
|
|
2411
|
+
request_created: true,
|
|
2412
|
+
request_number: testResult.data.result.number,
|
|
2413
|
+
request_sys_id: testResult.data.result.sys_id,
|
|
2414
|
+
status: 'Check the request in ServiceNow to verify flow execution'
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
else {
|
|
2418
|
+
linkResults.test_results = {
|
|
2419
|
+
request_created: false,
|
|
2420
|
+
error: 'Failed to create test request'
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
// Format results
|
|
2425
|
+
let resultText = `๐ Catalog-Flow Link Results\n${'='.repeat(50)}\n\n`;
|
|
2426
|
+
resultText += `**Catalog Item:** ${linkResults.catalog_item.name} (${linkResults.catalog_item.sys_id})\n`;
|
|
2427
|
+
resultText += `**Flow:** ${linkResults.flow.name} (${linkResults.flow.sys_id})\n`;
|
|
2428
|
+
resultText += `**Link Type:** ${link_type}\n\n`;
|
|
2429
|
+
if (linkResults.link_created) {
|
|
2430
|
+
resultText += `โ
**Link Created Successfully!**\n`;
|
|
2431
|
+
resultText += ` Type: ${linkResults.link_details.type}\n`;
|
|
2432
|
+
if (linkResults.link_details.sys_id) {
|
|
2433
|
+
resultText += ` Record: ${linkResults.link_details.table} (${linkResults.link_details.sys_id})\n`;
|
|
2434
|
+
}
|
|
2435
|
+
if (linkResults.variable_mappings.length > 0) {
|
|
2436
|
+
resultText += `\n๐ **Variable Mappings Created:**\n`;
|
|
2437
|
+
linkResults.variable_mappings.forEach(mapping => {
|
|
2438
|
+
resultText += ` - ${mapping.catalog_variable} โ ${mapping.flow_input}\n`;
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
if (linkResults.test_results) {
|
|
2442
|
+
resultText += `\n๐งช **Test Results:**\n`;
|
|
2443
|
+
if (linkResults.test_results.request_created) {
|
|
2444
|
+
resultText += ` โ
Test request created: ${linkResults.test_results.request_number}\n`;
|
|
2445
|
+
resultText += ` ๐ ${linkResults.test_results.status}\n`;
|
|
2446
|
+
}
|
|
2447
|
+
else {
|
|
2448
|
+
resultText += ` โ ${linkResults.test_results.error}\n`;
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
resultText += `\n๐ก **Next Steps:**\n`;
|
|
2452
|
+
resultText += `1. Test the catalog item by creating a request\n`;
|
|
2453
|
+
resultText += `2. Verify the flow executes with correct inputs\n`;
|
|
2454
|
+
resultText += `3. Monitor the request fulfillment process\n`;
|
|
2455
|
+
if (link_type === 'flow_catalog_process') {
|
|
2456
|
+
resultText += `\n๐ **Note:** Using modern Flow Designer catalog process\n`;
|
|
2457
|
+
resultText += ` - Flow will trigger on: ${trigger_condition}\n`;
|
|
2458
|
+
resultText += ` - Execution context: ${execution_options.run_as || 'system'}\n`;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
else {
|
|
2462
|
+
resultText += `โ **Failed to create link**\n`;
|
|
2463
|
+
if (linkResults.errors.length > 0) {
|
|
2464
|
+
resultText += `\nErrors:\n`;
|
|
2465
|
+
linkResults.errors.forEach(error => {
|
|
2466
|
+
resultText += ` - ${error}\n`;
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
return {
|
|
2471
|
+
content: [{
|
|
2472
|
+
type: 'text',
|
|
2473
|
+
text: resultText
|
|
2474
|
+
}]
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
catch (error) {
|
|
2478
|
+
logger_js_1.logger.error('Error linking catalog to flow:', error);
|
|
2479
|
+
linkResults.errors.push(error.message);
|
|
2480
|
+
return {
|
|
2481
|
+
content: [{
|
|
2482
|
+
type: 'text',
|
|
2483
|
+
text: `โ Failed to link catalog to flow\n\nError: ${error.message}\n\n` +
|
|
2484
|
+
`๐ก Troubleshooting:\n` +
|
|
2485
|
+
`1. Verify catalog item exists: ${catalog_item_id}\n` +
|
|
2486
|
+
`2. Verify flow exists: ${flow_id}\n` +
|
|
2487
|
+
`3. Check permissions for creating ${link_type} links\n` +
|
|
2488
|
+
`4. Ensure flow is active and published\n\n` +
|
|
2489
|
+
`Results: ${JSON.stringify(linkResults, null, 2)}`
|
|
2490
|
+
}]
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
1600
2494
|
getServiceNowUrl() {
|
|
1601
2495
|
// This would need to get the instance URL from credentials
|
|
1602
2496
|
return 'https://instance.service-now.com';
|