From 1e2c842858461f3284499171c26d253c6cd6d3c8 Mon Sep 17 00:00:00 2001 From: "Kyle Capehart (Huntersville)" Date: Tue, 7 May 2024 12:47:22 -0400 Subject: [PATCH 1/5] feat: create new rule for inactive flows --- src/main/rules/InactiveFlow.ts | 35 ++++++++++++++ src/main/store/DefaultRuleStore.ts | 4 +- tests/InactiveFlow.test.ts | 36 ++++++++++++++ tests/testfiles/ActiveFlow_Demo.json | 66 ++++++++++++++++++++++++++ tests/testfiles/ObsoleteFlow_Demo.json | 66 ++++++++++++++++++++++++++ 5 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 src/main/rules/InactiveFlow.ts create mode 100644 tests/InactiveFlow.test.ts create mode 100644 tests/testfiles/ActiveFlow_Demo.json create mode 100644 tests/testfiles/ObsoleteFlow_Demo.json diff --git a/src/main/rules/InactiveFlow.ts b/src/main/rules/InactiveFlow.ts new file mode 100644 index 0000000..378d0d0 --- /dev/null +++ b/src/main/rules/InactiveFlow.ts @@ -0,0 +1,35 @@ +import * as IdPrefixes from '../data/IdPrefixes.json'; +import { RuleCommon } from '../models/RuleCommon'; +import * as core from '../internals/internals'; + +export class InactiveFlow extends RuleCommon implements core.IRuleDefinition { + + constructor() { + super({ + name: 'InactiveFlow', + label: 'Inactive Flow', + description: 'Avoid having inactive flows in your org.', + supportedTypes: core.FlowType.allTypes(), + docRefs: [], + isConfigurable: false, + autoFixable: false + }, + ); + } + + public execute(flow: core.Flow): core.RuleResult { + const inactiveFlows = []; + console.log(flow.name); + for (const node of flow.elements) { + const nodeElementString = JSON.stringify(node.element); + if (node.subtype == "status" && nodeElementString != '\"Active\"') { + inactiveFlows.push(node); + } + } + let results = []; + for (const det of inactiveFlows) { + results.push(new core.ResultDetails(det)); + } + return new core.RuleResult(this, results); + } +} \ No newline at end of file diff --git a/src/main/store/DefaultRuleStore.ts b/src/main/store/DefaultRuleStore.ts index 690c961..3f7eec2 100644 --- a/src/main/store/DefaultRuleStore.ts +++ b/src/main/store/DefaultRuleStore.ts @@ -6,6 +6,7 @@ import { DuplicateDMLOperation } from '../rules/DuplicateDMLOperation'; import { FlowDescription } from '../rules/FlowDescription'; import { FlowName } from '../rules/FlowName'; import { HardcodedId } from '../rules/HardcodedId'; +import { InactiveFlow } from '../rules/InactiveFlow'; import { MissingFaultPath } from '../rules/MissingFaultPath'; import { MissingNullHandler } from '../rules/MissingNullHandler'; import { ProcessBuilder } from '../rules/ProcessBuilder'; @@ -27,5 +28,6 @@ export const DefaultRuleStore: {} = { ProcessBuilder, SOQLQueryInLoop, UnconnectedElement, - UnusedVariable + UnusedVariable, + InactiveFlow, }; \ No newline at end of file diff --git a/tests/InactiveFlow.test.ts b/tests/InactiveFlow.test.ts new file mode 100644 index 0000000..f82dbef --- /dev/null +++ b/tests/InactiveFlow.test.ts @@ -0,0 +1,36 @@ +import { assert, expect } from 'chai'; +import 'mocha'; +import * as core from '../src'; +import obsolete from './testfiles/ObsoleteFlow_Demo.json'; +import active from './testfiles/ActiveFlow_Demo.json'; + +describe('In the ObsoleteFlow flow', () => { + let obsoleteflow, activeflow: core.Flow; + + before('arrange', () => { + // ARRANGE + obsoleteflow = new core.Flow({ + path: './testfiles/ObsoleteFlow_Demo.flow', + xmldata: obsolete, + }); + activeflow = new core.Flow({ + path: './testfiles/ActiveFlow_Demo.flow', + xmldata: active, + }); + }); + + it('there should be one result for the rule InactiveFlow', () => { + + const results: core.ScanResult[] = core.scan([obsoleteflow]); + const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs); + expect(occurringResults.length).to.equal(1); + expect(occurringResults[0].ruleName).to.equal("InactiveFlow"); + }); + + it('there should be no results for an active flow', () => { + + const results: core.ScanResult[] = core.scan([activeflow]); + const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs); + expect(occurringResults.length).to.equal(0); + }); +}); \ No newline at end of file diff --git a/tests/testfiles/ActiveFlow_Demo.json b/tests/testfiles/ActiveFlow_Demo.json new file mode 100644 index 0000000..f44a038 --- /dev/null +++ b/tests/testfiles/ActiveFlow_Demo.json @@ -0,0 +1,66 @@ +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "apiVersion": [ + "58.0" + ], + "description": [ + "This flow demonstrates an active flow that does not violate the rule \"Inactive Flow\"." + ], + "environments": [ + "Default" + ], + "interviewLabel": [ + "Active Flow {!$Flow.CurrentDateTime}" + ], + "label": [ + "Active Flow" + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "AUTO_LAYOUT_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "AutoLaunchedFlow" + ], + "status": [ + "Active" + ] + } +} \ No newline at end of file diff --git a/tests/testfiles/ObsoleteFlow_Demo.json b/tests/testfiles/ObsoleteFlow_Demo.json new file mode 100644 index 0000000..e386c75 --- /dev/null +++ b/tests/testfiles/ObsoleteFlow_Demo.json @@ -0,0 +1,66 @@ +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "apiVersion": [ + "58.0" + ], + "description": [ + "This flow demonstrates a violation of the rule \"Inactive Flow\"." + ], + "environments": [ + "Default" + ], + "interviewLabel": [ + "Obsolete Flow {!$Flow.CurrentDateTime}" + ], + "label": [ + "Obsolete Flow" + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "AUTO_LAYOUT_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "AutoLaunchedFlow" + ], + "status": [ + "Obsolete" + ] + } +} \ No newline at end of file From c2391f187080f03ee41671d2a312fb97845418a6 Mon Sep 17 00:00:00 2001 From: "Kyle Capehart (Huntersville)" Date: Tue, 7 May 2024 12:51:44 -0400 Subject: [PATCH 2/5] fix weird spacing in test --- tests/InactiveFlow.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/InactiveFlow.test.ts b/tests/InactiveFlow.test.ts index f82dbef..9020e0e 100644 --- a/tests/InactiveFlow.test.ts +++ b/tests/InactiveFlow.test.ts @@ -19,7 +19,7 @@ describe('In the ObsoleteFlow flow', () => { }); }); - it('there should be one result for the rule InactiveFlow', () => { + it('there should be one result for the rule InactiveFlow', () => { const results: core.ScanResult[] = core.scan([obsoleteflow]); const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs); @@ -27,7 +27,7 @@ describe('In the ObsoleteFlow flow', () => { expect(occurringResults[0].ruleName).to.equal("InactiveFlow"); }); - it('there should be no results for an active flow', () => { + it('there should be no results for an active flow', () => { const results: core.ScanResult[] = core.scan([activeflow]); const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs); From 52d4c7e99f41146f5203348b373ce656532ea860 Mon Sep 17 00:00:00 2001 From: "Kyle Capehart (Huntersville)" Date: Tue, 7 May 2024 14:51:08 -0400 Subject: [PATCH 3/5] make rule configurable --- src/main/rules/InactiveFlow.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/rules/InactiveFlow.ts b/src/main/rules/InactiveFlow.ts index 378d0d0..495e98d 100644 --- a/src/main/rules/InactiveFlow.ts +++ b/src/main/rules/InactiveFlow.ts @@ -11,7 +11,7 @@ export class InactiveFlow extends RuleCommon implements core.IRuleDefinition { description: 'Avoid having inactive flows in your org.', supportedTypes: core.FlowType.allTypes(), docRefs: [], - isConfigurable: false, + isConfigurable: true, autoFixable: false }, ); From 8c97d9b645549d229940d4e51007353b8b1f278f Mon Sep 17 00:00:00 2001 From: "Kyle Capehart (Huntersville)" Date: Tue, 7 May 2024 16:35:37 -0400 Subject: [PATCH 4/5] update rule description and readme --- src/main/rules/InactiveFlow.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/rules/InactiveFlow.ts b/src/main/rules/InactiveFlow.ts index 495e98d..9078b2f 100644 --- a/src/main/rules/InactiveFlow.ts +++ b/src/main/rules/InactiveFlow.ts @@ -1,4 +1,3 @@ -import * as IdPrefixes from '../data/IdPrefixes.json'; import { RuleCommon } from '../models/RuleCommon'; import * as core from '../internals/internals'; @@ -8,7 +7,7 @@ export class InactiveFlow extends RuleCommon implements core.IRuleDefinition { super({ name: 'InactiveFlow', label: 'Inactive Flow', - description: 'Avoid having inactive flows in your org.', + description: 'It\'s better to delete flows that are no longer used rather than have them be inactive. Inactive flows can still delete records when testing them, and parent flows will run an inactive subflow if no active version is found.', supportedTypes: core.FlowType.allTypes(), docRefs: [], isConfigurable: true, @@ -19,7 +18,6 @@ export class InactiveFlow extends RuleCommon implements core.IRuleDefinition { public execute(flow: core.Flow): core.RuleResult { const inactiveFlows = []; - console.log(flow.name); for (const node of flow.elements) { const nodeElementString = JSON.stringify(node.element); if (node.subtype == "status" && nodeElementString != '\"Active\"') { From d33915f873bf15206d13e467172e653adb925985 Mon Sep 17 00:00:00 2001 From: "Kyle Capehart (Huntersville)" Date: Tue, 7 May 2024 16:49:28 -0400 Subject: [PATCH 5/5] add new rule to readme --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 99366e2..ff8c0d0 100644 --- a/readme.md +++ b/readme.md @@ -27,6 +27,7 @@ _An Extensible Rule Engine for Salesforce Flows used by the Lightning Flow Scann | **Unconnected Element** ([`UnconnectedElement`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/UnconnectedElement.ts)) | Unconnected elements which are not being used by the Flow should be avoided to keep Flows efficient and maintainable. | | **Unused Variable** ([`UnusedVariable`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/UnusedVariable.ts)) | To maintain the efficiency and manageability of your Flow, it's advisable to avoid including unconnected variables that are not in use. | | **Process Builder** ([`ProcessBuilder`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/ProcessBuilder.ts)) | Salesforce is transitioning away from Workflow Rules and Process Builder in favor of Flow. Ensure you're prepared for this transition by migrating your organization's automation to Flow. Refer to official documentation for more information on the transition process and tools available. | +| **Inactive Flow** ([`InactiveFlow`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/InactiveFlow.ts)) | It's better to delete flows that are no longer used rather than have them be inactive. Inactive flows can still delete records when testing them, and parent flows will run an inactive subflow if no active version is found. | ## Configurations