Skip to content

Commit

Permalink
Merge pull request #140 from Lightning-Flow-Scanner/feature/same-reco…
Browse files Browse the repository at this point in the history
…rd-field-update

feat(RuleEngine): same record field update new rule fixes #134
  • Loading branch information
junners authored Nov 16, 2024
2 parents 4b23ad7 + 3d99847 commit a40d728
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 0 deletions.
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ _An Extensible Rule Engine capable of conducting static analysis on the metadata
| **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. |
| **Unsafe Running Context** ([`UnsafeRunningContext`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/UnsafeRunningContext.ts)) | This flow is configured to run in System Mode without Sharing. This system context grants all running users the permission to view and edit all data in your org. Running a flow in System Mode without Sharing can lead to unsafe data access. |
| **Same Record Field Updates** ([`SameRecordFieldUpdates`](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/SameRecordFieldUpdates.ts)) | Much like triggers, before contexts can update the same record by accessing the trigger variables `$Record` without needing to invoke a DML. |

## Core Functions

Expand Down
58 changes: 58 additions & 0 deletions src/main/rules/SameRecordFieldUpdates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as core from "../internals/internals";
import { RuleCommon } from "../models/RuleCommon";

export class SameRecordFieldUpdates extends RuleCommon implements core.IRuleDefinition {
protected qualifiedRecordTriggerTypes: Set<string> = new Set<string>(["Create", "Update"]);

constructor() {
super(
{
name: "SameRecordFieldUpdates",
label: "Same Record Field Updates",
description:
"Before-save same-record field updates allows you to update the record using variable assignments to `$Record`. This is significantly faster than doing another DML on the same-record that triggered the flow",
supportedTypes: [...core.FlowType.backEndTypes],
docRefs: [
{
label: "Learn about same record field updates",
path: "https://architect.salesforce.com/decision-guides/trigger-automation#Same_Record_Field_Updates",
},
],
isConfigurable: false,
autoFixable: false,
},
{ severity: "warning" }
);
}

public execute(flow: core.Flow): core.RuleResult {
const results: core.ResultDetails[] = [];

const isBeforeSaveType = flow.start?.triggerType === "RecordBeforeSave";
const isQualifiedTriggerTypes = this.qualifiedRecordTriggerTypes.has(
flow.start?.recordTriggerType
);

if (!isBeforeSaveType || !isQualifiedTriggerTypes) {
return new core.RuleResult(this, []);
}

const resultDetails: core.ResultDetails[] = [];

const potentialElements = flow.elements?.filter(
(node) => node.subtype === "recordUpdates"
) as core.FlowNode[];

for (const node of potentialElements) {
if (
typeof node.element === "object" &&
"inputReference" in node.element &&
node.element.inputReference === "$Record"
) {
resultDetails.push(new core.ResultDetails(node));
}
}

return new core.RuleResult(this, resultDetails);
}
}
136 changes: 136 additions & 0 deletions tests/SameRecordFieldUpdates.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import "mocha";

import { ParsedFlow } from "../src/main/models/ParsedFlow";
import { SameRecordFieldUpdates } from "../src/main/rules/SameRecordFieldUpdates";
import { RuleResult, Flow } from "../src";

describe("SameRecordFieldUpdates", () => {
let expect;
let rule;
before(async () => {
expect = (await import("chai")).expect;
rule = new SameRecordFieldUpdates();
});

it("should flag same record updates on before context flows", async () => {
const testData: ParsedFlow = {
flow: {
start: {
locationX: "50",
locationY: "0",
connector: { targetReference: "Update_triggering_records" },
object: "Account",
recordTriggerType: "Create",
triggerType: "RecordBeforeSave",
},
elements: [
{
element: {
description: "test",
name: "Update_triggering_records",
label: "Update triggering records",
locationX: "176",
locationY: "287",
inputAssignments: { field: "Active__c", value: { stringValue: "Yes" } },
inputReference: "$Record",
},
subtype: "recordUpdates",
metaType: "node",
connectors: [],
name: "Update_triggering_records",
locationX: "176",
locationY: "287",
},
{
element: {
locationX: "50",
locationY: "0",
connector: { targetReference: "Update_triggering_records" },
object: "Account",
recordTriggerType: "Create",
triggerType: "RecordBeforeSave",
},
subtype: "start",
metaType: "node",
connectors: [
{
element: { targetReference: "Update_triggering_records" },
processed: false,
type: "connector",
reference: "Update_triggering_records",
},
],
name: "flowstart",
locationX: "50",
locationY: "0",
},
],
},
} as {} as ParsedFlow;

const ruleResult: RuleResult = rule.execute(testData.flow as Flow);

expect(ruleResult.occurs).to.be.true;
});

it("should not flag same record updates on after context flows", async () => {
const testData: ParsedFlow = {
flow: {
start: {
locationX: "50",
locationY: "0",
connector: { targetReference: "Update_triggering_records" },
object: "Account",
recordTriggerType: "Create",
triggerType: "RecordAfterSave",
},
elements: [
{
element: {
description: "test",
name: "Update_triggering_records",
label: "Update triggering records",
locationX: "176",
locationY: "287",
inputAssignments: { field: "Active__c", value: { stringValue: "Yes" } },
inputReference: "$Record",
},
subtype: "recordUpdates",
metaType: "node",
connectors: [],
name: "Update_triggering_records",
locationX: "176",
locationY: "287",
},
{
element: {
locationX: "50",
locationY: "0",
connector: { targetReference: "Update_triggering_records" },
object: "Account",
recordTriggerType: "Create",
triggerType: "RecordAfterSave",
},
subtype: "start",
metaType: "node",
connectors: [
{
element: { targetReference: "Update_triggering_records" },
processed: false,
type: "connector",
reference: "Update_triggering_records",
},
],
name: "flowstart",
locationX: "50",
locationY: "0",
},
],
},
} as {} as ParsedFlow;

const ruleResult: RuleResult = rule.execute(testData.flow as Flow);

expect(ruleResult.occurs).to.be.false;
});
});

0 comments on commit a40d728

Please sign in to comment.