Skip to content

Commit

Permalink
refactor: i learnt a lot
Browse files Browse the repository at this point in the history
  • Loading branch information
tomcarman committed Jun 30, 2024
1 parent 522a97b commit 003f3ae
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 154 deletions.
11 changes: 11 additions & 0 deletions src/common/experimental/FlowElementWrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { AnyFlowElement } from './FlowNodeTypes.js';

export class FlowElementWrapper {
public type: string;
public element: AnyFlowElement;

public constructor(typeOfElement: string, element: AnyFlowElement) {
this.type = typeOfElement;
this.element = element;
}
}
37 changes: 17 additions & 20 deletions src/common/experimental/FlowNodeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,36 @@ import {
FlowLoop,
FlowStep,
FlowStart,
FlowChoice,
FlowConstant,
FlowDynamicChoiceSet,
FlowFormula,
FlowStage,
FlowTextTemplate,
FlowVariable,
} from '@salesforce/types/metadata';

export type NodeType = NodeWithConnector | NodeWithFaultConnector | FlowWait | FlowDecision | FlowLoop | FlowStep;

export type NodeWithConnector =
export type AnyFlowNode =
| FlowAssignment
| FlowCollectionProcessor
| FlowCustomError
| FlowRecordRollback
| FlowScreen
| FlowSubflow
| FlowTransform
| FlowStart
| FlowActionCall
| FlowApexPluginCall
| FlowOrchestratedStage
| FlowRecordCreate
| FlowRecordDelete
| FlowRecordLookup
| FlowRecordUpdate;

export type NodeWithFaultConnector =
| FlowActionCall
| FlowApexPluginCall
| FlowOrchestratedStage
| FlowRecordCreate
| FlowRecordDelete
| FlowRecordLookup
| FlowRecordUpdate;
| FlowRecordUpdate
| FlowWait
| FlowDecision
| FlowLoop
| FlowStep
| FlowStart;

export type NodeWithDefaultConnector = FlowWait | FlowDecision;
export type NodeWithNextValueConnector = FlowLoop;
export type NodeWithNoMoreValuesConnector = FlowLoop;
export type NodeWithWaitEvents = FlowWait;
export type NodeWithRules = FlowDecision;
export type NodeWithScheduledPaths = FlowStart;
export type AnyFlowElement = [
FlowChoice | FlowConstant | FlowDynamicChoiceSet | FlowFormula | FlowStage | FlowTextTemplate | FlowVariable
];
109 changes: 49 additions & 60 deletions src/common/experimental/FlowNodeWrapper.ts
Original file line number Diff line number Diff line change
@@ -1,111 +1,100 @@
import { FlowConnector } from '@salesforce/types/metadata';
import {
NodeType,
NodeWithConnector,
NodeWithFaultConnector,
NodeWithDefaultConnector,
NodeWithNextValueConnector,
NodeWithNoMoreValuesConnector,
NodeWithWaitEvents,
NodeWithRules,
NodeWithScheduledPaths,
} from './FlowNodeTypes.js';
import type { FlowConnector } from '@salesforce/types/metadata';
import { arrayify } from '../util.js';
import type { AnyFlowNode } from './FlowNodeTypes.js';

type Connector = FlowConnector & {
type: string;
};

export class FlowNodeWrapper {
public type: string;
public name: string;
public node: AnyFlowNode;
public connectors: Connector[] = [];

public constructor(nodeType: NodeType) {
this.name = nodeType.name ?? '';
this.handleConnectors(nodeType);
public constructor(typeOfNode: string, node: AnyFlowNode) {
this.type = typeOfNode;
this.name = node.name ?? '';
this.node = node;
this.handleConnectors(node);
}

private handleConnectors(nodeType: NodeType): void {
this.addStandardConnectors(nodeType);
this.addFaultConnector(nodeType);
this.addDefaultConnector(nodeType);
this.addNextValueConnector(nodeType);
this.addNoMoreValuesConnector(nodeType);
this.addWaitEventConnectors(nodeType);
this.addRuleConnectors(nodeType);
this.addScheduledPaths(nodeType);
private handleConnectors(node: AnyFlowNode): void {
this.addStandardConnector(node);
this.addFaultConnector(node);
this.addDefaultConnector(node);
this.addNextValueConnector(node);
this.addNoMoreValuesConnector(node);
this.addWaitEventConnectors(node);
this.addRuleConnectors(node);
this.addScheduledPaths(node);
}

private addConnector(connectorType: string, connector: FlowConnector): void {
this.connectors.push({ type: connectorType, ...connector });
}

private addStandardConnectors(nodeType: NodeType): void {
if (hasConnector(nodeType) && nodeType.connector) {
const connectors: FlowConnector[] = Array.isArray(nodeType.connector) ? nodeType.connector : [nodeType.connector];
connectors.forEach((connector) => this.addConnector('connector', connector));
private addStandardConnector(node: AnyFlowNode): void {
if ('connector' in node && node.connector) {
this.addConnector('connector', node.connector);
}
if ('connectors' in node && node.connectors) {
arrayify(node.connectors).forEach((connector) => this.addConnector('connector', connector));
}
}

private addFaultConnector(nodeType: NodeType): void {
if (hasFaultConnector(nodeType) && nodeType.faultConnector) {
this.addConnector('faultConnector', nodeType.faultConnector);
private addFaultConnector(node: AnyFlowNode): void {
if ('faultConnector' in node && node.faultConnector) {
this.addConnector('faultConnector', node.faultConnector);
}
}

private addDefaultConnector(nodeType: NodeType): void {
if (hasDefaultConnector(nodeType) && nodeType.defaultConnector) {
this.addConnector('defaultConnector', nodeType.defaultConnector);
private addDefaultConnector(node: AnyFlowNode): void {
if ('defaultConnector' in node && node.defaultConnector) {
this.addConnector('defaultConnector', node.defaultConnector);
}
}

private addNextValueConnector(nodeType: NodeType): void {
if (hasNextValueConnector(nodeType) && nodeType.nextValueConnector) {
this.addConnector('nextValueConnector', nodeType.nextValueConnector);
private addNextValueConnector(node: AnyFlowNode): void {
if ('nextValueConnector' in node && node.nextValueConnector) {
this.addConnector('nextValueConnector', node.nextValueConnector);
}
}

private addNoMoreValuesConnector(nodeType: NodeType): void {
if (hasNoMoreValuesConnector(nodeType) && nodeType.noMoreValuesConnector) {
this.addConnector('noMoreValuesConnector', nodeType.noMoreValuesConnector);
private addNoMoreValuesConnector(node: AnyFlowNode): void {
if ('noMoreValuesConnector' in node && node.noMoreValuesConnector) {
this.addConnector('noMoreValuesConnector', node.noMoreValuesConnector);
}
}
private addWaitEventConnectors(nodeType: NodeType): void {
if (hasWaitEvents(nodeType)) {
nodeType.waitEvents.forEach((waitEvent) => {

private addWaitEventConnectors(node: AnyFlowNode): void {
if ('waitEvents' in node && node.waitEvents) {
arrayify(node.waitEvents).forEach((waitEvent) => {
if (waitEvent.connector) {
this.addConnector('connector', waitEvent.connector);
}
});
}
}

private addRuleConnectors(nodeType: NodeType): void {
if (hasRules(nodeType)) {
nodeType.rules.forEach((rule) => {
if (rule.connector) {
private addRuleConnectors(node: AnyFlowNode): void {
// Narrow type with 'fields', as both FlowRule and FlowScreenRule can be assigned to node.rules
if ('rules' in node && node.rules && !('fields' in node)) {
arrayify(node.rules).forEach((rule) => {
if ('connector' in rule && rule.connector) {
this.addConnector('connector', rule.connector);
}
});
}
}

private addScheduledPaths(nodeType: NodeType): void {
if (hasScheduledPaths(nodeType)) {
nodeType.scheduledPaths.forEach((path) => {
private addScheduledPaths(node: AnyFlowNode): void {
if ('scheduledPaths' in node && node.scheduledPaths) {
arrayify(node.scheduledPaths).forEach((path) => {
if (path.connector) {
this.addConnector('connector', path.connector);
}
});
}
}
}

const hasConnector = (node: NodeType): node is NodeWithConnector => 'connector' in node;
const hasFaultConnector = (node: NodeWithConnector): node is NodeWithFaultConnector => 'faultConnector' in node;
const hasDefaultConnector = (node: NodeType): node is NodeWithDefaultConnector => 'defaultConnector' in node;
const hasNextValueConnector = (node: NodeType): node is NodeWithNextValueConnector => 'nextValueConnector' in node;
const hasNoMoreValuesConnector = (node: NodeType): node is NodeWithNoMoreValuesConnector =>
'noMoreValuesConnector' in node;
const hasWaitEvents = (node: NodeType): node is NodeWithWaitEvents => 'waitEvents' in node;
const hasRules = (node: NodeType): node is NodeWithRules => 'rules' in node;
const hasScheduledPaths = (node: NodeType): node is NodeWithScheduledPaths => 'scheduledPaths' in node;
28 changes: 28 additions & 0 deletions src/common/experimental/FlowWalker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { FlowWrapper } from './FlowWrapper.js';
import { FlowNodeWrapper } from './FlowNodeWrapper.js';

export function walk(flow: FlowWrapper): void {
const startNode = flow.nodes.find((node: FlowNodeWrapper) => node.type === 'start');

if (startNode) {
const visitedNodes = new Set<string>();
const stack: FlowNodeWrapper[] = [startNode];

while (stack.length > 0) {
const currentNode = stack.pop();
console.debug('currentNode: ', currentNode?.name);

if (currentNode && !visitedNodes.has(currentNode.name)) {
visitedNodes.add(currentNode.name);
const connectors = currentNode.connectors;

connectors.forEach((connector) => {
const targetNode = flow.nodes.find((node) => node.name === connector.targetReference);
if (targetNode) {
stack.push(targetNode);
}
});
}
}
}
}
39 changes: 33 additions & 6 deletions src/common/experimental/FlowWrapper.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { Flow } from '@salesforce/types/metadata';
import { FlowNodeWrapper } from './FlowNodeWrapper.js';
import { NodeType } from './FlowNodeTypes.js';
import { FlowElementWrapper } from './FlowElementWrapper.js';
import { AnyFlowNode, AnyFlowElement } from './FlowNodeTypes.js';

export class FlowWrapper {
public flowName: string;
public nodes: FlowNodeWrapper[] = [];
public elements: FlowElementWrapper[] = [];

public constructor(flow: Flow) {
this.flowName = flow.fullName ?? '';

const nodeProperties: Array<keyof Flow> = [
const flowNodes: Array<keyof Flow> = [
'assignments',
'collectionProcessors',
'customErrors',
Expand All @@ -30,14 +32,39 @@ export class FlowWrapper {
'steps',
'start',
];
nodeProperties.forEach((property) => {

const flowElements: Array<keyof Flow> = [
'choices',
'constants',
'dynamicChoiceSets',
'formulas',
'stages',
'textTemplates',
'variables',
];

flowNodes.forEach((property) => {
console.log('prop ', property);
if (flow[property] !== undefined) {
if (Array.isArray(flow[property])) {
(flow[property] as AnyFlowNode[]).forEach((node) => {
this.nodes.push(new FlowNodeWrapper(property, node));
});
} else {
this.nodes.push(new FlowNodeWrapper(property, flow[property] as AnyFlowNode));
}
}
});

flowElements.forEach((property) => {
console.log('prop elem ', property);
if (flow[property] !== undefined) {
if (Array.isArray(flow[property])) {
(flow[property] as NodeType[]).forEach((node) => {
this.nodes.push(new FlowNodeWrapper(node));
(flow[property] as unknown as AnyFlowElement[]).forEach((node) => {
this.elements.push(new FlowElementWrapper(property, node));
});
} else {
this.nodes.push(new FlowNodeWrapper(flow[property] as NodeType));
this.elements.push(new FlowElementWrapper(property, flow[property] as AnyFlowElement));
}
}
});
Expand Down
6 changes: 6 additions & 0 deletions src/common/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export function normaliseNewlines(fileText: string): string {
return fileText.replace(/\r\n/g, '\n');
}

// Many of the @salesforce/types/metadata types (correctly) assign metadata values to arrays,
// but when parsing xml, if there is only one element, the parser does not know it should be an array.
export function arrayify<T>(input: T[] | T): T[] {
return Array.isArray(input) ? input : [input];
}

export function getLineAndColNumber(ruleId: string, file: string, fileText: string, value: string): Location {
let location: Location;

Expand Down
Loading

0 comments on commit 003f3ae

Please sign in to comment.