snow-flow 1.1.45 → 1.1.48
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/settings.json +11 -0
- package/dist/mcp/servicenow-deployment-mcp.js +280 -20
- package/dist/mcp/servicenow-deployment-mcp.js.map +1 -1
- package/dist/mcp/servicenow-operations-mcp.js +390 -5
- package/dist/mcp/servicenow-operations-mcp.js.map +1 -1
- package/dist/mcp/servicenow-update-set-mcp.js +26 -3
- package/dist/mcp/servicenow-update-set-mcp.js.map +1 -1
- package/dist/orchestrator/flow-composer.d.ts.map +1 -1
- package/dist/orchestrator/flow-composer.js +4 -0
- package/dist/orchestrator/flow-composer.js.map +1 -1
- package/dist/orchestrator/flow-pattern-templates.d.ts.map +1 -1
- package/dist/orchestrator/flow-pattern-templates.js +2 -0
- package/dist/orchestrator/flow-pattern-templates.js.map +1 -1
- package/dist/orchestrator/flow-validation-engine.d.ts +24 -0
- package/dist/orchestrator/flow-validation-engine.d.ts.map +1 -1
- package/dist/orchestrator/flow-validation-engine.js +250 -0
- package/dist/orchestrator/flow-validation-engine.js.map +1 -1
- package/dist/strategies/global-scope-strategy.d.ts.map +1 -1
- package/dist/strategies/global-scope-strategy.js +9 -0
- package/dist/strategies/global-scope-strategy.js.map +1 -1
- package/dist/version.d.ts +16 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +82 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -651,6 +651,49 @@ class ServiceNowOperationsMCP {
|
|
|
651
651
|
},
|
|
652
652
|
required: ['catalog_item_id', 'flow_id']
|
|
653
653
|
}
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
name: 'snow_cleanup_test_artifacts',
|
|
657
|
+
description: 'Clean up test artifacts while preserving Update Set audit trail',
|
|
658
|
+
inputSchema: {
|
|
659
|
+
type: 'object',
|
|
660
|
+
properties: {
|
|
661
|
+
artifact_types: {
|
|
662
|
+
type: 'array',
|
|
663
|
+
description: 'Types of artifacts to clean up',
|
|
664
|
+
items: {
|
|
665
|
+
type: 'string',
|
|
666
|
+
enum: ['catalog_items', 'flows', 'users', 'requests', 'all']
|
|
667
|
+
},
|
|
668
|
+
default: ['catalog_items', 'flows', 'users']
|
|
669
|
+
},
|
|
670
|
+
test_patterns: {
|
|
671
|
+
type: 'array',
|
|
672
|
+
description: 'Name patterns that identify test artifacts',
|
|
673
|
+
items: { type: 'string' },
|
|
674
|
+
default: ['Test%', 'Mock%', 'Demo%', '%_test_%', '%test', '%mock']
|
|
675
|
+
},
|
|
676
|
+
max_age_hours: {
|
|
677
|
+
type: 'number',
|
|
678
|
+
description: 'Only clean artifacts older than this (hours)',
|
|
679
|
+
default: 1
|
|
680
|
+
},
|
|
681
|
+
dry_run: {
|
|
682
|
+
type: 'boolean',
|
|
683
|
+
description: 'Preview what would be deleted without actually deleting',
|
|
684
|
+
default: false
|
|
685
|
+
},
|
|
686
|
+
preserve_update_set_entries: {
|
|
687
|
+
type: 'boolean',
|
|
688
|
+
description: 'Keep Update Set entries as audit trail',
|
|
689
|
+
default: true
|
|
690
|
+
},
|
|
691
|
+
update_set_filter: {
|
|
692
|
+
type: 'string',
|
|
693
|
+
description: 'Only clean from specific Update Set (optional)'
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
654
697
|
}
|
|
655
698
|
],
|
|
656
699
|
};
|
|
@@ -690,6 +733,8 @@ class ServiceNowOperationsMCP {
|
|
|
690
733
|
return await this.handleTestFlowWithMock(args);
|
|
691
734
|
case 'snow_link_catalog_to_flow':
|
|
692
735
|
return await this.handleLinkCatalogToFlow(args);
|
|
736
|
+
case 'snow_cleanup_test_artifacts':
|
|
737
|
+
return await this.handleCleanupTestArtifacts(args);
|
|
693
738
|
default:
|
|
694
739
|
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Tool ${name} not found`);
|
|
695
740
|
}
|
|
@@ -1557,6 +1602,34 @@ class ServiceNowOperationsMCP {
|
|
|
1557
1602
|
try {
|
|
1558
1603
|
switch (action) {
|
|
1559
1604
|
case 'create': {
|
|
1605
|
+
// Get default catalog if none provided
|
|
1606
|
+
let catalogId = params.sc_catalogs || params.catalog_id;
|
|
1607
|
+
let categoryId = params.sc_categories || params.category_id;
|
|
1608
|
+
if (!catalogId) {
|
|
1609
|
+
// Find default Service Catalog
|
|
1610
|
+
const defaultCatalogResult = await this.client.searchRecords('sc_catalog', 'active=true^ORDERBYsys_created_on', 1);
|
|
1611
|
+
if (defaultCatalogResult.success && defaultCatalogResult.data?.result?.length > 0) {
|
|
1612
|
+
catalogId = defaultCatalogResult.data.result[0].sys_id;
|
|
1613
|
+
logger_js_1.logger.info('Using default catalog:', {
|
|
1614
|
+
catalogId,
|
|
1615
|
+
catalogName: defaultCatalogResult.data.result[0].title
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
else {
|
|
1619
|
+
logger_js_1.logger.warn('No catalogs found, catalog item may not be visible');
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
if (!categoryId) {
|
|
1623
|
+
// Find a default category like "Hardware" or "General"
|
|
1624
|
+
const defaultCategoryResult = await this.client.searchRecords('sc_category', 'active=true^titleLIKEHardware^ORtitleLIKEGeneral^ORtitleLIKEIT^ORDERBYsys_created_on', 1);
|
|
1625
|
+
if (defaultCategoryResult.success && defaultCategoryResult.data?.result?.length > 0) {
|
|
1626
|
+
categoryId = defaultCategoryResult.data.result[0].sys_id;
|
|
1627
|
+
logger_js_1.logger.info('Using default category:', {
|
|
1628
|
+
categoryId,
|
|
1629
|
+
categoryName: defaultCategoryResult.data.result[0].title
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1560
1633
|
const catalogItem = {
|
|
1561
1634
|
name: params.name,
|
|
1562
1635
|
short_description: params.short_description,
|
|
@@ -1567,8 +1640,8 @@ class ServiceNowOperationsMCP {
|
|
|
1567
1640
|
active: params.active !== false,
|
|
1568
1641
|
workflow: params.workflow,
|
|
1569
1642
|
picture: params.picture,
|
|
1570
|
-
sc_catalogs:
|
|
1571
|
-
sc_categories:
|
|
1643
|
+
sc_catalogs: catalogId,
|
|
1644
|
+
sc_categories: categoryId,
|
|
1572
1645
|
sys_class_name: 'sc_cat_item'
|
|
1573
1646
|
};
|
|
1574
1647
|
const result = await this.client.createRecord('sc_cat_item', catalogItem);
|
|
@@ -1981,10 +2054,49 @@ class ServiceNowOperationsMCP {
|
|
|
1981
2054
|
cleanup_status: null
|
|
1982
2055
|
};
|
|
1983
2056
|
try {
|
|
1984
|
-
// Step 1: Find the flow
|
|
1985
|
-
|
|
2057
|
+
// Step 1: Find the flow with intelligent fallback search
|
|
2058
|
+
let flowResult = await this.client.searchRecords('sys_hub_flow', `sys_id=${flow_id}^ORname=${flow_id}`, 1);
|
|
2059
|
+
// If not found, try different search strategies
|
|
1986
2060
|
if (!flowResult.success || !flowResult.data?.result?.length) {
|
|
1987
|
-
|
|
2061
|
+
logger_js_1.logger.info('Flow not found with exact match, trying fuzzy search...');
|
|
2062
|
+
// Try partial name matching
|
|
2063
|
+
flowResult = await this.client.searchRecords('sys_hub_flow', `nameCONTAINS${flow_id}`, 5);
|
|
2064
|
+
if (!flowResult.success || !flowResult.data?.result?.length) {
|
|
2065
|
+
// Try searching in Business Rules as fallback
|
|
2066
|
+
logger_js_1.logger.info('No flows found, searching for Business Rules as fallback...');
|
|
2067
|
+
const businessRuleResult = await this.client.searchRecords('sys_script', `nameCONTAINS${flow_id}^ORshort_descriptionCONTAINS${flow_id}`, 5);
|
|
2068
|
+
if (businessRuleResult.success && businessRuleResult.data?.result?.length) {
|
|
2069
|
+
testResults.execution_results.status = 'fallback_business_rule';
|
|
2070
|
+
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.`);
|
|
2071
|
+
const businessRules = businessRuleResult.data.result.map((br) => ({
|
|
2072
|
+
name: br.name,
|
|
2073
|
+
sys_id: br.sys_id,
|
|
2074
|
+
table: br.collection,
|
|
2075
|
+
when: br.when
|
|
2076
|
+
}));
|
|
2077
|
+
return {
|
|
2078
|
+
content: [{
|
|
2079
|
+
type: 'text',
|
|
2080
|
+
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`
|
|
2081
|
+
}]
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
throw new Error(`Flow not found: ${flow_id}. Tried exact match, partial name match, and Business Rule fallback.`);
|
|
2085
|
+
}
|
|
2086
|
+
else {
|
|
2087
|
+
// Found partial matches, ask user to clarify
|
|
2088
|
+
const matches = flowResult.data.result.map((f) => ({
|
|
2089
|
+
name: f.name,
|
|
2090
|
+
sys_id: f.sys_id,
|
|
2091
|
+
active: f.active
|
|
2092
|
+
}));
|
|
2093
|
+
return {
|
|
2094
|
+
content: [{
|
|
2095
|
+
type: 'text',
|
|
2096
|
+
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.**`
|
|
2097
|
+
}]
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
1988
2100
|
}
|
|
1989
2101
|
const flow = flowResult.data.result[0];
|
|
1990
2102
|
testResults.flow_id = flow.sys_id;
|
|
@@ -2491,6 +2603,279 @@ class ServiceNowOperationsMCP {
|
|
|
2491
2603
|
};
|
|
2492
2604
|
}
|
|
2493
2605
|
}
|
|
2606
|
+
async handleCleanupTestArtifacts(args) {
|
|
2607
|
+
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;
|
|
2608
|
+
logger_js_1.logger.info('Starting test artifact cleanup', {
|
|
2609
|
+
artifact_types,
|
|
2610
|
+
test_patterns,
|
|
2611
|
+
max_age_hours,
|
|
2612
|
+
dry_run
|
|
2613
|
+
});
|
|
2614
|
+
const cleanupResults = {
|
|
2615
|
+
start_time: new Date().toISOString(),
|
|
2616
|
+
dry_run,
|
|
2617
|
+
artifacts_found: {
|
|
2618
|
+
catalog_items: [],
|
|
2619
|
+
flows: [],
|
|
2620
|
+
users: [],
|
|
2621
|
+
requests: []
|
|
2622
|
+
},
|
|
2623
|
+
artifacts_deleted: {
|
|
2624
|
+
catalog_items: 0,
|
|
2625
|
+
flows: 0,
|
|
2626
|
+
users: 0,
|
|
2627
|
+
requests: 0
|
|
2628
|
+
},
|
|
2629
|
+
update_set_entries_preserved: 0,
|
|
2630
|
+
errors: [],
|
|
2631
|
+
summary: ''
|
|
2632
|
+
};
|
|
2633
|
+
try {
|
|
2634
|
+
// Calculate cutoff time
|
|
2635
|
+
const cutoffTime = new Date();
|
|
2636
|
+
cutoffTime.setHours(cutoffTime.getHours() - max_age_hours);
|
|
2637
|
+
const cutoffISO = cutoffTime.toISOString();
|
|
2638
|
+
// Process each artifact type
|
|
2639
|
+
for (const artifactType of artifact_types) {
|
|
2640
|
+
if (artifactType === 'all' || artifact_types.includes('catalog_items')) {
|
|
2641
|
+
await this.cleanupTestCatalogItems(test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter);
|
|
2642
|
+
}
|
|
2643
|
+
if (artifactType === 'all' || artifact_types.includes('flows')) {
|
|
2644
|
+
await this.cleanupTestFlows(test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter);
|
|
2645
|
+
}
|
|
2646
|
+
if (artifactType === 'all' || artifact_types.includes('users')) {
|
|
2647
|
+
await this.cleanupTestUsers(test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter);
|
|
2648
|
+
}
|
|
2649
|
+
if (artifactType === 'all' || artifact_types.includes('requests')) {
|
|
2650
|
+
await this.cleanupTestRequests(test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
// Generate summary
|
|
2654
|
+
const totalFound = Object.values(cleanupResults.artifacts_found)
|
|
2655
|
+
.reduce((sum, items) => (typeof sum === 'number' ? sum : 0) + (Array.isArray(items) ? items.length : 0), 0);
|
|
2656
|
+
const totalDeleted = Object.values(cleanupResults.artifacts_deleted)
|
|
2657
|
+
.reduce((sum, count) => sum + (typeof count === 'number' ? count : 0), 0);
|
|
2658
|
+
cleanupResults.summary = dry_run
|
|
2659
|
+
? `🔍 Dry Run: Found ${totalFound} test artifacts that would be cleaned up`
|
|
2660
|
+
: `🧹 Cleaned up ${totalDeleted} test artifacts successfully`;
|
|
2661
|
+
// Format results
|
|
2662
|
+
let resultText = `🧹 Test Artifact Cleanup Results\n${'='.repeat(50)}\n\n`;
|
|
2663
|
+
if (dry_run) {
|
|
2664
|
+
resultText += `🔍 **DRY RUN MODE** - No actual deletion performed\n\n`;
|
|
2665
|
+
}
|
|
2666
|
+
resultText += `⏰ **Cleanup Configuration:**\n`;
|
|
2667
|
+
resultText += `- Artifact Types: ${artifact_types.join(', ')}\n`;
|
|
2668
|
+
resultText += `- Test Patterns: ${test_patterns.join(', ')}\n`;
|
|
2669
|
+
resultText += `- Max Age: ${max_age_hours} hours\n`;
|
|
2670
|
+
resultText += `- Cutoff Time: ${cutoffISO}\n`;
|
|
2671
|
+
resultText += `- Preserve Update Set Entries: ${preserve_update_set_entries ? '✅' : '❌'}\n\n`;
|
|
2672
|
+
// Report findings by type
|
|
2673
|
+
['catalog_items', 'flows', 'users', 'requests'].forEach(type => {
|
|
2674
|
+
const found = cleanupResults.artifacts_found[type] || [];
|
|
2675
|
+
const deleted = cleanupResults.artifacts_deleted[type] || 0;
|
|
2676
|
+
if (found.length > 0) {
|
|
2677
|
+
resultText += `📦 **${type.replace('_', ' ').toUpperCase()}:**\n`;
|
|
2678
|
+
resultText += ` ${dry_run ? 'Found' : 'Deleted'}: ${dry_run ? found.length : deleted}\n`;
|
|
2679
|
+
if (dry_run && found.length > 0) {
|
|
2680
|
+
found.slice(0, 10).forEach((item) => {
|
|
2681
|
+
resultText += ` - ${item.name} (${item.sys_id}) - Created: ${item.sys_created_on}\n`;
|
|
2682
|
+
});
|
|
2683
|
+
if (found.length > 10) {
|
|
2684
|
+
resultText += ` ... and ${found.length - 10} more\n`;
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
resultText += '\n';
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
if (cleanupResults.update_set_entries_preserved > 0) {
|
|
2691
|
+
resultText += `📋 **Update Set Audit Trail Preserved:** ${cleanupResults.update_set_entries_preserved} entries\n\n`;
|
|
2692
|
+
}
|
|
2693
|
+
if (cleanupResults.errors.length > 0) {
|
|
2694
|
+
resultText += `❌ **Errors:**\n`;
|
|
2695
|
+
cleanupResults.errors.forEach(error => {
|
|
2696
|
+
resultText += ` - ${error}\n`;
|
|
2697
|
+
});
|
|
2698
|
+
resultText += '\n';
|
|
2699
|
+
}
|
|
2700
|
+
resultText += `✅ **${cleanupResults.summary}**\n\n`;
|
|
2701
|
+
if (!dry_run && totalDeleted > 0) {
|
|
2702
|
+
resultText += `💡 **Note:** Update Set entries have been preserved as audit trail.\n`;
|
|
2703
|
+
resultText += `This shows that testing was performed and cleanup was completed.\n\n`;
|
|
2704
|
+
}
|
|
2705
|
+
if (dry_run) {
|
|
2706
|
+
resultText += `▶️ **Next Steps:**\n`;
|
|
2707
|
+
resultText += `1. Review the artifacts that would be deleted\n`;
|
|
2708
|
+
resultText += `2. Run again with dry_run: false to perform actual cleanup\n`;
|
|
2709
|
+
resultText += `3. Verify Update Set entries are preserved as intended\n`;
|
|
2710
|
+
}
|
|
2711
|
+
return {
|
|
2712
|
+
content: [{
|
|
2713
|
+
type: 'text',
|
|
2714
|
+
text: resultText
|
|
2715
|
+
}]
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
catch (error) {
|
|
2719
|
+
logger_js_1.logger.error('Error during test artifact cleanup:', error);
|
|
2720
|
+
return {
|
|
2721
|
+
content: [{
|
|
2722
|
+
type: 'text',
|
|
2723
|
+
text: `❌ Test artifact cleanup failed\n\nError: ${error.message}\n\n` +
|
|
2724
|
+
`🔧 Troubleshooting:\n` +
|
|
2725
|
+
`1. Check ServiceNow connection and permissions\n` +
|
|
2726
|
+
`2. Verify test patterns are correct\n` +
|
|
2727
|
+
`3. Ensure artifacts exist and are accessible\n\n` +
|
|
2728
|
+
`Debug Info: ${JSON.stringify(cleanupResults, null, 2)}`
|
|
2729
|
+
}]
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
async cleanupTestCatalogItems(patterns, cutoffTime, dryRun, results, updateSetFilter) {
|
|
2734
|
+
try {
|
|
2735
|
+
// Build query for test catalog items
|
|
2736
|
+
const patternQueries = patterns.map(pattern => {
|
|
2737
|
+
if (pattern.includes('%')) {
|
|
2738
|
+
return `nameLIKE${pattern.replace(/%/g, '')}`;
|
|
2739
|
+
}
|
|
2740
|
+
else {
|
|
2741
|
+
return `name=${pattern}`;
|
|
2742
|
+
}
|
|
2743
|
+
});
|
|
2744
|
+
const query = `(${patternQueries.join('^OR')})^sys_created_on<${cutoffTime}`;
|
|
2745
|
+
const searchResult = await this.client.searchRecords('sc_cat_item', query, 100);
|
|
2746
|
+
if (searchResult.success && searchResult.data?.result) {
|
|
2747
|
+
results.artifacts_found.catalog_items = searchResult.data.result.map((item) => ({
|
|
2748
|
+
sys_id: item.sys_id,
|
|
2749
|
+
name: item.name,
|
|
2750
|
+
sys_created_on: item.sys_created_on
|
|
2751
|
+
}));
|
|
2752
|
+
if (!dryRun) {
|
|
2753
|
+
// Delete each catalog item
|
|
2754
|
+
for (const item of searchResult.data.result) {
|
|
2755
|
+
const deleteResult = await this.client.deleteRecord('sc_cat_item', item.sys_id);
|
|
2756
|
+
if (deleteResult.success) {
|
|
2757
|
+
results.artifacts_deleted.catalog_items++;
|
|
2758
|
+
}
|
|
2759
|
+
else {
|
|
2760
|
+
results.errors.push(`Failed to delete catalog item ${item.name}: ${deleteResult.error}`);
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
catch (error) {
|
|
2767
|
+
results.errors.push(`Error cleaning catalog items: ${error.message}`);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
async cleanupTestFlows(patterns, cutoffTime, dryRun, results, updateSetFilter) {
|
|
2771
|
+
try {
|
|
2772
|
+
const patternQueries = patterns.map(pattern => {
|
|
2773
|
+
if (pattern.includes('%')) {
|
|
2774
|
+
return `nameLIKE${pattern.replace(/%/g, '')}`;
|
|
2775
|
+
}
|
|
2776
|
+
else {
|
|
2777
|
+
return `name=${pattern}`;
|
|
2778
|
+
}
|
|
2779
|
+
});
|
|
2780
|
+
const query = `(${patternQueries.join('^OR')})^sys_created_on<${cutoffTime}`;
|
|
2781
|
+
const searchResult = await this.client.searchRecords('sys_hub_flow', query, 100);
|
|
2782
|
+
if (searchResult.success && searchResult.data?.result) {
|
|
2783
|
+
results.artifacts_found.flows = searchResult.data.result.map((flow) => ({
|
|
2784
|
+
sys_id: flow.sys_id,
|
|
2785
|
+
name: flow.name,
|
|
2786
|
+
sys_created_on: flow.sys_created_on
|
|
2787
|
+
}));
|
|
2788
|
+
if (!dryRun) {
|
|
2789
|
+
for (const flow of searchResult.data.result) {
|
|
2790
|
+
const deleteResult = await this.client.deleteRecord('sys_hub_flow', flow.sys_id);
|
|
2791
|
+
if (deleteResult.success) {
|
|
2792
|
+
results.artifacts_deleted.flows++;
|
|
2793
|
+
}
|
|
2794
|
+
else {
|
|
2795
|
+
results.errors.push(`Failed to delete flow ${flow.name}: ${deleteResult.error}`);
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
catch (error) {
|
|
2802
|
+
results.errors.push(`Error cleaning flows: ${error.message}`);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
async cleanupTestUsers(patterns, cutoffTime, dryRun, results, updateSetFilter) {
|
|
2806
|
+
try {
|
|
2807
|
+
const patternQueries = patterns.map(pattern => {
|
|
2808
|
+
if (pattern.includes('%')) {
|
|
2809
|
+
return `user_nameLIKE${pattern.replace(/%/g, '')}^ORfirst_nameLIKE${pattern.replace(/%/g, '')}`;
|
|
2810
|
+
}
|
|
2811
|
+
else {
|
|
2812
|
+
return `user_name=${pattern}^ORfirst_name=${pattern}`;
|
|
2813
|
+
}
|
|
2814
|
+
});
|
|
2815
|
+
const query = `(${patternQueries.join('^OR')})^sys_created_on<${cutoffTime}^active=false`;
|
|
2816
|
+
const searchResult = await this.client.searchRecords('sys_user', query, 100);
|
|
2817
|
+
if (searchResult.success && searchResult.data?.result) {
|
|
2818
|
+
results.artifacts_found.users = searchResult.data.result.map((user) => ({
|
|
2819
|
+
sys_id: user.sys_id,
|
|
2820
|
+
name: user.user_name || user.name,
|
|
2821
|
+
sys_created_on: user.sys_created_on
|
|
2822
|
+
}));
|
|
2823
|
+
if (!dryRun) {
|
|
2824
|
+
for (const user of searchResult.data.result) {
|
|
2825
|
+
const deleteResult = await this.client.deleteRecord('sys_user', user.sys_id);
|
|
2826
|
+
if (deleteResult.success) {
|
|
2827
|
+
results.artifacts_deleted.users++;
|
|
2828
|
+
}
|
|
2829
|
+
else {
|
|
2830
|
+
results.errors.push(`Failed to delete user ${user.user_name}: ${deleteResult.error}`);
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
catch (error) {
|
|
2837
|
+
results.errors.push(`Error cleaning users: ${error.message}`);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
async cleanupTestRequests(patterns, cutoffTime, dryRun, results, updateSetFilter) {
|
|
2841
|
+
try {
|
|
2842
|
+
const patternQueries = patterns.map(pattern => {
|
|
2843
|
+
if (pattern.includes('%')) {
|
|
2844
|
+
return `short_descriptionLIKE${pattern.replace(/%/g, '')}`;
|
|
2845
|
+
}
|
|
2846
|
+
else {
|
|
2847
|
+
return `short_description=${pattern}`;
|
|
2848
|
+
}
|
|
2849
|
+
});
|
|
2850
|
+
const query = `(${patternQueries.join('^OR')})^sys_created_on<${cutoffTime}`;
|
|
2851
|
+
const searchResult = await this.client.searchRecords('sc_request', query, 100);
|
|
2852
|
+
if (searchResult.success && searchResult.data?.result) {
|
|
2853
|
+
results.artifacts_found.requests = searchResult.data.result.map((req) => ({
|
|
2854
|
+
sys_id: req.sys_id,
|
|
2855
|
+
name: req.number || req.short_description,
|
|
2856
|
+
sys_created_on: req.sys_created_on
|
|
2857
|
+
}));
|
|
2858
|
+
if (!dryRun) {
|
|
2859
|
+
for (const request of searchResult.data.result) {
|
|
2860
|
+
// Cancel request instead of deleting (safer)
|
|
2861
|
+
const updateResult = await this.client.updateRecord('sc_request', request.sys_id, {
|
|
2862
|
+
state: '4', // Cancelled
|
|
2863
|
+
comments: 'Cancelled by test cleanup automation'
|
|
2864
|
+
});
|
|
2865
|
+
if (updateResult.success) {
|
|
2866
|
+
results.artifacts_deleted.requests++;
|
|
2867
|
+
}
|
|
2868
|
+
else {
|
|
2869
|
+
results.errors.push(`Failed to cancel request ${request.number}: ${updateResult.error}`);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
catch (error) {
|
|
2876
|
+
results.errors.push(`Error cleaning requests: ${error.message}`);
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2494
2879
|
getServiceNowUrl() {
|
|
2495
2880
|
// This would need to get the instance URL from credentials
|
|
2496
2881
|
return 'https://instance.service-now.com';
|