snow-flow 1.4.37 โ 1.4.39
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/.claude-flow/metrics/system-metrics.json +1524 -0
- package/CLAUDE.md +0 -8
- package/README.md +2 -10
- package/dist/mcp/servicenow-deployment-mcp-refactored.d.ts.map +1 -1
- package/dist/mcp/servicenow-deployment-mcp-refactored.js.map +1 -1
- package/dist/mcp/servicenow-deployment-mcp.js +0 -110
- package/dist/mcp/servicenow-deployment-mcp.js.map +1 -1
- package/dist/mcp/servicenow-intelligent-mcp.d.ts +0 -3
- package/dist/mcp/servicenow-intelligent-mcp.d.ts.map +1 -1
- package/dist/mcp/servicenow-intelligent-mcp.js +129 -575
- package/dist/mcp/servicenow-intelligent-mcp.js.map +1 -1
- package/dist/mcp/servicenow-operations-mcp-refactored.d.ts +0 -2
- package/dist/mcp/servicenow-operations-mcp-refactored.d.ts.map +1 -1
- package/dist/mcp/servicenow-operations-mcp-refactored.js +0 -144
- package/dist/mcp/servicenow-operations-mcp-refactored.js.map +1 -1
- package/dist/mcp/servicenow-operations-mcp.js +1 -769
- package/dist/mcp/servicenow-operations-mcp.js.map +1 -1
- package/dist/mcp/snow-flow-mcp.js +66 -129
- package/dist/mcp/snow-flow-mcp.js.map +1 -1
- package/dist/mcp/start-all-mcp-servers.js +0 -8
- package/dist/mcp/start-all-mcp-servers.js.map +1 -1
- package/dist/version.d.ts +3 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +17 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -521,137 +521,6 @@ class ServiceNowOperationsMCP {
|
|
|
521
521
|
required: ['query']
|
|
522
522
|
}
|
|
523
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
|
-
}
|
|
654
|
-
},
|
|
655
524
|
{
|
|
656
525
|
name: 'snow_cleanup_test_artifacts',
|
|
657
526
|
description: 'Clean up test artifacts while preserving Update Set audit trail',
|
|
@@ -880,10 +749,6 @@ class ServiceNowOperationsMCP {
|
|
|
880
749
|
return await this.handleCatalogItemManager(args);
|
|
881
750
|
case 'snow_catalog_item_search':
|
|
882
751
|
return await this.handleCatalogItemSearch(args);
|
|
883
|
-
case 'snow_test_flow_with_mock':
|
|
884
|
-
return await this.handleTestFlowWithMock(args);
|
|
885
|
-
case 'snow_link_catalog_to_flow':
|
|
886
|
-
return await this.handleLinkCatalogToFlow(args);
|
|
887
752
|
case 'snow_cleanup_test_artifacts':
|
|
888
753
|
return await this.handleCleanupTestArtifacts(args);
|
|
889
754
|
case 'snow_create_user_group':
|
|
@@ -2414,602 +2279,6 @@ class ServiceNowOperationsMCP {
|
|
|
2414
2279
|
}
|
|
2415
2280
|
return false;
|
|
2416
2281
|
}
|
|
2417
|
-
async handleTestFlowWithMock(args) {
|
|
2418
|
-
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;
|
|
2419
|
-
logger_js_1.logger.info(`Testing flow ${flow_id} with mock data`, {
|
|
2420
|
-
create_test_user,
|
|
2421
|
-
mock_catalog_items,
|
|
2422
|
-
simulate_approvals,
|
|
2423
|
-
cleanup_after_test
|
|
2424
|
-
});
|
|
2425
|
-
const testResults = {
|
|
2426
|
-
flow_id,
|
|
2427
|
-
test_run_id: `test_${Date.now()}`,
|
|
2428
|
-
created_data: {
|
|
2429
|
-
test_user: null,
|
|
2430
|
-
catalog_items: [],
|
|
2431
|
-
test_requests: []
|
|
2432
|
-
},
|
|
2433
|
-
execution_results: {
|
|
2434
|
-
status: 'pending',
|
|
2435
|
-
start_time: new Date().toISOString(),
|
|
2436
|
-
end_time: null,
|
|
2437
|
-
duration_ms: 0,
|
|
2438
|
-
execution_id: null,
|
|
2439
|
-
steps_executed: [],
|
|
2440
|
-
errors: [],
|
|
2441
|
-
approvals_simulated: []
|
|
2442
|
-
},
|
|
2443
|
-
cleanup_status: null
|
|
2444
|
-
};
|
|
2445
|
-
try {
|
|
2446
|
-
// Step 1: Find the flow with intelligent fallback search
|
|
2447
|
-
let flowResult = await this.client.searchRecords('sys_hub_flow', `sys_id=${flow_id}^ORname=${flow_id}`, 1);
|
|
2448
|
-
// If not found, try different search strategies
|
|
2449
|
-
if (!flowResult.success || !flowResult.data?.result?.length) {
|
|
2450
|
-
logger_js_1.logger.info('Flow not found with exact match, trying fuzzy search...');
|
|
2451
|
-
// Try partial name matching
|
|
2452
|
-
flowResult = await this.client.searchRecords('sys_hub_flow', `nameCONTAINS${flow_id}`, 5);
|
|
2453
|
-
if (!flowResult.success || !flowResult.data?.result?.length) {
|
|
2454
|
-
// Try searching in Business Rules as fallback
|
|
2455
|
-
logger_js_1.logger.info('No flows found, searching for Business Rules as fallback...');
|
|
2456
|
-
const businessRuleResult = await this.client.searchRecords('sys_script', `nameCONTAINS${flow_id}^ORshort_descriptionCONTAINS${flow_id}`, 5);
|
|
2457
|
-
if (businessRuleResult.success && businessRuleResult.data?.result?.length) {
|
|
2458
|
-
testResults.execution_results.status = 'fallback_business_rule';
|
|
2459
|
-
testResults.execution_results.errors.push(`Flow '${flow_id}' not found, but found ${businessRuleResult.data.result.length} related Business Rules. Consider testing the Business Rule directly.`);
|
|
2460
|
-
const businessRules = businessRuleResult.data.result.map((br) => ({
|
|
2461
|
-
name: br.name,
|
|
2462
|
-
sys_id: br.sys_id,
|
|
2463
|
-
table: br.collection,
|
|
2464
|
-
when: br.when
|
|
2465
|
-
}));
|
|
2466
|
-
return {
|
|
2467
|
-
content: [{
|
|
2468
|
-
type: 'text',
|
|
2469
|
-
text: `โ ๏ธ **Flow Test - Intelligent Fallback**\n\nFlow '${flow_id}' not found, but discovered related Business Rules:\n\n${businessRules.map((br, i) => `${i + 1}. **${br.name}**\n - Sys ID: ${br.sys_id}\n - Table: ${br.table}\n - Trigger: ${br.when}\n`).join('\n')}\n\n๐ก **Recommendation:** Test the Business Rule directly as it may provide the same functionality as the intended flow.\n\n๐ง **Next Steps:**\n1. Verify the Business Rule logic matches your flow requirements\n2. Test by creating a record on the ${businessRules[0]?.table || 'target'} table\n3. Monitor System Logs for Business Rule execution`
|
|
2470
|
-
}]
|
|
2471
|
-
};
|
|
2472
|
-
}
|
|
2473
|
-
throw new Error(`Flow not found: ${flow_id}. Tried exact match, partial name match, and Business Rule fallback.`);
|
|
2474
|
-
}
|
|
2475
|
-
else {
|
|
2476
|
-
// Found partial matches, ask user to clarify
|
|
2477
|
-
const matches = flowResult.data.result.map((f) => ({
|
|
2478
|
-
name: f.name,
|
|
2479
|
-
sys_id: f.sys_id,
|
|
2480
|
-
active: f.active
|
|
2481
|
-
}));
|
|
2482
|
-
return {
|
|
2483
|
-
content: [{
|
|
2484
|
-
type: 'text',
|
|
2485
|
-
text: `๐ **Multiple Flow Matches Found**\n\nThe flow identifier '${flow_id}' matched multiple flows:\n\n${matches.map((f, i) => `${i + 1}. **${f.name}** (${f.active ? 'Active' : 'Inactive'})\n - Sys ID: ${f.sys_id}\n`).join('\n')}\n\n๐ก **Please specify the exact flow sys_id or unique name to continue testing.**`
|
|
2486
|
-
}]
|
|
2487
|
-
};
|
|
2488
|
-
}
|
|
2489
|
-
}
|
|
2490
|
-
const flow = flowResult.data.result[0];
|
|
2491
|
-
testResults.flow_id = flow.sys_id;
|
|
2492
|
-
// Step 2: Create test user if requested
|
|
2493
|
-
if (create_test_user) {
|
|
2494
|
-
const userData = test_user_data || {
|
|
2495
|
-
user_name: `test_user_${Date.now()}`,
|
|
2496
|
-
first_name: 'Test',
|
|
2497
|
-
last_name: 'User',
|
|
2498
|
-
email: `test_${Date.now()}@example.com`,
|
|
2499
|
-
department: 'IT',
|
|
2500
|
-
active: true
|
|
2501
|
-
};
|
|
2502
|
-
const userResult = await this.client.createRecord('sys_user', userData);
|
|
2503
|
-
if (userResult.success && userResult.data?.result) {
|
|
2504
|
-
testResults.created_data.test_user = {
|
|
2505
|
-
sys_id: userResult.data.result.sys_id,
|
|
2506
|
-
user_name: userData.user_name,
|
|
2507
|
-
email: userData.email
|
|
2508
|
-
};
|
|
2509
|
-
logger_js_1.logger.info('Created test user:', testResults.created_data.test_user);
|
|
2510
|
-
}
|
|
2511
|
-
}
|
|
2512
|
-
// Step 3: Create mock catalog items if requested
|
|
2513
|
-
if (mock_catalog_items) {
|
|
2514
|
-
const catalogData = mock_catalog_data || [
|
|
2515
|
-
{
|
|
2516
|
-
name: 'Test iPhone 6S',
|
|
2517
|
-
short_description: 'Test mobile device for flow testing',
|
|
2518
|
-
price: '699.00',
|
|
2519
|
-
active: true,
|
|
2520
|
-
billable: true
|
|
2521
|
-
},
|
|
2522
|
-
{
|
|
2523
|
-
name: 'Test Laptop',
|
|
2524
|
-
short_description: 'Test laptop for equipment provisioning',
|
|
2525
|
-
price: '1299.00',
|
|
2526
|
-
active: true,
|
|
2527
|
-
billable: true
|
|
2528
|
-
}
|
|
2529
|
-
];
|
|
2530
|
-
for (const item of catalogData) {
|
|
2531
|
-
const catalogResult = await this.client.createRecord('sc_cat_item', item);
|
|
2532
|
-
if (catalogResult.success && catalogResult.data?.result) {
|
|
2533
|
-
testResults.created_data.catalog_items.push({
|
|
2534
|
-
sys_id: catalogResult.data.result.sys_id,
|
|
2535
|
-
name: item.name,
|
|
2536
|
-
price: item.price
|
|
2537
|
-
});
|
|
2538
|
-
logger_js_1.logger.info('Created mock catalog item:', item.name);
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
// Step 4: Execute the flow with test data
|
|
2543
|
-
try {
|
|
2544
|
-
// Prepare test inputs
|
|
2545
|
-
const flowInputs = {
|
|
2546
|
-
...test_inputs,
|
|
2547
|
-
test_mode: true,
|
|
2548
|
-
test_run_id: testResults.test_run_id
|
|
2549
|
-
};
|
|
2550
|
-
// If we created a test user, use them as the requested_for
|
|
2551
|
-
if (testResults.created_data.test_user) {
|
|
2552
|
-
flowInputs.requested_for = testResults.created_data.test_user.sys_id;
|
|
2553
|
-
flowInputs.opened_by = testResults.created_data.test_user.sys_id;
|
|
2554
|
-
}
|
|
2555
|
-
// If we created catalog items, use the first one
|
|
2556
|
-
if (testResults.created_data.catalog_items.length > 0) {
|
|
2557
|
-
flowInputs.cat_item = testResults.created_data.catalog_items[0].sys_id;
|
|
2558
|
-
}
|
|
2559
|
-
// Execute flow via REST API
|
|
2560
|
-
const executeResult = await this.executeFlowViaAPI(flow.sys_id, flowInputs);
|
|
2561
|
-
if (executeResult.success) {
|
|
2562
|
-
testResults.execution_results.status = 'running';
|
|
2563
|
-
testResults.execution_results.execution_id = executeResult.execution_id;
|
|
2564
|
-
// Monitor flow execution
|
|
2565
|
-
const executionStatus = await this.monitorFlowExecution(executeResult.execution_id, simulate_approvals);
|
|
2566
|
-
testResults.execution_results = {
|
|
2567
|
-
...testResults.execution_results,
|
|
2568
|
-
...executionStatus
|
|
2569
|
-
};
|
|
2570
|
-
}
|
|
2571
|
-
else {
|
|
2572
|
-
testResults.execution_results.status = 'failed';
|
|
2573
|
-
testResults.execution_results.errors.push(executeResult.error || 'Unknown execution error');
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
catch (execError) {
|
|
2577
|
-
testResults.execution_results.status = 'error';
|
|
2578
|
-
testResults.execution_results.errors.push(execError.message);
|
|
2579
|
-
logger_js_1.logger.error('Flow execution error:', execError);
|
|
2580
|
-
}
|
|
2581
|
-
// Step 5: Cleanup if requested
|
|
2582
|
-
if (cleanup_after_test) {
|
|
2583
|
-
const cleanupResults = {
|
|
2584
|
-
user_deleted: false,
|
|
2585
|
-
catalog_items_deleted: 0,
|
|
2586
|
-
requests_cancelled: 0
|
|
2587
|
-
};
|
|
2588
|
-
// Delete test user
|
|
2589
|
-
if (testResults.created_data.test_user) {
|
|
2590
|
-
const deleteUserResult = await this.client.deleteRecord('sys_user', testResults.created_data.test_user.sys_id);
|
|
2591
|
-
cleanupResults.user_deleted = deleteUserResult.success;
|
|
2592
|
-
}
|
|
2593
|
-
// Delete catalog items
|
|
2594
|
-
for (const item of testResults.created_data.catalog_items) {
|
|
2595
|
-
const deleteItemResult = await this.client.deleteRecord('sc_cat_item', item.sys_id);
|
|
2596
|
-
if (deleteItemResult.success) {
|
|
2597
|
-
cleanupResults.catalog_items_deleted++;
|
|
2598
|
-
}
|
|
2599
|
-
}
|
|
2600
|
-
testResults.cleanup_status = cleanupResults;
|
|
2601
|
-
}
|
|
2602
|
-
// Format results
|
|
2603
|
-
const resultText = this.formatTestResults(testResults);
|
|
2604
|
-
return {
|
|
2605
|
-
content: [{
|
|
2606
|
-
type: 'text',
|
|
2607
|
-
text: resultText
|
|
2608
|
-
}]
|
|
2609
|
-
};
|
|
2610
|
-
}
|
|
2611
|
-
catch (error) {
|
|
2612
|
-
logger_js_1.logger.error('Error testing flow with mock data:', error);
|
|
2613
|
-
testResults.execution_results.status = 'error';
|
|
2614
|
-
testResults.execution_results.errors.push(error.message);
|
|
2615
|
-
return {
|
|
2616
|
-
content: [{
|
|
2617
|
-
type: 'text',
|
|
2618
|
-
text: `โ Flow test failed
|
|
2619
|
-
|
|
2620
|
-
๐จ Error: ${error.message}
|
|
2621
|
-
|
|
2622
|
-
๐ง Troubleshooting Steps:
|
|
2623
|
-
1. Check authentication: snow_auth_diagnostics()
|
|
2624
|
-
2. Verify flow exists: snow_get_by_sysid("${flow_id}")
|
|
2625
|
-
3. Check Update Set: snow_update_set_current()
|
|
2626
|
-
4. Try alternative: snow_test_flow_with_mock() (always works)
|
|
2627
|
-
|
|
2628
|
-
๐ก Alternative Tools:
|
|
2629
|
-
โข For reliable testing: snow_test_flow_with_mock()
|
|
2630
|
-
โข For verification: snow_get_by_sysid()
|
|
2631
|
-
โข For comprehensive testing: snow_comprehensive_flow_test()
|
|
2632
|
-
|
|
2633
|
-
๐ Test Results:
|
|
2634
|
-
${JSON.stringify(testResults, null, 2)}
|
|
2635
|
-
|
|
2636
|
-
๐ Documentation: See CLAUDE.md for Flow Testing Guidelines`
|
|
2637
|
-
}]
|
|
2638
|
-
};
|
|
2639
|
-
}
|
|
2640
|
-
}
|
|
2641
|
-
async executeFlowViaAPI(flowId, inputs) {
|
|
2642
|
-
try {
|
|
2643
|
-
// Use the ServiceNow Flow API to execute the flow
|
|
2644
|
-
const result = await this.client.createRecord('sys_flow_context', {
|
|
2645
|
-
flow: flowId,
|
|
2646
|
-
inputs: JSON.stringify(inputs),
|
|
2647
|
-
state: 'executing'
|
|
2648
|
-
});
|
|
2649
|
-
if (result.success && result.data?.result) {
|
|
2650
|
-
return {
|
|
2651
|
-
success: true,
|
|
2652
|
-
execution_id: result.data.result.sys_id
|
|
2653
|
-
};
|
|
2654
|
-
}
|
|
2655
|
-
return {
|
|
2656
|
-
success: false,
|
|
2657
|
-
error: 'Failed to start flow execution'
|
|
2658
|
-
};
|
|
2659
|
-
}
|
|
2660
|
-
catch (error) {
|
|
2661
|
-
logger_js_1.logger.error('Error executing flow via API:', error);
|
|
2662
|
-
return {
|
|
2663
|
-
success: false,
|
|
2664
|
-
error: error.message
|
|
2665
|
-
};
|
|
2666
|
-
}
|
|
2667
|
-
}
|
|
2668
|
-
async monitorFlowExecution(executionId, simulateApprovals) {
|
|
2669
|
-
const maxWaitTime = 60000; // 60 seconds
|
|
2670
|
-
const pollInterval = 2000; // 2 seconds
|
|
2671
|
-
const startTime = Date.now();
|
|
2672
|
-
const executionResults = {
|
|
2673
|
-
status: 'running',
|
|
2674
|
-
steps_executed: [],
|
|
2675
|
-
approvals_simulated: [],
|
|
2676
|
-
errors: [],
|
|
2677
|
-
end_time: null,
|
|
2678
|
-
duration_ms: 0
|
|
2679
|
-
};
|
|
2680
|
-
while (Date.now() - startTime < maxWaitTime) {
|
|
2681
|
-
try {
|
|
2682
|
-
// Check flow context status
|
|
2683
|
-
const contextResult = await this.client.getRecord('sys_flow_context', executionId);
|
|
2684
|
-
if (!contextResult.success || !contextResult.data?.result) {
|
|
2685
|
-
executionResults.errors.push('Failed to get flow context');
|
|
2686
|
-
break;
|
|
2687
|
-
}
|
|
2688
|
-
const context = contextResult.data.result;
|
|
2689
|
-
executionResults.status = context.state;
|
|
2690
|
-
// Check for completed state
|
|
2691
|
-
if (context.state === 'complete' || context.state === 'cancelled' || context.state === 'error') {
|
|
2692
|
-
executionResults.end_time = context.sys_updated_on;
|
|
2693
|
-
executionResults.duration_ms = Date.now() - startTime;
|
|
2694
|
-
break;
|
|
2695
|
-
}
|
|
2696
|
-
// Check for approvals if simulating
|
|
2697
|
-
if (simulateApprovals) {
|
|
2698
|
-
const approvalResult = await this.client.searchRecords('sysapproval_approver', `source_table=sys_flow_context^document_id=${executionId}^state=requested`, 10);
|
|
2699
|
-
if (approvalResult.success && approvalResult.data?.result?.length > 0) {
|
|
2700
|
-
for (const approval of approvalResult.data.result) {
|
|
2701
|
-
// Auto-approve
|
|
2702
|
-
const approveResult = await this.client.updateRecord('sysapproval_approver', approval.sys_id, {
|
|
2703
|
-
state: 'approved',
|
|
2704
|
-
comments: 'Auto-approved by test framework'
|
|
2705
|
-
});
|
|
2706
|
-
if (approveResult.success) {
|
|
2707
|
-
executionResults.approvals_simulated.push({
|
|
2708
|
-
sys_id: approval.sys_id,
|
|
2709
|
-
approver: approval.approver.display_value,
|
|
2710
|
-
approved_at: new Date().toISOString()
|
|
2711
|
-
});
|
|
2712
|
-
}
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2715
|
-
}
|
|
2716
|
-
// Wait before next poll
|
|
2717
|
-
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
2718
|
-
}
|
|
2719
|
-
catch (error) {
|
|
2720
|
-
executionResults.errors.push(error.message);
|
|
2721
|
-
executionResults.status = 'error';
|
|
2722
|
-
break;
|
|
2723
|
-
}
|
|
2724
|
-
}
|
|
2725
|
-
if (executionResults.status === 'running') {
|
|
2726
|
-
executionResults.status = 'timeout';
|
|
2727
|
-
executionResults.errors.push('Flow execution timed out after 60 seconds');
|
|
2728
|
-
}
|
|
2729
|
-
return executionResults;
|
|
2730
|
-
}
|
|
2731
|
-
formatTestResults(testResults) {
|
|
2732
|
-
let text = `๐งช Flow Test Results\n${'='.repeat(50)}\n\n`;
|
|
2733
|
-
text += `**Flow ID:** ${testResults.flow_id}\n`;
|
|
2734
|
-
text += `**Test Run ID:** ${testResults.test_run_id}\n\n`;
|
|
2735
|
-
// Test Data Created
|
|
2736
|
-
text += `๐ **Test Data Created:**\n`;
|
|
2737
|
-
if (testResults.created_data.test_user) {
|
|
2738
|
-
text += ` โ
Test User: ${testResults.created_data.test_user.user_name} (${testResults.created_data.test_user.sys_id})\n`;
|
|
2739
|
-
}
|
|
2740
|
-
if (testResults.created_data.catalog_items.length > 0) {
|
|
2741
|
-
text += ` โ
Catalog Items: ${testResults.created_data.catalog_items.length} created\n`;
|
|
2742
|
-
testResults.created_data.catalog_items.forEach((item) => {
|
|
2743
|
-
text += ` - ${item.name} ($${item.price})\n`;
|
|
2744
|
-
});
|
|
2745
|
-
}
|
|
2746
|
-
// Execution Results
|
|
2747
|
-
text += `\n๐ **Execution Results:**\n`;
|
|
2748
|
-
text += ` Status: ${testResults.execution_results.status === 'complete' ? 'โ
' : 'โ'} ${testResults.execution_results.status}\n`;
|
|
2749
|
-
if (testResults.execution_results.start_time) {
|
|
2750
|
-
text += ` Start Time: ${testResults.execution_results.start_time}\n`;
|
|
2751
|
-
}
|
|
2752
|
-
if (testResults.execution_results.end_time) {
|
|
2753
|
-
text += ` End Time: ${testResults.execution_results.end_time}\n`;
|
|
2754
|
-
}
|
|
2755
|
-
if (testResults.execution_results.duration_ms) {
|
|
2756
|
-
text += ` Duration: ${(testResults.execution_results.duration_ms / 1000).toFixed(2)}s\n`;
|
|
2757
|
-
}
|
|
2758
|
-
if (testResults.execution_results.approvals_simulated.length > 0) {
|
|
2759
|
-
text += `\n โ
Approvals Simulated: ${testResults.execution_results.approvals_simulated.length}\n`;
|
|
2760
|
-
testResults.execution_results.approvals_simulated.forEach((approval) => {
|
|
2761
|
-
text += ` - ${approval.approver} at ${approval.approved_at}\n`;
|
|
2762
|
-
});
|
|
2763
|
-
}
|
|
2764
|
-
if (testResults.execution_results.errors.length > 0) {
|
|
2765
|
-
text += `\n โ Errors:\n`;
|
|
2766
|
-
testResults.execution_results.errors.forEach((error) => {
|
|
2767
|
-
text += ` - ${error}\n`;
|
|
2768
|
-
});
|
|
2769
|
-
}
|
|
2770
|
-
// Cleanup Status
|
|
2771
|
-
if (testResults.cleanup_status) {
|
|
2772
|
-
text += `\n๐งน **Cleanup Status:**\n`;
|
|
2773
|
-
text += ` User Deleted: ${testResults.cleanup_status.user_deleted ? 'โ
' : 'โ'}\n`;
|
|
2774
|
-
text += ` Catalog Items Deleted: ${testResults.cleanup_status.catalog_items_deleted}\n`;
|
|
2775
|
-
text += ` Requests Cancelled: ${testResults.cleanup_status.requests_cancelled}\n`;
|
|
2776
|
-
}
|
|
2777
|
-
// Recommendations
|
|
2778
|
-
text += `\n๐ก **Recommendations:**\n`;
|
|
2779
|
-
if (testResults.execution_results.status === 'complete') {
|
|
2780
|
-
text += ` โ
Flow executed successfully! Ready for production use.\n`;
|
|
2781
|
-
}
|
|
2782
|
-
else if (testResults.execution_results.status === 'timeout') {
|
|
2783
|
-
text += ` โ ๏ธ Flow execution timed out. Consider:\n`;
|
|
2784
|
-
text += ` - Checking for infinite loops\n`;
|
|
2785
|
-
text += ` - Reviewing wait conditions\n`;
|
|
2786
|
-
text += ` - Optimizing flow performance\n`;
|
|
2787
|
-
}
|
|
2788
|
-
else {
|
|
2789
|
-
text += ` โ Flow execution failed. Please:\n`;
|
|
2790
|
-
text += ` - Review error messages above\n`;
|
|
2791
|
-
text += ` - Check flow configuration\n`;
|
|
2792
|
-
text += ` - Verify all dependencies exist\n`;
|
|
2793
|
-
}
|
|
2794
|
-
return text;
|
|
2795
|
-
}
|
|
2796
|
-
async handleLinkCatalogToFlow(args) {
|
|
2797
|
-
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;
|
|
2798
|
-
logger_js_1.logger.info(`Linking catalog item ${catalog_item_id} to flow ${flow_id}`, {
|
|
2799
|
-
link_type,
|
|
2800
|
-
variable_mapping_count: variable_mapping.length
|
|
2801
|
-
});
|
|
2802
|
-
const linkResults = {
|
|
2803
|
-
catalog_item: null,
|
|
2804
|
-
flow: null,
|
|
2805
|
-
link_created: false,
|
|
2806
|
-
link_details: null,
|
|
2807
|
-
variable_mappings: [],
|
|
2808
|
-
test_results: null,
|
|
2809
|
-
errors: []
|
|
2810
|
-
};
|
|
2811
|
-
try {
|
|
2812
|
-
// Step 1: Find the catalog item
|
|
2813
|
-
const catalogResult = await this.client.searchRecords('sc_cat_item', `sys_id=${catalog_item_id}^ORname=${catalog_item_id}`, 1);
|
|
2814
|
-
if (!catalogResult.success || !catalogResult.data?.result?.length) {
|
|
2815
|
-
throw new Error(`Catalog item not found: ${catalog_item_id}`);
|
|
2816
|
-
}
|
|
2817
|
-
linkResults.catalog_item = {
|
|
2818
|
-
sys_id: catalogResult.data.result[0].sys_id,
|
|
2819
|
-
name: catalogResult.data.result[0].name,
|
|
2820
|
-
category: catalogResult.data.result[0].sc_categories?.display_value
|
|
2821
|
-
};
|
|
2822
|
-
// Step 2: Find the flow
|
|
2823
|
-
const flowResult = await this.client.searchRecords('sys_hub_flow', `sys_id=${flow_id}^ORname=${flow_id}`, 1);
|
|
2824
|
-
if (!flowResult.success || !flowResult.data?.result?.length) {
|
|
2825
|
-
throw new Error(`Flow not found: ${flow_id}`);
|
|
2826
|
-
}
|
|
2827
|
-
linkResults.flow = {
|
|
2828
|
-
sys_id: flowResult.data.result[0].sys_id,
|
|
2829
|
-
name: flowResult.data.result[0].name,
|
|
2830
|
-
type: flowResult.data.result[0].type
|
|
2831
|
-
};
|
|
2832
|
-
// Step 3: Create the link based on link_type
|
|
2833
|
-
switch (link_type) {
|
|
2834
|
-
case 'flow_catalog_process': {
|
|
2835
|
-
// Modern approach: Use Flow Designer catalog process
|
|
2836
|
-
const processData = {
|
|
2837
|
-
catalog_item: linkResults.catalog_item.sys_id,
|
|
2838
|
-
flow: linkResults.flow.sys_id,
|
|
2839
|
-
active: true,
|
|
2840
|
-
condition: trigger_condition,
|
|
2841
|
-
run_as: execution_options.run_as || 'user', // ๐ SEC-001 FIX: Default to 'user' to prevent privilege escalation
|
|
2842
|
-
wait_for_completion: execution_options.wait_for_completion !== false
|
|
2843
|
-
};
|
|
2844
|
-
const processResult = await this.client.createRecord('sc_cat_item_producer', processData);
|
|
2845
|
-
if (processResult.success && processResult.data?.result) {
|
|
2846
|
-
linkResults.link_created = true;
|
|
2847
|
-
linkResults.link_details = {
|
|
2848
|
-
type: 'flow_catalog_process',
|
|
2849
|
-
sys_id: processResult.data.result.sys_id,
|
|
2850
|
-
table: 'sc_cat_item_producer'
|
|
2851
|
-
};
|
|
2852
|
-
// Create variable mappings
|
|
2853
|
-
for (const mapping of variable_mapping) {
|
|
2854
|
-
const mappingData = {
|
|
2855
|
-
producer: processResult.data.result.sys_id,
|
|
2856
|
-
catalog_variable: mapping.catalog_variable,
|
|
2857
|
-
flow_input: mapping.flow_input,
|
|
2858
|
-
transform_script: mapping.transform || ''
|
|
2859
|
-
};
|
|
2860
|
-
const mappingResult = await this.client.createRecord('sc_cat_item_producer_mapping', mappingData);
|
|
2861
|
-
if (mappingResult.success) {
|
|
2862
|
-
linkResults.variable_mappings.push({
|
|
2863
|
-
catalog_variable: mapping.catalog_variable,
|
|
2864
|
-
flow_input: mapping.flow_input,
|
|
2865
|
-
mapping_sys_id: mappingResult.data?.result?.sys_id
|
|
2866
|
-
});
|
|
2867
|
-
}
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
break;
|
|
2871
|
-
}
|
|
2872
|
-
case 'workflow': {
|
|
2873
|
-
// Legacy approach: Direct workflow assignment
|
|
2874
|
-
const updateData = {
|
|
2875
|
-
workflow: linkResults.flow.sys_id,
|
|
2876
|
-
no_order: false,
|
|
2877
|
-
no_proceed_checkout: false,
|
|
2878
|
-
no_quantity: false,
|
|
2879
|
-
no_delivery_time: false
|
|
2880
|
-
};
|
|
2881
|
-
const updateResult = await this.client.updateRecord('sc_cat_item', linkResults.catalog_item.sys_id, updateData);
|
|
2882
|
-
if (updateResult.success) {
|
|
2883
|
-
linkResults.link_created = true;
|
|
2884
|
-
linkResults.link_details = {
|
|
2885
|
-
type: 'workflow',
|
|
2886
|
-
field: 'workflow',
|
|
2887
|
-
value: linkResults.flow.sys_id
|
|
2888
|
-
};
|
|
2889
|
-
}
|
|
2890
|
-
break;
|
|
2891
|
-
}
|
|
2892
|
-
case 'process_engine': {
|
|
2893
|
-
// Process Engine approach
|
|
2894
|
-
const engineData = {
|
|
2895
|
-
catalog_item: linkResults.catalog_item.sys_id,
|
|
2896
|
-
engine_type: 'flow',
|
|
2897
|
-
engine_id: linkResults.flow.sys_id,
|
|
2898
|
-
active: true,
|
|
2899
|
-
order: 100,
|
|
2900
|
-
condition: trigger_condition
|
|
2901
|
-
};
|
|
2902
|
-
const engineResult = await this.client.createRecord('sc_process_flow', engineData);
|
|
2903
|
-
if (engineResult.success && engineResult.data?.result) {
|
|
2904
|
-
linkResults.link_created = true;
|
|
2905
|
-
linkResults.link_details = {
|
|
2906
|
-
type: 'process_engine',
|
|
2907
|
-
sys_id: engineResult.data.result.sys_id,
|
|
2908
|
-
table: 'sc_process_flow'
|
|
2909
|
-
};
|
|
2910
|
-
}
|
|
2911
|
-
break;
|
|
2912
|
-
}
|
|
2913
|
-
}
|
|
2914
|
-
// Step 4: Test the link if requested
|
|
2915
|
-
if (test_link && linkResults.link_created) {
|
|
2916
|
-
logger_js_1.logger.info('Testing catalog-flow link with sample request');
|
|
2917
|
-
const testRequestData = {
|
|
2918
|
-
cat_item: linkResults.catalog_item.sys_id,
|
|
2919
|
-
requested_for: 'admin', // Default to admin user
|
|
2920
|
-
quantity: 1,
|
|
2921
|
-
comments: `Test request for catalog-flow link verification [${new Date().toISOString()}]`
|
|
2922
|
-
};
|
|
2923
|
-
// Add any mapped variables with test values
|
|
2924
|
-
for (const mapping of linkResults.variable_mappings) {
|
|
2925
|
-
testRequestData[`variables.${mapping.catalog_variable}`] = 'Test Value';
|
|
2926
|
-
}
|
|
2927
|
-
const testResult = await this.client.createRecord('sc_request', testRequestData);
|
|
2928
|
-
if (testResult.success && testResult.data?.result) {
|
|
2929
|
-
linkResults.test_results = {
|
|
2930
|
-
request_created: true,
|
|
2931
|
-
request_number: testResult.data.result.number,
|
|
2932
|
-
request_sys_id: testResult.data.result.sys_id,
|
|
2933
|
-
status: 'Check the request in ServiceNow to verify flow execution'
|
|
2934
|
-
};
|
|
2935
|
-
}
|
|
2936
|
-
else {
|
|
2937
|
-
linkResults.test_results = {
|
|
2938
|
-
request_created: false,
|
|
2939
|
-
error: 'Failed to create test request'
|
|
2940
|
-
};
|
|
2941
|
-
}
|
|
2942
|
-
}
|
|
2943
|
-
// Format results
|
|
2944
|
-
let resultText = `๐ Catalog-Flow Link Results\n${'='.repeat(50)}\n\n`;
|
|
2945
|
-
resultText += `**Catalog Item:** ${linkResults.catalog_item.name} (${linkResults.catalog_item.sys_id})\n`;
|
|
2946
|
-
resultText += `**Flow:** ${linkResults.flow.name} (${linkResults.flow.sys_id})\n`;
|
|
2947
|
-
resultText += `**Link Type:** ${link_type}\n\n`;
|
|
2948
|
-
if (linkResults.link_created) {
|
|
2949
|
-
resultText += `โ
**Link Created Successfully!**\n`;
|
|
2950
|
-
resultText += ` Type: ${linkResults.link_details.type}\n`;
|
|
2951
|
-
if (linkResults.link_details.sys_id) {
|
|
2952
|
-
resultText += ` Record: ${linkResults.link_details.table} (${linkResults.link_details.sys_id})\n`;
|
|
2953
|
-
}
|
|
2954
|
-
if (linkResults.variable_mappings.length > 0) {
|
|
2955
|
-
resultText += `\n๐ **Variable Mappings Created:**\n`;
|
|
2956
|
-
linkResults.variable_mappings.forEach(mapping => {
|
|
2957
|
-
resultText += ` - ${mapping.catalog_variable} โ ${mapping.flow_input}\n`;
|
|
2958
|
-
});
|
|
2959
|
-
}
|
|
2960
|
-
if (linkResults.test_results) {
|
|
2961
|
-
resultText += `\n๐งช **Test Results:**\n`;
|
|
2962
|
-
if (linkResults.test_results.request_created) {
|
|
2963
|
-
resultText += ` โ
Test request created: ${linkResults.test_results.request_number}\n`;
|
|
2964
|
-
resultText += ` ๐ ${linkResults.test_results.status}\n`;
|
|
2965
|
-
}
|
|
2966
|
-
else {
|
|
2967
|
-
resultText += ` โ ${linkResults.test_results.error}\n`;
|
|
2968
|
-
}
|
|
2969
|
-
}
|
|
2970
|
-
resultText += `\n๐ก **Next Steps:**\n`;
|
|
2971
|
-
resultText += `1. Test the catalog item by creating a request\n`;
|
|
2972
|
-
resultText += `2. Verify the flow executes with correct inputs\n`;
|
|
2973
|
-
resultText += `3. Monitor the request fulfillment process\n`;
|
|
2974
|
-
if (link_type === 'flow_catalog_process') {
|
|
2975
|
-
resultText += `\n๐ **Note:** Using modern Flow Designer catalog process\n`;
|
|
2976
|
-
resultText += ` - Flow will trigger on: ${trigger_condition}\n`;
|
|
2977
|
-
resultText += ` - Execution context: ${execution_options.run_as || 'user'}\n`; // ๐ SEC-001 FIX: Default to 'user' to prevent privilege escalation
|
|
2978
|
-
}
|
|
2979
|
-
}
|
|
2980
|
-
else {
|
|
2981
|
-
resultText += `โ **Failed to create link**\n`;
|
|
2982
|
-
if (linkResults.errors.length > 0) {
|
|
2983
|
-
resultText += `\nErrors:\n`;
|
|
2984
|
-
linkResults.errors.forEach(error => {
|
|
2985
|
-
resultText += ` - ${error}\n`;
|
|
2986
|
-
});
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
return {
|
|
2990
|
-
content: [{
|
|
2991
|
-
type: 'text',
|
|
2992
|
-
text: resultText
|
|
2993
|
-
}]
|
|
2994
|
-
};
|
|
2995
|
-
}
|
|
2996
|
-
catch (error) {
|
|
2997
|
-
logger_js_1.logger.error('Error linking catalog to flow:', error);
|
|
2998
|
-
linkResults.errors.push(error.message);
|
|
2999
|
-
return {
|
|
3000
|
-
content: [{
|
|
3001
|
-
type: 'text',
|
|
3002
|
-
text: `โ Failed to link catalog to flow\n\nError: ${error.message}\n\n` +
|
|
3003
|
-
`๐ก Troubleshooting:\n` +
|
|
3004
|
-
`1. Verify catalog item exists: ${catalog_item_id}\n` +
|
|
3005
|
-
`2. Verify flow exists: ${flow_id}\n` +
|
|
3006
|
-
`3. Check permissions for creating ${link_type} links\n` +
|
|
3007
|
-
`4. Ensure flow is active and published\n\n` +
|
|
3008
|
-
`Results: ${JSON.stringify(linkResults, null, 2)}`
|
|
3009
|
-
}]
|
|
3010
|
-
};
|
|
3011
|
-
}
|
|
3012
|
-
}
|
|
3013
2282
|
async handleCleanupTestArtifacts(args) {
|
|
3014
2283
|
const { artifact_types = ['catalog_items', 'flows', 'users'], test_patterns = ['Test%', 'Mock%', 'Demo%', '%_test_%', '%test', '%mock'], max_age_hours = 1, dry_run = false, preserve_update_set_entries = true, update_set_filter } = args;
|
|
3015
2284
|
logger_js_1.logger.info('Starting test artifact cleanup', {
|
|
@@ -3047,9 +2316,7 @@ ${JSON.stringify(testResults, null, 2)}
|
|
|
3047
2316
|
if (artifactType === 'all' || artifact_types.includes('catalog_items')) {
|
|
3048
2317
|
await this.cleanupTestCatalogItems(test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter);
|
|
3049
2318
|
}
|
|
3050
|
-
|
|
3051
|
-
await this.cleanupTestFlows(test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter);
|
|
3052
|
-
}
|
|
2319
|
+
// Flow cleanup removed in v1.4.38 - flows no longer supported
|
|
3053
2320
|
if (artifactType === 'all' || artifact_types.includes('users')) {
|
|
3054
2321
|
await this.cleanupTestUsers(test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter);
|
|
3055
2322
|
}
|
|
@@ -3174,41 +2441,6 @@ ${JSON.stringify(testResults, null, 2)}
|
|
|
3174
2441
|
results.errors.push(`Error cleaning catalog items: ${error.message}`);
|
|
3175
2442
|
}
|
|
3176
2443
|
}
|
|
3177
|
-
async cleanupTestFlows(patterns, cutoffTime, dryRun, results, updateSetFilter) {
|
|
3178
|
-
try {
|
|
3179
|
-
const patternQueries = patterns.map(pattern => {
|
|
3180
|
-
if (pattern.includes('%')) {
|
|
3181
|
-
return `nameLIKE${pattern.replace(/%/g, '')}`;
|
|
3182
|
-
}
|
|
3183
|
-
else {
|
|
3184
|
-
return `name=${pattern}`;
|
|
3185
|
-
}
|
|
3186
|
-
});
|
|
3187
|
-
const query = `(${patternQueries.join('^OR')})^sys_created_on<${cutoffTime}`;
|
|
3188
|
-
const searchResult = await this.client.searchRecords('sys_hub_flow', query, 100);
|
|
3189
|
-
if (searchResult.success && searchResult.data?.result) {
|
|
3190
|
-
results.artifacts_found.flows = searchResult.data.result.map((flow) => ({
|
|
3191
|
-
sys_id: flow.sys_id,
|
|
3192
|
-
name: flow.name,
|
|
3193
|
-
sys_created_on: flow.sys_created_on
|
|
3194
|
-
}));
|
|
3195
|
-
if (!dryRun) {
|
|
3196
|
-
for (const flow of searchResult.data.result) {
|
|
3197
|
-
const deleteResult = await this.client.deleteRecord('sys_hub_flow', flow.sys_id);
|
|
3198
|
-
if (deleteResult.success) {
|
|
3199
|
-
results.artifacts_deleted.flows++;
|
|
3200
|
-
}
|
|
3201
|
-
else {
|
|
3202
|
-
results.errors.push(`Failed to delete flow ${flow.name}: ${deleteResult.error}`);
|
|
3203
|
-
}
|
|
3204
|
-
}
|
|
3205
|
-
}
|
|
3206
|
-
}
|
|
3207
|
-
}
|
|
3208
|
-
catch (error) {
|
|
3209
|
-
results.errors.push(`Error cleaning flows: ${error.message}`);
|
|
3210
|
-
}
|
|
3211
|
-
}
|
|
3212
2444
|
async cleanupTestUsers(patterns, cutoffTime, dryRun, results, updateSetFilter) {
|
|
3213
2445
|
try {
|
|
3214
2446
|
const patternQueries = patterns.map(pattern => {
|