├── CHANGELOG.md ├── src ├── index.ts ├── renderConfig.ts └── flowParser.ts ├── jest.config.js ├── tests ├── salesforce-project │ ├── .husky │ │ └── pre-commit │ ├── .vscode │ │ ├── settings.json │ │ ├── extensions.json │ │ └── launch.json │ ├── jest.config.js │ ├── .prettierignore │ ├── .prettierrc │ ├── sfdx-project.json │ ├── .eslintignore │ ├── salesforce-project │ │ └── main │ │ │ └── default │ │ │ ├── lwc │ │ │ ├── .eslintrc.json │ │ │ └── jsconfig.json │ │ │ ├── email │ │ │ └── unfiled$public │ │ │ │ ├── Account_Change_1699286413354.email │ │ │ │ └── Account_Change_1699286413354.email-meta.xml │ │ │ ├── workflows │ │ │ └── Account.workflow-meta.xml │ │ │ └── flows │ │ │ ├── Sheduled-Auto-Lanuch-Flow.flow-meta.xml │ │ │ ├── PlatformTrigger_Dummy.flow-meta.xml │ │ │ ├── Copy_Account_Phone_to_New_Contact.flow-meta.xml │ │ │ ├── Scheduled_Duff.flow-meta.xml │ │ │ ├── Formula_Start_Condition.flow-meta.xml │ │ │ ├── Custom_Start_Logic_Flow.flow-meta.xml │ │ │ ├── Send_Email_on_Account_Change.flow-meta.xml │ │ │ ├── Post_to_Chatter.flow-meta.xml │ │ │ ├── Create_Follow_Up_on_New_Prospect.flow-meta.xml │ │ │ ├── Custom_Error.flow-meta.xml │ │ │ ├── Post_to_Chatter_on_Account_Change.flow-meta.xml │ │ │ ├── New_Order_Email_to_Supplier.flow-meta.xml │ │ │ ├── Case_Priority_is_High.flow-meta.xml │ │ │ ├── Closed_Won_Opportunities.flow-meta.xml │ │ │ ├── Loop-Bug.flow-meta.xml │ │ │ ├── Create_Follow_Up_with_Decision_Maker.flow-meta.xml │ │ │ └── Closed_Won_Opportunities_Multi_Schedule.flow-meta.xml │ ├── config │ │ └── project-scratch-def.json │ ├── .forceignore │ ├── README.md │ └── package.json └── flow-parser.test.ts ├── .gitignore ├── tsconfig.json ├── tsup.config.ts ├── package.json ├── README.md └── LICENSE /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 2 | 3 | * First release -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { parseFlow } from './flowParser' -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | }; -------------------------------------------------------------------------------- /tests/salesforce-project/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run precommit -------------------------------------------------------------------------------- /tests/salesforce-project/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/node_modules": true, 4 | "**/bower_components": true, 5 | "**/.sfdx": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/salesforce-project/jest.config.js: -------------------------------------------------------------------------------- 1 | const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config'); 2 | 3 | module.exports = { 4 | ...jestConfig, 5 | modulePathIgnorePatterns: ['/.localdevserver'] 6 | }; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | 3 | # Ignore test-related files 4 | /coverage.data 5 | /coverage/ 6 | 7 | # Build files 8 | /dist 9 | 10 | # Salesforce Project (used for building flows for testing) 11 | .sf/ 12 | .sfdx/ 13 | /tests/salesforce-project/coverage -------------------------------------------------------------------------------- /tests/salesforce-project/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "salesforce.salesforcedx-vscode", 4 | "redhat.vscode-xml", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "financialforce.lana" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /tests/salesforce-project/.prettierignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running prettier 2 | # More information: https://prettier.io/docs/en/ignore.html 3 | # 4 | 5 | **/staticresources/** 6 | .localdevserver 7 | .sfdx 8 | .vscode 9 | 10 | coverage/ -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "module": "commonjs", 5 | "outDir": "./dist", 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "skipLibCheck": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"], 5 | format: ["cjs", "esm"], // Build for commonJS and ESmodules 6 | dts: true, // Generate declaration file (.d.ts) 7 | splitting: false, 8 | sourcemap: true, 9 | clean: true, 10 | }); -------------------------------------------------------------------------------- /tests/salesforce-project/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "overrides": [ 4 | { 5 | "files": "**/lwc/**/*.html", 6 | "options": { "parser": "lwc" } 7 | }, 8 | { 9 | "files": "*.{cmp,page,component}", 10 | "options": { "parser": "html" } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tests/salesforce-project/sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageDirectories": [ 3 | { 4 | "path": "salesforce-project", 5 | "default": true 6 | } 7 | ], 8 | "name": "sf-flow-visualiser", 9 | "namespace": "", 10 | "sfdcLoginUrl": "https://login.salesforce.com", 11 | "sourceApiVersion": "59.0" 12 | } 13 | -------------------------------------------------------------------------------- /tests/salesforce-project/.eslintignore: -------------------------------------------------------------------------------- 1 | **/lwc/**/*.css 2 | **/lwc/**/*.html 3 | **/lwc/**/*.json 4 | **/lwc/**/*.svg 5 | **/lwc/**/*.xml 6 | **/aura/**/*.auradoc 7 | **/aura/**/*.cmp 8 | **/aura/**/*.css 9 | **/aura/**/*.design 10 | **/aura/**/*.evt 11 | **/aura/**/*.json 12 | **/aura/**/*.svg 13 | **/aura/**/*.tokens 14 | **/aura/**/*.xml 15 | **/aura/**/*.app 16 | .sfdx 17 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/lwc/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@salesforce/eslint-config-lwc/recommended"], 3 | "overrides": [ 4 | { 5 | "files": ["*.test.js"], 6 | "rules": { 7 | "@lwc/lwc/no-unexpected-wire-adapter-usages": "off" 8 | }, 9 | "env": { 10 | "node": true 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tests/salesforce-project/config/project-scratch-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "sf-flow-visualiser", 3 | "edition": "Developer", 4 | "features": ["EnableSetPasswordInApi"], 5 | "settings": { 6 | "lightningExperienceSettings": { 7 | "enableS1DesktopEnabled": true 8 | }, 9 | "mobileSettings": { 10 | "enableS1EncryptedStoragePref2": false 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/flow-parser.test.ts: -------------------------------------------------------------------------------- 1 | import { parseFlow } from '../src/flowParser'; 2 | 3 | test('parses a flow to mermaid', () => { 4 | const result = parseFlow('123', 'mermaid'); 5 | expect(result).toBe("Going to render as mermaid"); 6 | }); 7 | 8 | test('parses a flow to plantuml', () => { 9 | const result = parseFlow('123', 'plantuml'); 10 | expect(result).toBe("Going to render as plantuml"); 11 | }); -------------------------------------------------------------------------------- /tests/salesforce-project/.forceignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status 2 | # More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm 3 | # 4 | 5 | package.xml 6 | 7 | # LWC configuration files 8 | **/jsconfig.json 9 | **/.eslintrc.json 10 | 11 | # LWC Jest 12 | **/__tests__/** -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/email/unfiled$public/Account_Change_1699286413354.email: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Your account record has changed. 
6 |
7 | _____________________________________________________________________
8 | Powered by Salesforce
9 | http://cs121.salesforce.com/ 10 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/lwc/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true 4 | }, 5 | "include": [ 6 | "**/*", 7 | "../../../../.sfdx/typings/lwc/**/*.d.ts" 8 | ], 9 | "paths": { 10 | "c/*": [ 11 | "*" 12 | ] 13 | }, 14 | "typeAcquisition": { 15 | "include": [ 16 | "jest" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/email/unfiled$public/Account_Change_1699286413354.email-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | UTF-8 5 | Account Change 6 | Account 7 | 8 | Account change notice 9 | custom 10 | SFX 11 | 12 | -------------------------------------------------------------------------------- /tests/salesforce-project/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Apex Replay Debugger", 9 | "type": "apex-replay", 10 | "request": "launch", 11 | "logFile": "${command:AskForLogFileName}", 12 | "stopOnEntry": true, 13 | "trace": true 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/workflows/Account.workflow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Notify_Owner_of_Account_Change 5 | Notify Owner of Account Change 6 | false 7 | 8 | owner 9 | 10 | CurrentUser 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "salesforce-flow-visualiser", 3 | "version": "1.0.0", 4 | "description": "Parse Salesforce Flow meta.xml to text-based graphical formats", 5 | "main": "dist/index.js", 6 | "module": "./dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "files": [ 9 | "/dist" 10 | ], 11 | "scripts": { 12 | "build": "tsup", 13 | "test": "jest" 14 | }, 15 | "keywords": [ 16 | "Salesforce", 17 | "Flow", 18 | "Mermaid", 19 | "PlantUML", 20 | "Graphical" 21 | ], 22 | "author": "Todd Halfpenny", 23 | "license": "ISC", 24 | "repository": { 25 | "type": "git", 26 | "url": "git@github.com:toddhalfpenny/salesforce-flow-visualiser.git" 27 | }, 28 | "devDependencies": { 29 | "@types/jest": "^29.5.7", 30 | "jest": "^29.7.0", 31 | "ts-jest": "^29.1.1", 32 | "ts-node": "^10.9.1", 33 | "tsup": "^7.2.0", 34 | "typescript": "^5.2.2" 35 | }, 36 | "dependencies": { 37 | "fast-xml-parser": "^4.3.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/salesforce-project/README.md: -------------------------------------------------------------------------------- 1 | # Salesforce DX Project: Next Steps 2 | 3 | Now that you’ve created a Salesforce DX project, what’s next? Here are some documentation resources to get you started. 4 | 5 | ## How Do You Plan to Deploy Your Changes? 6 | 7 | Do you want to deploy a set of changes, or create a self-contained application? Choose a [development model](https://developer.salesforce.com/tools/vscode/en/user-guide/development-models). 8 | 9 | ## Configure Your Salesforce DX Project 10 | 11 | The `sfdx-project.json` file contains useful configuration information for your project. See [Salesforce DX Project Configuration](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_ws_config.htm) in the _Salesforce DX Developer Guide_ for details about this file. 12 | 13 | ## Read All About It 14 | 15 | - [Salesforce Extensions Documentation](https://developer.salesforce.com/tools/vscode/) 16 | - [Salesforce CLI Setup Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm) 17 | - [Salesforce DX Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_intro.htm) 18 | - [Salesforce CLI Command Reference](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference.htm) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Salesforce Flow Visualiser 2 | 3 | Utility for parsing Salesforce Flow meta.xml to text-based graphical formats such as [mermaid](https://Mermaid.js.org/) and [PlantUML](https://plantuml.com/). 4 | 5 | ## Installtion 6 | 7 | ``` 8 | npm i salesforce-flow-visualiser 9 | ``` 10 | 11 | ## Usage 12 | 13 | ``` 14 | import { parseFlow } from 'salesforce-flow-visualiser'; 15 | 16 | # Pass in XML string and get back a markdown string that includes mermaid representation of the Salesforce Flow 17 | 18 | const mermaidStr = await parseFlow(, 'mermaid', {outputAsMarkdown :true}); 19 | ``` 20 | 21 | ## Render Language Support 22 | * [Mermaid](https://mermaid.js.org/) 23 | * [PlantUML](https://plantuml.com/) 24 | 25 | ## Roadmap / To do 26 | * Tests... yeah of course 27 | * Move each language out to it's own file 28 | * Options... things like conditional wrapping into .md output 29 | * Lint warnings? 30 | * Other render engines? 31 | 32 | ## Contributing / Development 33 | 34 | ### Testing 35 | 36 | #### Unit tests 37 | We're using jest. 38 | ``` 39 | npm run test 40 | ``` 41 | 42 | #### To test locally 43 | * Build the package (as below) 44 | * In the *salesforce-flow-visualiser* directory 45 | ``` 46 | npm link 47 | ``` 48 | * In the project dir of the project that is using **salesforce-flow-visualiser** 49 | ``` 50 | npm link salesforce-flow-visualiser 51 | ``` 52 | 53 | ### Build 54 | We're using tsup 55 | ``` 56 | npm run build 57 | ``` 58 | 59 | ### Publishing 60 | ``` 61 | ``` -------------------------------------------------------------------------------- /tests/salesforce-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "salesforce-app", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Salesforce App", 6 | "scripts": { 7 | "lint": "eslint **/{aura,lwc}/**", 8 | "test": "npm run test:unit", 9 | "test:unit": "sfdx-lwc-jest", 10 | "test:unit:watch": "sfdx-lwc-jest --watch", 11 | "test:unit:debug": "sfdx-lwc-jest --debug", 12 | "test:unit:coverage": "sfdx-lwc-jest --coverage", 13 | "prettier": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", 14 | "prettier:verify": "prettier --list-different \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", 15 | "postinstall": "husky install", 16 | "precommit": "lint-staged" 17 | }, 18 | "devDependencies": { 19 | "@lwc/eslint-plugin-lwc": "^1.1.2", 20 | "@prettier/plugin-xml": "^2.0.1", 21 | "@salesforce/eslint-config-lwc": "^3.2.3", 22 | "@salesforce/eslint-plugin-aura": "^2.0.0", 23 | "@salesforce/eslint-plugin-lightning": "^1.0.0", 24 | "@salesforce/sfdx-lwc-jest": "^1.1.0", 25 | "eslint": "^8.11.0", 26 | "eslint-plugin-import": "^2.25.4", 27 | "eslint-plugin-jest": "^26.1.2", 28 | "husky": "^7.0.4", 29 | "lint-staged": "^12.3.7", 30 | "prettier": "^2.6.0", 31 | "prettier-plugin-apex": "^1.10.0" 32 | }, 33 | "lint-staged": { 34 | "**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}": [ 35 | "prettier --write" 36 | ], 37 | "**/{aura,lwc}/**": [ 38 | "eslint" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Sheduled-Auto-Lanuch-Flow.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 58.0 4 | Default 5 | Sample Flow Scheduled Path {!$Flow.CurrentDateTime} 6 | 7 | 8 | BuilderType 9 | 10 | LightningFlowBuilder 11 | 12 | 13 | 14 | CanvasMode 15 | 16 | FREE_FORM_CANVAS 17 | 18 | 19 | 20 | OriginBuilderType 21 | 22 | LightningFlowBuilder 23 | 24 | 25 | AutoLaunchedFlow 26 | 27 | Get_Account 28 | 29 | 964 30 | 324 31 | false 32 | and 33 | 34 | Name 35 | EqualTo 36 | 37 | Acme 38 | 39 | 40 | true 41 | Account 42 | true 43 | 44 | 45 | 970 46 | 48 47 | Case 48 | 49 | 50 | Get_Account 51 | 52 | AsyncAfterCommit 53 | 54 | 55 | Active 56 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/PlatformTrigger_Dummy.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | 5 | Duff_Assignment 6 | 7 | 176 8 | 170 9 | 10 | DUFFVAR 11 | Assign 12 | 13 | $Record.StatusType 14 | 15 | 16 | 17 | Default 18 | PlatformTrigger Dummy {!$Flow.CurrentDateTime} 19 | 20 | 21 | BuilderType 22 | 23 | LightningFlowBuilder 24 | 25 | 26 | 27 | CanvasMode 28 | 29 | AUTO_LAYOUT_CANVAS 30 | 31 | 32 | 33 | OriginBuilderType 34 | 35 | LightningFlowBuilder 36 | 37 | 38 | AutoLaunchedFlow 39 | 40 | 50 41 | 0 42 | 43 | Duff_Assignment 44 | 45 | PlatformStatusAlertEvent 46 | PlatformEvent 47 | 48 | Draft 49 | 50 | DUFFVAR 51 | String 52 | false 53 | false 54 | false 55 | 56 | DUMMY 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Copy_Account_Phone_to_New_Contact.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Default 5 | Copy Account Phone to New Contact {!$Flow.CurrentDateTime} 6 | 7 | 8 | BuilderType 9 | 10 | LightningFlowBuilder 11 | 12 | 13 | 14 | CanvasMode 15 | 16 | AUTO_LAYOUT_CANVAS 17 | 18 | 19 | 20 | OriginBuilderType 21 | 22 | LightningFlowBuilder 23 | 24 | 25 | AutoLaunchedFlow 26 | 27 | Set_Contact_Phone 28 | 29 | 176 30 | 287 31 | 32 | Phone 33 | 34 | $Record.Account.Phone 35 | 36 | 37 | $Record 38 | 39 | 40 | 50 41 | 0 42 | 43 | Set_Contact_Phone 44 | 45 | and 46 | 47 | Phone 48 | IsNull 49 | 50 | true 51 | 52 | 53 | Contact 54 | Create 55 | RecordBeforeSave 56 | 57 | Draft 58 | 59 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Scheduled_Duff.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Default 5 | Scheduled Duff {!$Flow.CurrentDateTime} 6 | 7 | 8 | BuilderType 9 | 10 | LightningFlowBuilder 11 | 12 | 13 | 14 | CanvasMode 15 | 16 | AUTO_LAYOUT_CANVAS 17 | 18 | 19 | 20 | OriginBuilderType 21 | 22 | LightningFlowBuilder 23 | 24 | 25 | AutoLaunchedFlow 26 | 27 | Get_Owner 28 | 29 | 176 30 | 252 31 | false 32 | and 33 | 34 | OwnerId 35 | EqualTo 36 | 37 | $Record.OwnerId 38 | 39 | 40 | true 41 | Contact 42 | true 43 | 44 | 45 | 50 46 | 0 47 | 48 | Get_Owner 49 | 50 | Case 51 | 52 | Once 53 | 2023-11-08 54 | 13:00:00.000Z 55 | 56 | Scheduled 57 | 58 | Draft 59 | 60 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Formula_Start_Condition.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Default 5 | 6 | StartConditions 7 | Boolean 8 | ADDMONTHS({!$Record.LastActivityDate},2) > TODAY() 9 | 10 | Formula Start Condition {!$Flow.CurrentDateTime} 11 | 12 | 13 | BuilderType 14 | 15 | LightningFlowBuilder 16 | 17 | 18 | 19 | CanvasMode 20 | 21 | AUTO_LAYOUT_CANVAS 22 | 23 | 24 | 25 | OriginBuilderType 26 | 27 | LightningFlowBuilder 28 | 29 | 30 | AutoLaunchedFlow 31 | 32 | Get_some_stuff 33 | 34 | 176 35 | 323 36 | false 37 | and 38 | 39 | AccountId 40 | EqualTo 41 | 42 | $Record.Id 43 | 44 | 45 | true 46 | Contact 47 | true 48 | 49 | 50 | 50 51 | 0 52 | 53 | Get_some_stuff 54 | 55 | ADDMONTHS({!$Record.LastActivityDate},2) > today() 56 | Account 57 | Create 58 | RecordAfterSave 59 | 60 | Draft 61 | 62 | -------------------------------------------------------------------------------- /src/renderConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * renderConfig.js 3 | */ 4 | export const NODE_CONFIG = { 5 | 'actionCalls': { 6 | background: "#344568", 7 | color: "white", 8 | label: "Action", 9 | icon: "<&pulse>", 10 | mermaidIcon: { 11 | "apex": ">_", 12 | "emailAlert" : "✉", 13 | "emailSimple": "✉", 14 | "submit": "⚡" 15 | 16 | }, 17 | mermaidClose: ")", 18 | mermaidOpen: "(" 19 | }, 20 | 'assignments': { 21 | background: "#F97924", 22 | color: "white", 23 | label: "Assignment", 24 | icon: "<&menu>", 25 | mermaidIcon: "⚌" , 26 | mermaidClose: ")", 27 | mermaidOpen: "(" 28 | }, 29 | 'collectionProcessors': { 30 | background: "#DD7A00", 31 | color: "white", 32 | label: { 33 | "FilterCollectionProcessor": "Collection Filter", 34 | "SortCollectionProcessor": "Collection Sort", 35 | }, 36 | icon: "<&pulse>", 37 | mermaidIcon: { 38 | "FilterCollectionProcessor": "⑂", 39 | "SortCollectionProcessor" : "⇅", 40 | 41 | }, 42 | mermaidClose: ")", 43 | mermaidOpen: "(" 44 | }, 45 | 'customErrors': { 46 | background: "#032D60", 47 | color: "white", 48 | label: "Custom Error", 49 | icon: "<&pencil>", 50 | mermaidIcon: "🚫" , 51 | mermaidClose: ")", 52 | mermaidOpen: "(" 53 | }, 54 | 'decisions': { 55 | background: "#DD7A00", 56 | color: "white", 57 | label: "Decision", 58 | icon: "<&fork>", 59 | mermaidIcon: "⇋" , 60 | mermaidClose: "}}", 61 | mermaidOpen: "{{" 62 | }, 63 | 'loops': { 64 | background: "#E07D1C", 65 | label: "Loop", 66 | mermaidIcon: "↻", 67 | mermaidClose: ")", 68 | mermaidOpen: "(" 69 | }, 70 | 'recordCreates': { 71 | background: "#F9548A", 72 | color: "white", 73 | label: "Create Records", 74 | icon: "<&medical-cross>", 75 | mermaidIcon: "+" , 76 | mermaidClose: ")", 77 | mermaidOpen: "(" 78 | }, 79 | 'recordDeletes': { 80 | background: "#F9548A", 81 | color: "white", 82 | label: "Delete Records", 83 | icon: "<&medical-cross>", 84 | mermaidIcon: "-" , 85 | mermaidClose: ")", 86 | mermaidOpen: "(" 87 | }, 88 | 'recordLookups': { 89 | background: "#F9548A", 90 | color: "white", 91 | label: "Get Records", 92 | icon: "<&medical-cross>", 93 | mermaidIcon: "🔍" , 94 | mermaidClose: ")", 95 | mermaidOpen: "(" 96 | }, 97 | 'recordUpdates': { 98 | background: "#F9548A", 99 | color: "white", 100 | label: "Update Records", 101 | icon: "<&pencil>", 102 | mermaidIcon: "📝" , 103 | mermaidClose: ")", 104 | mermaidOpen: "(" 105 | }, 106 | 'screens': { 107 | background: "#1B96FF", 108 | color: "white", 109 | label: "Screen", 110 | icon: "<&pencil>", 111 | mermaidIcon: "💻" , 112 | mermaidClose: ")", 113 | mermaidOpen: "(" 114 | }, 115 | 'subflows': { 116 | background: "#032D60", 117 | color: "white", 118 | label: "Subflow", 119 | icon: "<&pencil>", 120 | mermaidIcon: "⎘" , 121 | mermaidClose: ")", 122 | mermaidOpen: "(" 123 | }, 124 | }; -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Custom_Start_Logic_Flow.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Default 5 | Custom Start Logic Flow {!$Flow.CurrentDateTime} 6 | 7 | 8 | BuilderType 9 | 10 | LightningFlowBuilder 11 | 12 | 13 | 14 | CanvasMode 15 | 16 | AUTO_LAYOUT_CANVAS 17 | 18 | 19 | 20 | OriginBuilderType 21 | 22 | LightningFlowBuilder 23 | 24 | 25 | AutoLaunchedFlow 26 | 27 | Get_me_some_records 28 | 29 | 176 30 | 323 31 | false 32 | and 33 | 34 | AccountId 35 | EqualTo 36 | 37 | $Record.Id 38 | 39 | 40 | true 41 | Case 42 | true 43 | 44 | 45 | 50 46 | 0 47 | 48 | Get_me_some_records 49 | 50 | true 51 | (1 OR 2) AND 3 52 | 53 | Industry 54 | StartsWith 55 | 56 | Banking 57 | 58 | 59 | 60 | Industry 61 | EndsWith 62 | 63 | Banking 64 | 65 | 66 | 67 | Rating 68 | Contains 69 | 70 | Warm 71 | 72 | 73 | Account 74 | CreateAndUpdate 75 | RecordAfterSave 76 | 77 | Draft 78 | 79 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Send_Email_on_Account_Change.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Send an email to the account owner to notify them of a change 5 | Email_Account_Owner 6 | 7 | 176 8 | 323 9 | Account.Notify_Owner_of_Account_Change 10 | emailAlert 11 | CurrentTransaction 12 | 13 | SObjectRowId 14 | 15 | $Record.Id 16 | 17 | 18 | 19 | 59.0 20 | Default 21 | Send Email on Account Change {!$Flow.CurrentDateTime} 22 | 23 | 24 | BuilderType 25 | 26 | LightningFlowBuilder 27 | 28 | 29 | 30 | CanvasMode 31 | 32 | AUTO_LAYOUT_CANVAS 33 | 34 | 35 | 36 | OriginBuilderType 37 | 38 | LightningFlowBuilder 39 | 40 | 41 | AutoLaunchedFlow 42 | 43 | 50 44 | 0 45 | 46 | Email_Account_Owner 47 | 48 | and 49 | 50 | AccountNumber 51 | IsChanged 52 | 53 | true 54 | 55 | 56 | 57 | AnnualRevenue 58 | IsChanged 59 | 60 | true 61 | 62 | 63 | 64 | Name 65 | IsChanged 66 | 67 | true 68 | 69 | 70 | 71 | Rating 72 | IsChanged 73 | 74 | true 75 | 76 | 77 | Account 78 | Update 79 | RecordAfterSave 80 | 81 | Draft 82 | 83 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Post_to_Chatter.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chatter_Mention_Post 5 | 6 | 176 7 | 134 8 | chatterPost 9 | chatterPost 10 | CurrentTransaction 11 | 12 | text 13 | 14 | chatterBody 15 | 16 | 17 | 18 | subjectNameOrId 19 | 20 | postTargetID 21 | 22 | 23 | 24 | chatterPostID 25 | feedItemId 26 | 27 | 28 | 59.0 29 | Default 30 | Post to Chatter {!$Flow.CurrentDateTime} 31 | 32 | 33 | BuilderType 34 | 35 | LightningFlowBuilder 36 | 37 | 38 | 39 | CanvasMode 40 | 41 | AUTO_LAYOUT_CANVAS 42 | 43 | 44 | 45 | OriginBuilderType 46 | 47 | LightningFlowBuilder 48 | 49 | 50 | AutoLaunchedFlow 51 | 52 | 50 53 | 0 54 | 55 | Chatter_Mention_Post 56 | 57 | 58 | Active 59 | 60 | chatterBody 61 | true 62 | @[{!userMentionID}], please review this record. 63 | 64 | 65 | chatterPostID 66 | String 67 | false 68 | false 69 | true 70 | 71 | 72 | postTargetID 73 | String 74 | false 75 | true 76 | false 77 | 78 | 79 | userMentionID 80 | String 81 | false 82 | true 83 | false 84 | 85 | 86 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Create_Follow_Up_on_New_Prospect.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Default 5 | Create Follow-Up on New Prospect {!$Flow.CurrentDateTime} 6 | 7 | 8 | BuilderType 9 | 10 | LightningFlowBuilder 11 | 12 | 13 | 14 | CanvasMode 15 | 16 | AUTO_LAYOUT_CANVAS 17 | 18 | 19 | 20 | OriginBuilderType 21 | 22 | LightningFlowBuilder 23 | 24 | 25 | AutoLaunchedFlow 26 | 27 | Create a task named Follow-Up Discovery Call, assigned to the account’s owner 28 | Create_Follow_Up 29 | 30 | 176 31 | 323 32 | 33 | OwnerId 34 | 35 | $Record.OwnerId 36 | 37 | 38 | 39 | Priority 40 | 41 | Normal 42 | 43 | 44 | 45 | Status 46 | 47 | Not Started 48 | 49 | 50 | 51 | Subject 52 | 53 | Follow-Up Discovery Call 54 | 55 | 56 | 57 | WhatId 58 | 59 | $Record.Id 60 | 61 | 62 | Task 63 | true 64 | 65 | 66 | 50 67 | 0 68 | 69 | Create_Follow_Up 70 | 71 | and 72 | 73 | Type 74 | EqualTo 75 | 76 | Prospect 77 | 78 | 79 | Account 80 | Create 81 | RecordAfterSave 82 | 83 | Draft 84 | 85 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Custom_Error.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | 5 | My_Custom_Error 6 | 7 | 176 8 | 431 9 | 10 | {!My_Custom_Error}{!MyCustomError} 11 | Account 12 | true 13 | 14 | 15 | Default 16 | Custom Error {!$Flow.CurrentDateTime} 17 | 18 | 19 | BuilderType 20 | 21 | LightningFlowBuilder 22 | 23 | 24 | 25 | CanvasMode 26 | 27 | AUTO_LAYOUT_CANVAS 28 | 29 | 30 | 31 | OriginBuilderType 32 | 33 | LightningFlowBuilder 34 | 35 | 36 | AutoLaunchedFlow 37 | 38 | 50 39 | 0 40 | 41 | Post_to_Chatter 42 | 43 | and 44 | 45 | Priority 46 | EqualTo 47 | 48 | High 49 | 50 | 51 | Case 52 | CreateAndUpdate 53 | RecordAfterSave 54 | 55 | Draft 56 | 57 | Post_to_Chatter 58 | 59 | 176 60 | 323 61 | 62 | My_Custom_Error 63 | 64 | Post_to_Chatter 65 | 66 | postTargetID 67 | 68 | $Record.Id 69 | 70 | 71 | 72 | userMentionID 73 | 74 | $Record.OwnerId 75 | 76 | 77 | true 78 | 79 | 80 | Whoop! 81 | MyCustomError 82 | false 83 | <p>Whoop!</p> 84 | 85 | 86 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Post_to_Chatter_on_Account_Change.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chatter_Notification 5 | 6 | 176 7 | 323 8 | chatterPost 9 | chatterPost 10 | CurrentTransaction 11 | 12 | text 13 | 14 | ChatterBody 15 | 16 | 17 | 18 | subjectNameOrId 19 | 20 | $Record.Owner.Username 21 | 22 | 23 | 24 | type 25 | 26 | User 27 | 28 | 29 | true 30 | 31 | 59.0 32 | Default 33 | Post to Chatter on Account Change {!$Flow.CurrentDateTime} 34 | 35 | 36 | BuilderType 37 | 38 | LightningFlowBuilder 39 | 40 | 41 | 42 | CanvasMode 43 | 44 | AUTO_LAYOUT_CANVAS 45 | 46 | 47 | 48 | OriginBuilderType 49 | 50 | LightningFlowBuilder 51 | 52 | 53 | AutoLaunchedFlow 54 | 55 | 50 56 | 0 57 | 58 | Chatter_Notification 59 | 60 | and 61 | 62 | AccountNumber 63 | IsChanged 64 | 65 | true 66 | 67 | 68 | 69 | AnnualRevenue 70 | IsChanged 71 | 72 | true 73 | 74 | 75 | 76 | Name 77 | IsChanged 78 | 79 | true 80 | 81 | 82 | 83 | Rating 84 | IsChanged 85 | 86 | true 87 | 88 | 89 | Account 90 | Update 91 | RecordAfterSave 92 | 93 | Draft 94 | 95 | ChatterBody 96 | true 97 | @[{!$Record.OwnerId}], your account record has changed. 98 | 99 | 100 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/New_Order_Email_to_Supplier.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Send_Email_to_Supplier 5 | 6 | 264 7 | 398 8 | emailSimple 9 | emailSimple 10 | 11 | Loop_through_Suppliers 12 | 13 | CurrentTransaction 14 | 15 | emailBody 16 | 17 | Congrats on your new order with Wired Brain Coffee! It's been a pleasure doing business with you! 18 | 19 | 20 | 21 | emailAddresses 22 | 23 | Loop_through_Suppliers.Primary_Contact_Email__c 24 | 25 | 26 | 27 | emailSubject 28 | 29 | ThankYou 30 | 31 | 32 | 33 | 55.0 34 | 35 | Thank_You_Message 36 | 37 | 264 38 | 278 39 | 40 | ThankYou 41 | Assign 42 | 43 | Thank You {!Loop_through_Suppliers.Name} 44 | 45 | 46 | 47 | Send_Email_to_Supplier 48 | 49 | 50 | Send an email to the supplier when a new order has been placed. This will be called from Apex 51 | New Order Email to Supplier {!$Flow.CurrentDateTime} 52 | 53 | 54 | Loop_through_Suppliers 55 | 56 | 176 57 | 158 58 | SupplierList 59 | Asc 60 | 61 | Thank_You_Message 62 | 63 | 64 | 65 | BuilderType 66 | 67 | LightningFlowBuilder 68 | 69 | 70 | 71 | CanvasMode 72 | 73 | AUTO_LAYOUT_CANVAS 74 | 75 | 76 | 77 | OriginBuilderType 78 | 79 | LightningFlowBuilder 80 | 81 | 82 | AutoLaunchedFlow 83 | 84 | 50 85 | 0 86 | 87 | Loop_through_Suppliers 88 | 89 | 90 | Active 91 | 92 | SupplierList 93 | SObject 94 | true 95 | true 96 | false 97 | Account 98 | 99 | 100 | ThankYou 101 | String 102 | false 103 | false 104 | false 105 | 106 | Thank You 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Case_Priority_is_High.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | 5 | Daysinayear 6 | Number 7 | 8 | 365.25 9 | 10 | 11 | 12 | My_Custom_Error 13 | 14 | 176 15 | 431 16 | 17 | {!My_Custom_Error}{!MyCustomError} 18 | Account 19 | true 20 | 21 | 22 | Default 23 | 24 | ShirtforceBirthdayStilltoCome 25 | Boolean 26 | if(DAYOFYEAR(today()) < 305, true, false) 27 | 28 | 29 | TableRow 30 | String 31 | '<tr border="1" style="border-collapse:collapse">'+ 32 | '<td>'+{!Loop_Users.Id}+'</td>'+ 33 | '<td>'+{!Loop_Users.Name}+'</td>'+ 34 | '<td>'+{!Loop_Users.Username}+'</td>'+ 35 | '<td>'+TEXT({!Loop_Users.CreatedDate})+'</td>'+ 36 | '<td>'+TEXT({!Loop_Users.LastLoginDate})+'</td>'+ 37 | '<td>'+IF({!Loop_Users.IsActive},'Active','Deactivated')+'</td>'+ 38 | '</tr>' 39 | > 40 | Case Priority is High. {!$Flow.CurrentDateTime} 41 | 42 | 43 | BuilderType 44 | 45 | LightningFlowBuilder 46 | 47 | 48 | 49 | CanvasMode 50 | 51 | AUTO_LAYOUT_CANVAS 52 | 53 | 54 | 55 | OriginBuilderType 56 | 57 | LightningFlowBuilder 58 | 59 | 60 | AutoLaunchedFlow 61 | 62 | 50 63 | 0 64 | 65 | Post_to_Chatter 66 | 67 | and 68 | 69 | Priority 70 | EqualTo 71 | 72 | High 73 | 74 | 75 | Case 76 | CreateAndUpdate 77 | RecordAfterSave 78 | 79 | Draft 80 | 81 | Post_to_Chatter 82 | 83 | 176 84 | 323 85 | 86 | My_Custom_Error 87 | 88 | Post_to_Chatter 89 | 90 | postTargetID 91 | 92 | $Record.Id 93 | 94 | 95 | 96 | userMentionID 97 | 98 | $Record.OwnerId 99 | 100 | 101 | true 102 | 103 | 104 | Whoop! 105 | MyCustomError 106 | false 107 | <p>"Whoop!</p> 108 | 109 | 110 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Closed_Won_Opportunities.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | If a high-value opportunity is closed and won, create a draft contract 5 | Default 6 | Closed Won Opportunities {!$Flow.CurrentDateTime} 7 | 8 | 9 | BuilderType 10 | 11 | LightningFlowBuilder 12 | 13 | 14 | 15 | CanvasMode 16 | 17 | AUTO_LAYOUT_CANVAS 18 | 19 | 20 | 21 | OriginBuilderType 22 | 23 | LightningFlowBuilder 24 | 25 | 26 | AutoLaunchedFlow 27 | 28 | Create a draft contract when an opportunity is won and is over 25,000 29 | Create_Draft_Contract 30 | 31 | 50 32 | 276 33 | 34 | AccountId 35 | 36 | $Record.Account.Id 37 | 38 | 39 | 40 | Status 41 | 42 | Draft 43 | 44 | 45 | Contract 46 | true 47 | 48 | 49 | Creates a task for the Account owner to reach out and welcome a new customer 50 | Personalized_Welcome_Task 51 | 52 | 314 53 | 276 54 | 55 | ActivityDate 56 | 57 | 2023-11-06 58 | 59 | 60 | 61 | Description 62 | 63 | Reach out with phone call to welcome new customer 64 | 65 | 66 | 67 | OwnerId 68 | 69 | $Record.OwnerId 70 | 71 | 72 | 73 | Subject 74 | 75 | Personalized Welcome 76 | 77 | 78 | 79 | WhatId 80 | 81 | $Record.Id 82 | 83 | 84 | Task 85 | true 86 | 87 | 88 | 56 89 | 0 90 | 91 | Create_Draft_Contract 92 | 93 | true 94 | and 95 | 96 | StageName 97 | EqualTo 98 | 99 | Closed Won 100 | 101 | 102 | 103 | Amount 104 | GreaterThan 105 | 106 | 25000.0 107 | 108 | 109 | Opportunity 110 | CreateAndUpdate 111 | 112 | X5_Days_After_Close 113 | 114 | Personalized_Welcome_Task 115 | 116 | 117 | 5 118 | Days 119 | CloseDate 120 | RecordField 121 | 122 | RecordAfterSave 123 | 124 | Draft 125 | 126 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Loop-Bug.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 58.0 4 | 5 | X1_site 6 | 7 | 264 8 | 647 9 | 10 | countSites 11 | Add 12 | 13 | 1.0 14 | 15 | 16 | 17 | loop_Sites_opp 18 | 19 | 20 | calcul le temps moyen d'entretien par Site 21 | Default 22 | 23 | tempsMoyenEntretien 24 | Number 25 | {!get_opportunity.Temps_d_intervention__c}/({!countSites}) 26 | 2 27 | 28 | maintenancePlan_tempsMoyenEntretien {!$Flow.CurrentDateTime} 29 | 30 | 31 | loop_Sites_opp 32 | 33 | 176 34 | 539 35 | get_sites_opportunite 36 | Asc 37 | 38 | X1_site 39 | 40 | 41 | update_maintenace_plan 42 | 43 | 44 | 45 | BuilderType 46 | 47 | LightningFlowBuilder 48 | 49 | 50 | 51 | CanvasMode 52 | 53 | AUTO_LAYOUT_CANVAS 54 | 55 | 56 | 57 | OriginBuilderType 58 | 59 | LightningFlowBuilder 60 | 61 | 62 | AutoLaunchedFlow 63 | 64 | get_opportunity 65 | 66 | 176 67 | 323 68 | false 69 | 70 | get_sites_opportunite 71 | 72 | and 73 | 74 | Id 75 | EqualTo 76 | 77 | $Record.Opportunite__c 78 | 79 | 80 | true 81 | Opportunity 82 | true 83 | 84 | 85 | get_sites_opportunite 86 | 87 | 176 88 | 431 89 | false 90 | 91 | loop_Sites_opp 92 | 93 | and 94 | 95 | Opportunite__c 96 | EqualTo 97 | 98 | get_opportunity.Id 99 | 100 | 101 | false 102 | Site_opportunite__c 103 | Id 104 | Site__c 105 | true 106 | 107 | 108 | update_maintenace_plan 109 | 110 | 176 111 | 839 112 | 113 | temps_d_entretien_moyen_par_actif__c 114 | 115 | tempsMoyenEntretien 116 | 117 | 118 | $Record 119 | 120 | 121 | 50 122 | 0 123 | 124 | get_opportunity 125 | 126 | MaintenancePlan 127 | Create 128 | RecordAfterSave 129 | 130 | Active 131 | 132 | countSites 133 | Number 134 | false 135 | false 136 | false 137 | 0 138 | 139 | 0.0 140 | 141 | 142 | 143 | workTypeId 144 | String 145 | false 146 | false 147 | false 148 | 149 | 150 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Create_Follow_Up_with_Decision_Maker.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | 5 | Filter 6 | FilterCollectionProcessor 7 | 8 | 176 9 | 539 10 | currentItem_Filter 11 | FilterCollectionProcessor 12 | ContactRoles 13 | and 14 | 15 | currentItem_Filter.Role 16 | EqualTo 17 | 18 | Decision Maker 19 | 20 | 21 | 22 | Create_Task 23 | 24 | 25 | 26 | Sort_Contact_Roles 27 | SortCollectionProcessor 28 | 29 | 176 30 | 431 31 | SortCollectionProcessor 32 | ContactRoles 33 | 34 | Filter 35 | 36 | 37 | false 38 | LastModifiedDate 39 | Asc 40 | 41 | 42 | Default 43 | Create Follow-Up with Decision Maker {!$Flow.CurrentDateTime} 44 | 45 | 46 | BuilderType 47 | 48 | LightningFlowBuilder 49 | 50 | 51 | 52 | CanvasMode 53 | 54 | AUTO_LAYOUT_CANVAS 55 | 56 | 57 | 58 | OriginBuilderType 59 | 60 | LightningFlowBuilder 61 | 62 | 63 | AutoLaunchedFlow 64 | 65 | Create_Task 66 | 67 | 176 68 | 647 69 | 70 | OwnerId 71 | 72 | $Record.OwnerId 73 | 74 | 75 | 76 | Priority 77 | 78 | Normal 79 | 80 | 81 | 82 | Status 83 | 84 | Not Started 85 | 86 | 87 | 88 | Subject 89 | 90 | Closed Lost Follow-Up 91 | 92 | 93 | 94 | WhatId 95 | 96 | $Record.Id 97 | 98 | 99 | 100 | WhoId 101 | 102 | currentItem_Filter.Id 103 | 104 | 105 | Task 106 | true 107 | 108 | 109 | Get_Decision_Maker 110 | 111 | 176 112 | 323 113 | false 114 | 115 | Sort_Contact_Roles 116 | 117 | and 118 | 119 | OpportunityId 120 | EqualTo 121 | 122 | $Record.Id 123 | 124 | 125 | OpportunityContactRole 126 | ContactRoles 127 | Id 128 | Role 129 | CreatedDate 130 | Desc 131 | 132 | 133 | 50 134 | 0 135 | 136 | Get_Decision_Maker 137 | 138 | true 139 | and 140 | 141 | StageName 142 | EqualTo 143 | 144 | Closed Lost 145 | 146 | 147 | 148 | Amount 149 | GreaterThanOrEqualTo 150 | 151 | 100000.0 152 | 153 | 154 | Opportunity 155 | Update 156 | RecordAfterSave 157 | 158 | Draft 159 | 160 | ContactRoles 161 | SObject 162 | true 163 | false 164 | false 165 | OpportunityContactRole 166 | 167 | 168 | currentItem_Filter 169 | SObject 170 | false 171 | false 172 | false 173 | OpportunityContactRole 174 | 175 | 176 | -------------------------------------------------------------------------------- /tests/salesforce-project/salesforce-project/main/default/flows/Closed_Won_Opportunities_Multi_Schedule.flow-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fault_Email 5 | 6 | 842 7 | 384 8 | Account.Notify_Owner_of_Account_Change 9 | emailAlert 10 | CurrentTransaction 11 | 12 | SObjectRowId 13 | 14 | $Record.Id 15 | 16 | 17 | Account.Notify_Owner_of_Account_Change 18 | 1 19 | 20 | 21 | Mail_Account_Holder 22 | 23 | 578 24 | 276 25 | Account.Notify_Owner_of_Account_Change 26 | emailAlert 27 | 28 | Fault_Email 29 | 30 | CurrentTransaction 31 | 32 | SObjectRowId 33 | 34 | $Record.AccountId 35 | 36 | 37 | Account.Notify_Owner_of_Account_Change 38 | 1 39 | 40 | 59.0 41 | Default 42 | Closed Won OpportunitiesClosed Won Opportunities {!$Flow.CurrentDateTime} 43 | 44 | 45 | BuilderType 46 | 47 | LightningFlowBuilder 48 | 49 | 50 | 51 | CanvasMode 52 | 53 | AUTO_LAYOUT_CANVAS 54 | 55 | 56 | 57 | OriginBuilderType 58 | 59 | LightningFlowBuilder 60 | 61 | 62 | AutoLaunchedFlow 63 | 64 | Create a draft contract when an opportunity is won and is over 25,000 65 | Create_Draft_Contract 66 | 67 | 50 68 | 276 69 | 70 | AccountId 71 | 72 | $Record.Account.Id 73 | 74 | 75 | 76 | Status 77 | 78 | Draft 79 | 80 | 81 | Contract 82 | true 83 | 84 | 85 | Creates a task for the Account owner to reach out and welcome a new customer 86 | Personalized_Welcome_Task 87 | 88 | 314 89 | 276 90 | 91 | ActivityDate 92 | 93 | 2023-11-06 94 | 95 | 96 | 97 | Description 98 | 99 | Reach out with phone call to welcome new customer 100 | 101 | 102 | 103 | OwnerId 104 | 105 | $Record.OwnerId 106 | 107 | 108 | 109 | Subject 110 | 111 | Personalized Welcome 112 | 113 | 114 | 115 | WhatId 116 | 117 | $Record.Id 118 | 119 | 120 | Task 121 | true 122 | 123 | 124 | 188 125 | 0 126 | 127 | Create_Draft_Contract 128 | 129 | true 130 | and 131 | 132 | StageName 133 | EqualTo 134 | 135 | Closed Won 136 | 137 | 138 | 139 | Amount 140 | GreaterThan 141 | 142 | 25000.0 143 | 144 | 145 | Opportunity 146 | CreateAndUpdate 147 | 148 | X5_Days_After_Close 149 | 150 | Personalized_Welcome_Task 151 | 152 | 153 | 5 154 | Days 155 | CloseDate 156 | RecordField 157 | 158 | 159 | Much_later 160 | 161 | Mail_Account_Holder 162 | 163 | 164 | 10 165 | Days 166 | RecordTriggerEvent 167 | 168 | RecordAfterSave 169 | 170 | Draft 171 | 172 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /src/flowParser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * index.ts 3 | * TODO 4 | * - Move FlowMap to a class 5 | * - Move each language to their own file 6 | * - Handle options 7 | */ 8 | 9 | import { NODE_CONFIG } from "./renderConfig"; 10 | 11 | const { XMLParser } = require("fast-xml-parser"); 12 | 13 | interface FlowMap { 14 | "description"? : string; 15 | "label"? : string; 16 | "processType"? : string; // TODO 17 | "start"? : any; 18 | "status"? : "Active" | "Draft"; 19 | [propName: string]: any; 20 | } 21 | 22 | // TODO FILL OUT 23 | interface FlowObj { 24 | "description"? : string; 25 | "label"? : string; 26 | "processType"? : string; // TODO 27 | "start"? : { 28 | "connector" : string; 29 | "scheduledPaths": any; 30 | }; 31 | "status"? : "Active" | "Draft"; 32 | "subflows"? : any | any[]; 33 | "actionCalls"? : any | any[]; 34 | "assignments"? : any | any[]; 35 | "decisions"?: any | any[]; 36 | } 37 | 38 | 39 | /*=================================================================== 40 | * E X P O R T E D 41 | *=================================================================*/ 42 | 43 | export function parseFlow(xml:string, renderAs: "plantuml" | "mermaid" = "mermaid", options: any = {}): Promise<{flowMap: FlowMap, uml: string}> { 44 | return new Promise(async (resolve, reject) => { 45 | try { 46 | const parser = new XMLParser(); 47 | const flowObj = parser.parse(xml).Flow; 48 | const flowMap = await createFlowMap(flowObj); 49 | // console.log("flowMap", flowMap); 50 | if (Object.keys(flowMap).length === 0) { 51 | reject("no-renderable-content-found"); 52 | } 53 | 54 | switch (renderAs) { 55 | case 'mermaid': 56 | resolve({ 57 | flowMap: flowMap, 58 | uml: await generateMermaidContent(flowMap, options) 59 | }); 60 | break; 61 | case 'plantuml': 62 | resolve({ 63 | flowMap: flowMap, 64 | uml: await generatePlantUMLContent(flowMap) 65 | }); 66 | break; 67 | default: 68 | reject("unknown-renderAs-" + renderAs); 69 | } 70 | } catch (error) { 71 | console.error("salesforce-flow-visualiser", error); 72 | reject(error); 73 | } 74 | }); 75 | } 76 | 77 | 78 | 79 | /*=================================================================== 80 | * P R I V A T E 81 | *=================================================================*/ 82 | function createFlowMap(flowObj: any) :Promise { 83 | return new Promise(async (resolve, reject) => { 84 | let flowMap:FlowMap = {}; 85 | for (const property in flowObj) { 86 | switch (property) { 87 | case 'constants' : 88 | case 'description' : 89 | case 'formulas' : 90 | case 'label' : 91 | case 'processType' : 92 | case 'status' : 93 | case 'textTemplates' : 94 | flowMap[property] = flowObj[property]; 95 | break; 96 | case 'start' : 97 | flowMap[property] = flowObj[property]; 98 | flowMap[property].type = property; 99 | flowMap[property].nextNode = flowObj[property].connector?.targetReference; 100 | flowMap[property].scheduledPaths = (!flowMap[property].scheduledPaths) ? [] : (flowMap[property].scheduledPaths.length) ? flowMap[property].scheduledPaths : [flowMap[property].scheduledPaths]; 101 | break; 102 | default : 103 | // If only one entry (e.g one loop) then it will be an object, not an Array, so make it an Array of one 104 | if (!flowObj[property].length) { 105 | flowObj[property] = [flowObj[property] ] 106 | } 107 | // Loop through array and create an mapped entry for each 108 | for (const el of flowObj[property] ) { 109 | if (el.name) { 110 | let nextNode; 111 | switch (property) { 112 | case 'decisions': 113 | nextNode = (el.defaultConnector) ? el.defaultConnector.targetReference : "END"; 114 | let tmpRules = (el.rules.length) ? el.rules : [el.rules]; 115 | el.rules2 = tmpRules.map((ruleEl: any) =>{ 116 | return { 117 | name: ruleEl.name, 118 | label: ruleEl.label, 119 | nextNode: ruleEl.connector, 120 | nextNodeLabel: el.defaultConnectorLabel, 121 | } 122 | }); 123 | break; 124 | case 'loops': 125 | nextNode = (el.noMoreValuesConnector) ? el.noMoreValuesConnector.targetReference : "END"; 126 | break; 127 | default: 128 | if (el.connector) { 129 | nextNode = el.connector.targetReference; 130 | } 131 | break; 132 | } 133 | 134 | if ((NODE_CONFIG)[property]) { 135 | const mappedEl = { 136 | name: el.name, 137 | label: el.label, 138 | type: property, 139 | nextNode: nextNode, 140 | faultPath: el.faultConnector?.targetReference, 141 | nextNodeLabel: el.defaultConnectorLabel, 142 | nextValueConnector : (el.nextValueConnector) ? 143 | el.nextValueConnector.targetReference : null, 144 | rules: el.rules2, 145 | elementSubtype: el.elementSubtype, 146 | actionType: el.actionType 147 | } 148 | flowMap[el.name] = mappedEl; 149 | } else if (property === 'variables') { 150 | flowMap.variables = flowObj[property]; 151 | } 152 | } 153 | } 154 | break; 155 | } 156 | 157 | } 158 | resolve( flowMap ); 159 | }); 160 | } 161 | 162 | function getFlowType(flowMap: FlowMap): string { 163 | if (flowMap.processType === 'Flow') { 164 | return "Screen flow"; 165 | } else { 166 | switch ( flowMap.start.triggerType ) { 167 | case "Scheduled": 168 | return "Scheduled flow;" 169 | case "RecordAfterSave": 170 | return "Record triggered flow: After Save (" + flowMap.start.object + ")"; 171 | case "RecordBeforeSave": 172 | return "Record triggered flow: Before Save (" + flowMap.start.object + ")"; 173 | case "PlatformEvent": 174 | return "PlatformEvent triggered flow (" + flowMap.start.object + ")"; 175 | default: 176 | return "Autolanuched flow - No trigger"; 177 | } 178 | } 179 | } 180 | 181 | /*=================================================================== 182 | * M E R M A I D 183 | *=================================================================*/ 184 | function generateMermaidContent(flowMap: FlowMap, options: any):Promise { 185 | console.log("options", options) 186 | return new Promise(async (resolve, reject) => { 187 | const title = "# "+ flowMap['label'] + "\n### " + getFlowType(flowMap) + "\n*" + flowMap['status'] + "*\n"; 188 | const variables = await getVariablesMd(flowMap.variables) + "\n"; 189 | const mdStart = "## Flow\n```mermaid\n"; 190 | const nodeDefStr = await getNodeDefStr(flowMap) + "\n\n"; 191 | const mdClasses = await getMermaidClasses() + "\n\n"; 192 | const mdBody = await getMermaidBody(flowMap) + "\n\n"; 193 | const mdEnd = "```\n"; 194 | const mdDiagram = "flowchart TB\n" + nodeDefStr + mdBody + mdClasses 195 | if ( options.wrapInMarkdown === false) { 196 | resolve(mdDiagram); 197 | } else { 198 | resolve(title + variables + mdStart + mdDiagram + mdEnd); 199 | } 200 | }); 201 | } 202 | 203 | function getMermaidBody(flowMap :FlowMap): Promise { 204 | return new Promise(async (resolve, reject) => { 205 | let bodyStr = ""; 206 | for (const property in flowMap) { 207 | const node = flowMap[property]; 208 | const type = node.type; 209 | const nextNode = (node.nextNode) ? node.nextNode : "END" 210 | const faultNode = (node.faultPath) ? node.faultPath : "END" 211 | switch (type) { 212 | case 'actionCalls': 213 | case 'assignments': 214 | case 'collectionProcessors': 215 | case 'customErrors': 216 | case 'recordCreates': 217 | case 'recordDeletes': 218 | case 'recordLookups': 219 | case 'recordUpdates': 220 | case 'screens': 221 | bodyStr += node.name + " --> " + nextNode + "\n"; 222 | if (node.faultPath) { 223 | bodyStr += node.name + " -. Fault .->" + faultNode + "\n"; 224 | } 225 | break; 226 | case 'start': 227 | if (nextNode !== "END") { 228 | // 'start' may not have a default path 229 | const defaultPathLabel = (node.scheduledPaths.length > 0) ? "|Run Immediately|" : ""; 230 | bodyStr += "START(( START )) --> " + defaultPathLabel + nextNode + "\n"; 231 | } 232 | // scheduled paths 233 | for (const path of node.scheduledPaths ) { 234 | path.label = (path.label) ? path.label : 'Run Immediately'; 235 | bodyStr += "START(( START )) --> |" + path.label + "| " + path.connector.targetReference + "\n"; 236 | // bodyStr += "START(( START )) --> |" + (path.label) ? path.label : 'Run Immediately' + "| " + path.connector.targetReference + "\n"; 237 | } 238 | 239 | break; 240 | case 'decisions': 241 | // rules 242 | for (const rule of node.rules ) { 243 | bodyStr += node.name + " --> |" + rule.label + "| " + rule.nextNode.targetReference + "\n"; 244 | } 245 | 246 | // default 247 | bodyStr += node.name + " --> |" + node.nextNodeLabel + "| " + nextNode + "\n"; 248 | break; 249 | case 'loops': 250 | let loopNextNode = node.nextValueConnector; 251 | bodyStr += node.name + " --> " + loopNextNode + "\n"; 252 | bodyStr += node.name + " ---> " + node.nextNode + "\n"; 253 | break; 254 | case 'subflows': 255 | bodyStr += node.name + " --> " + nextNode + "\n"; 256 | break; 257 | default: 258 | // do nothing 259 | break; 260 | } 261 | } 262 | resolve(bodyStr); 263 | }); 264 | } 265 | 266 | function getNodeDefStr(flowMap: FlowMap): Promise { 267 | return new Promise(async (resolve, reject) => { 268 | let nodeDefStr = "\START(( START ))\n"; 269 | for (const property in flowMap) { 270 | const type = flowMap[property].type; 271 | let label:string = ((NODE_CONFIG)[type]) ? (NODE_CONFIG)[type].label : ""; 272 | let icon:string = ((NODE_CONFIG)[type]) ? (NODE_CONFIG)[type].mermaidIcon : null; 273 | switch (type) { 274 | case 'actionCalls': 275 | icon = ((NODE_CONFIG)[type].mermaidIcon[flowMap[property].actionType]) ? 276 | (NODE_CONFIG)[type].mermaidIcon[flowMap[property].actionType] : 277 | (NODE_CONFIG)[type].mermaidIcon.submit; 278 | case 'collectionProcessors': 279 | icon = ((NODE_CONFIG)[type].mermaidIcon[flowMap[property].elementSubtype]) ? 280 | (NODE_CONFIG)[type].mermaidIcon[flowMap[property].elementSubtype] : 281 | (NODE_CONFIG)[type].mermaidIcon.submit; 282 | 283 | label = ((NODE_CONFIG)[type].label[flowMap[property].elementSubtype]) ? 284 | (NODE_CONFIG)[type].label[flowMap[property].elementSubtype] : 285 | (NODE_CONFIG)[type].label; 286 | case 'assignments': 287 | case 'customErrors': 288 | case 'decisions': 289 | case 'loops': 290 | case 'recordCreates': 291 | case 'recordDeletes': 292 | case 'recordLookups': 293 | case 'recordUpdates': 294 | case 'screens': 295 | case 'subflows': 296 | nodeDefStr += property + (NODE_CONFIG)[type].mermaidOpen + '"' + icon + " " + label +"
" + flowMap[property].label + '"' + (NODE_CONFIG)[type].mermaidClose + ':::' + type + "\n" 297 | break; 298 | default: 299 | // do nothing 300 | break; 301 | } 302 | } 303 | resolve(nodeDefStr + "\END(( END ))\n"); 304 | }); 305 | } 306 | 307 | function getVariablesMd(vars :any[]): string { 308 | let vStr = "## Variables\n|Name|Datatype|Collection|Input|Output|objectType|\n|-|-|-|-|-|-|\n"; 309 | if (!vars) vars = []; 310 | for (const v of vars) { 311 | vStr += "|" + v.name + "|" + v.dataType + "|" + v.isCollection + "|" + v.isInput + "|" + v.isOutput + "|" + ((v.objectType) ? v.objectType : "") + "\n"; 312 | } 313 | return vStr; 314 | } 315 | 316 | function getMermaidClasses(): string { 317 | let classStr = ""; 318 | for (const property in NODE_CONFIG) { 319 | classStr += "classDef " + property + " fill:" + (NODE_CONFIG)[property].background + ",color:" + (NODE_CONFIG)[property].color + "\n"; 320 | } 321 | return classStr; 322 | } 323 | 324 | 325 | /*=================================================================== 326 | * P L A N T U M L 327 | *=================================================================*/ 328 | function generatePlantUMLContent(flowMap: FlowMap):Promise { 329 | return new Promise((resolve, reject) => { 330 | const START_STR = "' THIS IS A TEMPORARY FILE\n@startuml " + flowMap['label'] + "\nstart\n"; 331 | const TITLE_STR = "title " + flowMap['label'] + "\n"; 332 | let nextNode = flowMap[flowMap['start'].connector.targetReference]; 333 | let end = false; 334 | let bodyStr = ''; 335 | while (!end) { 336 | bodyStr += getPlantUMLNodeStr(nextNode, flowMap); 337 | if (!nextNode.nextNode || nextNode.nextNode === "END") { 338 | end = true; 339 | } else { 340 | nextNode = flowMap[nextNode.nextNode] 341 | } 342 | } 343 | const END_STR = "stop\n@enduml"; 344 | resolve(START_STR + TITLE_STR + bodyStr + END_STR); 345 | }); 346 | } 347 | 348 | 349 | function getPlantUMLNodeStr(node: any, flowMap: FlowMap) { 350 | let nextNode; 351 | let end; 352 | switch (node.type) { 353 | case 'decisions': 354 | return processDecisions(node, flowMap); 355 | case 'loops': 356 | let loopName = node.name; 357 | nextNode = flowMap[node.nextValueConnector]; 358 | let bodyStr = "floating note left: " + loopName + "\n repeat :<&loop-circular>;\n"; 359 | end = false; 360 | while (!end) { 361 | bodyStr += getPlantUMLNodeStr(nextNode, flowMap); 362 | if (!nextNode.nextNode || nextNode.nextNode === loopName) { 363 | end = true; 364 | } else { 365 | nextNode = flowMap[nextNode.nextNode] 366 | } 367 | } 368 | return bodyStr + "repeat while (more data?)\n"; 369 | default: 370 | if ((NODE_CONFIG)[node.type]) { 371 | const cnf = (NODE_CONFIG)[node.type]; 372 | return cnf.background + ":" + cnf.icon + ";\nfloating note left\n**" + node.label + "**\n" + cnf.label + "\nend note\n"; 373 | } else { 374 | return "' " + node.name + " NOT IMPLEMENTED \n"; 375 | } 376 | } 377 | } 378 | 379 | function processDecisions(node: any, flowMap: FlowMap) { 380 | const START_STR = "switch (" + node.label + ")\n" 381 | const DEFATAULT_STR = "\ncase (" + node.nextNodeLabel + ")\n"; 382 | 383 | let nextNode; 384 | let end; 385 | let rulesStr = ""; 386 | for (const rule of node.rules ) { 387 | rulesStr += "case (" + rule.label + ")\n"; 388 | 389 | nextNode = nextNode = flowMap[rule.nextNode.targetReference]; 390 | end = false; 391 | while (!end) { 392 | rulesStr += getPlantUMLNodeStr(nextNode, flowMap); 393 | if (!nextNode.nextNode || nextNode.nextNode === node.nextNode) { 394 | end = true; 395 | } else { 396 | nextNode = flowMap[nextNode.nextNode] 397 | } 398 | } 399 | } 400 | const END_STR = "endswitch\n"; 401 | return START_STR + rulesStr + DEFATAULT_STR + END_STR; 402 | } --------------------------------------------------------------------------------