From 3190ea7c1c2f3f1a5ce9d562ca9a4d48019d1f37 Mon Sep 17 00:00:00 2001 From: Ruben Date: Fri, 24 Nov 2023 20:19:57 +0100 Subject: [PATCH 1/2] improve/split dmlinloop --- readme.md | 11 +- src/main/libs/Compiler.ts | 16 + src/main/rules/DMLStatementInLoop.ts | 66 +--- src/main/rules/SOQLQueryInLoop.ts | 53 +++ src/main/store/DefaultRuleStore.ts | 2 + ...st.ts => DMLStatementInALoop_Demo.test.ts} | 2 +- tests/DMLStatementInALoop_Demo_Fixed.test.ts | 25 ++ tests/SOQLQueryInALoop_Demo.test.ts | 26 ++ tests/SOQLQueryInALoop_Demo_Fixed.test.ts | 25 ++ tests/getRules_findsAll.test.ts | 4 +- .../DMLStatementInALoop_Demo_Fixed.json | 357 ++++++++++++++++++ tests/testfiles/SOQLQueryInALoop_Demo.json | 299 +++++++++++++++ .../SOQLQueryInALoop_Demo_Fixed.json | 282 ++++++++++++++ 13 files changed, 1114 insertions(+), 54 deletions(-) create mode 100644 src/main/rules/SOQLQueryInLoop.ts rename tests/{demo_dmlStatementInALoop.test.ts => DMLStatementInALoop_Demo.test.ts} (93%) create mode 100644 tests/DMLStatementInALoop_Demo_Fixed.test.ts create mode 100644 tests/SOQLQueryInALoop_Demo.test.ts create mode 100644 tests/SOQLQueryInALoop_Demo_Fixed.test.ts create mode 100644 tests/testfiles/DMLStatementInALoop_Demo_Fixed.json create mode 100644 tests/testfiles/SOQLQueryInALoop_Demo.json create mode 100644 tests/testfiles/SOQLQueryInALoop_Demo_Fixed.json diff --git a/readme.md b/readme.md index 6afb9f9a..1caf582d 100644 --- a/readme.md +++ b/readme.md @@ -49,7 +49,7 @@ ___ ### DML Statement In A Loop -To prevent exceeding Apex governor limits, it is advisable to consolidate all your record-related operations, including creation, updates, or deletions, at the conclusion of the flow. +To prevent exceeding Apex governor limits, it is advisable to consolidate all your database operations, including record creation, updates, or deletions, at the conclusion of the flow. **Configuration ID: `DMLStatementInLoop`** _([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/DMLStatementInLoop.ts))_ @@ -121,6 +121,15 @@ _([View source code](https://github.com/Force-Config-Control/lightning-flow-scan ___ +### SOQL Query In A Loop + +To prevent exceeding Apex governor limits, it is advisable to consolidate all your SOQL queries at the conclusion of the flow. + +**Configuration ID: `SOQLQueryInLoop`** +_([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/SOQLQueryInLoop.ts))_ + +___ + ### Unconnected Element To maintain the efficiency and manageability of your Flow, it's best to avoid including unconnected elements that are not in use. diff --git a/src/main/libs/Compiler.ts b/src/main/libs/Compiler.ts index 97ca685f..14df36a8 100644 --- a/src/main/libs/Compiler.ts +++ b/src/main/libs/Compiler.ts @@ -8,6 +8,22 @@ export class Compiler { this.visitedElements = new Set(); } + isInLoop = (flow: Flow, element: FlowNode, startOfLoop: FlowNode): boolean => { + const connectors = element.connectors || []; + for (const connector of connectors) { + if (connector.reference) { + const referencedElement = (flow.elements as FlowNode[]).find(el => el.name === connector.reference); + if (referencedElement === startOfLoop) { + return true; + } + if (this.isInLoop(flow, referencedElement, startOfLoop)) { + return true; + } + } + } + return false; + }; + traverseFlow(flow: Flow, startElementName: string, visitCallback: (element: FlowNode) => void) { // Iterative Deepening Depth-First Search (IDDFS) let depth = 0; diff --git a/src/main/rules/DMLStatementInLoop.ts b/src/main/rules/DMLStatementInLoop.ts index 53899cd8..cae0b569 100644 --- a/src/main/rules/DMLStatementInLoop.ts +++ b/src/main/rules/DMLStatementInLoop.ts @@ -5,6 +5,7 @@ import { FlowType } from '../models/FlowType'; import { RuleResult } from '../models/RuleResult'; import { RuleCommon } from '../models/RuleCommon'; import { ResultDetails } from '../models/ResultDetails'; +import { Compiler } from '../libs/Compiler'; export class DMLStatementInLoop extends RuleCommon implements IRuleDefinition { @@ -12,75 +13,40 @@ export class DMLStatementInLoop extends RuleCommon implements IRuleDefinition { super({ name: 'DMLStatementInLoop', label: 'DML Statement In A Loop', - description: "To prevent exceeding Apex governor limits, it is advisable to consolidate all your record-related operations, including creation, updates, or deletions, at the conclusion of the flow.", + description: "To prevent exceeding Apex governor limits, it is advisable to consolidate all your database operations, including record creation, updates, or deletions, at the conclusion of the flow.", type: 'pattern', supportedTypes: [...FlowType.backEndTypes, ...FlowType.visualTypes], docRefs: [{ 'label': 'Flow Best Practices', 'path': 'https://help.salesforce.com/s/articleView?id=sf.flow_prep_bestpractices.htm&type=5' }], isConfigurable: false - }, - ); + }); } public execute(flow: Flow): RuleResult { if (flow.type[0] === 'Survey') { return new RuleResult(this, []); } - const dmlStatementTypes = ['recordLookups', 'recordDeletes', 'recordUpdates', 'recordCreates']; - const flowElements: FlowNode[] = flow.elements.filter(node => node.metaType === 'node') as FlowNode[]; + + const dmlStatementTypes = ['recordDeletes', 'recordUpdates', 'recordCreates']; const loopElements: FlowNode[] = flow.elements.filter(node => node.subtype === 'loops') as FlowNode[]; - const dmlInLoopIndexes: number[] = []; + const dmlStatementsInLoops: FlowNode[] = []; + const compiler = new Compiler(); + // Check if a DML statement is inside a loop for (const loopElement of loopElements) { - const startOfLoop = flowElements.findIndex(element => element.name === this.findStartOfLoopReference(loopElement)); - let reachedEndOfLoop = false; - let indexesToProcess: number[] = [startOfLoop]; - const processedLoopElementIndexes: number[] = []; - do { - indexesToProcess = indexesToProcess.filter(index => !processedLoopElementIndexes.includes(index)); - if (indexesToProcess.length <= 0 || (indexesToProcess.length == 1 && indexesToProcess[0] == -1)) { - break; - } - for (const [index, element] of flowElements.entries()) { - if (indexesToProcess.includes(index)) { - const connectors = []; - for (const connector of element.connectors) { - if (connector.reference) { - connectors.push(connector); - } - } - if (dmlStatementTypes.includes(element.subtype)) { - dmlInLoopIndexes.push(index); - } - if (connectors.length > 0) { - const elementsByReferences = flowElements.filter(anElement => connectors.map(c => c.reference).includes(anElement.name)); - for (const nextElement of elementsByReferences) { - const nextIndex = flowElements.findIndex(anElement => nextElement.name === anElement.name); - if ('loops' === nextElement.subtype) { - reachedEndOfLoop = true; - } else if (!processedLoopElementIndexes.includes(nextIndex)) { - indexesToProcess.push(nextIndex); - } - } - } - processedLoopElementIndexes.push(index); - } + const startOfLoop = loopElement; + + compiler.traverseFlow(flow, loopElement.name, (element) => { + if (dmlStatementTypes.includes(element.subtype) && compiler.isInLoop(flow, element, startOfLoop)) { + dmlStatementsInLoops.push(element); } - } while (reachedEndOfLoop === false); - } - const dmlStatementsInLoops: FlowNode[] = []; - for (const [index, element] of flowElements.entries()) { - if (dmlInLoopIndexes.includes(index)) { - dmlStatementsInLoops.push(element); - } + }); } + let results = []; for (const det of dmlStatementsInLoops) { results.push(new ResultDetails(det)); } - return new RuleResult(this, results); - } - private findStartOfLoopReference(loopElement: FlowNode) { - return loopElement.connectors.find(el => el.type === 'nextValueConnector').reference; + return new RuleResult(this, results); } } diff --git a/src/main/rules/SOQLQueryInLoop.ts b/src/main/rules/SOQLQueryInLoop.ts new file mode 100644 index 00000000..2963660d --- /dev/null +++ b/src/main/rules/SOQLQueryInLoop.ts @@ -0,0 +1,53 @@ +import { IRuleDefinition } from '../interfaces/IRuleDefinition'; +import { Flow } from '../models/Flow'; +import { FlowNode } from '../models/FlowNode'; +import { FlowType } from '../models/FlowType'; +import { RuleResult } from '../models/RuleResult'; +import { RuleCommon } from '../models/RuleCommon'; +import { ResultDetails } from '../models/ResultDetails'; +import { Compiler } from '../libs/Compiler'; + +export class SOQLQueryInLoop extends RuleCommon implements IRuleDefinition { + + constructor() { + super({ + name: 'SOQLQueryInLoop', + label: 'SOQL Query In A Loop', + description: "To prevent exceeding Apex governor limits, it is advisable to consolidate all your SOQL queries at the conclusion of the flow.", + type: 'pattern', + supportedTypes: [...FlowType.backEndTypes, ...FlowType.visualTypes], + docRefs: [{ 'label': 'Flow Best Practices', 'path': 'https://help.salesforce.com/s/articleView?id=sf.flow_prep_bestpractices.htm&type=5' }], + isConfigurable: false + }); + } + + public execute(flow: Flow): RuleResult { + if (flow.type[0] === 'Survey') { + return new RuleResult(this, []); + } + + const dmlStatementTypes = ['recordLookups']; + const loopElements: FlowNode[] = flow.elements.filter(node => node.subtype === 'loops') as FlowNode[]; + const dmlStatementsInLoops: FlowNode[] = []; + const compiler = new Compiler(); + + // Check if a DML statement is inside a loop + for (const loopElement of loopElements) { + const startOfLoop = loopElement; + + compiler.traverseFlow(flow, loopElement.name, (element) => { + if (dmlStatementTypes.includes(element.subtype) && compiler.isInLoop(flow, element, startOfLoop)) { + dmlStatementsInLoops.push(element); + } + }); + } + + let results = []; + for (const det of dmlStatementsInLoops) { + results.push(new ResultDetails(det)); + } + + return new RuleResult(this, results); + } + } + \ No newline at end of file diff --git a/src/main/store/DefaultRuleStore.ts b/src/main/store/DefaultRuleStore.ts index 030dfd3c..361ea815 100644 --- a/src/main/store/DefaultRuleStore.ts +++ b/src/main/store/DefaultRuleStore.ts @@ -7,6 +7,7 @@ import { FlowName } from '../rules/FlowName'; import { HardcodedId } from '../rules/HardcodedId'; import { MissingFaultPath } from '../rules/MissingFaultPath'; import { MissingNullHandler } from '../rules/MissingNullHandler'; +import { SOQLQueryInLoop } from '../rules/SOQLQueryInLoop'; import { UnconnectedElement } from '../rules/UnconnectedElement'; import { UnusedVariable } from '../rules/UnusedVariable'; @@ -20,6 +21,7 @@ export const DefaultRuleStore: {} = { HardcodedId, MissingFaultPath, MissingNullHandler, + SOQLQueryInLoop, UnconnectedElement, UnusedVariable }; \ No newline at end of file diff --git a/tests/demo_dmlStatementInALoop.test.ts b/tests/DMLStatementInALoop_Demo.test.ts similarity index 93% rename from tests/demo_dmlStatementInALoop.test.ts rename to tests/DMLStatementInALoop_Demo.test.ts index 8d02cc6b..79953a67 100644 --- a/tests/demo_dmlStatementInALoop.test.ts +++ b/tests/DMLStatementInALoop_Demo.test.ts @@ -5,7 +5,7 @@ import { Flow } from '../src/main/models/Flow'; import { ScanResult } from '../src/main/models/ScanResult'; import dmlstatementsinaloop from './testfiles/DMLStatementInALoop_Demo.json'; -describe('In the DML_statements_in_a_loop flow', () => { +describe('In the DMLStatementInALoop_Demo flow', () => { let flow: Flow; before('arrange', () => { diff --git a/tests/DMLStatementInALoop_Demo_Fixed.test.ts b/tests/DMLStatementInALoop_Demo_Fixed.test.ts new file mode 100644 index 00000000..a0da786c --- /dev/null +++ b/tests/DMLStatementInALoop_Demo_Fixed.test.ts @@ -0,0 +1,25 @@ +import { assert, expect } from 'chai'; +import 'mocha'; +import { scan } from '../src'; +import { Flow } from '../src/main/models/Flow'; +import { ScanResult } from '../src/main/models/ScanResult'; +import dmlstatementsinaloop from './testfiles/DMLStatementInALoop_Demo_Fixed.json'; + +describe('In the DMLStatementInALoop_Demo_Fixed flow', () => { + let flow: Flow; + + before('arrange', () => { + // ARRANGE + flow = new Flow({ + path: './testfiles/DML_statements_in_a_loop_fixed.flow', + xmldata: dmlstatementsinaloop, + }); + }); + + it('there should be no result for the rule DMLStatementInLoop', () => { + + const results: ScanResult[] = scan([flow]); + 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/SOQLQueryInALoop_Demo.test.ts b/tests/SOQLQueryInALoop_Demo.test.ts new file mode 100644 index 00000000..e95c0419 --- /dev/null +++ b/tests/SOQLQueryInALoop_Demo.test.ts @@ -0,0 +1,26 @@ +import { assert, expect } from 'chai'; +import 'mocha'; +import { scan } from '../src'; +import { Flow } from '../src/main/models/Flow'; +import { ScanResult } from '../src/main/models/ScanResult'; +import flowfile from './testfiles/SOQLQueryInALoop_Demo.json'; + +describe('In the SOQLQueryInALoop_Demo flow', () => { + let flow: Flow; + + before('arrange', () => { + // ARRANGE + flow = new Flow({ + path: './testfiles/SOQL_Query_In_Loop_Demo.flow', + xmldata: flowfile, + }); + }); + + it('there should be one result for the rule SOQLQueryInLoop', () => { + + const results: ScanResult[] = scan([flow]); + const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs); + expect(occurringResults.length).to.equal(1); + expect(occurringResults[0].ruleName).to.equal("SOQLQueryInLoop"); + }); +}); \ No newline at end of file diff --git a/tests/SOQLQueryInALoop_Demo_Fixed.test.ts b/tests/SOQLQueryInALoop_Demo_Fixed.test.ts new file mode 100644 index 00000000..be1ce20e --- /dev/null +++ b/tests/SOQLQueryInALoop_Demo_Fixed.test.ts @@ -0,0 +1,25 @@ +import { assert, expect } from 'chai'; +import 'mocha'; +import { scan } from '../src'; +import { Flow } from '../src/main/models/Flow'; +import { ScanResult } from '../src/main/models/ScanResult'; +import flowfile from './testfiles/SOQLQueryInALoop_Demo_Fixed.json'; + +describe('In the SOQLQueryInALoop_Demo flow', () => { + let flow: Flow; + + before('arrange', () => { + // ARRANGE + flow = new Flow({ + path: './testfiles/SOQL_Query_In_Loop_Demo.flow', + xmldata: flowfile, + }); + }); + + it('there should be no result for the rule SOQLQueryInLoop', () => { + + const results: ScanResult[] = scan([flow]); + 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/getRules_findsAll.test.ts b/tests/getRules_findsAll.test.ts index ab91a91f..b175e172 100644 --- a/tests/getRules_findsAll.test.ts +++ b/tests/getRules_findsAll.test.ts @@ -3,8 +3,8 @@ import { expect } from 'chai'; import 'mocha'; describe('GetRuleDefinitions function', () => { - it('should return 11 rules', () => { + it('should return 12 rules', () => { const result = getRules(); - expect(result.length).to.equal(11); + expect(result.length).to.equal(12); }); }); diff --git a/tests/testfiles/DMLStatementInALoop_Demo_Fixed.json b/tests/testfiles/DMLStatementInALoop_Demo_Fixed.json new file mode 100644 index 00000000..30aa649e --- /dev/null +++ b/tests/testfiles/DMLStatementInALoop_Demo_Fixed.json @@ -0,0 +1,357 @@ +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "actionCalls": [ + { + "name": [ + "create_case_manually" + ], + "label": [ + "create case manually" + ], + "locationX": [ + "440" + ], + "locationY": [ + "542" + ], + "actionName": [ + "FeedItem.NewTaskFromFeedItem" + ], + "actionType": [ + "quickAction" + ], + "flowTransactionModel": [ + "CurrentTransaction" + ], + "inputParameters": [ + { + "name": [ + "contextId" + ], + "value": [ + { + "elementReference": [ + "aLoop.Id" + ] + } + ] + } + ], + "nameSegment": [ + "FeedItem.NewTaskFromFeedItem" + ], + "versionSegment": [ + "1" + ] + } + ], + "apiVersion": [ + "58.0" + ], + "assignments": [ + { + "description": [ + "Assigns values to newCase variable." + ], + "name": [ + "createNewCaseAssignment" + ], + "label": [ + "createNewCaseAssignment" + ], + "locationX": [ + "264" + ], + "locationY": [ + "242" + ], + "assignmentItems": [ + { + "assignToReference": [ + "newCase.AccountId" + ], + "operator": [ + "Assign" + ], + "value": [ + { + "elementReference": [ + "aLoop.Id" + ] + } + ] + }, + { + "assignToReference": [ + "newCase.OwnerId" + ], + "operator": [ + "Assign" + ], + "value": [ + { + "elementReference": [ + "aLoop.OwnerId" + ] + } + ] + }, + { + "assignToReference": [ + "newCases" + ], + "operator": [ + "Add" + ], + "value": [ + { + "elementReference": [ + "newCase" + ] + } + ] + } + ], + "connector": [ + { + "targetReference": [ + "aLoop" + ] + } + ] + } + ], + "description": [ + "This flow demonstrates how to resolve a violation of the rule \"DML Statement In A Loop\"." + ], + "environments": [ + "Default" + ], + "interviewLabel": [ + "DML Statement In A Loop Fixed {!$Flow.CurrentDateTime}" + ], + "label": [ + "Fix: DML Statement In A Loop" + ], + "loops": [ + { + "description": [ + "an example loop" + ], + "name": [ + "aLoop" + ], + "label": [ + "aLoop" + ], + "locationX": [ + "176" + ], + "locationY": [ + "134" + ], + "collectionReference": [ + "Accounts" + ], + "iterationOrder": [ + "Asc" + ], + "nextValueConnector": [ + { + "targetReference": [ + "createNewCaseAssignment" + ] + } + ], + "noMoreValuesConnector": [ + { + "targetReference": [ + "insert_cases" + ] + } + ] + } + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "AUTO_LAYOUT_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "AutoLaunchedFlow" + ], + "recordCreates": [ + { + "name": [ + "insert_cases" + ], + "label": [ + "insert cases" + ], + "locationX": [ + "176" + ], + "locationY": [ + "434" + ], + "faultConnector": [ + { + "targetReference": [ + "create_case_manually" + ] + } + ], + "inputReference": [ + "newCases" + ] + } + ], + "start": [ + { + "locationX": [ + "50" + ], + "locationY": [ + "0" + ], + "connector": [ + { + "targetReference": [ + "aLoop" + ] + } + ] + } + ], + "status": [ + "Active" + ], + "variables": [ + { + "description": [ + "A collection of Accounts" + ], + "name": [ + "Accounts" + ], + "dataType": [ + "SObject" + ], + "isCollection": [ + "true" + ], + "isInput": [ + "true" + ], + "isOutput": [ + "true" + ], + "objectType": [ + "Account" + ] + }, + { + "name": [ + "newCase" + ], + "dataType": [ + "SObject" + ], + "isCollection": [ + "false" + ], + "isInput": [ + "false" + ], + "isOutput": [ + "false" + ], + "objectType": [ + "Case" + ] + }, + { + "description": [ + "Collection of New Cases" + ], + "name": [ + "newCases" + ], + "dataType": [ + "SObject" + ], + "isCollection": [ + "true" + ], + "isInput": [ + "false" + ], + "isOutput": [ + "false" + ], + "objectType": [ + "Case" + ] + }, + { + "description": [ + "a task" + ], + "name": [ + "task" + ], + "dataType": [ + "SObject" + ], + "isCollection": [ + "false" + ], + "isInput": [ + "false" + ], + "isOutput": [ + "false" + ], + "objectType": [ + "Task" + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/testfiles/SOQLQueryInALoop_Demo.json b/tests/testfiles/SOQLQueryInALoop_Demo.json new file mode 100644 index 00000000..d38f0aae --- /dev/null +++ b/tests/testfiles/SOQLQueryInALoop_Demo.json @@ -0,0 +1,299 @@ +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "actionCalls": [ + { + "name": [ + "dosomethingelse" + ], + "label": [ + "dosomethingelse" + ], + "locationX": [ + "704" + ], + "locationY": [ + "350" + ], + "actionName": [ + "NewCase" + ], + "actionType": [ + "quickAction" + ], + "flowTransactionModel": [ + "CurrentTransaction" + ], + "nameSegment": [ + "NewCase" + ], + "versionSegment": [ + "1" + ] + } + ], + "apiVersion": [ + "58.0" + ], + "decisions": [ + { + "name": [ + "notnull" + ], + "label": [ + "notnull" + ], + "locationX": [ + "352" + ], + "locationY": [ + "350" + ], + "defaultConnector": [ + { + "isGoTo": [ + "true" + ], + "targetReference": [ + "dosomethingelse" + ] + } + ], + "defaultConnectorLabel": [ + "Default Outcome" + ], + "rules": [ + { + "name": [ + "notnulln" + ], + "conditionLogic": [ + "and" + ], + "conditions": [ + { + "leftValueReference": [ + "SOQL_Query_Example" + ], + "operator": [ + "IsNull" + ], + "rightValue": [ + { + "booleanValue": [ + "false" + ] + } + ] + } + ], + "connector": [ + { + "targetReference": [ + "aLoop" + ] + } + ], + "label": [ + "notnull" + ] + } + ] + } + ], + "description": [ + "This flow demonstrates a violation of the rule \"SOQL Query In A Loop\"." + ], + "environments": [ + "Default" + ], + "interviewLabel": [ + "SOQL Query In A Loop {!$Flow.CurrentDateTime}" + ], + "label": [ + "SOQL Query In A Loop" + ], + "loops": [ + { + "description": [ + "an example loop" + ], + "name": [ + "aLoop" + ], + "label": [ + "aLoop" + ], + "locationX": [ + "176" + ], + "locationY": [ + "134" + ], + "collectionReference": [ + "Accounts" + ], + "iterationOrder": [ + "Asc" + ], + "nextValueConnector": [ + { + "targetReference": [ + "SOQL_Query_Example" + ] + } + ] + } + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "AUTO_LAYOUT_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "AutoLaunchedFlow" + ], + "recordLookups": [ + { + "name": [ + "SOQL_Query_Example" + ], + "label": [ + "SOQL Query Example" + ], + "locationX": [ + "352" + ], + "locationY": [ + "242" + ], + "assignNullValuesIfNoRecordsFound": [ + "false" + ], + "connector": [ + { + "targetReference": [ + "notnull" + ] + } + ], + "faultConnector": [ + { + "targetReference": [ + "dosomethingelse" + ] + } + ], + "filterLogic": [ + "and" + ], + "filters": [ + { + "field": [ + "Active__c" + ], + "operator": [ + "EqualTo" + ], + "value": [ + { + "stringValue": [ + "Yes" + ] + } + ] + } + ], + "getFirstRecordOnly": [ + "true" + ], + "object": [ + "Account" + ], + "storeOutputAutomatically": [ + "true" + ] + } + ], + "start": [ + { + "locationX": [ + "50" + ], + "locationY": [ + "0" + ], + "connector": [ + { + "targetReference": [ + "aLoop" + ] + } + ] + } + ], + "status": [ + "Active" + ], + "variables": [ + { + "description": [ + "A collection of Accounts" + ], + "name": [ + "Accounts" + ], + "dataType": [ + "SObject" + ], + "isCollection": [ + "true" + ], + "isInput": [ + "true" + ], + "isOutput": [ + "true" + ], + "objectType": [ + "Account" + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/testfiles/SOQLQueryInALoop_Demo_Fixed.json b/tests/testfiles/SOQLQueryInALoop_Demo_Fixed.json new file mode 100644 index 00000000..8f3e57fb --- /dev/null +++ b/tests/testfiles/SOQLQueryInALoop_Demo_Fixed.json @@ -0,0 +1,282 @@ +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "actionCalls": [ + { + "name": [ + "dosomethingelse" + ], + "label": [ + "dosomethingelse" + ], + "locationX": [ + "440" + ], + "locationY": [ + "434" + ], + "actionName": [ + "NewCase" + ], + "actionType": [ + "quickAction" + ], + "flowTransactionModel": [ + "CurrentTransaction" + ], + "nameSegment": [ + "NewCase" + ], + "versionSegment": [ + "1" + ] + } + ], + "apiVersion": [ + "58.0" + ], + "decisions": [ + { + "name": [ + "notnull" + ], + "label": [ + "notnull" + ], + "locationX": [ + "176" + ], + "locationY": [ + "434" + ], + "defaultConnectorLabel": [ + "Default Outcome" + ], + "rules": [ + { + "name": [ + "notnulln" + ], + "conditionLogic": [ + "and" + ], + "conditions": [ + { + "leftValueReference": [ + "SOQL_Query_Example" + ], + "operator": [ + "IsNull" + ], + "rightValue": [ + { + "booleanValue": [ + "false" + ] + } + ] + } + ], + "label": [ + "notnull" + ] + } + ] + } + ], + "description": [ + "This flow demonstrates how to resolve a violation of the rule \"SOQL Query In A Loop\"." + ], + "environments": [ + "Default" + ], + "interviewLabel": [ + "Fix: SOQL Query In A Loop {!$Flow.CurrentDateTime}" + ], + "label": [ + "Fix: SOQL Query In A Loop" + ], + "loops": [ + { + "description": [ + "an example loop" + ], + "name": [ + "aLoop" + ], + "label": [ + "aLoop" + ], + "locationX": [ + "176" + ], + "locationY": [ + "134" + ], + "collectionReference": [ + "Accounts" + ], + "iterationOrder": [ + "Asc" + ], + "noMoreValuesConnector": [ + { + "targetReference": [ + "SOQL_Query_Example" + ] + } + ] + } + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "AUTO_LAYOUT_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "AutoLaunchedFlow" + ], + "recordLookups": [ + { + "name": [ + "SOQL_Query_Example" + ], + "label": [ + "SOQL Query Example" + ], + "locationX": [ + "176" + ], + "locationY": [ + "326" + ], + "assignNullValuesIfNoRecordsFound": [ + "false" + ], + "connector": [ + { + "targetReference": [ + "notnull" + ] + } + ], + "faultConnector": [ + { + "targetReference": [ + "dosomethingelse" + ] + } + ], + "filterLogic": [ + "and" + ], + "filters": [ + { + "field": [ + "Active__c" + ], + "operator": [ + "EqualTo" + ], + "value": [ + { + "stringValue": [ + "Yes" + ] + } + ] + } + ], + "getFirstRecordOnly": [ + "true" + ], + "object": [ + "Account" + ], + "storeOutputAutomatically": [ + "true" + ] + } + ], + "start": [ + { + "locationX": [ + "50" + ], + "locationY": [ + "0" + ], + "connector": [ + { + "targetReference": [ + "aLoop" + ] + } + ] + } + ], + "status": [ + "Active" + ], + "variables": [ + { + "description": [ + "A collection of Accounts" + ], + "name": [ + "Accounts" + ], + "dataType": [ + "SObject" + ], + "isCollection": [ + "true" + ], + "isInput": [ + "true" + ], + "isOutput": [ + "true" + ], + "objectType": [ + "Account" + ] + } + ] + } +} \ No newline at end of file From 2ddc0d9b1c06fda01bef4312e8b0ca852f14cf99 Mon Sep 17 00:00:00 2001 From: Ruben Date: Fri, 24 Nov 2023 20:21:21 +0100 Subject: [PATCH 2/2] 2.26.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2206e6a2..003d4b0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lightning-flow-scanner-core", - "version": "2.25.0", + "version": "2.26.0", "main": "out/**", "types": "out/index.d.ts", "scripts": {