diff --git a/README.md b/README.md index c17c8bf..da3383e 100644 --- a/README.md +++ b/README.md @@ -321,7 +321,7 @@ deno run \ --allow-read \ --allow-write \ jsr:@goog/flow-lens \ - --diagramTool="graphviz" \ + --diagramTool="mermaid" \ --gitRepo="/path/to/salesforce_project/" \ --gitDiffFromHash="HEAD~1" \ --gitDiffToHash="HEAD" \ @@ -336,8 +336,8 @@ deno run \ { "path": "force-app/main/default/flows/Demo.flow-meta.xml", "difference": { - "old": "digraph {\nlabel=<Demo>\ntitle = \"Demo\";\nlabelloc = \"t\";\nnode [shape=box, style=filled]\nSet_the_Description [\n label=<\n\n \n \n \n \n \n \n
-\n Assignment ⬅️\n
Set the Description
\n>\n color=\"#DD7A00\"\n fontcolor=\"white\"\n];\nGet_the_Acme_Account [\n label=<\n\n \n \n \n \n \n \n
Δ\n Record Lookup 🔍\n
Get the 'Acme' Account
\n>\n color=\"#F9548A\"\n fontcolor=\"white\"\n];\nUpdate_the_Acme_Account [\n label=<\n\n \n \n \n \n \n \n
Δ\n Record Update ✏️\n
Update the 'Acme' Account
\n>\n color=\"#F9548A\"\n fontcolor=\"white\"\n];\nFLOW_START -> Get_the_Acme_Account [label=\"\" color=\"black\" style=\"\"]\nGet_the_Acme_Account -> Set_the_Description [label=\"\" color=\"black\" style=\"\"]\nSet_the_Description -> Update_the_Acme_Account [label=\"\" color=\"black\" style=\"\"]\n}", - "new": "digraph {\nlabel=<Demo>\ntitle = \"Demo\";\nlabelloc = \"t\";\nnode [shape=box, style=filled]\nSet_the_Type [\n label=<\n\n \n \n \n \n \n \n
+\n Assignment ⬅️\n
Set the Type
\n>\n color=\"#DD7A00\"\n fontcolor=\"white\"\n];\nGet_the_Acme_Account [\n label=<\n\n \n \n \n \n \n \n
Δ\n Record Lookup 🔍\n
Get the 'Acme' Account
\n>\n color=\"#F9548A\"\n fontcolor=\"white\"\n];\nUpdate_the_Acme_Account [\n label=<\n\n \n \n \n \n \n \n
Δ\n Record Update ✏️\n
Update the 'Acme' Account
\n>\n color=\"#F9548A\"\n fontcolor=\"white\"\n];\nLog_Error [\n label=<\n\n \n \n \n \n \n \n
+\n Action Call ⚡\n
Log Error
\n>\n color=\"#344568\"\n fontcolor=\"white\"\n];\nLog_Error2 [\n label=<\n\n \n \n \n \n \n \n
+\n Action Call ⚡\n
Log Error
\n>\n color=\"#344568\"\n fontcolor=\"white\"\n];\nFLOW_START -> Get_the_Acme_Account [label=\"\" color=\"black\" style=\"\"]\nGet_the_Acme_Account -> Set_the_Type [label=\"\" color=\"black\" style=\"\"]\nGet_the_Acme_Account -> Log_Error [label=\"Fault\" color=\"red\" style=\"dashed\"]\nSet_the_Type -> Update_the_Acme_Account [label=\"\" color=\"black\" style=\"\"]\nUpdate_the_Acme_Account -> Log_Error2 [label=\"Fault\" color=\"red\" style=\"dashed\"]\n}" + "old": "---\ntitle: \"Demo\"\n---\nstateDiagram-v2\n\n classDef pink fill:#F9548A, color:white\n classDef orange fill:#DD7A00, color:white\n classDef navy fill:#344568, color:white\n classDef blue fill:#1B96FF, color:white\n classDef modified stroke-width: 5px, stroke: orange\n classDef added stroke-width: 5px, stroke: green\n classDef deleted stroke-width: 5px, stroke: red\n\n state \"-Assignment 📝
Set the Description
Get_the_Acme_Account.Description = This is a Demonstration!\" as Set_the_Description\n class Set_the_Description orange deleted\n state \"ΔRecord Lookup 🔍
Get the 'Acme' Account
sObject: Account
Fields Queried: all
Filter Logic: and
1. Name EqualTo Acme
Limit: First Record Only\" as Get_the_Acme_Account\n class Get_the_Acme_Account pink modified\n state \"ΔRecord Update ✏️
Update the 'Acme' Account
Reference Update
Get_the_Acme_Account
\" as Update_the_Acme_Account\n class Update_the_Acme_Account pink modified\n FLOW_START --> Get_the_Acme_Account\n Get_the_Acme_Account --> Set_the_Description\n Set_the_Description --> Update_the_Acme_Account", + "new": "---\ntitle: \"Demo\"\n---\nstateDiagram-v2\n\n classDef pink fill:#F9548A, color:white\n classDef orange fill:#DD7A00, color:white\n classDef navy fill:#344568, color:white\n classDef blue fill:#1B96FF, color:white\n classDef modified stroke-width: 5px, stroke: orange\n classDef added stroke-width: 5px, stroke: green\n classDef deleted stroke-width: 5px, stroke: red\n\n state \"+Assignment 📝
Set the Type
Get_the_Acme_Account.Type = Other\" as Set_the_Type\n class Set_the_Type orange added\n state \"ΔRecord Lookup 🔍
Get the 'Acme' Account
sObject: Account
Fields Queried: all
Filter Logic: and
1. Name EqualTo Acme
Limit: First Record Only\" as Get_the_Acme_Account\n class Get_the_Acme_Account pink modified\n state \"ΔRecord Update ✏️
Update the 'Acme' Account
Reference Update
Get_the_Acme_Account
\" as Update_the_Acme_Account\n class Update_the_Acme_Account pink modified\n state \"+Action Call
Log Error\" as Log_Error\n class Log_Error navy added\n state \"+Action Call
Log Error\" as Log_Error2\n class Log_Error2 navy added\n FLOW_START --> Get_the_Acme_Account\n Get_the_Acme_Account --> Set_the_Type\n Get_the_Acme_Account --> Log_Error : ❌ Fault ❌\n Set_the_Type --> Update_the_Acme_Account\n Update_the_Acme_Account --> Log_Error2 : ❌ Fault ❌" } } ] diff --git a/deno.json b/deno.json index b9db947..5ec8342 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@goog/flow-lens", - "version": "0.1.11", + "version": "0.1.12", "license": "Apache", "exports": "./src/main/main.ts", "imports": { diff --git a/docs/img/Diff_New.png b/docs/img/Diff_New.png index 68919a4..f3155d3 100644 Binary files a/docs/img/Diff_New.png and b/docs/img/Diff_New.png differ diff --git a/docs/img/Diff_Old.png b/docs/img/Diff_Old.png index c592487..6df0363 100644 Binary files a/docs/img/Diff_Old.png and b/docs/img/Diff_Old.png differ diff --git a/src/main/flow_parser.ts b/src/main/flow_parser.ts index 8ba750f..4f972d5 100644 --- a/src/main/flow_parser.ts +++ b/src/main/flow_parser.ts @@ -136,6 +136,7 @@ export class FlowParser { this.beingParsed.apexPluginCalls = ensureArray(flow.apexPluginCalls); this.beingParsed.assignments = ensureArray(flow.assignments); + setAssignments(this.beingParsed.assignments); this.beingParsed.collectionProcessors = ensureArray( flow.collectionProcessors, ); @@ -443,6 +444,20 @@ function ensureArray(input: T[] | undefined): T[] | undefined { return input ? (Array.isArray(input) ? input : [input]) : input; } +/** + * Set Assignments + * + * Assignment items are nested properties which also need to be converted to an + * array. + */ +function setAssignments(assignments: flowTypes.FlowAssignment[] | undefined) { + assignments?.forEach((assignment) => { + assignment.assignmentItems = ensureArray( + assignment.assignmentItems, + ) as flowTypes.FlowAssignmentItem[]; + }); +} + /** * Set Orchestrated Stage Steps * diff --git a/src/main/uml_generator.ts b/src/main/uml_generator.ts index 04836a2..4339e17 100644 --- a/src/main/uml_generator.ts +++ b/src/main/uml_generator.ts @@ -194,9 +194,38 @@ export abstract class UmlGenerator { type: "Assignment", color: SkinColor.ORANGE, icon: Icon.ASSIGNMENT, + innerNodes: this.getFlowAssignmentInnerNodes(node), }); } + private getFlowAssignmentInnerNodes( + node: flowTypes.FlowAssignment, + ): InnerNode[] { + const result: InnerNode[] = []; + if (!node.assignmentItems) { + return result; + } + + const assignments: string[] = []; + for (const item of node.assignmentItems) { + const operator = item.operator === flowTypes.FlowAssignmentOperator.ASSIGN + ? "=" + : item.operator; + assignments.push( + `${item.assignToReference} ${operator} ${toString(item.value)}`, + ); + } + + result.push({ + id: `${node.name}__Assignments`, + type: "", + label: "", + content: assignments, + }); + + return result; + } + private getFlowCollectionProcessor( node: flowTypes.FlowCollectionProcessor, ): string { diff --git a/src/test/uml_generator_test.ts b/src/test/uml_generator_test.ts index f19c712..5263077 100644 --- a/src/test/uml_generator_test.ts +++ b/src/test/uml_generator_test.ts @@ -46,7 +46,10 @@ const NODE_NAMES = { const UML_REPRESENTATIONS = { apexPluginCall: (name: string) => `state Apex Plugin Call ${name}`, - assignment: (name: string) => `state Assignment ${name}`, + assignment: (name: string) => + `state Assignment ${name} + var1 = Hello World + var2 AddItem Test Value`, collectionProcessor: (name: string) => `state Collection Processor ${name}`, decision: (name: string) => `state Decision ${name}${EOL}`, loop: (name: string) => `state Loop ${name}`, @@ -189,6 +192,33 @@ function getFlowNodes(name: string): flowTypes.FlowNode[] { ] as flowTypes.FlowRecordDelete[]; } + if (name === NODE_NAMES.assignment) { + return [ + { + ...baseNode, + elementSubtype: "Assignment", + assignmentItems: [ + { + assignToReference: "var1", + operator: flowTypes.FlowAssignmentOperator.ASSIGN, + value: { + stringValue: "Hello World", + }, + processMetadataValues: [], + }, + { + assignToReference: "var2", + operator: flowTypes.FlowAssignmentOperator.ADD_ITEM, + value: { + stringValue: "Test Value", + }, + processMetadataValues: [], + }, + ], + }, + ] as flowTypes.FlowAssignment[]; + } + // Return basic node for other types return [baseNode] as flowTypes.FlowNode[]; } @@ -541,4 +571,64 @@ Deno.test("UmlGenerator", async (t) => { }); }, ); + + await t.step( + "should generate proper inner node content for FlowAssignment", + () => { + // Setup test data + const assignmentNode: flowTypes.FlowAssignment = { + name: "testAssignment", + label: "Test Assignment", + description: "Test assignment description", + elementSubtype: "Assignment", + locationX: 0, + locationY: 0, + assignmentItems: [ + { + assignToReference: "var1", + operator: flowTypes.FlowAssignmentOperator.ASSIGN, + value: { stringValue: "Hello World" }, + processMetadataValues: [], + }, + { + assignToReference: "var2", + operator: flowTypes.FlowAssignmentOperator.ADD, + value: { numberValue: 42 }, + processMetadataValues: [], + }, + { + assignToReference: "var3", + operator: flowTypes.FlowAssignmentOperator.SUBTRACT, + value: { elementReference: "someVariable" }, + processMetadataValues: [], + }, + { + assignToReference: "var4", + operator: flowTypes.FlowAssignmentOperator.ADD_ITEM, + value: { formulaExpression: "1 + 1" }, + processMetadataValues: [], + }, + ], + }; + + mockParsedFlow.assignments = [assignmentNode]; + const uml = systemUnderTest.generateUml(); + + const expectedContent = [ + "Assignment testAssignment", + "var1 = Hello World", + "var2 Add 42", + "var3 Subtract someVariable", + "var4 AddItem 1 + 1", + ]; + + expectedContent.forEach((content) => { + assertEquals( + uml.includes(content), + true, + `Expected UML: ${uml} to contain: ${content}`, + ); + }); + }, + ); });