diff --git a/package.json b/package.json index 2b5fb37..f93c47c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lightning-flow-scanner-core", - "version": "2.23.0", + "version": "2.24.0", "main": "out/**", "types": "out/index.d.ts", "scripts": { diff --git a/readme.md b/readme.md index 8308067..5d9565c 100644 --- a/readme.md +++ b/readme.md @@ -1,28 +1,28 @@ -# Lightning Flow Scanner(Rule Engine) +# Lightning Flow Scanner(Rule Set) -##### _This rule engine is used in both the [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ForceConfigControl.lightningflowscanner&ssr=false#review-details) and the [SFDX Plugin](https://www.npmjs.com/package/lightning-flow-scanner) of the same name._ +##### _This core rule set is used in both the [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ForceConfigControl.lightningflowscanner&ssr=false#review-details) and the [SFDX Plugin](https://www.npmjs.com/package/lightning-flow-scanner) of the same name._ ## Rules -- [Old API version](#old-api-version) +- [Outdated API Version](#outdated-api-version) - [Copy of API Name](#copy-of-api-name) - [DML statements in a loop](#dml-statements-in-a-loop) - [Duplicate DML operations](#duplicate-dml-operations) - [Missing flow description](#missing-flow-description) - [Flow naming conventions](#flow-naming-conventions) - [Hardcoded Ids](#hardcoded-ids) -- [Missing error handlers](#missing-error-handlers) +- [Missing fault path](#missing-fault-path) - [Missing null handlers](#missing-null-handlers) - [Unconnected elements](#unconnected-elements) - [Unused variables](#unused-variables) ___ -### Old API version +### Outdated API Version -Newer API components may cause older versions of Flows to start behaving incorrectly due to differences in the underlying mechanics. The Api Version has been available as an attribute on the Flow Object since API v50.0. It is recommended to limit variation between API versions and to maintain them on a regular basis. +Introducing newer API components may lead to unexpected issues with older versions of Flows, as they might not align with the underlying mechanics. Starting from API version 50.0, the 'Api Version' attribute has been readily available on the Flow Object. To ensure smooth operation and reduce discrepancies between API versions, it is strongly advised to regularly update and maintain them. -_Default Value: `>50.0`_ +_Default Value: `>49.0`_ _Configuration example:_ ``` @@ -38,45 +38,45 @@ _([View source code](https://github.com/Force-Config-Control/lightning-flow-scan ___ -### Copy of API Name +### Copy API Name -Having multiple elements called Copy_X_Of_Element will decrease the readability of the Flow. If you copy and paste them, make sure to update the API name of the new copy. +Maintaining multiple elements with a similar name, like 'Copy_X_Of_Element,' can diminish the overall readability of your Flow. When copying and pasting these elements, it's crucial to remember to update the API name of the newly created copy. -**Configuration ID: `CopyOf`** -_([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/CopyOf.ts))_ +**Configuration ID: `CopyAPIName`** +_([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/CopyAPIName.ts))_ ___ -### DML statements in a loop +### DML Statement In A Loop -To avoid hitting Apex governor limits, we recommend grouping all of your changes together at the end of the flow, whether those changes create, update, or delete records. +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. **Configuration ID: `DMLStatementInLoop`** _([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/DMLStatementInLoop.ts))_ ___ -### Duplicate DML operations +### Duplicate DML Operation -If the flow commits changes to the database or performs actions between two screens, don't let users navigate back between screen. Otherwise, the flow may perform duplicate database operations. +When the flow executes database changes or actions between two screens, it's important to prevent users from navigating back between screens. Failure to do so may result in duplicate database operations being performed within the flow. -**Configuration ID: `DuplicateDMLOperations`** -_([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/DuplicateDMLOperations.ts))_ +**Configuration ID: `DuplicateDMLOperation`** +_([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/DuplicateDMLOperation.ts))_ ___ -### Missing flow description +### Missing Flow Description -Descriptions are useful for documentation purposes. It is recommended to provide information about where it is used and what it will do. +Descriptions play a vital role in documentation. We highly recommend including details about where they are used and their intended purpose. **Configuration ID: `FlowDescription`** _([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/FlowDescription.ts))_ ___ -### Flow naming conventions +### Flow Naming Convention -Readability of a flow is very important. Setting a naming convention for the Flow Name will improve the findability/searchability and overall consistency. It is recommended to at least provide a domain and a short description of the actions undertaken in the flow, in example Service_OrderFulfillment. +The readability of a flow is of utmost importance. Establishing a naming convention for the Flow Name significantly enhances findability, searchability, and maintains overall consistency. It is advisable to include at least a domain and a brief description of the actions carried out in the flow, for instance, 'Service_OrderFulfillment'. _Default Value: `[A-Za-z0-9]+_[A-Za-z0-9]+`_ @@ -94,45 +94,45 @@ _([View source code](https://github.com/Force-Config-Control/lightning-flow-scan ___ -### Hardcoded Ids +### Hardcoded Id -IDs are org-specific, so don’t hard-code IDs. Instead, pass them into variables when the flow starts. You can do so, for example, by using merge fields in URL parameters or by using a Get Records element. +Avoid hard-coding IDs as they are org-specific. Instead, pass them into variables at the start of the flow. You can achieve this by utilizing merge fields in URL parameters or employing a Get Records element. -**Configuration ID: `HardcodedIds`** -_([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/HardcodedIds.ts))_ +**Configuration ID: `HardcodedId`** +_([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/HardcodedId.ts))_ ___ -### Missing error handlers +### Missing Fault Path -Sometimes a flow doesn’t perform an operation that you configured it to do. By default, the flow shows an error message to the user and emails the admin who created the flow. However, you can control that behavior. +At times, a flow may fail to execute a configured operation as intended. By default, the flow displays an error message to the user and notifies the admin who created the flow via email. However, you can customize this behavior by incorporating a Fault Path. **Configuration ID: `MissingFaultPath`** _([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/MissingFaultPath.ts))_ ___ -### Missing null handlers +### Missing Null Handler -If a Get Records operation does not find any data it will return null. Use a decision element on the operation result variable to validate that the result is not null. +When a Get Records operation doesn't find any data, it returns null. To ensure data validation, utilize a decision element on the operation result variable to check for a non-null result. **Configuration ID: `MissingNullHandler`** _([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/MissingNullHandler.ts))_ ___ -### Unconnected elements +### Unconnected Element -Unconnected elements which are not in use by the Flow should be avoided to keep the Flow as efficient and maintainable as possible. +To maintain the efficiency and manageability of your Flow, it's best to avoid including unconnected elements that are not in use. -**Configuration ID: `UnconnectedElements`** -_([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/UnconnectedElements.ts))_ +**Configuration ID: `UnconnectedElement`** +_([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/UnconnectedElement.ts))_ ___ -### Unused variables +### Unused Variable -Unconnected variables which are not in use by the Flow should be avoided to keep the Flow as efficient and maintainable as possible. +To maintain the efficiency and manageability of your Flow, it's advisable to avoid including unconnected variables that are not in use. -**Configuration ID: `UnusedVariables`** -_([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/UnusedVariables.ts))_ +**Configuration ID: `UnusedVariable`** +_([View source code](https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/UnusedVariable.ts))_ diff --git a/src/main/libs/Compiler.ts b/src/main/libs/Compiler.ts new file mode 100644 index 0000000..97ca685 --- /dev/null +++ b/src/main/libs/Compiler.ts @@ -0,0 +1,55 @@ +import { Flow } from '../models/Flow'; +import { FlowNode } from '../models/FlowNode'; + +export class Compiler { + private visitedElements: Set; + + constructor() { + this.visitedElements = new Set(); + } + + traverseFlow(flow: Flow, startElementName: string, visitCallback: (element: FlowNode) => void) { + // Iterative Deepening Depth-First Search (IDDFS) + let depth = 0; + let elementsToVisit = [startElementName]; + + while (elementsToVisit.length > 0) { + const nextElements = []; + + for (const elementName of elementsToVisit) { + if (!this.visitedElements.has(elementName)) { + const currentElement = flow.elements.find(element => element instanceof FlowNode && element.name === elementName) as FlowNode; + if (currentElement) { + visitCallback(currentElement); + this.visitedElements.add(elementName); + nextElements.push(...this.findNextElements(flow, currentElement)); + } + } + } + + elementsToVisit = nextElements; + depth++; + // add logic to control depth or terminate the traversal based on requirements. + } + } + + private findNextElements(flow: Flow, currentElement: FlowNode): string[] { + const nextElements: string[] = []; + + if (currentElement.connectors && currentElement.connectors.length > 0) { + for (const connector of currentElement.connectors) { + if (connector.reference) { + // Check if the reference exists in the flow elements + const nextElement = flow.elements.find( + element => element instanceof FlowNode && element.name === connector.reference + ); + if (nextElement instanceof FlowNode) { + nextElements.push(nextElement.name); + } + } + } + } + + return nextElements; + } +} diff --git a/src/main/libs/FixFlows.ts b/src/main/libs/FixFlows.ts index 4b73652..75ec1b5 100644 --- a/src/main/libs/FixFlows.ts +++ b/src/main/libs/FixFlows.ts @@ -4,16 +4,16 @@ import { FlowVariable } from '../models/FlowVariable'; import { ResultDetails } from '../models/ResultDetails'; import { RuleResult } from '../models/RuleResult'; import { ScanResult } from '../models/ScanResult'; -import { UnconnectedElements } from '../rules/UnconnectedElements'; -import { UnusedVariables } from '../rules/UnusedVariables'; +import { UnconnectedElement } from '../rules/UnconnectedElement'; +import { UnusedVariable } from '../rules/UnusedVariable'; import { BuildFlow } from './BuildFlow'; export function FixFlows(flows: Flow[]): ScanResult[] { const flowResults: ScanResult[] = []; for (const flow of flows) { - const unconnectedElementsResult: RuleResult = new UnconnectedElements().execute(flow); - const unusedVariablesResult: RuleResult = new UnusedVariables().execute(flow); + const unconnectedElementsResult: RuleResult = new UnconnectedElement().execute(flow); + const unusedVariablesResult: RuleResult = new UnusedVariable().execute(flow); const ruleResults: RuleResult[] = [unusedVariablesResult, unconnectedElementsResult]; const unusedVariableReferences = unusedVariablesResult.details ? (unusedVariablesResult.details as ResultDetails[]).map(unusedVariable => unusedVariable.name) : []; const unconnectedElementsReferences = unconnectedElementsResult.details ? (unconnectedElementsResult.details as ResultDetails[]).map(unconnectedElement => unconnectedElement.name) : []; diff --git a/src/main/models/Flow.ts b/src/main/models/Flow.ts index fbde3a8..348ecff 100644 --- a/src/main/models/Flow.ts +++ b/src/main/models/Flow.ts @@ -20,6 +20,7 @@ export class Flow { public uri?; public root?; public elements?: FlowElement[]; + public startReference; private flowVariables = [ 'choices', @@ -93,8 +94,8 @@ export class Flow { this.processType = this.xmldata.processType; this.processMetadataValues = this.xmldata.processMetadataValues; this.startElementReference = this.xmldata.startElementReference; - this.status = this.xmldata.status; this.start = this.xmldata.start; + this.status = this.xmldata.status; this.type = this.xmldata.processType; const allNodes: (FlowVariable | FlowNode | FlowMetadata)[] = []; for (const nodeType in this.xmldata) { @@ -126,6 +127,24 @@ export class Flow { } } this.elements = allNodes; + this.startReference = this.findStart(); + } + + private findStart() { + let start = ''; + const flowElements: FlowNode[] = this.elements.filter(node => node instanceof FlowNode) as FlowNode[]; + if (this.startElementReference) { + start = this.startElementReference[0]; + } else if(flowElements.find(n => { + return n.subtype === 'start'; + })){ + let startElement = flowElements.find(n => { + return n.subtype === 'start'; + }); + start = startElement.connectors[0].reference; + } + return start; } } + diff --git a/src/main/models/RuleResult.ts b/src/main/models/RuleResult.ts index 0ac1dc2..a32facb 100644 --- a/src/main/models/RuleResult.ts +++ b/src/main/models/RuleResult.ts @@ -9,13 +9,14 @@ export class RuleResult { public severity: string; public details: ResultDetails[] = []; - constructor(info: IRuleDefinition, occurs: boolean, details?: ResultDetails[]) { + constructor(info: IRuleDefinition, details: ResultDetails[]) { this.ruleDefinition = info; this.ruleName = info.name; this.severity = info.severity ? info.severity : 'error'; - this.occurs = occurs; - if (details) { - this.details = details; + this.occurs = false; + this.details = details; + if(details.length > 0){ + this.occurs = true; } } diff --git a/src/main/rules/APIVersion.ts b/src/main/rules/APIVersion.ts index 12fe98b..3507055 100644 --- a/src/main/rules/APIVersion.ts +++ b/src/main/rules/APIVersion.ts @@ -11,8 +11,8 @@ export class APIVersion extends RuleCommon implements IRuleDefinition { constructor() { super({ name: 'APIVersion', - label: 'Old API version', - description: 'Newer API components may cause older versions of Flows to start behaving incorrectly due to differences in the underlying mechanics. The Api Version has been available as an attribute on the Flow Object since API v50.0. It is recommended to limit variation between API versions and to maintain them on a regular basis.', + label: 'Outdated API Version', + description: "Introducing newer API components may lead to unexpected issues with older versions of Flows, as they might not align with the underlying mechanics. Starting from API version 50.0, the 'Api Version' attribute has been readily available on the Flow Object. To ensure smooth operation and reduce discrepancies between API versions, it is strongly advised to regularly update and maintain them.", type: 'flow', supportedTypes: FlowType.allTypes(), docRefs: [], @@ -30,12 +30,14 @@ export class APIVersion extends RuleCommon implements IRuleDefinition { if (flowAPIVersionNumber) { if (options && options.expression) { const expressionEvaluation = eval(flowAPIVersionNumber + options.expression); - return new RuleResult(this, !expressionEvaluation, [new ResultDetails(new FlowAttribute(!expressionEvaluation ? ('' + flowAPIVersionNumber) : undefined, "apiVersion", options.expression))]); + return (!expressionEvaluation ? + new RuleResult(this, [new ResultDetails(new FlowAttribute(!expressionEvaluation ? ('' + flowAPIVersionNumber) : undefined, "apiVersion", options.expression))]) : + new RuleResult(this, [])); } else { - return new RuleResult(this, false); + return new RuleResult(this, []); } } else { - return new RuleResult(this, true, [new ResultDetails(new FlowAttribute('API Version <50', "apiVersion", "<50"))]); + return new RuleResult(this, [new ResultDetails(new FlowAttribute('API Version <49', "apiVersion", "<49"))]); } } } diff --git a/src/main/rules/CopyOf.ts b/src/main/rules/CopyAPIName.ts similarity index 71% rename from src/main/rules/CopyOf.ts rename to src/main/rules/CopyAPIName.ts index 45d6296..22087cd 100644 --- a/src/main/rules/CopyOf.ts +++ b/src/main/rules/CopyAPIName.ts @@ -6,13 +6,13 @@ import { RuleCommon } from '../models/RuleCommon'; import { FlowNode } from '../models/FlowNode'; import { ResultDetails } from '../models/ResultDetails'; -export class CopyOf extends RuleCommon implements IRuleDefinition { +export class CopyAPIName extends RuleCommon implements IRuleDefinition { constructor() { super({ - name: 'CopyOf', + name: 'CopyAPIName', label: 'Copy Of API Name', - description: 'Having multiple elements called Copy_X_Of_Element will decrease the readability of the Flow. If you copy and paste them, make sure to update the API name of the new copy.', + description: "Maintaining multiple elements with a similar name, like 'Copy_X_Of_Element,' can diminish the overall readability of your Flow. When copying and pasting these elements, it's crucial to remember to update the API name of the newly created copy.", type: 'pattern', supportedTypes: FlowType.allTypes(), docRefs: [], @@ -35,6 +35,6 @@ export class CopyOf extends RuleCommon implements IRuleDefinition { for (const det of copyOfElements) { results.push(new ResultDetails(det)); } - return new RuleResult(this, copyOfElements.length > 0, results); + return new RuleResult(this, results); } } \ No newline at end of file diff --git a/src/main/rules/DMLStatementInLoop.ts b/src/main/rules/DMLStatementInLoop.ts index de4dbb1..6f57f6c 100644 --- a/src/main/rules/DMLStatementInLoop.ts +++ b/src/main/rules/DMLStatementInLoop.ts @@ -11,8 +11,8 @@ export class DMLStatementInLoop extends RuleCommon implements IRuleDefinition { constructor() { super({ name: 'DMLStatementInLoop', - label: 'DML statements in a loop', - description: 'To avoid hitting Apex governor limits, we recommend grouping all of your database changes together at the end of the flow, whether those changes create, update, or delete records.', + 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.", 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' }], @@ -23,7 +23,7 @@ export class DMLStatementInLoop extends RuleCommon implements IRuleDefinition { public execute(flow: Flow): RuleResult { if (flow.type[0] === 'Survey') { - return new RuleResult(this, false); + return new RuleResult(this, []); } const dmlStatementTypes = ['recordLookups', 'recordDeletes', 'recordUpdates', 'recordCreates']; const flowElements: FlowNode[] = flow.elements.filter(node => node.metaType === 'node') as FlowNode[]; @@ -77,7 +77,7 @@ export class DMLStatementInLoop extends RuleCommon implements IRuleDefinition { for (const det of dmlStatementsInLoops) { results.push(new ResultDetails(det)); } - return new RuleResult(this, dmlStatementsInLoops.length > 0, results); + return new RuleResult(this, results); } private findStartOfLoopReference(loopElement: FlowNode) { diff --git a/src/main/rules/DuplicateDMLOperations.ts b/src/main/rules/DuplicateDMLOperation.ts similarity index 88% rename from src/main/rules/DuplicateDMLOperations.ts rename to src/main/rules/DuplicateDMLOperation.ts index a8e52f8..03c7e83 100644 --- a/src/main/rules/DuplicateDMLOperations.ts +++ b/src/main/rules/DuplicateDMLOperation.ts @@ -6,13 +6,13 @@ import { RuleResult } from '../models/RuleResult'; import { RuleCommon } from '../models/RuleCommon'; import { ResultDetails } from '../models/ResultDetails'; -export class DuplicateDMLOperations extends RuleCommon implements IRuleDefinition { +export class DuplicateDMLOperation extends RuleCommon implements IRuleDefinition { constructor() { super({ - name: 'DuplicateDMLOperations', - label: 'Duplicate DML operations', - description: "If the flow commits changes to the database or performs actions between two screens, don't let users navigate back between screen. Otherwise, the flow may perform duplicate database operations.", + name: 'DuplicateDMLOperation', + label: 'Duplicate DML Operation', + description: "When the flow executes database changes or actions between two screens, it's important to prevent users from navigating back between screens. Failure to do so may result in duplicate database operations being performed within the flow.", type: 'pattern', supportedTypes: FlowType.visualTypes, docRefs: [], @@ -22,7 +22,7 @@ export class DuplicateDMLOperations extends RuleCommon implements IRuleDefinitio public execute(flow: Flow): RuleResult { if (flow.type[0] === 'Survey') { - return new RuleResult(this, false); + return new RuleResult(this, []); } const flowElements: FlowNode[] = flow.elements.filter(node => node instanceof FlowNode) as FlowNode[]; const processedElementIndexes: number[] = []; @@ -79,7 +79,7 @@ export class DuplicateDMLOperations extends RuleCommon implements IRuleDefinitio for (const det of DuplicateDMLOperations) { results.push(new ResultDetails(det)); } - return new RuleResult(this, DuplicateDMLOperations.length > 0, results); + return new RuleResult(this, results); } private flagDML(element, dmlFlag) { diff --git a/src/main/rules/FlowDescription.ts b/src/main/rules/FlowDescription.ts index 17dbab5..f673ac4 100644 --- a/src/main/rules/FlowDescription.ts +++ b/src/main/rules/FlowDescription.ts @@ -11,8 +11,8 @@ export class FlowDescription extends RuleCommon implements IRuleDefinition { constructor() { super({ name: 'FlowDescription', - label: 'Missing flow description', - description: 'Descriptions are useful for documentation purposes. It is recommended to provide information about where it is used and what it will do.', + label: 'Missing Flow Description', + description: "Descriptions play a vital role in documentation. We highly recommend including details about where they are used and their intended purpose.", type: 'flow', supportedTypes: FlowType.allTypes(), docRefs: [], @@ -22,9 +22,11 @@ export class FlowDescription extends RuleCommon implements IRuleDefinition { public execute(flow: Flow): RuleResult { if (flow.type[0] === 'Survey') { - return new RuleResult(this, false); + return new RuleResult(this, []); } const missingFlowDescription = !flow.xmldata.description; - return new RuleResult(this, missingFlowDescription, [new ResultDetails(new FlowAttribute(!missingFlowDescription ? 'undefined' : undefined, "description", "!==null"))]); + return (missingFlowDescription ? + new RuleResult(this, [new ResultDetails(new FlowAttribute('undefined', "description", "!==null"))]) : + new RuleResult(this, [])); } -} +} \ No newline at end of file diff --git a/src/main/rules/FlowName.ts b/src/main/rules/FlowName.ts index c63b76e..9a98632 100644 --- a/src/main/rules/FlowName.ts +++ b/src/main/rules/FlowName.ts @@ -12,7 +12,7 @@ export class FlowName extends RuleCommon implements IRuleDefinition { super({ name: 'FlowName', label: 'Flow Naming Convention', - description: 'Readability of a flow is very important. Setting a naming convention for the Flow Name will improve the findability/searchability and overall consistency. It is recommended to at least provide a domain and a short description of the actions undertaken in the flow, in example Service_OrderFulfillment.', + description: "The readability of a flow is of utmost importance. Establishing a naming convention for the Flow Name significantly enhances findability, searchability, and maintains overall consistency. It is advisable to include at least a domain and a brief description of the actions carried out in the flow, for instance, 'Service_OrderFulfillment'.", type: 'flow', supportedTypes: FlowType.allTypes(), docRefs: [{ 'label': "Naming your Flows is more critical than ever. By Stephen Church", 'path': 'https://www.linkedin.com/posts/stephen-n-church_naming-your-flows-this-is-more-critical-activity-7099733198175158274-1sPx?utm_source=share&utm_medium=member_desktop' }], @@ -23,6 +23,8 @@ export class FlowName extends RuleCommon implements IRuleDefinition { public execute(flow: Flow, options?: { expression: string }): RuleResult { const regexExp = (options && options.expression) ? options.expression : '[A-Za-z0-9]+_[A-Za-z0-9]+'; const conventionApplied = new RegExp(regexExp).test(flow.name); - return new RuleResult(this, !conventionApplied, [new ResultDetails(new FlowAttribute(flow.name, 'name', regexExp))]); + return (!conventionApplied ? + new RuleResult(this, [new ResultDetails(new FlowAttribute(flow.name, 'name', regexExp))]) : + new RuleResult(this, [])); } } diff --git a/src/main/rules/HardcodedIds.ts b/src/main/rules/HardcodedId.ts similarity index 81% rename from src/main/rules/HardcodedIds.ts rename to src/main/rules/HardcodedId.ts index d1a9c9b..a93217a 100644 --- a/src/main/rules/HardcodedIds.ts +++ b/src/main/rules/HardcodedId.ts @@ -6,13 +6,13 @@ import { RuleResult } from '../models/RuleResult'; import { RuleCommon } from '../models/RuleCommon'; import { ResultDetails } from '../models/ResultDetails'; -export class HardcodedIds extends RuleCommon implements IRuleDefinition { +export class HardcodedId extends RuleCommon implements IRuleDefinition { constructor() { super({ - name: 'HardcodedIds', - label: 'Hardcoded Ids', - description: 'IDs are org-specific, so don’t hard-code IDs. Instead, pass them into variables when the flow starts. You can do so, for example, by using merge fields in URL parameters or by using a Get Records element.', + name: 'HardcodedId', + label: 'Hardcoded Id', + description: 'Avoid hard-coding IDs as they are org-specific. Instead, pass them into variables at the start of the flow. You can achieve this by utilizing merge fields in URL parameters or employing a Get Records element.', type: 'pattern', supportedTypes: FlowType.allTypes(), docRefs: [{ 'label': 'Flow Best Practices', 'path': 'https://help.salesforce.com/s/articleView?id=sf.flow_prep_bestpractices.htm&type=5' }, {'label' : "Don't hard code Record Type IDs in Flow. By Stephen Church.", 'path' : 'https://www.linkedin.com/feed/update/urn:li:activity:6947530300012826624/?updateEntityUrn=urn%3Ali%3Afs_feedUpdate%3A%28V2%2Curn%3Ali%3Aactivity%3A6947530300012826624%29'}], @@ -47,6 +47,6 @@ export class HardcodedIds extends RuleCommon implements IRuleDefinition { for (const det of nodesWithHardcodedIds) { results.push(new ResultDetails(det)); } - return new RuleResult(this, nodesWithHardcodedIds.length > 0, results); + return new RuleResult(this, results); } } \ No newline at end of file diff --git a/src/main/rules/MissingFaultPath.ts b/src/main/rules/MissingFaultPath.ts index 5cbac85..8081d8c 100644 --- a/src/main/rules/MissingFaultPath.ts +++ b/src/main/rules/MissingFaultPath.ts @@ -5,38 +5,56 @@ 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 MissingFaultPath extends RuleCommon implements IRuleDefinition { - constructor() { super({ name: 'MissingFaultPath', label: 'Missing error handlers', - description: 'Sometimes a flow doesn’t perform an operation that you configured it to do. By default, the flow shows an error message to the user and emails the admin who created the flow. However, you can control that behavior.', + description: "At times, a flow may fail to execute a configured operation as intended. By default, the flow displays an error message to the user and notifies the admin who created the flow via email. However, you can customize this behavior by incorporating a Fault Path.", 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 - } - ); + isConfigurable: false, + }); } public execute(flow: Flow): RuleResult { if (flow.type[0] === 'Survey') { - return new RuleResult(this, false); + return new RuleResult(this, []); } - const typesWithFaultPath = ['recordLookups', 'recordDeletes', 'recordUpdates', 'recordCreates', 'waits', 'actionCalls']; - const flowElementsWhereFaultPathIsApplicable: FlowNode[] = flow.elements.filter(node => node instanceof FlowNode && typesWithFaultPath.includes(node.subtype)) as FlowNode[]; - const elementsWithoutFaultPath: FlowNode[] = []; - for (const element of flowElementsWhereFaultPathIsApplicable) { - if (!element.connectors.find(connector => 'faultConnector' === connector.type)) { - elementsWithoutFaultPath.push(element); + + const compiler = new Compiler(); + const results: ResultDetails[] = []; + const elementsWhereFaultPathIsApplicable = (flow.elements.filter((node) => node instanceof FlowNode && ['recordLookups', 'recordDeletes', 'recordUpdates', 'recordCreates', 'waits', 'actionCalls'].includes(node.subtype)) as FlowNode[]).map((e) => e.name); + + const visitCallback = (element: FlowNode) => { + // Check if the element should have a fault path + if (!element.connectors.find((connector) => connector.type === 'faultConnector') && elementsWhereFaultPathIsApplicable.includes(element.name)) { + // Check if the element is part of another fault path + if (!this.isPartOfFaultHandlingFlow(element, flow)) { + results.push(new ResultDetails(element)); + } + } + }; + + // Use the Compiler for traversal + compiler.traverseFlow(flow, flow.startReference, visitCallback); + + return new RuleResult(this, results); + } + + private isPartOfFaultHandlingFlow(element: FlowNode, flow: Flow): boolean { + const flowelements = flow.elements.filter(el => el instanceof FlowNode) as FlowNode[]; + for (const otherElement of flowelements) { + if (otherElement !== element) { + // Check if the otherElement has a faultConnector pointing to element + if (otherElement.connectors.find((connector) => connector.type === 'faultConnector' && connector.reference === element.name)) { + return true; + } } } - let results = []; - for (const det of elementsWithoutFaultPath) { - results.push(new ResultDetails(det)); - } - return new RuleResult(this, elementsWithoutFaultPath.length > 0, results); + return false; } } diff --git a/src/main/rules/MissingNullHandler.ts b/src/main/rules/MissingNullHandler.ts index b32f5fb..61ceacb 100644 --- a/src/main/rules/MissingNullHandler.ts +++ b/src/main/rules/MissingNullHandler.ts @@ -11,8 +11,8 @@ export class MissingNullHandler extends RuleCommon implements IRuleDefinition { constructor() { super({ name: 'MissingNullHandler', - label: 'Missing null handlers', - description: 'If a Get Records operation does not find any data it will return null. Use a decision element on the operation result variable to validate that the result is not null.', + label: 'Missing Null Handler', + description: "When a Get Records operation doesn't find any data, it returns null. To ensure data validation, utilize a decision element on the operation result variable to check for a non-null result.", type: 'pattern', supportedTypes: [...FlowType.backEndTypes, ...FlowType.visualTypes], docRefs: [], @@ -22,7 +22,7 @@ export class MissingNullHandler extends RuleCommon implements IRuleDefinition { public execute(flow: Flow): RuleResult { if (flow.type[0] === 'Survey') { - return new RuleResult(this, false); + return new RuleResult(this, []); } const getOperations = ['recordLookups']; const getOperationElements: FlowNode[] = flow.elements.filter(node => node.metaType === 'node' && getOperations.includes(node.subtype)) as FlowNode[]; @@ -80,6 +80,6 @@ export class MissingNullHandler extends RuleCommon implements IRuleDefinition { for (const det of getOperationsWithoutNullHandler) { results.push(new ResultDetails(det)); } - return new RuleResult(this, getOperationsWithoutNullHandler.length > 0, results); + return new RuleResult(this, results); } } diff --git a/src/main/rules/UnconnectedElements.ts b/src/main/rules/UnconnectedElement.ts similarity index 89% rename from src/main/rules/UnconnectedElements.ts rename to src/main/rules/UnconnectedElement.ts index 1a5e609..94e8f91 100644 --- a/src/main/rules/UnconnectedElements.ts +++ b/src/main/rules/UnconnectedElement.ts @@ -7,13 +7,13 @@ import { RuleResult } from '../models/RuleResult'; import { RuleCommon } from '../models/RuleCommon'; import { ResultDetails } from '../models/ResultDetails'; -export class UnconnectedElements extends RuleCommon implements IRuleDefinition { +export class UnconnectedElement extends RuleCommon implements IRuleDefinition { constructor() { super({ - name: 'UnconnectedElements', - label: 'Unconnected elements', - description: 'Removing unconnected elements which are not being used by the Flow makes your Flow more efficient and maintainable.', + name: 'UnconnectedElement', + label: 'Unconnected Element', + description: "To maintain the efficiency and manageability of your Flow, it's best to avoid including unconnected elements that are not in use.", type: 'pattern', supportedTypes: [...FlowType.backEndTypes, ...FlowType.visualTypes], docRefs: [], @@ -23,7 +23,7 @@ export class UnconnectedElements extends RuleCommon implements IRuleDefinition { public execute(flow: Flow): RuleResult { if (flow.type[0] === 'Survey') { - return new RuleResult(this, false); + return new RuleResult(this, []); } const flowElements: FlowNode[] = flow.elements.filter(node => node instanceof FlowNode) as FlowNode[]; let indexesToProcess = [this.findStart(flowElements)]; @@ -86,7 +86,7 @@ export class UnconnectedElements extends RuleCommon implements IRuleDefinition { for (const det of unconnectedElements) { results.push(new ResultDetails(det)); } - return new RuleResult(this, unconnectedElements.length > 0, results); + return new RuleResult(this, results); } private findStart(nodes: FlowElement[]) { diff --git a/src/main/rules/UnusedVariables.ts b/src/main/rules/UnusedVariable.ts similarity index 81% rename from src/main/rules/UnusedVariables.ts rename to src/main/rules/UnusedVariable.ts index 770dea4..8408ae2 100644 --- a/src/main/rules/UnusedVariables.ts +++ b/src/main/rules/UnusedVariable.ts @@ -7,13 +7,13 @@ import {RuleResult} from '../models/RuleResult'; import {RuleCommon} from '../models/RuleCommon'; import { ResultDetails } from '../models/ResultDetails'; -export class UnusedVariables extends RuleCommon implements IRuleDefinition{ +export class UnusedVariable extends RuleCommon implements IRuleDefinition{ constructor() { super({ - name: 'UnusedVariables', - label: 'Unused variables', - description: 'Removing unconnected variables which are not being used by the Flow makes your Flow more efficient and maintainable.', + name: 'UnusedVariable', + label: 'Unused Variable', + description: "To maintain the efficiency and manageability of your Flow, it's advisable to avoid including unconnected variables that are not in use.", type: 'pattern', supportedTypes: [...FlowType.backEndTypes, ...FlowType.visualTypes], docRefs: [], @@ -23,7 +23,7 @@ export class UnusedVariables extends RuleCommon implements IRuleDefinition{ public execute(flow: Flow) : RuleResult { if(flow.type[0] === 'Survey'){ - return new RuleResult( this, false); + return new RuleResult( this, []); } const unusedVariables: FlowVariable[] = []; for (const variable of flow.elements.filter(node => node instanceof FlowVariable) as FlowVariable[]) { @@ -42,7 +42,7 @@ export class UnusedVariables extends RuleCommon implements IRuleDefinition{ for (const det of unusedVariables) { results.push(new ResultDetails(det)); } - return new RuleResult( this, unusedVariables.length > 0, results); + return new RuleResult( this, results); } diff --git a/src/main/store/DefaultRuleStore.ts b/src/main/store/DefaultRuleStore.ts index 2ab9a34..030dfd3 100644 --- a/src/main/store/DefaultRuleStore.ts +++ b/src/main/store/DefaultRuleStore.ts @@ -1,25 +1,25 @@ import { APIVersion } from '../rules/APIVersion'; -import { CopyOf } from '../rules/CopyOf'; +import { CopyAPIName } from '../rules/CopyAPIName'; import { DMLStatementInLoop } from '../rules/DMLStatementInLoop'; -import { DuplicateDMLOperations } from '../rules/DuplicateDMLOperations'; +import { DuplicateDMLOperation } from '../rules/DuplicateDMLOperation'; import { FlowDescription } from '../rules/FlowDescription'; import { FlowName } from '../rules/FlowName'; -import { HardcodedIds } from '../rules/HardcodedIds'; +import { HardcodedId } from '../rules/HardcodedId'; import { MissingFaultPath } from '../rules/MissingFaultPath'; import { MissingNullHandler } from '../rules/MissingNullHandler'; -import { UnconnectedElements } from '../rules/UnconnectedElements'; -import { UnusedVariables } from '../rules/UnusedVariables'; +import { UnconnectedElement } from '../rules/UnconnectedElement'; +import { UnusedVariable } from '../rules/UnusedVariable'; export const DefaultRuleStore: {} = { APIVersion, - CopyOf, + CopyAPIName, DMLStatementInLoop, - DuplicateDMLOperations, + DuplicateDMLOperation, FlowDescription, FlowName, - HardcodedIds, + HardcodedId, MissingFaultPath, MissingNullHandler, - UnconnectedElements, - UnusedVariables + UnconnectedElement, + UnusedVariable }; \ No newline at end of file diff --git a/tests/DMLPreventedByHidingNav.test.ts b/tests/DMLPreventedByHidingNav.test.ts index d27a1b4..56555e0 100644 --- a/tests/DMLPreventedByHidingNav.test.ts +++ b/tests/DMLPreventedByHidingNav.test.ts @@ -16,11 +16,11 @@ describe('A screen flow with a DML statements between where the screen after the }); }); - it('DuplicateDMLOperations should have no result', () => { + it('DuplicateDMLOperation should have no result', () => { const ruleConfig = { rules: { - DuplicateDMLOperations: + DuplicateDMLOperation: { severity: 'error', }, @@ -29,6 +29,6 @@ describe('A screen flow with a DML statements between where the screen after the const results: ScanResult[] = scan([flow], ruleConfig); expect(results[0].ruleResults.length).to.equal(1); - expect(results[0].ruleResults[0].ruleName).to.equal('DuplicateDMLOperations'); + expect(results[0].ruleResults[0].ruleName).to.equal('DuplicateDMLOperation'); }); }); diff --git a/tests/copyOfFalseTest.test.ts b/tests/copyOfFalseTest.test.ts index 7fd89ec..dfed24d 100644 --- a/tests/copyOfFalseTest.test.ts +++ b/tests/copyOfFalseTest.test.ts @@ -16,11 +16,11 @@ describe('In a normal flow without copied elements', () => { }); }); - it('CopyOf should have no result', () => { + it('CopyAPIName should have no result', () => { const ruleConfig = { rules: { - CopyOf: { + CopyAPIName: { severity: 'error', }, }, @@ -29,7 +29,7 @@ describe('In a normal flow without copied elements', () => { const results: ScanResult[] = scan([flow], ruleConfig); expect(results[0].ruleResults.length).to.equal(1); - expect(results[0].ruleResults[0].ruleName).to.equal('CopyOf'); + expect(results[0].ruleResults[0].ruleName).to.equal('CopyAPIName'); expect(results[0].ruleResults[0].occurs).to.equal(false); }); }); diff --git a/tests/copyOfTrueTest.test.ts b/tests/copyOfTrueTest.test.ts index fb86472..ed609d8 100644 --- a/tests/copyOfTrueTest.test.ts +++ b/tests/copyOfTrueTest.test.ts @@ -16,11 +16,11 @@ describe('In a normal flow without copied elements', () => { }); }); - it('CopyOf should have no result', () => { + it('CopyAPIName should have no result', () => { const ruleConfig = { rules: { - CopyOf: { + CopyAPIName: { severity: 'error', }, }, @@ -29,7 +29,7 @@ describe('In a normal flow without copied elements', () => { const results: ScanResult[] = scan([flow], ruleConfig); expect(results[0].ruleResults.length).to.equal(1); - expect(results[0].ruleResults[0].ruleName).to.equal('CopyOf'); + expect(results[0].ruleResults[0].ruleName).to.equal('CopyAPIName'); expect(results[0].ruleResults[0].occurs).to.equal(true); }); }); diff --git a/tests/demo_dmlStatementInALoop.test.ts b/tests/demo_dmlStatementInALoop.test.ts new file mode 100644 index 0000000..8d02cc6 --- /dev/null +++ b/tests/demo_dmlStatementInALoop.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 dmlstatementsinaloop from './testfiles/DMLStatementInALoop_Demo.json'; + +describe('In the DML_statements_in_a_loop flow', () => { + let flow: Flow; + + before('arrange', () => { + // ARRANGE + flow = new Flow({ + path: './testfiles/DML_statements_in_a_loop.flow', + xmldata: dmlstatementsinaloop, + }); + }); + + it('there should be one result for the rule DMLStatementInLoop', () => { + + 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("DMLStatementInLoop"); + }); +}); \ No newline at end of file diff --git a/tests/demo_flowNamingConvention.test.ts b/tests/demo_flowNamingConvention.test.ts new file mode 100644 index 0000000..cb07c01 --- /dev/null +++ b/tests/demo_flowNamingConvention.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 FlowNamingConvention from './testfiles/FlowNamingConvention_Demo.json'; + +describe('In the FlowNamingConvention flow', () => { + let flow: Flow; + + before('arrange', () => { + // ARRANGE + flow = new Flow({ + path: './testfiles/FlowNamingConvention.flow', + xmldata: FlowNamingConvention, + }); + }); + + it('there should be one result for the rule FlowName', () => { + + 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("FlowName"); + }); +}); \ No newline at end of file diff --git a/tests/demo_hardcodedId.test.ts b/tests/demo_hardcodedId.test.ts new file mode 100644 index 0000000..6cd4574 --- /dev/null +++ b/tests/demo_hardcodedId.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 hardcodedids from './testfiles/HardcodedID_Demo.json'; + +describe('In the Hardcoded_Ids flow', () => { + let flow: Flow; + + before('arrange', () => { + // ARRANGE + flow = new Flow({ + path: './testfiles/Hardcoded_Ids.flow', + xmldata: hardcodedids, + }); + }); + + it('there should be one result for the rule HardcodedIds', () => { + + 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("HardcodedId"); + }); +}); \ No newline at end of file diff --git a/tests/demo_missingFaultPath.test.ts b/tests/demo_missingFaultPath.test.ts new file mode 100644 index 0000000..56b9201 --- /dev/null +++ b/tests/demo_missingFaultPath.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 missingerrorhandler from './testfiles/missingerrorhandler.json'; + +describe('In the Missing_error_handlers flow', () => { + let flow: Flow; + + before('arrange', () => { + // ARRANGE + flow = new Flow({ + path: './testfiles/Missing_error_handlers.flow-meta.flow', + xmldata: missingerrorhandler, + }); + }); + + it('there should be one result for the rule MissingFaultPath', () => { + + 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("MissingFaultPath"); + }); +}); \ No newline at end of file diff --git a/tests/demo_missingFaultPathFixed.test.ts b/tests/demo_missingFaultPathFixed.test.ts new file mode 100644 index 0000000..767172f --- /dev/null +++ b/tests/demo_missingFaultPathFixed.test.ts @@ -0,0 +1,24 @@ +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 missingerrorhandler from './testfiles/missingerrorhandler_fixed.json'; + +describe('Demo Flow Missing_Error_Handler_Fixed', () => { + let flow: Flow; + + before('arrange', () => { + // ARRANGE + flow = new Flow({ + path: './testfiles/Missing_Error_Handler_Fixed.flow-meta.flow', + xmldata: missingerrorhandler, + }); + }); + + it('Should have no result', () => { + 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/demo_missingFlowDescription.test.ts b/tests/demo_missingFlowDescription.test.ts new file mode 100644 index 0000000..4d6a965 --- /dev/null +++ b/tests/demo_missingFlowDescription.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 missingFlowDescription from './testfiles/MissingFlowDescription_Demo.json'; + +describe('In the Missing_Flow_Description flow', () => { + let flow: Flow; + + before('arrange', () => { + // ARRANGE + flow = new Flow({ + path: './testfiles/Missing_Flow_Description.flow', + xmldata: missingFlowDescription, + }); + }); + + it('there should be one result for the rule FlowDescription', () => { + + 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("FlowDescription"); + }); +}); \ No newline at end of file diff --git a/tests/duplicateDMLOperationsByNav_exception.test.ts b/tests/duplicateDMLOperationsByNav_exception.test.ts index f04225f..cb8a96b 100644 --- a/tests/duplicateDMLOperationsByNav_exception.test.ts +++ b/tests/duplicateDMLOperationsByNav_exception.test.ts @@ -16,11 +16,11 @@ describe('When scanning a screen flow with 2 DML statements between screens and }); }); - it('DuplicateDMLOperations should have 1 result', () => { + it('DuplicateDMLOperation should have 1 result', () => { const ruleConfig = { rules: { - DuplicateDMLOperations: + DuplicateDMLOperation: { severity: 'error', }, @@ -28,12 +28,12 @@ describe('When scanning a screen flow with 2 DML statements between screens and exceptions: { CreateANewAccountWithChild: - {"DuplicateDMLOperations":["ViewAccountId"]} + {"DuplicateDMLOperation":["ViewAccountId"]} } }; const results: ScanResult[] = scan([flow], ruleConfig); expect(results[0].ruleResults.length).to.equal(1); - expect(results[0].ruleResults[0].ruleName).to.equal('DuplicateDMLOperations'); + expect(results[0].ruleResults[0].ruleName).to.equal('DuplicateDMLOperation'); expect(results[0].ruleResults[0].details.length).to.equal(1); }); }); diff --git a/tests/duplicateDMLOperationsByNav_exceptiontwo.test.ts b/tests/duplicateDMLOperationsByNav_exceptiontwo.test.ts index 958ffa5..249d416 100644 --- a/tests/duplicateDMLOperationsByNav_exceptiontwo.test.ts +++ b/tests/duplicateDMLOperationsByNav_exceptiontwo.test.ts @@ -5,7 +5,7 @@ import { Flow } from '../src/main/models/Flow'; import { ScanResult } from '../src/main/models/ScanResult'; import CreateANewAccountWithChild from './testfiles/CreateANewAccountWithChild.json'; -describe('When scanning a screen flow with 2 DML statements between screens and both marked as exception', () => { +describe('strange', () => { let flow: Flow; before('arrange', () => { @@ -16,11 +16,11 @@ describe('When scanning a screen flow with 2 DML statements between screens and }); }); - it('DuplicateDMLOperations should have 0 results', () => { + it('error', () => { const ruleConfig = { rules: { - DuplicateDMLOperations: + DuplicateDMLOperation: { severity: 'error', }, @@ -33,13 +33,13 @@ describe('When scanning a screen flow with 2 DML statements between screens and exceptions: { CreateANewAccountWithChild: - { "DuplicateDMLOperations": ["ViewAccountId", "ViewAccountId_0"] } + { "DuplicateDMLOperation": ["ViewAccountId", "ViewAccountId_0"] } } }; const results: ScanResult[] = scan([flow], ruleConfig); const ruleResult = results[0].ruleResults.find( - (result) => result.ruleName === 'DuplicateDMLOperations' + (result) => result.ruleName === 'DuplicateDMLOperation' ); expect(ruleResult?.occurs).to.be.false; diff --git a/tests/duplicateDMLOperationsByNavigation_0InScreenFlow.test.ts b/tests/duplicateDMLOperationsByNavigation_0InScreenFlow.test.ts index 164b1dd..becd336 100644 --- a/tests/duplicateDMLOperationsByNavigation_0InScreenFlow.test.ts +++ b/tests/duplicateDMLOperationsByNavigation_0InScreenFlow.test.ts @@ -16,11 +16,11 @@ describe('When scanning a screen flow with 2 screens, a DML statement in between }); }); - it('DuplicateDMLOperations should have no result', () => { + it('DuplicateDMLOperation should have no result', () => { const ruleConfig = { rules: { - DuplicateDMLOperations: { + DuplicateDMLOperation: { severity: 'error', }, }, @@ -29,7 +29,7 @@ describe('When scanning a screen flow with 2 screens, a DML statement in between const results: ScanResult[] = scan([flow], ruleConfig); expect(results[0].ruleResults.length).to.equal(1); - expect(results[0].ruleResults[0].ruleName).to.equal('DuplicateDMLOperations'); + expect(results[0].ruleResults[0].ruleName).to.equal('DuplicateDMLOperation'); expect(results[0].ruleResults[0].details.length).to.equal(0); }); }); diff --git a/tests/duplicateDMLOperationsByNavigation_1InScreenFlow.test.ts b/tests/duplicateDMLOperationsByNavigation_1InScreenFlow.test.ts index 7ea7249..7c45a5b 100644 --- a/tests/duplicateDMLOperationsByNavigation_1InScreenFlow.test.ts +++ b/tests/duplicateDMLOperationsByNavigation_1InScreenFlow.test.ts @@ -16,11 +16,11 @@ describe('When scanning a screen flow with 2 screens, one DML statement in betwe }); }); - it('DuplicateDMLOperations should have a result', () => { + it('DuplicateDMLOperation should have a result', () => { const ruleConfig = { rules: { - DuplicateDMLOperations: { + DuplicateDMLOperation: { severity: 'error', }, }, @@ -29,7 +29,7 @@ describe('When scanning a screen flow with 2 screens, one DML statement in betwe const results: ScanResult[] = scan([flow], ruleConfig); expect(results[0].ruleResults.length).to.equal(1); - expect(results[0].ruleResults[0].ruleName).to.equal('DuplicateDMLOperations'); + expect(results[0].ruleResults[0].ruleName).to.equal('DuplicateDMLOperation'); expect(results[0].ruleResults[0].details.length).to.equal(1); }); }); diff --git a/tests/duplicateDMLOperationsByNavigation_1UnconnectedInScreenFlow.test.ts b/tests/duplicateDMLOperationsByNavigation_1UnconnectedInScreenFlow.test.ts index 5e1d005..847a8b7 100644 --- a/tests/duplicateDMLOperationsByNavigation_1UnconnectedInScreenFlow.test.ts +++ b/tests/duplicateDMLOperationsByNavigation_1UnconnectedInScreenFlow.test.ts @@ -16,11 +16,11 @@ describe('When scanning a screen flow with 2 screens, a DML statement in between }); }); - it('DuplicateDMLOperations should have no result', () => { + it('DuplicateDMLOperation should have no result', () => { const ruleConfig = { rules: { - DuplicateDMLOperations: { + DuplicateDMLOperation: { severity: 'error', }, }, @@ -29,7 +29,7 @@ describe('When scanning a screen flow with 2 screens, a DML statement in between const results: ScanResult[] = scan([flow], ruleConfig); expect(results[0].ruleResults.length).to.equal(1); - expect(results[0].ruleResults[0].ruleName).to.equal('DuplicateDMLOperations'); + expect(results[0].ruleResults[0].ruleName).to.equal('DuplicateDMLOperation'); expect(results[0].ruleResults[0].details.length).to.equal(0); }); }); diff --git a/tests/duplicateDMLOperationsByNavigation_2InScreenFlow.test.ts b/tests/duplicateDMLOperationsByNavigation_2InScreenFlow.test.ts index 92944b6..7f5f758 100644 --- a/tests/duplicateDMLOperationsByNavigation_2InScreenFlow.test.ts +++ b/tests/duplicateDMLOperationsByNavigation_2InScreenFlow.test.ts @@ -16,11 +16,11 @@ describe('When scanning a screen flow with 2 screens, a DML statement in between }); }); - it('DuplicateDMLOperations should have 2 results', () => { + it('DuplicateDMLOperation should have 2 results', () => { const ruleConfig = { rules: { - DuplicateDMLOperations: { + DuplicateDMLOperation: { severity: 'error', }, }, @@ -29,7 +29,7 @@ describe('When scanning a screen flow with 2 screens, a DML statement in between const results: ScanResult[] = scan([flow], ruleConfig); expect(results[0].ruleResults.length).to.equal(1); - expect(results[0].ruleResults[0].ruleName).to.equal('DuplicateDMLOperations'); + expect(results[0].ruleResults[0].ruleName).to.equal('DuplicateDMLOperation'); expect(results[0].ruleResults[0].details.length).to.equal(2); }); }); diff --git a/tests/duplicateDMLOperationsByNavigation_NoResult.test.ts b/tests/duplicateDMLOperationsByNavigation_NoResult.test.ts index 17d9319..0eb8393 100644 --- a/tests/duplicateDMLOperationsByNavigation_NoResult.test.ts +++ b/tests/duplicateDMLOperationsByNavigation_NoResult.test.ts @@ -16,11 +16,11 @@ describe('When scanning a screen flow with 2 screens, a DML statement in between }); }); - it('DuplicateDMLOperations should have no result', () => { + it('DuplicateDMLOperation should have no result', () => { const ruleConfig = { rules: { - DuplicateDMLOperations: { + DuplicateDMLOperation: { severity: 'error', }, }, diff --git a/tests/multipleRulesAndFlows.test.ts b/tests/multipleRulesAndFlows.test.ts index f1eb0b6..aebf9c6 100644 --- a/tests/multipleRulesAndFlows.test.ts +++ b/tests/multipleRulesAndFlows.test.ts @@ -28,11 +28,11 @@ describe('When scanning multiple flows', () => { const ruleConfig = { rules: { - DuplicateDMLOperations: + DuplicateDMLOperation: { severity: 'error', }, - UnusedVariables: + UnusedVariable: { severity: 'error', }, @@ -45,9 +45,9 @@ describe('When scanning multiple flows', () => { exceptions: { CreateANewAccountWithChild: - { "DuplicateDMLOperations": ["ViewAccountId", "ViewAccountId_0"] }, + { "DuplicateDMLOperation": ["ViewAccountId", "ViewAccountId_0"] }, CreateANewAccountImproved: - { "UnusedVariables": ["createAccount2"] } + { "UnusedVariable": ["createAccount2"] } } }; const results: ScanResult[] = scan(flows, ruleConfig); diff --git a/tests/noRulesProvided2.test.ts b/tests/noRulesProvided2.test.ts index 7eff7c2..5fc3996 100644 --- a/tests/noRulesProvided2.test.ts +++ b/tests/noRulesProvided2.test.ts @@ -27,7 +27,7 @@ describe('When running with empty object rules in the rule config', () => { exceptions: { CreateANewAccountWithChild: - {"DuplicateDMLOperations":["ViewAccountId"]} + {"DuplicateDMLOperation":["ViewAccountId"]} } }; const results: ScanResult[] = scan([flow], ruleConfig); diff --git a/tests/testfiles/CopyOfAPIName_Demo.json b/tests/testfiles/CopyOfAPIName_Demo.json new file mode 100644 index 0000000..c57304b --- /dev/null +++ b/tests/testfiles/CopyOfAPIName_Demo.json @@ -0,0 +1 @@ +{"Flow":{"$":{"xmlns":"http://soap.sforce.com/2006/04/metadata"},"apiVersion":["49.0"],"description":["This flow demonstrates a violation of the rule \"Copy of API Name\"."],"environments":["Default"],"interviewLabel":["Copy of API Name {!$Flow.CurrentDateTime}"],"label":["Copy of API Name"],"processMetadataValues":[{"name":["BuilderType"],"value":[{"stringValue":["LightningFlowBuilder"]}]},{"name":["CanvasMode"],"value":[{"stringValue":["AUTO_LAYOUT_CANVAS"]}]},{"name":["OriginBuilderType"],"value":[{"stringValue":["LightningFlowBuilder"]}]}],"processType":["Flow"],"screens":[{"name":["Copy_1_of_mockscreen"],"label":["Copy 1 of mockscreen"],"locationX":["176"],"locationY":["242"],"allowBack":["true"],"allowFinish":["true"],"allowPause":["true"],"showFooter":["true"],"showHeader":["true"]},{"name":["mockscreen"],"label":["mockscreen"],"locationX":["176"],"locationY":["134"],"allowBack":["true"],"allowFinish":["true"],"allowPause":["true"],"connector":[{"targetReference":["Copy_1_of_mockscreen"]}],"showFooter":["true"],"showHeader":["true"]}],"start":[{"locationX":["50"],"locationY":["0"],"connector":[{"targetReference":["mockscreen"]}]}],"status":["Active"]}} \ No newline at end of file diff --git a/tests/testfiles/DMLStatementInALoop_Demo.json b/tests/testfiles/DMLStatementInALoop_Demo.json new file mode 100644 index 0000000..3f00b8d --- /dev/null +++ b/tests/testfiles/DMLStatementInALoop_Demo.json @@ -0,0 +1,267 @@ +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "actionCalls": [ + { + "description": [ + "a Case failed to create automatically. Please examine its details manually." + ], + "name": [ + "create_case_manually" + ], + "label": [ + "create case manually" + ], + "locationX": [ + "528" + ], + "locationY": [ + "350" + ], + "actionName": [ + "FeedItem.NewTaskFromFeedItem" + ], + "actionType": [ + "quickAction" + ], + "flowTransactionModel": [ + "CurrentTransaction" + ], + "inputParameters": [ + { + "name": [ + "contextId" + ], + "value": [ + { + "elementReference": [ + "createCase.OwnerId" + ] + } + ] + } + ], + "nameSegment": [ + "FeedItem.NewTaskFromFeedItem" + ], + "versionSegment": [ + "1" + ] + } + ], + "apiVersion": [ + "58.0" + ], + "description": [ + "This flow demonstrates a violation of the rule \"DML statements in a loop\"." + ], + "environments": [ + "Default" + ], + "interviewLabel": [ + "DML statements in a loop {!$Flow.CurrentDateTime}" + ], + "label": [ + "DML statements in a loop" + ], + "loops": [ + { + "description": [ + "an example loop" + ], + "name": [ + "aLoop" + ], + "label": [ + "aLoop" + ], + "locationX": [ + "176" + ], + "locationY": [ + "134" + ], + "collectionReference": [ + "Accounts" + ], + "iterationOrder": [ + "Asc" + ], + "nextValueConnector": [ + { + "targetReference": [ + "createNewCase" + ] + } + ] + } + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "AUTO_LAYOUT_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "AutoLaunchedFlow" + ], + "recordCreates": [ + { + "description": [ + "creates a new case" + ], + "name": [ + "createNewCase" + ], + "label": [ + "createNewCase" + ], + "locationX": [ + "264" + ], + "locationY": [ + "242" + ], + "connector": [ + { + "targetReference": [ + "aLoop" + ] + } + ], + "faultConnector": [ + { + "targetReference": [ + "create_case_manually" + ] + } + ], + "inputReference": [ + "createCase" + ] + } + ], + "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": [ + "createCase" + ], + "dataType": [ + "SObject" + ], + "isCollection": [ + "false" + ], + "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/DuplicateDMLOperation_Demo.json b/tests/testfiles/DuplicateDMLOperation_Demo.json new file mode 100644 index 0000000..ea6fae0 --- /dev/null +++ b/tests/testfiles/DuplicateDMLOperation_Demo.json @@ -0,0 +1 @@ +{"Flow":{"$":{"xmlns":"http://soap.sforce.com/2006/04/metadata"},"actionCalls":[{"name":["create_account_manually"],"label":["create account manually"],"locationX":["440"],"locationY":["350"],"actionName":["FeedItem.NewTaskFromFeedItem"],"actionType":["quickAction"],"flowTransactionModel":["CurrentTransaction"],"inputParameters":[{"name":["contextId"],"value":[{"elementReference":["$User.Id"]}]}],"nameSegment":["FeedItem.NewTaskFromFeedItem"],"versionSegment":["1"]}],"apiVersion":["58.0"],"description":["This flow demonstrates a violation of the rule \"Duplicate DML operations\"."],"environments":["Default"],"interviewLabel":["Duplicate DML operations {!$Flow.CurrentDateTime}"],"label":["Duplicate DML operations"],"processMetadataValues":[{"name":["BuilderType"],"value":[{"stringValue":["LightningFlowBuilder"]}]},{"name":["CanvasMode"],"value":[{"stringValue":["AUTO_LAYOUT_CANVAS"]}]},{"name":["OriginBuilderType"],"value":[{"stringValue":["LightningFlowBuilder"]}]}],"processType":["Flow"],"recordCreates":[{"name":["createAccount"],"label":["createAccount"],"locationX":["176"],"locationY":["242"],"connector":[{"targetReference":["mock_screen_2"]}],"faultConnector":[{"targetReference":["create_account_manually"]}],"inputAssignments":[{"field":["Name"],"value":[{"elementReference":["account_name"]}]}],"object":["Account"],"storeOutputAutomatically":["true"]}],"screens":[{"name":["mock_screen_1"],"label":["mock screen 1"],"locationX":["176"],"locationY":["134"],"allowBack":["true"],"allowFinish":["true"],"allowPause":["true"],"connector":[{"targetReference":["createAccount"]}],"fields":[{"name":["account_name"],"dataType":["String"],"fieldText":["account name"],"fieldType":["InputField"],"isRequired":["false"]}],"showFooter":["true"],"showHeader":["true"]},{"name":["mock_screen_2"],"label":["mock screen 2"],"locationX":["176"],"locationY":["350"],"allowBack":["true"],"allowFinish":["true"],"allowPause":["true"],"showFooter":["true"],"showHeader":["true"]}],"start":[{"locationX":["50"],"locationY":["0"],"connector":[{"targetReference":["mock_screen_1"]}]}],"status":["Active"],"variables":[{"name":["Account"],"dataType":["SObject"],"isCollection":["false"],"isInput":["false"],"isOutput":["false"],"objectType":["Account"]}]}} \ No newline at end of file diff --git a/tests/testfiles/FlowNamingConvention_Demo.json b/tests/testfiles/FlowNamingConvention_Demo.json new file mode 100644 index 0000000..01c823c --- /dev/null +++ b/tests/testfiles/FlowNamingConvention_Demo.json @@ -0,0 +1,154 @@ +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "apiVersion": [ + "58.0" + ], + "decisions": [ + { + "name": [ + "isAccountsProvided" + ], + "label": [ + "isAccountsProvided" + ], + "locationX": [ + "176" + ], + "locationY": [ + "134" + ], + "defaultConnectorLabel": [ + "Default Outcome" + ], + "rules": [ + { + "name": [ + "NotNull" + ], + "conditionLogic": [ + "and" + ], + "conditions": [ + { + "leftValueReference": [ + "Accounts" + ], + "operator": [ + "IsNull" + ], + "rightValue": [ + { + "booleanValue": [ + "false" + ] + } + ] + } + ], + "label": [ + "NotNull" + ] + } + ] + } + ], + "description": [ + "This flow demonstrates a violation of the rule \"Flow naming conventions\"." + ], + "environments": [ + "Default" + ], + "interviewLabel": [ + "Flow naming conventions {!$Flow.CurrentDateTime}" + ], + "label": [ + "Flow naming conventions" + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "AUTO_LAYOUT_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "AutoLaunchedFlow" + ], + "start": [ + { + "locationX": [ + "50" + ], + "locationY": [ + "0" + ], + "connector": [ + { + "targetReference": [ + "isAccountsProvided" + ] + } + ] + } + ], + "status": [ + "Active" + ], + "variables": [ + { + "name": [ + "Accounts" + ], + "dataType": [ + "SObject" + ], + "isCollection": [ + "true" + ], + "isInput": [ + "true" + ], + "isOutput": [ + "false" + ], + "objectType": [ + "Account" + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/testfiles/HardcodedID_Demo.json b/tests/testfiles/HardcodedID_Demo.json new file mode 100644 index 0000000..c29dc35 --- /dev/null +++ b/tests/testfiles/HardcodedID_Demo.json @@ -0,0 +1,205 @@ +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "actionCalls": [ + { + "name": [ + "create_account_manually" + ], + "label": [ + "create account manually" + ], + "locationX": [ + "440" + ], + "locationY": [ + "242" + ], + "actionName": [ + "FeedItem.NewTaskFromFeedItem" + ], + "actionType": [ + "quickAction" + ], + "flowTransactionModel": [ + "CurrentTransaction" + ], + "inputParameters": [ + { + "name": [ + "contextId" + ], + "value": [ + { + "elementReference": [ + "$User.Id" + ] + } + ] + } + ], + "nameSegment": [ + "FeedItem.NewTaskFromFeedItem" + ], + "versionSegment": [ + "1" + ] + } + ], + "apiVersion": [ + "58.0" + ], + "description": [ + "This flow demonstrates a violation of the rule \"Hardcoded Ids\"." + ], + "environments": [ + "Default" + ], + "interviewLabel": [ + "Hardcoded Ids {!$Flow.CurrentDateTime}" + ], + "label": [ + "Hardcoded Ids" + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "AUTO_LAYOUT_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "AutoLaunchedFlow" + ], + "recordCreates": [ + { + "name": [ + "create_Test_Account" + ], + "label": [ + "create Test Account" + ], + "locationX": [ + "176" + ], + "locationY": [ + "134" + ], + "faultConnector": [ + { + "targetReference": [ + "create_account_manually" + ] + } + ], + "inputAssignments": [ + { + "field": [ + "Name" + ], + "value": [ + { + "stringValue": [ + "Test Account" + ] + } + ] + }, + { + "field": [ + "OwnerId" + ], + "value": [ + { + "stringValue": [ + "005Dn000006GuG4IAK" + ] + } + ] + } + ], + "object": [ + "Account" + ], + "storeOutputAutomatically": [ + "true" + ] + } + ], + "start": [ + { + "locationX": [ + "50" + ], + "locationY": [ + "0" + ], + "connector": [ + { + "targetReference": [ + "create_Test_Account" + ] + } + ] + } + ], + "status": [ + "Active" + ], + "variables": [ + { + "name": [ + "Account" + ], + "dataType": [ + "SObject" + ], + "isCollection": [ + "false" + ], + "isInput": [ + "false" + ], + "isOutput": [ + "false" + ], + "objectType": [ + "Account" + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/testfiles/MissingFlowDescription_Demo.json b/tests/testfiles/MissingFlowDescription_Demo.json new file mode 100644 index 0000000..3edda97 --- /dev/null +++ b/tests/testfiles/MissingFlowDescription_Demo.json @@ -0,0 +1,139 @@ +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "apiVersion": [ + "58.0" + ], + "assignments": [ + { + "name": [ + "do_Anything" + ], + "label": [ + "do Anything" + ], + "locationX": [ + "176" + ], + "locationY": [ + "134" + ], + "assignmentItems": [ + { + "assignToReference": [ + "text" + ], + "operator": [ + "Assign" + ], + "value": [ + { + "stringValue": [ + "new text" + ] + } + ] + } + ] + } + ], + "environments": [ + "Default" + ], + "interviewLabel": [ + "Missing flow description {!$Flow.CurrentDateTime}" + ], + "label": [ + "Missing flow description" + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "AUTO_LAYOUT_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "AutoLaunchedFlow" + ], + "start": [ + { + "locationX": [ + "50" + ], + "locationY": [ + "0" + ], + "connector": [ + { + "targetReference": [ + "do_Anything" + ] + } + ] + } + ], + "status": [ + "Active" + ], + "variables": [ + { + "name": [ + "text" + ], + "dataType": [ + "String" + ], + "isCollection": [ + "false" + ], + "isInput": [ + "false" + ], + "isOutput": [ + "false" + ], + "value": [ + { + "stringValue": [ + "any text" + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/testfiles/missingerrorhandler.json b/tests/testfiles/missingerrorhandler.json new file mode 100644 index 0000000..d6a5972 --- /dev/null +++ b/tests/testfiles/missingerrorhandler.json @@ -0,0 +1,114 @@ +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "actionCalls": [ + { + "name": [ + "log_call" + ], + "label": [ + "log call" + ], + "locationX": [ + "176" + ], + "locationY": [ + "134" + ], + "actionName": [ + "LogACall" + ], + "actionType": [ + "quickAction" + ], + "flowTransactionModel": [ + "CurrentTransaction" + ], + "nameSegment": [ + "LogACall" + ], + "versionSegment": [ + "1" + ] + } + ], + "apiVersion": [ + "58.0" + ], + "description": [ + "This flow demonstrates a violation of the rule \"DML statements in a loop\"." + ], + "environments": [ + "Default" + ], + "interviewLabel": [ + "Missing error handlers {!$Flow.CurrentDateTime}" + ], + "label": [ + "Missing error handlers" + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "AUTO_LAYOUT_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "AutoLaunchedFlow" + ], + "start": [ + { + "locationX": [ + "50" + ], + "locationY": [ + "0" + ], + "connector": [ + { + "targetReference": [ + "log_call" + ] + } + ] + } + ], + "status": [ + "Active" + ] + } +} \ No newline at end of file diff --git a/tests/testfiles/missingerrorhandler_fixed.json b/tests/testfiles/missingerrorhandler_fixed.json new file mode 100644 index 0000000..bb79dc6 --- /dev/null +++ b/tests/testfiles/missingerrorhandler_fixed.json @@ -0,0 +1,164 @@ +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "actionCalls": [ + { + "name": [ + "dosomethingelse" + ], + "label": [ + "dosomethingelse" + ], + "locationX": [ + "440" + ], + "locationY": [ + "242" + ], + "actionName": [ + "NewTask" + ], + "actionType": [ + "quickAction" + ], + "flowTransactionModel": [ + "CurrentTransaction" + ], + "inputParameters": [ + { + "name": [ + "OwnerId" + ], + "value": [ + { + "elementReference": [ + "$User.Id" + ] + } + ] + } + ], + "nameSegment": [ + "NewTask" + ], + "versionSegment": [ + "1" + ] + }, + { + "name": [ + "log_call" + ], + "label": [ + "log call" + ], + "locationX": [ + "176" + ], + "locationY": [ + "134" + ], + "actionName": [ + "LogACall" + ], + "actionType": [ + "quickAction" + ], + "faultConnector": [ + { + "targetReference": [ + "dosomethingelse" + ] + } + ], + "flowTransactionModel": [ + "CurrentTransaction" + ], + "nameSegment": [ + "LogACall" + ], + "versionSegment": [ + "1" + ] + } + ], + "apiVersion": [ + "58.0" + ], + "description": [ + "This flow demonstrates how to resolve a violation of the rule \"Missing Error Handler\"." + ], + "environments": [ + "Default" + ], + "interviewLabel": [ + "Fix: Missing Error Handler {!$Flow.CurrentDateTime}" + ], + "label": [ + "Fix: Missing Error Handler" + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "AUTO_LAYOUT_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "AutoLaunchedFlow" + ], + "start": [ + { + "locationX": [ + "50" + ], + "locationY": [ + "0" + ], + "connector": [ + { + "targetReference": [ + "log_call" + ] + } + ] + } + ], + "status": [ + "Active" + ] + } +} \ No newline at end of file