diff --git a/docs/defaultrules.md b/docs/defaultrules.md deleted file mode 100644 index a353fbd..0000000 --- a/docs/defaultrules.md +++ /dev/null @@ -1,144 +0,0 @@ -# Default Rule Definitions - -- [Outdated API Version](#outdated-api-version) -- [Copy API Name](#copy-api-name) -- [DML Statement In A Loop](#dml-statement-in-a-loop) -- [Duplicate DML Operation](#duplicate-dml-operation) -- [Missing Flow Description](#missing-flow-description) -- [Flow Naming Convention](#flow-naming-convention) -- [Hardcoded Id](#hardcoded-id) -- [Missing Fault Path](#missing-fault-path) -- [Missing Null Handler](#missing-null-handler) -- [SOQL Query In A Loop](#soql-query-in-a-loop) -- [Unconnected Element](#unconnected-element) -- [Unused Variable](#unused-variable) - -___ - -### Outdated API Version - -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: `>49.0`_ - -_Configuration example:_ -``` -APIVersion: - { - severity: 'error', - expression: '===58' - } -``` - -**Configuration ID: `APIVersion`** -_([View source code](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/APIVersion.ts))_ - -___ - -### Copy API Name - -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: `CopyAPIName`** -_([View source code](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/CopyAPIName.ts))_ - -___ - -### DML Statement In A Loop - -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/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/DMLStatementInLoop.ts))_ - -___ - -### Duplicate DML Operation - -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: `DuplicateDMLOperation`** -_([View source code](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/DuplicateDMLOperation.ts))_ - -___ - -### Missing Flow Description - -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/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/FlowDescription.ts))_ - -___ - -### Flow Naming Convention - -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]+`_ - -_Configuration example:_ -``` -FlowName: - { - severity: 'error', - expression: '[A-Za-z0-9]' - } -``` - -**Configuration ID: `FlowName`** -_([View source code](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/FlowName.ts))_ - -___ - -### Hardcoded Id - -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: `HardcodedId`** -_([View source code](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/HardcodedId.ts))_ - -___ - -### Missing Fault Path - -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/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/MissingFaultPath.ts))_ - -___ - -### Missing Null Handler - -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/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/MissingNullHandler.ts))_ - -___ - -### 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/Lightning-Flow-Scanner/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. - -**Configuration ID: `UnconnectedElement`** -_([View source code](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/UnconnectedElement.ts))_ - -___ - -### Unused Variable - -To maintain the efficiency and manageability of your Flow, it's advisable to avoid including unconnected variables that are not in use. - -**Configuration ID: `UnusedVariable`** -_([View source code](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/UnusedVariable.ts))_ diff --git a/package.json b/package.json index 828e54d..1369abc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lightning-flow-scanner-core", - "version": "3.7.0", + "version": "3.8.0", "main": "out/**", "types": "index.d.ts", "scripts": { diff --git a/readme.md b/readme.md index 3de282f..b275166 100644 --- a/readme.md +++ b/readme.md @@ -11,22 +11,22 @@ _An Extensible Rule Engine for Salesforce Flows used by the Lightning Flow Scann ## Default Rules -| Rule | Description | -|--------------|:-----------| -| **Outdated API Version** | 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. | -| **Copy API Name** | 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. | -| **DML Statement In A Loop** | 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. | -| **Duplicate DML Operation** | 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. | -| **Hardcoded Id** | 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. | -| **Flow Naming Convention** | 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'. | -| **Missing Flow Description** | Descriptions play a vital role in documentation. We highly recommend including details about where they are used and their intended purpose. | -| **Missing Fault Path** | 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. | -| **Missing Null Handler** | 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. | -| **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. | -| **Unconnected Element** | Unconnected elements which are not being used by the Flow should be avoided to keep Flows efficient and maintainable. | -| **Unused Variable** | To maintain the efficiency and manageability of your Flow, it's advisable to avoid including unconnected variables that are not in use. | - -For more details on the ruleset and its configurability see _[Default Rule Definitions](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/docs/defaultrules.md)_ +| Rule (Configuration ID) | Description | +|--------------------------|-------------| +| **Auto Layout** ([`AutoLayout`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/AutoLayout.ts)) | With Canvas Mode set to Auto-Layout, Elements are spaced, connected, and aligned automatically, keeping your Flow neatly organized thus saving you time. | +| **Outdated API Version* ([`APIVersion`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/APIVersion.ts)) | 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. | +| **Copy API Name** ([`CopyAPIName`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/CopyAPIName.ts)) | 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. | +| **DML Statement In A Loop** ([`DMLStatementInLoop`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/DMLStatementInLoop.ts)) | 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. | +| **Duplicate DML Operation** ([`DuplicateDMLOperation`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/DuplicateDMLOperation.ts)) | 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. | +| **Hardcoded Id** ([`HardcodedId`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/HardcodedId.ts)) | 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. | +| **Flow Naming Convention** ([`FlowName`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/FlowName.ts)) | 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'. | +| **Missing Flow Description** ([`FlowDescription`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/FlowDescription.ts)) | Descriptions play a vital role in documentation. We highly recommend including details about where they are used and their intended purpose. | +| **Missing Fault Path** ([`MissingFaultPath`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/MissingFaultPath.ts)) | 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. | +| **Missing Null Handler** ([`MissingNullHandler`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/MissingNullHandler.ts)) | 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. | +| **SOQL Query In A Loop** ([`SOQLQueryInLoop`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/SOQLQueryInLoop.ts)) | To prevent exceeding Apex governor limits, it is advisable to consolidate all your SOQL queries at the conclusion of the flow. | +| **Unconnected Element** ([`UnconnectedElement`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/UnconnectedElement.ts)) | Unconnected elements which are not being used by the Flow should be avoided to keep Flows efficient and maintainable. | +| **Unused Variable** ([`UnusedVariable`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/UnusedVariable.ts)) | To maintain the efficiency and manageability of your Flow, it's advisable to avoid including unconnected variables that are not in use. | +| **Process Builder** ([`ProcessBuilder`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/ProcessBuilder.ts)) | Salesforce is transitioning away from Workflow Rules and Process Builder in favor of Flow. Ensure you're prepared for this transition by migrating your organization's automation to Flow. Refer to official documentation for more information on the transition process and tools available. | ## Configurations diff --git a/src/main/interfaces/IRuleDefinition.ts b/src/main/interfaces/IRuleDefinition.ts index a1b6e93..a11c3f5 100644 --- a/src/main/interfaces/IRuleDefinition.ts +++ b/src/main/interfaces/IRuleDefinition.ts @@ -6,7 +6,6 @@ export interface IRuleDefinition { label: string; description: string; supportedTypes: string[]; - type: string; docRefs: { label: string, path: string }[]; isConfigurable: boolean; autoFixable: boolean; diff --git a/src/main/models/RuleCommon.ts b/src/main/models/RuleCommon.ts index 69daac8..c7644ff 100644 --- a/src/main/models/RuleCommon.ts +++ b/src/main/models/RuleCommon.ts @@ -9,7 +9,6 @@ export class RuleCommon { public docRefs: { label: string, path: string }[] = []; public description: string; public supportedTypes: string[]; - public type: string; public isConfigurable: boolean; public autoFixable: boolean; @@ -20,7 +19,6 @@ export class RuleCommon { } ) { this.name = info.name; - this.type = info.type; this.supportedTypes = info.supportedTypes; this.label = info.label; this.description = info.description; diff --git a/src/main/models/RuleInfo.ts b/src/main/models/RuleInfo.ts index f47c718..a930dbe 100644 --- a/src/main/models/RuleInfo.ts +++ b/src/main/models/RuleInfo.ts @@ -3,7 +3,6 @@ export class RuleInfo { public name: string; public label: string; public description: string; - public type: string; public supportedTypes: string[]; public docRefs: {label: string, path: string}[]; public isConfigurable: boolean; diff --git a/src/main/rules/APIVersion.ts b/src/main/rules/APIVersion.ts index 7247073..efa8680 100644 --- a/src/main/rules/APIVersion.ts +++ b/src/main/rules/APIVersion.ts @@ -8,7 +8,6 @@ export class APIVersion extends RuleCommon implements core.IRuleDefinition { name: 'APIVersion', 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: core.FlowType.allTypes(), docRefs: [], isConfigurable: true, diff --git a/src/main/rules/AutoLayout.ts b/src/main/rules/AutoLayout.ts new file mode 100644 index 0000000..f05493b --- /dev/null +++ b/src/main/rules/AutoLayout.ts @@ -0,0 +1,29 @@ +import * as core from '../internals/internals'; +import { RuleCommon } from '../models/RuleCommon'; + +export class AutoLayout extends RuleCommon implements core.IRuleDefinition { + + constructor() { + super({ + name: 'AutoLayout', + label: 'Use Auto-Layout Mode', + description: "With Canvas Mode set to Auto-Layout, Elements are spaced, connected, and aligned automatically, keeping your Flow neatly organized thus saving you time.", + supportedTypes: core.FlowType.allTypes(), + docRefs: [], + isConfigurable: true, + autoFixable: false + }); + } + + public execute(flow: core.Flow, options?: { expression: string }): core.RuleResult { + + if (flow.xmldata.processMetadataValues && flow.xmldata.processMetadataValues[0]) { + + const CanvasMode = flow.xmldata.processMetadataValues.find(mdv => mdv.name[0] === 'CanvasMode'); + const AutoLayout = CanvasMode.value[0] && typeof CanvasMode.value[0] === 'object' && 'stringValue' in CanvasMode.value[0] && Array.isArray(CanvasMode.value[0].stringValue) && CanvasMode.value[0].stringValue[0] === "AUTO_LAYOUT_CANVAS"; + return (!AutoLayout ? + new core.RuleResult(this, [new core.ResultDetails(new core.FlowAttribute(CanvasMode.value[0]?.stringValue[0], "CanvasMode", '!== AUTO_LAYOUT_CANVAS'))]) : + new core.RuleResult(this, [])); + } + } +} \ No newline at end of file diff --git a/src/main/rules/CopyAPIName.ts b/src/main/rules/CopyAPIName.ts index 46df605..b210023 100644 --- a/src/main/rules/CopyAPIName.ts +++ b/src/main/rules/CopyAPIName.ts @@ -8,7 +8,6 @@ export class CopyAPIName extends RuleCommon implements core.IRuleDefinition { name: 'CopyAPIName', label: 'Copy API Name', 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: core.FlowType.allTypes(), docRefs: [], isConfigurable: false, diff --git a/src/main/rules/DMLStatementInLoop.ts b/src/main/rules/DMLStatementInLoop.ts index 66d6b8d..e340ec7 100644 --- a/src/main/rules/DMLStatementInLoop.ts +++ b/src/main/rules/DMLStatementInLoop.ts @@ -8,7 +8,6 @@ export class DMLStatementInLoop extends RuleCommon implements core.IRuleDefiniti name: 'DMLStatementInLoop', label: 'DML Statement In A Loop', 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: core.FlowType.backEndTypes, docRefs: [{ 'label': 'Flow Best Practices', 'path': 'https://help.salesforce.com/s/articleView?id=sf.flow_prep_bestpractices.htm&type=5' }], isConfigurable: false, diff --git a/src/main/rules/DuplicateDMLOperation.ts b/src/main/rules/DuplicateDMLOperation.ts index e331d25..7325378 100644 --- a/src/main/rules/DuplicateDMLOperation.ts +++ b/src/main/rules/DuplicateDMLOperation.ts @@ -8,7 +8,6 @@ export class DuplicateDMLOperation extends RuleCommon implements core.IRuleDefin 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: core.FlowType.visualTypes, docRefs: [], isConfigurable: false, diff --git a/src/main/rules/FlowDescription.ts b/src/main/rules/FlowDescription.ts index 0f630d0..87f5013 100644 --- a/src/main/rules/FlowDescription.ts +++ b/src/main/rules/FlowDescription.ts @@ -8,7 +8,6 @@ export class FlowDescription extends RuleCommon implements core.IRuleDefinition name: 'FlowDescription', 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: [...core.FlowType.backEndTypes, ...core.FlowType.visualTypes], docRefs: [], isConfigurable: false, diff --git a/src/main/rules/FlowName.ts b/src/main/rules/FlowName.ts index 5ecb890..0157645 100644 --- a/src/main/rules/FlowName.ts +++ b/src/main/rules/FlowName.ts @@ -8,7 +8,6 @@ export class FlowName extends RuleCommon implements core.IRuleDefinition { name: 'FlowName', label: 'Flow Naming Convention', 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: core.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' }], isConfigurable: true, diff --git a/src/main/rules/HardcodedId.ts b/src/main/rules/HardcodedId.ts index a618235..9287324 100644 --- a/src/main/rules/HardcodedId.ts +++ b/src/main/rules/HardcodedId.ts @@ -9,7 +9,6 @@ export class HardcodedId extends RuleCommon implements core.IRuleDefinition { 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: core.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'}], isConfigurable: false, diff --git a/src/main/rules/MissingFaultPath.ts b/src/main/rules/MissingFaultPath.ts index e0e4a89..793a0b4 100644 --- a/src/main/rules/MissingFaultPath.ts +++ b/src/main/rules/MissingFaultPath.ts @@ -7,7 +7,6 @@ export class MissingFaultPath extends RuleCommon implements core.IRuleDefinition name: 'MissingFaultPath', label: 'Missing Fault Path', 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: [...core.FlowType.backEndTypes, ...core.FlowType.visualTypes], docRefs: [{ label: 'Flow Best Practices', path: 'https://help.salesforce.com/s/articleView?id=sf.flow_prep_bestpractices.htm&type=5' }], isConfigurable: false, diff --git a/src/main/rules/MissingNullHandler.ts b/src/main/rules/MissingNullHandler.ts index 113e625..7ed6b73 100644 --- a/src/main/rules/MissingNullHandler.ts +++ b/src/main/rules/MissingNullHandler.ts @@ -8,7 +8,6 @@ export class MissingNullHandler extends RuleCommon implements core.IRuleDefiniti name: 'MissingNullHandler', 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: [...core.FlowType.backEndTypes, ...core.FlowType.visualTypes], docRefs: [], isConfigurable: false, diff --git a/src/main/rules/ProcessBuilder.ts b/src/main/rules/ProcessBuilder.ts new file mode 100644 index 0000000..b292f17 --- /dev/null +++ b/src/main/rules/ProcessBuilder.ts @@ -0,0 +1,22 @@ +import * as core from '../internals/internals'; +import { RuleCommon } from '../models/RuleCommon'; + +export class ProcessBuilder extends RuleCommon implements core.IRuleDefinition { + + constructor() { + super({ + name: 'ProcessBuilder', + label: 'Do not use Process Builder', + description: "Salesforce is transitioning away from Workflow Rules and Process Builder in favor of Flow. Ensure you're prepared for this transition by migrating your organization's automation to Flow. Refer to official documentation for more information on the transition process and tools available.", + supportedTypes: core.FlowType.processBuilder, + docRefs: [{'label': 'Process Builder Retirement', 'path': 'https://help.salesforce.com/s/articleView?id=000389396&type=1'}], + isConfigurable: true, + autoFixable: false + }); + } + + public execute(flow: core.Flow, options?: { expression: string }): core.RuleResult { + + return new core.RuleResult(this, [new core.ResultDetails(new core.FlowAttribute('Workflow', "processType", '== Workflow'))]) + } +} \ No newline at end of file diff --git a/src/main/rules/SOQLQueryInLoop.ts b/src/main/rules/SOQLQueryInLoop.ts index 5b61866..d761e55 100644 --- a/src/main/rules/SOQLQueryInLoop.ts +++ b/src/main/rules/SOQLQueryInLoop.ts @@ -8,7 +8,6 @@ export class SOQLQueryInLoop extends RuleCommon implements core.IRuleDefinition 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: core.FlowType.backEndTypes, docRefs: [{ 'label': 'Flow Best Practices', 'path': 'https://help.salesforce.com/s/articleView?id=sf.flow_prep_bestpractices.htm&type=5' }], isConfigurable: false, diff --git a/src/main/rules/UnconnectedElement.ts b/src/main/rules/UnconnectedElement.ts index b1ffeb8..1309724 100644 --- a/src/main/rules/UnconnectedElement.ts +++ b/src/main/rules/UnconnectedElement.ts @@ -8,7 +8,6 @@ export class UnconnectedElement extends RuleCommon implements core.IRuleDefiniti 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: [...core.FlowType.backEndTypes, ...core.FlowType.visualTypes], docRefs: [], isConfigurable: false, diff --git a/src/main/rules/UnusedVariable.ts b/src/main/rules/UnusedVariable.ts index 979674b..d74b111 100644 --- a/src/main/rules/UnusedVariable.ts +++ b/src/main/rules/UnusedVariable.ts @@ -8,7 +8,6 @@ export class UnusedVariable extends RuleCommon implements core.IRuleDefinition { 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: [...core.FlowType.backEndTypes, ...core.FlowType.visualTypes], docRefs: [], isConfigurable: false, diff --git a/src/main/store/DefaultRuleStore.ts b/src/main/store/DefaultRuleStore.ts index 361ea81..a35016f 100644 --- a/src/main/store/DefaultRuleStore.ts +++ b/src/main/store/DefaultRuleStore.ts @@ -1,4 +1,5 @@ import { APIVersion } from '../rules/APIVersion'; +import { AutoLayout } from '../rules/AutoLayout'; import { CopyAPIName } from '../rules/CopyAPIName'; import { DMLStatementInLoop } from '../rules/DMLStatementInLoop'; import { DuplicateDMLOperation } from '../rules/DuplicateDMLOperation'; @@ -13,6 +14,7 @@ import { UnusedVariable } from '../rules/UnusedVariable'; export const DefaultRuleStore: {} = { APIVersion, + AutoLayout, CopyAPIName, DMLStatementInLoop, DuplicateDMLOperation, diff --git a/tests/AutoLayout.test.ts b/tests/AutoLayout.test.ts new file mode 100644 index 0000000..6161e43 --- /dev/null +++ b/tests/AutoLayout.test.ts @@ -0,0 +1,53 @@ +import { assert, expect } from 'chai'; +import 'mocha'; +import * as core from '../src' +import Hidenav from './testfiles/hidenav.json'; +import Api58 from './testfiles/api58test.json'; + +describe('Autolayout', () => { + let flow: core.Flow; + + it(' should have a result when CanvasMode is set to FREE_FORM_CANVAS', () => { + flow = new core.Flow({ + path: './testfiles/Hidenav.flow-meta.xml', + xmldata: Hidenav, + }); + const ruleConfig = { + rules: + { + AutoLayout: + { + severity: 'error' + }, + } + }; + + const results: core.ScanResult[] = core.scan([flow], ruleConfig); + const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs); + expect(occurringResults.length).to.equal(1); + expect(occurringResults.find((res) => res.ruleName === 'AutoLayout')); + + }); + + it('should not have result when autolayout is configured', () => { + + flow = new core.Flow({ + path: './testfiles/Api58.flow-meta.xml', + xmldata: Api58, + }); + + const ruleConfig = { + rules: + { + AutoLayout: + { + severity: 'error' + }, + } + }; + const results: core.ScanResult[] = core.scan([flow], ruleConfig); + const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs); + expect(occurringResults.length).to.equal(0); + + }); +}); diff --git a/tests/testfiles/api58test.json b/tests/testfiles/api58test.json index 1d374b4..52c6b26 100644 --- a/tests/testfiles/api58test.json +++ b/tests/testfiles/api58test.json @@ -1 +1,368 @@ -{"Flow":{"$":{"xmlns":"http://soap.sforce.com/2006/04/metadata"},"apiVersion":["53.0"],"collectionProcessors":[{"name":["filter_test_accounts"],"elementSubtype":["FilterCollectionProcessor"],"label":["filter test accounts"],"locationX":["176"],"locationY":["134"],"assignNextValueToReference":["currentItem_af_0"],"collectionProcessorType":["FilterCollectionProcessor"],"collectionReference":["c"],"conditionLogic":["and"],"conditions":[{"leftValueReference":["currentItem_af_0.Name"],"operator":["Contains"],"rightValue":[{"stringValue":["test"]}]}],"connector":[{"targetReference":["sort_acc"]}]},{"name":["sort_acc"],"elementSubtype":["SortCollectionProcessor"],"label":["sort acc"],"locationX":["176"],"locationY":["242"],"collectionProcessorType":["SortCollectionProcessor"],"collectionReference":["filter_test_accounts"],"connector":[{"targetReference":["rb"]}],"sortOptions":[{"doesPutEmptyStringAndNullFirst":["false"],"sortField":["AccountNumber"],"sortOrder":["Asc"]}]}],"environments":["Default"],"interviewLabel":["api58test {!$Flow.CurrentDateTime}"],"label":["api58test"],"processMetadataValues":[{"name":["BuilderType"],"value":[{"stringValue":["LightningFlowBuilder"]}]},{"name":["CanvasMode"],"value":[{"stringValue":["AUTO_LAYOUT_CANVAS"]}]},{"name":["OriginBuilderType"],"value":[{"stringValue":["LightningFlowBuilder"]}]}],"processType":["Flow"],"recordRollbacks":[{"name":["rb"],"label":["rb"],"locationX":["176"],"locationY":["350"],"connector":[{"targetReference":["aa"]}]}],"screens":[{"name":["aa"],"label":["aa"],"locationX":["176"],"locationY":["458"],"allowBack":["true"],"allowFinish":["true"],"allowPause":["true"],"fields":[{"name":["bb"],"dataTypeMappings":[{"typeName":["T"],"typeValue":["Account"]}],"extensionName":["flowruntime:datatable"],"fieldType":["ComponentInstance"],"inputParameters":[{"name":["label"],"value":[{"stringValue":["Data Table"]}]},{"name":["selectionMode"],"value":[{"stringValue":["MULTI_SELECT"]}]},{"name":["minRowSelection"],"value":[{"numberValue":["0.0"]}]},{"name":["tableData"],"value":[{"elementReference":["filter_test_accounts"]}]},{"name":["columns"],"value":[{"stringValue":["[{\"apiName\":\"AccountNumber\",\"guid\":\"column-8e94\",\"editable\":false,\"hasCustomHeaderLabel\":false,\"customHeaderLabel\":\"\",\"wrapText\":true,\"order\":0,\"label\":\"Account Number\",\"type\":\"text\"}]"]}]}],"inputsOnNextNavToAssocScrn":["UseStoredValues"],"isRequired":["true"],"storeOutputAutomatically":["true"]}],"showFooter":["true"],"showHeader":["true"]}],"start":[{"locationX":["50"],"locationY":["0"],"connector":[{"targetReference":["filter_test_accounts"]}]}],"status":["Active"],"variables":[{"name":["c"],"dataType":["SObject"],"isCollection":["true"],"isInput":["true"],"isOutput":["false"],"objectType":["Account"]},{"name":["currentItem_af_0"],"dataType":["SObject"],"isCollection":["false"],"isInput":["false"],"isOutput":["false"],"objectType":["Account"]}]}} \ No newline at end of file +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "apiVersion": [ + "53.0" + ], + "collectionProcessors": [ + { + "name": [ + "filter_test_accounts" + ], + "elementSubtype": [ + "FilterCollectionProcessor" + ], + "label": [ + "filter test accounts" + ], + "locationX": [ + "176" + ], + "locationY": [ + "134" + ], + "assignNextValueToReference": [ + "currentItem_af_0" + ], + "collectionProcessorType": [ + "FilterCollectionProcessor" + ], + "collectionReference": [ + "c" + ], + "conditionLogic": [ + "and" + ], + "conditions": [ + { + "leftValueReference": [ + "currentItem_af_0.Name" + ], + "operator": [ + "Contains" + ], + "rightValue": [ + { + "stringValue": [ + "test" + ] + } + ] + } + ], + "connector": [ + { + "targetReference": [ + "sort_acc" + ] + } + ] + }, + { + "name": [ + "sort_acc" + ], + "elementSubtype": [ + "SortCollectionProcessor" + ], + "label": [ + "sort acc" + ], + "locationX": [ + "176" + ], + "locationY": [ + "242" + ], + "collectionProcessorType": [ + "SortCollectionProcessor" + ], + "collectionReference": [ + "filter_test_accounts" + ], + "connector": [ + { + "targetReference": [ + "rb" + ] + } + ], + "sortOptions": [ + { + "doesPutEmptyStringAndNullFirst": [ + "false" + ], + "sortField": [ + "AccountNumber" + ], + "sortOrder": [ + "Asc" + ] + } + ] + } + ], + "environments": [ + "Default" + ], + "interviewLabel": [ + "api58test {!$Flow.CurrentDateTime}" + ], + "label": [ + "api58test" + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "AUTO_LAYOUT_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "Flow" + ], + "recordRollbacks": [ + { + "name": [ + "rb" + ], + "label": [ + "rb" + ], + "locationX": [ + "176" + ], + "locationY": [ + "350" + ], + "connector": [ + { + "targetReference": [ + "aa" + ] + } + ] + } + ], + "screens": [ + { + "name": [ + "aa" + ], + "label": [ + "aa" + ], + "locationX": [ + "176" + ], + "locationY": [ + "458" + ], + "allowBack": [ + "true" + ], + "allowFinish": [ + "true" + ], + "allowPause": [ + "true" + ], + "fields": [ + { + "name": [ + "bb" + ], + "dataTypeMappings": [ + { + "typeName": [ + "T" + ], + "typeValue": [ + "Account" + ] + } + ], + "extensionName": [ + "flowruntime:datatable" + ], + "fieldType": [ + "ComponentInstance" + ], + "inputParameters": [ + { + "name": [ + "label" + ], + "value": [ + { + "stringValue": [ + "Data Table" + ] + } + ] + }, + { + "name": [ + "selectionMode" + ], + "value": [ + { + "stringValue": [ + "MULTI_SELECT" + ] + } + ] + }, + { + "name": [ + "minRowSelection" + ], + "value": [ + { + "numberValue": [ + "0.0" + ] + } + ] + }, + { + "name": [ + "tableData" + ], + "value": [ + { + "elementReference": [ + "filter_test_accounts" + ] + } + ] + }, + { + "name": [ + "columns" + ], + "value": [ + { + "stringValue": [ + "[{\"apiName\":\"AccountNumber\",\"guid\":\"column-8e94\",\"editable\":false,\"hasCustomHeaderLabel\":false,\"customHeaderLabel\":\"\",\"wrapText\":true,\"order\":0,\"label\":\"Account Number\",\"type\":\"text\"}]" + ] + } + ] + } + ], + "inputsOnNextNavToAssocScrn": [ + "UseStoredValues" + ], + "isRequired": [ + "true" + ], + "storeOutputAutomatically": [ + "true" + ] + } + ], + "showFooter": [ + "true" + ], + "showHeader": [ + "true" + ] + } + ], + "start": [ + { + "locationX": [ + "50" + ], + "locationY": [ + "0" + ], + "connector": [ + { + "targetReference": [ + "filter_test_accounts" + ] + } + ] + } + ], + "status": [ + "Active" + ], + "variables": [ + { + "name": [ + "c" + ], + "dataType": [ + "SObject" + ], + "isCollection": [ + "true" + ], + "isInput": [ + "true" + ], + "isOutput": [ + "false" + ], + "objectType": [ + "Account" + ] + }, + { + "name": [ + "currentItem_af_0" + ], + "dataType": [ + "SObject" + ], + "isCollection": [ + "false" + ], + "isInput": [ + "false" + ], + "isOutput": [ + "false" + ], + "objectType": [ + "Account" + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/testfiles/hidenav.json b/tests/testfiles/hidenav.json index 5daa8d3..c3e7eed 100644 --- a/tests/testfiles/hidenav.json +++ b/tests/testfiles/hidenav.json @@ -1 +1,261 @@ -{"Flow":{"$":{"xmlns":"http://soap.sforce.com/2006/04/metadata"},"assignments":[{"name":["NewAccountName"],"label":["NewAccountName"],"locationX":["428"],"locationY":["193"],"assignmentItems":[{"assignToReference":["createAccount.Name"],"operator":["Assign"],"value":[{"elementReference":["New_Account_Name"]}]}],"connector":[{"targetReference":["createAccountRecord"]}]}],"interviewLabel":["CreateANewAccountImprovedHideNav {!$Flow.CurrentDateTime}"],"label":["CreateANewAccountImprovedHideNav"],"processMetadataValues":[{"name":["BuilderType"],"value":[{"stringValue":["LightningFlowBuilder"]}]},{"name":["CanvasMode"],"value":[{"stringValue":["FREE_FORM_CANVAS"]}]},{"name":["OriginBuilderType"],"value":[{"stringValue":["LightningFlowBuilder"]}]}],"processType":["Flow"],"recordCreates":[{"name":["createAccountRecord"],"label":["createAccountRecord"],"locationX":["449"],"locationY":["463"],"connector":[{"targetReference":["ViewAccountId"]}],"inputReference":["createAccount"]}],"screens":[{"name":["NameNewAccount"],"label":["NameNewAccount"],"locationX":["176"],"locationY":["214"],"allowBack":["true"],"allowFinish":["true"],"allowPause":["true"],"connector":[{"targetReference":["NewAccountName"]}],"fields":[{"name":["New_Account_Name"],"dataType":["String"],"fieldText":["New Account Name"],"fieldType":["InputField"],"isRequired":["false"]}],"showFooter":["true"],"showHeader":["true"]},{"name":["ViewAccountId"],"label":["View AccountId"],"locationX":["172"],"locationY":["454"],"allowBack":["true"],"allowFinish":["true"],"allowPause":["true"],"fields":[{"name":["AccountNameDisplay"],"fieldText":["

{!createAccount.Name}

"],"fieldType":["DisplayText"]}],"showFooter":["false"],"showHeader":["true"]}],"start":[{"locationX":["50"],"locationY":["50"],"connector":[{"targetReference":["NameNewAccount"]}]}],"status":["Active"],"variables":[{"name":["createAccount"],"dataType":["SObject"],"isCollection":["false"],"isInput":["false"],"isOutput":["false"],"objectType":["Account"]}]}} \ No newline at end of file +{ + "Flow": { + "$": { + "xmlns": "http://soap.sforce.com/2006/04/metadata" + }, + "assignments": [ + { + "name": [ + "NewAccountName" + ], + "label": [ + "NewAccountName" + ], + "locationX": [ + "428" + ], + "locationY": [ + "193" + ], + "assignmentItems": [ + { + "assignToReference": [ + "createAccount.Name" + ], + "operator": [ + "Assign" + ], + "value": [ + { + "elementReference": [ + "New_Account_Name" + ] + } + ] + } + ], + "connector": [ + { + "targetReference": [ + "createAccountRecord" + ] + } + ] + } + ], + "interviewLabel": [ + "CreateANewAccountImprovedHideNav {!$Flow.CurrentDateTime}" + ], + "label": [ + "CreateANewAccountImprovedHideNav" + ], + "processMetadataValues": [ + { + "name": [ + "BuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + }, + { + "name": [ + "CanvasMode" + ], + "value": [ + { + "stringValue": [ + "FREE_FORM_CANVAS" + ] + } + ] + }, + { + "name": [ + "OriginBuilderType" + ], + "value": [ + { + "stringValue": [ + "LightningFlowBuilder" + ] + } + ] + } + ], + "processType": [ + "Flow" + ], + "recordCreates": [ + { + "name": [ + "createAccountRecord" + ], + "label": [ + "createAccountRecord" + ], + "locationX": [ + "449" + ], + "locationY": [ + "463" + ], + "connector": [ + { + "targetReference": [ + "ViewAccountId" + ] + } + ], + "inputReference": [ + "createAccount" + ] + } + ], + "screens": [ + { + "name": [ + "NameNewAccount" + ], + "label": [ + "NameNewAccount" + ], + "locationX": [ + "176" + ], + "locationY": [ + "214" + ], + "allowBack": [ + "true" + ], + "allowFinish": [ + "true" + ], + "allowPause": [ + "true" + ], + "connector": [ + { + "targetReference": [ + "NewAccountName" + ] + } + ], + "fields": [ + { + "name": [ + "New_Account_Name" + ], + "dataType": [ + "String" + ], + "fieldText": [ + "New Account Name" + ], + "fieldType": [ + "InputField" + ], + "isRequired": [ + "false" + ] + } + ], + "showFooter": [ + "true" + ], + "showHeader": [ + "true" + ] + }, + { + "name": [ + "ViewAccountId" + ], + "label": [ + "View AccountId" + ], + "locationX": [ + "172" + ], + "locationY": [ + "454" + ], + "allowBack": [ + "true" + ], + "allowFinish": [ + "true" + ], + "allowPause": [ + "true" + ], + "fields": [ + { + "name": [ + "AccountNameDisplay" + ], + "fieldText": [ + "

{!createAccount.Name}

" + ], + "fieldType": [ + "DisplayText" + ] + } + ], + "showFooter": [ + "false" + ], + "showHeader": [ + "true" + ] + } + ], + "start": [ + { + "locationX": [ + "50" + ], + "locationY": [ + "50" + ], + "connector": [ + { + "targetReference": [ + "NameNewAccount" + ] + } + ] + } + ], + "status": [ + "Active" + ], + "variables": [ + { + "name": [ + "createAccount" + ], + "dataType": [ + "SObject" + ], + "isCollection": [ + "false" + ], + "isInput": [ + "false" + ], + "isOutput": [ + "false" + ], + "objectType": [ + "Account" + ] + } + ] + } +} \ No newline at end of file