From 4f6e2402f6a7c4990b9fe34a9fa72bd0eb2175a9 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Wed, 6 May 2020 19:56:20 +0900 Subject: [PATCH 01/22] Update types for flow builder --- src/lib/parser/constants.ts | 2 ++ src/types/flow.ts | 2 ++ src/types/parser.ts | 48 +++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 src/lib/parser/constants.ts diff --git a/src/lib/parser/constants.ts b/src/lib/parser/constants.ts new file mode 100644 index 0000000..766634c --- /dev/null +++ b/src/lib/parser/constants.ts @@ -0,0 +1,2 @@ +export const SUPPORTED_PROCESS = ['Workflow', 'CustomEvent', 'InvocableProcess']; +export const SUPPORTED_FLOW = ['AutoLaunchedFlow']; diff --git a/src/types/flow.ts b/src/types/flow.ts index 4ac4789..dbd39c6 100644 --- a/src/types/flow.ts +++ b/src/types/flow.ts @@ -15,6 +15,7 @@ export interface Flow { recordCreates?: RecordCreate | Array; recordLookups?: RecordLookup | Array; waits: any; + start?: any; } export interface IteratableFlow { @@ -31,6 +32,7 @@ export interface IteratableFlow { recordCreates?: Array; recordLookups?: Array; waits: any; + start?: any; } interface Variable { diff --git a/src/types/parser.ts b/src/types/parser.ts index 95d0e08..ab8e335 100644 --- a/src/types/parser.ts +++ b/src/types/parser.ts @@ -1,3 +1,5 @@ +import { SUPPORTED_FLOW, SUPPORTED_PROCESS } from '../lib/parser/constants'; + export interface ReadableProcess { processType: string; name: string; @@ -10,6 +12,44 @@ export interface ReadableProcess { actionGroups: Array; } +export interface ReadableFlow { + processType: string; + name: string; + label: string; + description?: string; + start: ReadableStart; + elements?: Array; +} + +export interface ReadableFlowElement { + name: string; + label: string; + description?: string; + type: string; + element: any; +} + +export interface ReadableStart { + triggerType: string; + recordTriggerType?: string; + object?: string; + schedule?: { + startDate: string; + startTime: string; + frequency: string; + }; +} + +export interface ReadableAssignment { + assignments: Array; +} + +export interface ReadableAssignmentItem { + reference: string; + operator: string; + value: string; +} + export interface ReadableActionGroup { decision: ReadableDecision; actions?: Array; @@ -57,3 +97,11 @@ export interface ReadableWaitEventSummary { isAfter: boolean; field?: string; } + +export function implementsReadableFlow(arg: any): arg is ReadableFlow { + return SUPPORTED_FLOW.includes(arg.processType); +} + +export function implementsReadableProcess(arg: any): arg is ReadableProcess { + return SUPPORTED_PROCESS.includes(arg.processType); +} From 37e3c7544dfcdd3edfae7432605008fbc8835946 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Thu, 14 May 2020 11:52:40 +0900 Subject: [PATCH 02/22] WIP: Reorganize types --- src/types/{parser.ts => converter.ts} | 22 ++++++++++++++++++- src/types/{ => metadata}/flow.ts | 19 +--------------- src/types/{ => metadata}/flowRecordAction.ts | 0 .../{ => metadata}/processMetadataValue.ts | 0 4 files changed, 22 insertions(+), 19 deletions(-) rename src/types/{parser.ts => converter.ts} (76%) rename src/types/{ => metadata}/flow.ts (72%) rename src/types/{ => metadata}/flowRecordAction.ts (100%) rename src/types/{ => metadata}/processMetadataValue.ts (100%) diff --git a/src/types/parser.ts b/src/types/converter.ts similarity index 76% rename from src/types/parser.ts rename to src/types/converter.ts index ab8e335..2ff99a6 100644 --- a/src/types/parser.ts +++ b/src/types/converter.ts @@ -1,4 +1,24 @@ -import { SUPPORTED_FLOW, SUPPORTED_PROCESS } from '../lib/parser/constants'; +import { SUPPORTED_FLOW, SUPPORTED_PROCESS } from '../lib/converter/helper/constants'; +import { ProcessMetadataValue } from './metadata/processMetadataValue'; +import { Variable, Decision, ActionCall } from './metadata/flow'; +import { RecordUpdate, RecordCreate, RecordLookup } from './metadata/flowRecordAction'; + +export interface IteratableFlow { + processType: string; + label: string; + description: string; + startElementReference: string; + variables: Array; + processMetadataValues: Array; + formulas: any; + decisions: Array; + actionCalls?: Array; + recordUpdates?: Array; + recordCreates?: Array; + recordLookups?: Array; + waits: any; + start?: any; +} export interface ReadableProcess { processType: string; diff --git a/src/types/flow.ts b/src/types/metadata/flow.ts similarity index 72% rename from src/types/flow.ts rename to src/types/metadata/flow.ts index dbd39c6..ee622db 100644 --- a/src/types/flow.ts +++ b/src/types/metadata/flow.ts @@ -18,24 +18,7 @@ export interface Flow { start?: any; } -export interface IteratableFlow { - processType: string; - label: string; - description: string; - startElementReference: string; - variables: Array; - processMetadataValues: Array; - formulas: any; - decisions: Array; - actionCalls?: Array; - recordUpdates?: Array; - recordCreates?: Array; - recordLookups?: Array; - waits: any; - start?: any; -} - -interface Variable { +export interface Variable { name: string; objectType: string; } diff --git a/src/types/flowRecordAction.ts b/src/types/metadata/flowRecordAction.ts similarity index 100% rename from src/types/flowRecordAction.ts rename to src/types/metadata/flowRecordAction.ts diff --git a/src/types/processMetadataValue.ts b/src/types/metadata/processMetadataValue.ts similarity index 100% rename from src/types/processMetadataValue.ts rename to src/types/metadata/processMetadataValue.ts From adb56f1ee627c4a28d43a2575ebb570372770b29 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Thu, 14 May 2020 12:29:46 +0900 Subject: [PATCH 03/22] WIP: Refactoring flow parser --- .../{ => converter/helper}/actionLayout.json | 0 .../{ => converter/helper}/actionParser.ts | 10 +- .../{parser => converter/helper}/constants.ts | 0 src/lib/converter/helper/readableFlow.ts | 48 +++ .../helper/readableProcess.ts} | 353 ++++++------------ src/lib/converter/iteratableMetadata.ts | 28 ++ src/lib/converter/metadataConverter.ts | 39 ++ src/lib/converter/readableMetadata.ts | 140 +++++++ 8 files changed, 373 insertions(+), 245 deletions(-) rename src/lib/{ => converter/helper}/actionLayout.json (100%) rename src/lib/{ => converter/helper}/actionParser.ts (94%) rename src/lib/{parser => converter/helper}/constants.ts (100%) create mode 100644 src/lib/converter/helper/readableFlow.ts rename src/lib/{flowParser.ts => converter/helper/readableProcess.ts} (63%) create mode 100644 src/lib/converter/iteratableMetadata.ts create mode 100644 src/lib/converter/metadataConverter.ts create mode 100644 src/lib/converter/readableMetadata.ts diff --git a/src/lib/actionLayout.json b/src/lib/converter/helper/actionLayout.json similarity index 100% rename from src/lib/actionLayout.json rename to src/lib/converter/helper/actionLayout.json diff --git a/src/lib/actionParser.ts b/src/lib/converter/helper/actionParser.ts similarity index 94% rename from src/lib/actionParser.ts rename to src/lib/converter/helper/actionParser.ts index 1de5206..ba275dd 100644 --- a/src/lib/actionParser.ts +++ b/src/lib/converter/helper/actionParser.ts @@ -1,8 +1,8 @@ -import { ActionCall, InputParamValue } from '../types/flow'; -import { RecordCreate, RecordUpdate, RecordFilter, RecordLookup } from '../types/flowRecordAction'; -import { toArray } from './util/arrayUtils'; -import { ProcessMetadataValue } from '../types/processMetadataValue'; -import { ReadableCondition, ReadableActionItem, ReadableActionItemParameter } from '../types/parser'; +import { ActionCall, InputParamValue } from '../../../types/metadata/flow'; +import { RecordCreate, RecordUpdate, RecordFilter, RecordLookup } from '../../../types/metadata/flowRecordAction'; +import { toArray } from '../../util/arrayUtils'; +import { ProcessMetadataValue } from '../../../types/metadata/processMetadataValue'; +import { ReadableCondition, ReadableActionItem, ReadableActionItemParameter } from '../../../types/converter'; const layout = require('./actionLayout.json'); diff --git a/src/lib/parser/constants.ts b/src/lib/converter/helper/constants.ts similarity index 100% rename from src/lib/parser/constants.ts rename to src/lib/converter/helper/constants.ts diff --git a/src/lib/converter/helper/readableFlow.ts b/src/lib/converter/helper/readableFlow.ts new file mode 100644 index 0000000..77fa5a6 --- /dev/null +++ b/src/lib/converter/helper/readableFlow.ts @@ -0,0 +1,48 @@ +import ReadableMetadata from '../readableMetadata'; +import { ReadableFlow, ReadableStart } from '../../../types/converter'; + +export default class ReadableFlowMetadata extends ReadableMetadata { + createReadableFlow(): ReadableFlow { + return { + name: this.name, + label: this.getLabel(), + processType: this.getProcessType(), + description: this.getDescription(), + start: this.getReadableFlowStart(), + elements: undefined, // this.getReadableFlowElements() + }; + } + + private getFlowTriggerType() { + if (this.flow.start.recordTriggerType) { + return 'FLOW_TRIGGER_RECORD'; + } + if (this.flow.start.schedule) { + return 'FLOW_TRIGGER_SCHEDULED'; + } + return 'FLOW_TRIGGER_USER_OR_APPS'; + } + + private getFlowRecordTriggerType() { + switch (this.flow.start.recordTriggerType) { + case 'Create': + return 'FROW_TRIGGER_RECORD_CREATE_ONLY'; + case 'Update': + return 'FROW_TRIGGER_RECORD_UPDATE_ONLY'; + case 'CreateAndUpdate': + return 'FROW_TRIGGER_RECORD_CREATE_OR_UPDATE'; + default: + return undefined; + } + } + + private getReadableFlowStart(): ReadableStart { + const triggerType = this.getFlowTriggerType(); + return { + triggerType, + object: this.flow.start.object, + recordTriggerType: this.getFlowRecordTriggerType(), + schedule: triggerType === 'FLOW_TRIGGER_SCHEDULED' ? this.flow.start.schedule : undefined, + }; + } +} diff --git a/src/lib/flowParser.ts b/src/lib/converter/helper/readableProcess.ts similarity index 63% rename from src/lib/flowParser.ts rename to src/lib/converter/helper/readableProcess.ts index ff0fc7e..6ec7185 100644 --- a/src/lib/flowParser.ts +++ b/src/lib/converter/helper/readableProcess.ts @@ -1,65 +1,30 @@ -import { Flow, Decision, ActionCall, implementsActionCall, InputParamValue, IteratableFlow } from '../types/flow'; +import ReadableMetadata from '../readableMetadata'; import { - RecordCreate, - RecordUpdate, - implementsRecordCreate, - implementsRecordUpdate, - RecordLookup, -} from '../types/flowRecordAction'; + ReadableActionGroup, + ReadableCondition, + ReadableActionItem, + ReadableScheduledActionSection, + ReadableWaitEventSummary, + ReadableDecision, +} from '../../../types/converter'; +import { ActionCall, implementsActionCall, Decision } from '../../../types/metadata/flow'; import { + getRecordLookupFilter, convertToReadableActionCall, convertToReadableRecordCreate, convertToReadableRecordUpdate, - getRecordLookupFilter, } from './actionParser'; -import { toArray } from './util/arrayUtils'; -import { implementsProcessMetadataValue, ProcessMetadataValue } from '../types/processMetadataValue'; -import { unescapeHtml } from './util/stringUtils'; import { - ReadableProcess, - ReadableCondition, - ReadableActionGroup, - ReadableDecision, - ReadableActionItem, - ReadableScheduledActionSection, - ReadableWaitEventSummary, -} from '../types/parser'; - -export default class FlowParser { - private readonly flow: IteratableFlow; - - private readonly name: string; - - constructor(flow: Flow, name: string) { - this.flow = flow as IteratableFlow; - this.name = name; - - for (const arrayName of [ - 'processMetadataValues', - 'decisions', - 'actionCalls', - 'recordLookups', - 'formulas', - 'waits', - ]) { - this.flow[arrayName] = toArray(flow[arrayName]); - } - - const rawRecordUpdates = toArray(this.flow.recordUpdates); - this.flow.recordUpdates = - rawRecordUpdates.length !== 0 ? rawRecordUpdates.map(a => ({ ...a, actionType: 'recordUpdate' })) : []; - - const rawRecordCreates = toArray(this.flow.recordCreates); - this.flow.recordCreates = - rawRecordCreates.length !== 0 ? rawRecordCreates.map(a => ({ ...a, actionType: 'recordCreate' })) : []; - } - - isSupportedFlow() { - return ['Workflow', 'CustomEvent', 'InvocableProcess'].includes(this.flow.processType); - } + RecordUpdate, + RecordCreate, + implementsRecordCreate, + implementsRecordUpdate, +} from '../../../types/metadata/flowRecordAction'; +import { toArray } from '../../util/arrayUtils'; - createReadableProcess(): ReadableProcess { - const result: ReadableProcess = { +export default class ReadableProcessMetadata extends ReadableMetadata { + createReadableProcess() { + return { name: this.name, label: this.getLabel(), processType: this.getProcessType(), @@ -70,67 +35,25 @@ export default class FlowParser { eventMatchingConditions: this.getEventMatchingConditions(), actionGroups: this.getReadableActionGroups(), }; - return result; - } - - getLabel() { - return this.flow.label; - } - - private getProcessType() { - return this.flow.processType; - } - - /** - * Returns flow description - */ - private getDescription() { - return this.flow.description ? this.flow.description : ''; - } - - /** - * Returns sobject type of the flow - */ - private getObjectType() { - return this.flow.processMetadataValues.find(p => p.name === 'ObjectType').value.stringValue; - } - - /** - * Returns trigger type (e.g., only in create, both create and edit) for workflow type process - */ - private getTriggerType() { - const triggerTypePmv = this.flow.processMetadataValues.find(p => p.name === 'TriggerType'); - return triggerTypePmv ? triggerTypePmv.value.stringValue : undefined; - } - - /** - * Returns platform event type - */ - private getEventType() { - const eventTypePmv = this.flow.processMetadataValues.find(p => p.name === 'EventType'); - return eventTypePmv ? eventTypePmv.value.stringValue : undefined; } /** * Returns matching condition for platform event based process */ - getEventMatchingConditions(): Array { + private getEventMatchingConditions(): Array { if (this.getProcessType() === 'CustomEvent') { - const startElementName = this.getStartElement(); + const startElementName = this.getStartElementName(); const recordLookup = this.getRecordLookup(startElementName); return getRecordLookupFilter(this, recordLookup); } return undefined; } - private getStartElement() { - return this.flow.startElementReference; - } - /** * Returns readable action group (decision and actions) based on the metadata + * @returns {Array} */ - getReadableActionGroups(): Array { + private getReadableActionGroups(): Array { const actionGroups: Array = []; const decisions = this.getStandardDecisions(); for (const d of decisions) { @@ -156,7 +79,19 @@ export default class FlowParser { return actionGroups; } - convertToReadableDecision(decision: Decision): ReadableDecision { + /* Decisions */ + private getStandardDecisions(): Array { + return this.flow.decisions + .filter(d => d.processMetadataValues !== undefined) + .sort((d1, d2) => { + return ( + Number(d1.processMetadataValues.value.numberValue) - + Number(d2.processMetadataValues.value.numberValue) + ); + }); + } + + private convertToReadableDecision(decision: Decision): ReadableDecision { const readableDecision: ReadableDecision = { label: decision.rules.label, criteria: this.getActionExecutionCriteria(decision), @@ -169,7 +104,7 @@ export default class FlowParser { return readableDecision; } - getReadableDecisionConditions(decision: Decision): Array { + private getReadableDecisionConditions(decision: Decision): Array { const rawConditions = toArray(decision.rules.conditions); const result: Array = []; for (const c of rawConditions) { @@ -185,89 +120,10 @@ export default class FlowParser { return result; } - convertToReadableActionItems(actions: Array): Array { - return actions.map(a => this.convertToReadableActionItem(a)); - } - - convertToReadableActionItem = (action: ActionCall | RecordCreate | RecordUpdate): ReadableActionItem => { - if (implementsActionCall(action)) { - return convertToReadableActionCall(this, action); - } - if (implementsRecordCreate(action)) { - return convertToReadableRecordCreate(this, action); - } - if (implementsRecordUpdate(action)) { - return convertToReadableRecordUpdate(this, action); - } - return undefined; - }; - - getReadableScheduledActionSections(waitName: string): Array { - const wait = this.flow.waits.find(a => a.name === waitName); - if (!wait) { - return undefined; - } - const waitEvents = toArray(wait.waitEvents); - const sections: Array = []; - for (const waitEvent of waitEvents) { - const waitEventSummary = this.getReadableWaitEventSummary(waitEvent); - const waitEventActions = this.getWaitEventActions( - waitEvent, - Object.prototype.hasOwnProperty.call(waitEventSummary, 'field') - ); - const section: ReadableScheduledActionSection = { - summary: waitEventSummary, - actions: this.convertToReadableActionItems(waitEventActions), - }; - sections.push(section); - } - return sections; - } - - getReadableWaitEventSummary = (waitEvent): ReadableWaitEventSummary => { - const inputParams = toArray(waitEvent.inputParameters); - const rawTimeOffset = Number(inputParams.find(i => i.name === 'TimeOffset').value.numberValue); - const referencedFieldParam = inputParams.find(i => i.name === 'TimeFieldColumnEnumOrId'); - const summary: ReadableWaitEventSummary = { - offset: Math.abs(rawTimeOffset), - isAfter: rawTimeOffset > 0, - unit: inputParams.find(i => i.name === 'TimeOffsetUnit').value.stringValue, - }; - if (referencedFieldParam) { - summary.field = referencedFieldParam.value.stringValue; - } - return summary; - }; - - getWaitEventActions(waitEvent, comparedToField) { - let nextReference = waitEvent.connector.targetReference; - if (comparedToField) { - const decision = this.getDecision(nextReference); - if (!decision) { - return []; - } - nextReference = decision.rules.connector.targetReference; - } - return this.getActionSequence([], nextReference); - } - - private getDecision(name: string) { - return this.flow.decisions.find(d => d.name === name); - } - - private getRecordLookup(name: string): RecordLookup { - return this.flow.recordLookups.find(r => r.name === name); - } - - private getStandardDecisions(): Array { - return this.flow.decisions - .filter(d => d.processMetadataValues !== undefined) - .sort((d1, d2) => { - return ( - Number(d1.processMetadataValues.value.numberValue) - - Number(d2.processMetadataValues.value.numberValue) - ); - }); + private getDecisionFormulaExpression(decision: Decision): string { + const formulaName = decision.rules.conditions.leftValueReference; + const formulaExpression = this.getFormulaExpression(formulaName); + return formulaExpression ? unescape(formulaExpression) : undefined; } private getActionExecutionCriteria(decision: Decision) { @@ -290,14 +146,6 @@ export default class FlowParser { return 'CONDITIONS_ARE_MET'; } - /** - * Check if the given formula has always return true - * This is used in 'no criteria' decision. - */ - private hasAlwaysTrueFormula(name) { - return this.flow.formulas.some(f => f.name === name && f.dataType === 'Boolean' && f.expression === 'true'); - } - private hasIsChangedCondition(name) { return this.flow.decisions.some(d => d.rules && !Array.isArray(d.rules) && d.rules.name === name); } @@ -308,6 +156,21 @@ export default class FlowParser { return this.resolveValue(targetCondition.rightValue); } + private getConditionType = condition => { + if (condition.processMetadataValues) { + return condition.processMetadataValues.find(p => p.name === 'rightHandSideType').value.stringValue; + } + return undefined; + }; + + /** + * Check if the given formula has always return true. This method is used in 'no criteria' decision. + */ + private hasAlwaysTrueFormula(name) { + return this.flow.formulas.some(f => f.name === name && f.dataType === 'Boolean' && f.expression === 'true'); + } + + /* Actions */ private getActionSequence(actions: Array, nextActionName: string) { const nextAction = this.getAction(nextActionName); if (nextAction) { @@ -336,62 +199,72 @@ export default class FlowParser { ); } - private getConditionType = condition => { - if (condition.processMetadataValues) { - return condition.processMetadataValues.find(p => p.name === 'rightHandSideType').value.stringValue; + private convertToReadableActionItems( + actions: Array + ): Array { + return actions.map(a => this.convertToReadableActionItem(a)); + } + + private convertToReadableActionItem = (action: ActionCall | RecordCreate | RecordUpdate): ReadableActionItem => { + if (implementsActionCall(action)) { + return convertToReadableActionCall(this, action); + } + if (implementsRecordCreate(action)) { + return convertToReadableRecordCreate(this, action); + } + if (implementsRecordUpdate(action)) { + return convertToReadableRecordUpdate(this, action); } return undefined; }; - private getFormulaExpression(name) { - const formula = this.flow.formulas.find(f => f.name === name); - return formula ? formula.processMetadataValues.value.stringValue : undefined; - } - - private getDecisionFormulaExpression(decision: Decision): string { - const formulaName = decision.rules.conditions.leftValueReference; - const formulaExpression = this.getFormulaExpression(formulaName); - return formulaExpression ? unescape(formulaExpression) : undefined; - } - - private getObjectVariable(name) { - const variable = this.flow.variables.find(v => v.name === name); - return variable.objectType; - } - - resolveValue = (value: string | InputParamValue | ProcessMetadataValue) => { - if (!value) { - return '$GlobalConstant.null'; - } - // Chatter Message - if (implementsProcessMetadataValue(value)) { - if (value.name === 'textJson') { - return JSON.parse(unescapeHtml(value.value.stringValue)).message; - } - const key = Object.keys(value.value)[0]; - return value.value[key]; + /* Scheduled Actions */ + private getReadableScheduledActionSections(waitName: string): Array { + const wait = this.flow.waits.find(a => a.name === waitName); + if (!wait) { + return undefined; } - // String - if (typeof value === 'string') { - return this.replaceVariableNameToObjectName(value); + const waitEvents = toArray(wait.waitEvents); + const sections: Array = []; + for (const waitEvent of waitEvents) { + const waitEventSummary = this.getReadableWaitEventSummary(waitEvent); + const waitEventActions = this.getWaitEventActions( + waitEvent, + Object.prototype.hasOwnProperty.call(waitEventSummary, 'field') + ); + const section: ReadableScheduledActionSection = { + summary: waitEventSummary, + actions: this.convertToReadableActionItems(waitEventActions), + }; + sections.push(section); } - // Object - const key = Object.keys(value)[0]; // stringValue or elementReference - if (key === 'elementReference') { - if (!value[key].includes('.')) { - return this.getFormulaExpression(value[key]); - } - return this.replaceVariableNameToObjectName(value[key]); + return sections; + } + + private getReadableWaitEventSummary = (waitEvent): ReadableWaitEventSummary => { + const inputParams = toArray(waitEvent.inputParameters); + const rawTimeOffset = Number(inputParams.find(i => i.name === 'TimeOffset').value.numberValue); + const referencedFieldParam = inputParams.find(i => i.name === 'TimeFieldColumnEnumOrId'); + const summary: ReadableWaitEventSummary = { + offset: Math.abs(rawTimeOffset), + isAfter: rawTimeOffset > 0, + unit: inputParams.find(i => i.name === 'TimeOffsetUnit').value.stringValue, + }; + if (referencedFieldParam) { + summary.field = referencedFieldParam.value.stringValue; } - return value[key]; + return summary; }; - private replaceVariableNameToObjectName(string) { - if (!string.includes('.')) { - return string; + private getWaitEventActions(waitEvent, comparedToField) { + let nextReference = waitEvent.connector.targetReference; + if (comparedToField) { + const decision = this.getDecision(nextReference); + if (!decision) { + return []; + } + nextReference = decision.rules.connector.targetReference; } - const variableName = string.split('.')[0]; - const objectName = this.getObjectVariable(variableName); - return string.replace(variableName, `[${objectName}]`); + return this.getActionSequence([], nextReference); } } diff --git a/src/lib/converter/iteratableMetadata.ts b/src/lib/converter/iteratableMetadata.ts new file mode 100644 index 0000000..64a87ef --- /dev/null +++ b/src/lib/converter/iteratableMetadata.ts @@ -0,0 +1,28 @@ +import { toArray } from '../util/arrayUtils'; +import { Flow } from '../../types/metadata/flow'; +import { IteratableFlow } from '../../types/converter'; + +export default function convertToIteratableMetadata(flow: Flow): IteratableFlow { + return { + processType: flow.processType, + label: flow.label, + description: flow.description, + startElementReference: flow.startElementReference, + variables: toArray(flow.variables), + formulas: toArray(flow.formulas), + waits: toArray(flow.waits), + start: flow.start, + processMetadataValues: toArray(flow.processMetadataValues), + decisions: toArray(flow.decisions), + actionCalls: toArray(flow.actionCalls), + recordLookups: toArray(flow.recordLookups), + recordCreates: + toArray(flow.recordCreates).length > 0 + ? toArray(flow.recordCreates).map(action => ({ ...action, actionType: 'recordCreate' })) + : [], + recordUpdates: + toArray(flow.recordUpdates).length > 0 + ? toArray(flow.recordUpdates).map(action => ({ ...action, actionType: 'recordUpdate' })) + : [], + }; +} diff --git a/src/lib/converter/metadataConverter.ts b/src/lib/converter/metadataConverter.ts new file mode 100644 index 0000000..6811cc8 --- /dev/null +++ b/src/lib/converter/metadataConverter.ts @@ -0,0 +1,39 @@ +import { Flow } from '../../types/metadata/flow'; +import { ReadableProcess, ReadableFlow } from '../../types/converter'; +import ReadableFlowMetadata from './helper/readableFlow'; +import ReadableProcessMetadata from './helper/readableProcess'; + +interface MetadataConverter { + accept(builder: DocumentBuilder); +} + +export class ReadableFlowMetadataConverter implements MetadataConverter { + readableMetadata: ReadableFlow; + + constructor(flow: Flow, name: string) { + const metadataConverter = new ReadableFlowMetadata(flow, name); + this.readableMetadata = metadataConverter.createReadableFlow(); + } + + accept(builder: DocumentBuilder) { + return builder.visit(this); + } +} + +export class ReadableProcessMetadataConverter implements MetadataConverter { + readableMetadata: ReadableProcess; + + constructor(flow: Flow, name: string) { + const metadataConverter = new ReadableProcessMetadata(flow, name); + this.readableMetadata = metadataConverter.createReadableProcess(); + } + + accept(builder: DocumentBuilder) { + return builder.visit(this); + } +} + +interface DocumentBuilder { + visit(parser: ReadableFlowMetadataConverter); + visit(parser: ReadableProcessMetadataConverter); +} diff --git a/src/lib/converter/readableMetadata.ts b/src/lib/converter/readableMetadata.ts new file mode 100644 index 0000000..6a2bc41 --- /dev/null +++ b/src/lib/converter/readableMetadata.ts @@ -0,0 +1,140 @@ +import { InputParamValue, Flow } from '../../types/metadata/flow'; +import { IteratableFlow } from '../../types/converter'; +import { RecordLookup } from '../../types/metadata/flowRecordAction'; +import { implementsProcessMetadataValue, ProcessMetadataValue } from '../../types/metadata/processMetadataValue'; +import { unescapeHtml } from '../util/stringUtils'; +import { SUPPORTED_PROCESS, SUPPORTED_FLOW } from './helper/constants'; +import convertToIteratableMetadata from './iteratableMetadata'; + +export default class ReadableMetadata { + protected readonly flow: IteratableFlow; + + protected readonly name: string; + + constructor(flow: Flow, name: string) { + this.flow = convertToIteratableMetadata(flow); + this.name = name; + } + + /** + * @return {string} Returns display label of the flow + */ + public getLabel(): string { + return this.flow.label; + } + + /** + * @return {strign} Returns type of the flow / process + */ + protected getProcessType() { + return this.flow.processType; + } + + /** + * @return {boolean} Returns true if the flow is supported + */ + public isSupportedFlow() { + return ( + this.isProcess() || + (SUPPORTED_FLOW.includes(this.flow.processType) && this.flow.start.recordTriggerType !== undefined) + ); + } + + private isProcess() { + return SUPPORTED_PROCESS.includes(this.flow.processType); + } + + /** + * @return {string} Returns flow description + */ + protected getDescription() { + return this.flow.description ? this.flow.description : ''; + } + + /** + * @return {string} Returns sObject type of the flow + */ + protected getObjectType() { + return this.flow.processMetadataValues.find(p => p.name === 'ObjectType').value.stringValue; + } + + /** + * @return {string} Returns trigger type + */ + protected getTriggerType() { + const triggerTypePmv = this.flow.processMetadataValues.find(p => p.name === 'TriggerType'); + return triggerTypePmv ? triggerTypePmv.value.stringValue : undefined; + } + + /** + * @retuns {string} Returns platform event type + */ + protected getEventType() { + const eventTypePmv = this.flow.processMetadataValues.find(p => p.name === 'EventType'); + return eventTypePmv ? eventTypePmv.value.stringValue : undefined; + } + + /** + * @returns {string} Returns name of start element + */ + protected getStartElementName() { + return this.flow.startElementReference; + } + + protected getDecision(name: string) { + return this.flow.decisions.find(d => d.name === name); + } + + protected getRecordLookup(name: string): RecordLookup { + return this.flow.recordLookups.find(r => r.name === name); + } + + /** + * @returns {string} Returns formula expression + */ + protected getFormulaExpression(name) { + const formula = this.flow.formulas.find(f => f.name === name); + return formula ? formula.processMetadataValues.value.stringValue : undefined; + } + + private getObjectVariable(name) { + const variable = this.flow.variables.find(v => v.name === name); + return variable.objectType; + } + + resolveValue = (value: string | InputParamValue | ProcessMetadataValue) => { + if (!value) { + return '$GlobalConstant.null'; + } + // Chatter Message + if (implementsProcessMetadataValue(value)) { + if (value.name === 'textJson') { + return JSON.parse(unescapeHtml(value.value.stringValue)).message; + } + const key = Object.keys(value.value)[0]; + return value.value[key]; + } + // String + if (typeof value === 'string') { + return this.replaceVariableNameToObjectName(value); + } + // Object + const key = Object.keys(value)[0]; // stringValue or elementReference + if (key === 'elementReference') { + if (!value[key].includes('.')) { + return this.getFormulaExpression(value[key]); + } + return this.replaceVariableNameToObjectName(value[key]); + } + return value[key]; + }; + + private replaceVariableNameToObjectName(string) { + if (!string.includes('.')) { + return string; + } + const variableName = string.split('.')[0]; + const objectName = this.getObjectVariable(variableName); + return string.replace(variableName, `[${objectName}]`); + } +} From 37a7127079fd11b80dd5c6ca2fb7d9df652816b0 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Thu, 14 May 2020 14:33:00 +0900 Subject: [PATCH 04/22] Refactor pdf generation --- src/commands/flowdoc/pdf/generate.ts | 25 ++++--- src/lib/converter/metadataConverter.ts | 17 +++-- src/lib/converter/readableMetadata.ts | 15 ---- src/lib/pdf/flow/pdfFlowFormatter.ts | 69 +++++++++++++++++++ src/lib/pdf/pdfBuilder.ts | 49 +++++++++---- src/lib/pdf/process/pdfProcessFormatter.ts | 2 +- .../pdf/process/startConditionFormatter.ts | 2 +- 7 files changed, 133 insertions(+), 46 deletions(-) create mode 100644 src/lib/pdf/flow/pdfFlowFormatter.ts diff --git a/src/commands/flowdoc/pdf/generate.ts b/src/commands/flowdoc/pdf/generate.ts index 1abefb3..be453ca 100644 --- a/src/commands/flowdoc/pdf/generate.ts +++ b/src/commands/flowdoc/pdf/generate.ts @@ -1,10 +1,14 @@ import { flags, SfdxCommand } from '@salesforce/command'; import { Messages, SfdxError } from '@salesforce/core'; import * as fs from 'fs-extra'; -import { Flow } from '../../../types/flow'; -import FlowParser from '../../../lib/flowParser'; +import { Flow } from '../../../types/metadata/flow'; import fonts from '../../../style/font'; -import buildPdfContent from '../../../lib/pdf/pdfBuilder'; +import PdfBuilder from '../../../lib/pdf/pdfBuilder'; +import { + ReadableProcessMetadataConverter, + ReadableFlowMetadataConverter, +} from '../../../lib/converter/metadataConverter'; +import { isSupported, isProcess } from '../../../lib/util/flowUtils'; const Pdf = require('pdfmake'); @@ -44,20 +48,22 @@ export default class Generate extends SfdxCommand { const conn = this.org.getConnection(); conn.setApiVersion(API_VERSION); - const flow = await conn.metadata.read('Flow', this.args.file); + const flow = ((await conn.metadata.read('Flow', this.args.file)) as unknown) as Flow; if (Object.keys(flow).length === 0) { this.ux.stopSpinner('failed.'); throw new SfdxError(messages.getMessage('errorFlowNotFound')); } - const fp = new FlowParser((flow as unknown) as Flow, this.args.file); - if (!fp.isSupportedFlow()) { + + if (!isSupported(flow)) { this.ux.stopSpinner('failed.'); throw new SfdxError(messages.getMessage('errorUnsupportedFlow')); } this.ux.stopSpinner(); - const hrDoc = fp.createReadableProcess(); - const docDefinition = buildPdfContent(hrDoc, this.flags.locale); + const pdfBuilder = new PdfBuilder(this.flags.locale); + const docDefinition = isProcess(flow) + ? new ReadableProcessMetadataConverter(flow, this.args.file).accept(pdfBuilder) + : new ReadableFlowMetadataConverter(flow, this.args.file).accept(pdfBuilder); const printer = new Pdf(fonts); const pdfDoc = printer.createPdfKitDocument(docDefinition); @@ -67,8 +73,7 @@ export default class Generate extends SfdxCommand { const targetPath = `${this.args.file}.pdf`; pdfDoc.pipe(fs.createWriteStream(`${outdir}/${targetPath}`)); pdfDoc.end(); - const label: string = fp.getLabel(); - this.ux.log(`Documentation of '${label}' flow is successfully generated.`); + this.ux.log(`Documentation of '${flow.label}' flow is successfully generated.`); return flow; } diff --git a/src/lib/converter/metadataConverter.ts b/src/lib/converter/metadataConverter.ts index 6811cc8..2a7bd0e 100644 --- a/src/lib/converter/metadataConverter.ts +++ b/src/lib/converter/metadataConverter.ts @@ -16,7 +16,7 @@ export class ReadableFlowMetadataConverter implements MetadataConverter { } accept(builder: DocumentBuilder) { - return builder.visit(this); + return builder.buildFlowDocument(this); } } @@ -29,11 +29,18 @@ export class ReadableProcessMetadataConverter implements MetadataConverter { } accept(builder: DocumentBuilder) { - return builder.visit(this); + return builder.buildProcessDocument(this); } } -interface DocumentBuilder { - visit(parser: ReadableFlowMetadataConverter); - visit(parser: ReadableProcessMetadataConverter); +export abstract class DocumentBuilder { + protected locale: string; + + constructor(locale: string) { + this.locale = locale; + } + + abstract buildFlowDocument(converter: ReadableFlowMetadataConverter): any; + + abstract buildProcessDocument(converter: ReadableProcessMetadataConverter): any; } diff --git a/src/lib/converter/readableMetadata.ts b/src/lib/converter/readableMetadata.ts index 6a2bc41..34599f6 100644 --- a/src/lib/converter/readableMetadata.ts +++ b/src/lib/converter/readableMetadata.ts @@ -3,7 +3,6 @@ import { IteratableFlow } from '../../types/converter'; import { RecordLookup } from '../../types/metadata/flowRecordAction'; import { implementsProcessMetadataValue, ProcessMetadataValue } from '../../types/metadata/processMetadataValue'; import { unescapeHtml } from '../util/stringUtils'; -import { SUPPORTED_PROCESS, SUPPORTED_FLOW } from './helper/constants'; import convertToIteratableMetadata from './iteratableMetadata'; export default class ReadableMetadata { @@ -30,20 +29,6 @@ export default class ReadableMetadata { return this.flow.processType; } - /** - * @return {boolean} Returns true if the flow is supported - */ - public isSupportedFlow() { - return ( - this.isProcess() || - (SUPPORTED_FLOW.includes(this.flow.processType) && this.flow.start.recordTriggerType !== undefined) - ); - } - - private isProcess() { - return SUPPORTED_PROCESS.includes(this.flow.processType); - } - /** * @return {string} Returns flow description */ diff --git a/src/lib/pdf/flow/pdfFlowFormatter.ts b/src/lib/pdf/flow/pdfFlowFormatter.ts new file mode 100644 index 0000000..b6fa020 --- /dev/null +++ b/src/lib/pdf/flow/pdfFlowFormatter.ts @@ -0,0 +1,69 @@ +import i18n from '../../../config/i18n'; +import { th } from '../../../style/text'; +import { ReadableFlow } from '../../../types/converter'; + +export default class PdfProcessFormatter { + flow: ReadableFlow; + + i18n; + + constructor(flow: ReadableFlow, locale) { + this.flow = flow; + this.i18n = i18n(locale); + } + + buildHeader() { + return [ + { text: this.flow.label, style: 'h1' }, + { text: this.flow.name }, + { text: this.flow.description, margin: [0, 5] }, + ]; + } + + buildStart = () => { + const content: Array = [ + { + text: `${this.i18n.__('HEADER_FLOW_TRIGGER')}: ${this.i18n.__(this.flow.start.triggerType)}`, + margin: [0, 10, 0, 5], + }, + ]; + + if (this.flow.start.triggerType === 'FLOW_TRIGGER_RECORD') { + content.push({ + layout: 'lightHorizontalLines', + table: { + widths: [200, 'auto'], + body: [ + [th(this.i18n.__('OBJECT')), this.flow.start.object], + [ + th(this.i18n.__('HEADER_FLOW_TRIGGER_RECORD')), + this.i18n.__(this.flow.start.recordTriggerType), + ], + ], + }, + }); + } + + if (this.flow.start.triggerType === 'FLOW_TRIGGER_SCHEDULED') { + content.push({ + layout: 'lightHorizontalLines', + table: { + widths: [200, 'auto'], + body: [ + [ + th(this.i18n.__('FLOW_TRIGGER_SCHEDULED_DATETIME')), + `${this.flow.start.schedule.startDate}${this.flow.start.schedule.startTime} `, + ], + [th(this.i18n.__('FLOW_TRIGGER_SCHEDULED_FREQUENCY')), this.flow.start.schedule.frequency], + ], + }, + }); + } + + return content; + }; + + buildElements = () => { + return []; + }; +} diff --git a/src/lib/pdf/pdfBuilder.ts b/src/lib/pdf/pdfBuilder.ts index dfe72f3..1c315a0 100644 --- a/src/lib/pdf/pdfBuilder.ts +++ b/src/lib/pdf/pdfBuilder.ts @@ -1,20 +1,41 @@ import PdfProcessFormatter from './process/pdfProcessFormatter'; -import { ReadableProcess } from '../../types/parser'; +import PdfFlowFormatter from './flow/pdfFlowFormatter'; +import { + DocumentBuilder, + ReadableFlowMetadataConverter, + ReadableProcessMetadataConverter, +} from '../converter/metadataConverter'; const styles = require('../../style/style.json'); -export default function buildPdfContent(flow: ReadableProcess, locale: string) { - const content = []; - const ppf = new PdfProcessFormatter(flow, locale); - content.push(...ppf.buildHeader()); +export default class PdfBuilder extends DocumentBuilder { + buildFlowDocument(converter: ReadableFlowMetadataConverter) { + const content = []; + const formatter = new PdfFlowFormatter(converter.readableMetadata, this.locale); + content.push(...formatter.buildHeader()); + content.push(...formatter.buildStart()); + content.push(...formatter.buildElements()); + return { + content, + styles, + defaultStyle: { + font: 'NotoSans', + }, + }; + } - content.push(...ppf.buildStartCondition()); - content.push(...ppf.buildActionGroups()); - return { - content, - styles, - defaultStyle: { - font: 'NotoSans', - }, - }; + buildProcessDocument(converter: ReadableProcessMetadataConverter) { + const content = []; + const formatter = new PdfProcessFormatter(converter.readableMetadata, this.locale); + content.push(...formatter.buildHeader()); + content.push(...formatter.buildStartCondition()); + content.push(...formatter.buildActionGroups()); + return { + content, + styles, + defaultStyle: { + font: 'NotoSans', + }, + }; + } } diff --git a/src/lib/pdf/process/pdfProcessFormatter.ts b/src/lib/pdf/process/pdfProcessFormatter.ts index 99bf597..44f9353 100644 --- a/src/lib/pdf/process/pdfProcessFormatter.ts +++ b/src/lib/pdf/process/pdfProcessFormatter.ts @@ -6,7 +6,7 @@ import { ReadableCondition, ReadableActionItem, ReadableWaitEventSummary, -} from '../../../types/parser'; +} from '../../../types/converter'; import StartConditionFormatter from './startConditionFormatter'; import { toUpperSnakeCase } from '../../util/stringUtils'; diff --git a/src/lib/pdf/process/startConditionFormatter.ts b/src/lib/pdf/process/startConditionFormatter.ts index ba2216c..b98458a 100644 --- a/src/lib/pdf/process/startConditionFormatter.ts +++ b/src/lib/pdf/process/startConditionFormatter.ts @@ -1,5 +1,5 @@ import { th } from '../../../style/text'; -import { ReadableProcess } from '../../../types/parser'; +import { ReadableProcess } from '../../../types/converter'; export default class StartConditionFormatter { flow: ReadableProcess; From 28f379a46e2201d345090c34c82c4b10ba2a6201 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Thu, 14 May 2020 14:41:43 +0900 Subject: [PATCH 05/22] Refactor docx generation --- src/commands/flowdoc/docx/generate.ts | 26 ++++++----- src/lib/docx/docxBuilder.ts | 44 ++++++++++++------- src/lib/docx/process/docxProcessFormatter.ts | 2 +- src/lib/docx/process/docxTableUtils.ts | 2 +- .../docx/process/startConditionFormatter.ts | 2 +- src/lib/util/flowUtils.ts | 15 +++++++ 6 files changed, 61 insertions(+), 30 deletions(-) create mode 100644 src/lib/util/flowUtils.ts diff --git a/src/commands/flowdoc/docx/generate.ts b/src/commands/flowdoc/docx/generate.ts index 5eff976..e25de38 100644 --- a/src/commands/flowdoc/docx/generate.ts +++ b/src/commands/flowdoc/docx/generate.ts @@ -3,9 +3,13 @@ import { Messages, SfdxError } from '@salesforce/core'; import * as fs from 'fs-extra'; import * as docx from 'docx'; -import { Flow } from '../../../types/flow'; -import FlowParser from '../../../lib/flowParser'; -import buildDocxContent from '../../../lib/docx/docxBuilder'; +import { Flow } from '../../../types/metadata/flow'; +import { + ReadableProcessMetadataConverter, + ReadableFlowMetadataConverter, +} from '../../../lib/converter/metadataConverter'; +import { isSupported, isProcess } from '../../../lib/util/flowUtils'; +import DocxBuilder from '../../../lib/docx/docxBuilder'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('sfdx-flowdoc-plugin', 'messages'); @@ -45,21 +49,22 @@ export default class Generate extends SfdxCommand { const conn = this.org.getConnection(); conn.setApiVersion(API_VERSION); - const flow = await conn.metadata.read('Flow', this.args.file); - + const flow = ((await conn.metadata.read('Flow', this.args.file)) as unknown) as Flow; if (Object.keys(flow).length === 0) { this.ux.stopSpinner('failed.'); throw new SfdxError(messages.getMessage('errorFlowNotFound')); } - const fp = new FlowParser((flow as unknown) as Flow, this.args.file); - if (!fp.isSupportedFlow()) { + + if (!isSupported(flow)) { this.ux.stopSpinner('failed.'); throw new SfdxError(messages.getMessage('errorUnsupportedFlow')); } this.ux.stopSpinner(); - const hrDoc = fp.createReadableProcess(); - const doc = buildDocxContent(hrDoc, this.flags.locale); + const docxBuilder = new DocxBuilder(this.flags.locale); + const doc = isProcess(flow) + ? new ReadableProcessMetadataConverter(flow, this.args.file).accept(docxBuilder) + : new ReadableFlowMetadataConverter(flow, this.args.file).accept(docxBuilder); const outdir = this.flags.outdir ? this.flags.outdir : '.'; await fs.ensureDir(outdir); @@ -68,8 +73,7 @@ export default class Generate extends SfdxCommand { fs.writeFileSync(`${outdir}/${targetPath}`, buffer); }); - const label: string = fp.getLabel(); - this.ux.log(`Documentation of '${label}' flow is successfully generated.`); + this.ux.log(`Documentation of '${flow.label}' flow is successfully generated.`); return flow; } diff --git a/src/lib/docx/docxBuilder.ts b/src/lib/docx/docxBuilder.ts index 3ecc3c8..261146d 100644 --- a/src/lib/docx/docxBuilder.ts +++ b/src/lib/docx/docxBuilder.ts @@ -1,22 +1,34 @@ import * as docx from 'docx'; import * as path from 'path'; import * as fs from 'fs'; -import { ReadableProcess } from '../../types/parser'; import DocxProcessFormatter from './process/docxProcessFormatter'; +import { + DocumentBuilder, + ReadableProcessMetadataConverter, + ReadableFlowMetadataConverter, +} from '../converter/metadataConverter'; -export default function buildDocxContent(flow: ReadableProcess, locale: string): docx.File { - const jaStyles = fs.readFileSync(path.resolve(__dirname, '../../assets/fonts/styles.ja.xml'), 'utf-8'); - const enStyles = fs.readFileSync(path.resolve(__dirname, '../../assets/fonts/styles.en.xml'), 'utf-8'); - const content = []; - const dpf = new DocxProcessFormatter(flow, locale); - content.push(...dpf.prepareHeader()); - content.push(...dpf.prepareStartCondition()); - content.push(...dpf.prepareActionGroups()); - const doc = new docx.Document({ - externalStyles: locale === 'ja' ? jaStyles : enStyles, - }); - doc.addSection({ - children: content, - }); - return doc; +const jaStyles = fs.readFileSync(path.resolve(__dirname, '../../assets/fonts/styles.ja.xml'), 'utf-8'); +const enStyles = fs.readFileSync(path.resolve(__dirname, '../../assets/fonts/styles.en.xml'), 'utf-8'); + +export default class DocxBuilder extends DocumentBuilder { + buildFlowDocument(converter: ReadableFlowMetadataConverter) { + console.log(this.locale); + console.log(converter.readableMetadata); + } + + buildProcessDocument(converter: ReadableProcessMetadataConverter) { + const content = []; + const dpf = new DocxProcessFormatter(converter.readableMetadata, this.locale); + content.push(...dpf.prepareHeader()); + content.push(...dpf.prepareStartCondition()); + content.push(...dpf.prepareActionGroups()); + const doc = new docx.Document({ + externalStyles: this.locale === 'ja' ? jaStyles : enStyles, + }); + doc.addSection({ + children: content, + }); + return doc; + } } diff --git a/src/lib/docx/process/docxProcessFormatter.ts b/src/lib/docx/process/docxProcessFormatter.ts index 055d8ce..6f692ed 100644 --- a/src/lib/docx/process/docxProcessFormatter.ts +++ b/src/lib/docx/process/docxProcessFormatter.ts @@ -4,7 +4,7 @@ import { ReadableActionItem, ReadableActionItemParameter, ReadableWaitEventSummary, -} from '../../../types/parser'; +} from '../../../types/converter'; import i18n from '../../../config/i18n'; import StartConditionFormatter from './startConditionFormatter'; import { diff --git a/src/lib/docx/process/docxTableUtils.ts b/src/lib/docx/process/docxTableUtils.ts index b77ce0a..633e5ec 100644 --- a/src/lib/docx/process/docxTableUtils.ts +++ b/src/lib/docx/process/docxTableUtils.ts @@ -1,5 +1,5 @@ import { Table, TableRow, TableCell, Paragraph, TextRun, BorderStyle } from 'docx'; -import { ReadableCondition, ReadableActionItemParameter } from '../../../types/parser'; +import { ReadableCondition, ReadableActionItemParameter } from '../../../types/converter'; const CELL_DEFAULT_MARGIN = { top: 20, diff --git a/src/lib/docx/process/startConditionFormatter.ts b/src/lib/docx/process/startConditionFormatter.ts index e28ccd5..fff19e4 100644 --- a/src/lib/docx/process/startConditionFormatter.ts +++ b/src/lib/docx/process/startConditionFormatter.ts @@ -1,5 +1,5 @@ import { Paragraph } from 'docx'; -import { ReadableProcess } from '../../../types/parser'; +import { ReadableProcess } from '../../../types/converter'; import { createHorizontalHeaderTable, createProcessConditionTable } from './docxTableUtils'; export default class StartConditionFormatter { diff --git a/src/lib/util/flowUtils.ts b/src/lib/util/flowUtils.ts new file mode 100644 index 0000000..b8c9084 --- /dev/null +++ b/src/lib/util/flowUtils.ts @@ -0,0 +1,15 @@ +import { Flow } from '../../types/metadata/flow'; + +export const SUPPORTED_PROCESS = ['Workflow', 'CustomEvent', 'InvocableProcess']; +export const SUPPORTED_FLOW = ['AutoLaunchedFlow']; + +export function isSupported(flow: Flow) { + return ( + isProcess(flow) || + (SUPPORTED_FLOW.includes(this.flow.processType) && this.flow.start.recordTriggerType !== undefined) + ); +} + +export function isProcess(flow: Flow) { + return SUPPORTED_PROCESS.includes(flow.processType); +} From 29b1ccd4c24d48626543872b71a7b7484ddca4ac Mon Sep 17 00:00:00 2001 From: shunkosa Date: Thu, 14 May 2020 14:51:11 +0900 Subject: [PATCH 06/22] Refactor json command --- src/commands/flowdoc/json/display.ts | 25 +++++++------ src/commands/flowdoc/json/generate.ts | 28 ++++++++------- src/lib/json/jsonBuilder.ts | 52 ++++++++++++++++----------- 3 files changed, 63 insertions(+), 42 deletions(-) diff --git a/src/commands/flowdoc/json/display.ts b/src/commands/flowdoc/json/display.ts index f57ec21..c1fe1cd 100644 --- a/src/commands/flowdoc/json/display.ts +++ b/src/commands/flowdoc/json/display.ts @@ -1,9 +1,13 @@ import { flags, SfdxCommand } from '@salesforce/command'; import { Messages, SfdxError } from '@salesforce/core'; -import { Flow } from '../../../types/flow'; -import FlowParser from '../../../lib/flowParser'; -import buildLocalizedJson from '../../../lib/json/jsonBuilder'; +import { Flow } from '../../../types/metadata/flow'; +import { isSupported, isProcess } from '../../../lib/util/flowUtils'; +import JsonBuilder from '../../../lib/json/jsonBuilder'; +import { + ReadableProcessMetadataConverter, + ReadableFlowMetadataConverter, +} from '../../../lib/converter/metadataConverter'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('sfdx-flowdoc-plugin', 'messages'); @@ -41,22 +45,23 @@ export default class Display extends SfdxCommand { const conn = this.org.getConnection(); conn.setApiVersion(API_VERSION); - const flow = await conn.metadata.read('Flow', this.args.file); - + const flow = ((await conn.metadata.read('Flow', this.args.file)) as unknown) as Flow; if (Object.keys(flow).length === 0) { this.ux.stopSpinner('failed.'); throw new SfdxError(messages.getMessage('errorFlowNotFound')); } - const fp = new FlowParser((flow as unknown) as Flow, this.args.file); - if (!fp.isSupportedFlow()) { + + if (!isSupported(flow)) { this.ux.stopSpinner('failed.'); throw new SfdxError(messages.getMessage('errorUnsupportedFlow')); } this.ux.stopSpinner(); - const readableFlow = fp.createReadableProcess(); - const localizedJson = buildLocalizedJson(readableFlow, this.flags.locale); - this.ux.log(JSON.stringify(localizedJson, null, ' ')); + const jsonBuilder = new JsonBuilder(this.flags.locale); + const json = isProcess(flow) + ? new ReadableProcessMetadataConverter(flow, this.args.file).accept(jsonBuilder) + : new ReadableFlowMetadataConverter(flow, this.args.file).accept(jsonBuilder); + this.ux.log(JSON.stringify(json, null, ' ')); return flow; } diff --git a/src/commands/flowdoc/json/generate.ts b/src/commands/flowdoc/json/generate.ts index 1e12388..b8da388 100644 --- a/src/commands/flowdoc/json/generate.ts +++ b/src/commands/flowdoc/json/generate.ts @@ -2,9 +2,13 @@ import { flags, SfdxCommand } from '@salesforce/command'; import { Messages, SfdxError } from '@salesforce/core'; import * as fs from 'fs-extra'; -import { Flow } from '../../../types/flow'; -import FlowParser from '../../../lib/flowParser'; -import buildLocalizedJson from '../../../lib/json/jsonBuilder'; +import { Flow } from '../../../types/metadata/flow'; +import { isSupported, isProcess } from '../../../lib/util/flowUtils'; +import JsonBuilder from '../../../lib/json/jsonBuilder'; +import { + ReadableProcessMetadataConverter, + ReadableFlowMetadataConverter, +} from '../../../lib/converter/metadataConverter'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('sfdx-flowdoc-plugin', 'messages'); @@ -45,30 +49,30 @@ export default class Generate extends SfdxCommand { const conn = this.org.getConnection(); conn.setApiVersion(API_VERSION); - const flow = await conn.metadata.read('Flow', this.args.file); - + const flow = ((await conn.metadata.read('Flow', this.args.file)) as unknown) as Flow; if (Object.keys(flow).length === 0) { this.ux.stopSpinner('failed.'); throw new SfdxError(messages.getMessage('errorFlowNotFound')); } - const fp = new FlowParser((flow as unknown) as Flow, this.args.file); - if (!fp.isSupportedFlow()) { + + if (!isSupported(flow)) { this.ux.stopSpinner('failed.'); throw new SfdxError(messages.getMessage('errorUnsupportedFlow')); } this.ux.stopSpinner(); - const readableFlow = fp.createReadableProcess(); - const localizedJson = buildLocalizedJson(readableFlow, this.flags.locale); + const jsonBuilder = new JsonBuilder(this.flags.locale); + const json = isProcess(flow) + ? new ReadableProcessMetadataConverter(flow, this.args.file).accept(jsonBuilder) + : new ReadableFlowMetadataConverter(flow, this.args.file).accept(jsonBuilder); const targetPath = `${this.args.file}.json`; const outdir = this.flags.outdir ? this.flags.outdir : '.'; await fs.ensureDir(outdir); - fs.writeFileSync(`${outdir}/${targetPath}`, JSON.stringify(localizedJson, null, ' ')); + fs.writeFileSync(`${outdir}/${targetPath}`, JSON.stringify(json, null, ' ')); - const label: string = fp.getLabel(); - this.ux.log(`Documentation of '${label}' flow is successfully generated.`); + this.ux.log(`Documentation of '${flow.label}' flow is successfully generated.`); return flow; } diff --git a/src/lib/json/jsonBuilder.ts b/src/lib/json/jsonBuilder.ts index 4e52dc9..718f376 100644 --- a/src/lib/json/jsonBuilder.ts +++ b/src/lib/json/jsonBuilder.ts @@ -1,34 +1,46 @@ /* eslint-disable no-param-reassign */ import i18n from '../../config/i18n'; -import { ReadableProcess } from '../../types/parser'; import { toUpperSnakeCase } from '../util/stringUtils'; +import { + DocumentBuilder, + ReadableFlowMetadataConverter, + ReadableProcessMetadataConverter, +} from '../converter/metadataConverter'; -export default function buildLocalizedJson(flow: ReadableProcess, locale: string) { - const _i18n = i18n(locale); +export default class JsonBuilder extends DocumentBuilder { + i18n = i18n(this.locale); - if (flow.processType === 'Workflow') { - flow.triggerType = - flow.triggerType === 'onAllChanges' - ? _i18n.__('WHEN_A_RECORD_IS_CREATED_OR_EDITED') - : _i18n.__('ONLY_WHEN_A_RECORD_IS_CREATED'); + buildFlowDocument(converter: ReadableFlowMetadataConverter) { + console.log(this.locale); + console.log(converter.readableMetadata); } - if (flow.actionGroups) { - for (const actionGroup of flow.actionGroups) { - actionGroup.decision.criteria = _i18n.__(actionGroup.decision.criteria); - if (actionGroup.actions) { - for (const action of actionGroup.actions) { - if (action.details) { - for (const detail of action.details) { - detail.name = _i18n.__( - `ACTION_DETAIL_${toUpperSnakeCase(action.type)}_${toUpperSnakeCase(detail.name)}` - ); + buildProcessDocument(converter: ReadableProcessMetadataConverter) { + const flow = converter.readableMetadata; + if (flow.processType === 'Workflow') { + flow.triggerType = + flow.triggerType === 'onAllChanges' + ? this.i18n.__('WHEN_A_RECORD_IS_CREATED_OR_EDITED') + : this.i18n.__('ONLY_WHEN_A_RECORD_IS_CREATED'); + } + + if (flow.actionGroups) { + for (const actionGroup of flow.actionGroups) { + actionGroup.decision.criteria = this.i18n.__(actionGroup.decision.criteria); + if (actionGroup.actions) { + for (const action of actionGroup.actions) { + if (action.details) { + for (const detail of action.details) { + detail.name = this.i18n.__( + `ACTION_DETAIL_${toUpperSnakeCase(action.type)}_${toUpperSnakeCase(detail.name)}` + ); + } } + action.type = this.i18n.__(`ACTION_TYPE_${toUpperSnakeCase(action.type)}`); } - action.type = _i18n.__(`ACTION_TYPE_${toUpperSnakeCase(action.type)}`); } } } + return flow; } - return flow; } From 95f8060d355db64122418f504296edda543e8549 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Thu, 14 May 2020 14:51:21 +0900 Subject: [PATCH 07/22] Update rule --- .eslintrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index e18e8d4..444452e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -26,6 +26,7 @@ "import/prefer-default-export": "warn", "import/no-extraneous-dependencies": "off", "@typescript-eslint/no-use-before-define": "off", - "unicorn/filename-case": "off" + "unicorn/filename-case": "off", + "max-classes-per-file": "off" } } From 822e44c9a8a0d603c6fa44ee6026381c329be393 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Thu, 14 May 2020 14:51:28 +0900 Subject: [PATCH 08/22] Update message --- messages/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/messages.json b/messages/messages.json index 6eb442b..7d3736a 100644 --- a/messages/messages.json +++ b/messages/messages.json @@ -1,6 +1,6 @@ { "errorParamNotFound": "Flow API name is missing.", - "errorUnsupportedFlow": "Currently, only process is supported. the other type of processes will be available soon.", + "errorUnsupportedFlow": "The flow is not supported now.", "commandDescription": "generate pdf document", "nameFlagDescription": "developer name of flow", "localeFlagDescription": "locale of the document (en or ja)", From 965d029a2d79c5098445c9f82ba211c705cf09db Mon Sep 17 00:00:00 2001 From: shunkosa Date: Thu, 14 May 2020 15:11:17 +0900 Subject: [PATCH 09/22] Fixing tests --- test/lib/docx/docxBuilder.test.ts | 11 +++++------ test/lib/flowParser.test.ts | 21 --------------------- test/lib/flowUtils.test.ts | 13 +++++++++++++ test/lib/json/jsonBuilder.test.ts | 13 +++++++++++++ test/lib/pdf/pdfBuilder.test.ts | 11 +++++------ 5 files changed, 36 insertions(+), 33 deletions(-) delete mode 100644 test/lib/flowParser.test.ts create mode 100644 test/lib/flowUtils.test.ts create mode 100644 test/lib/json/jsonBuilder.test.ts diff --git a/test/lib/docx/docxBuilder.test.ts b/test/lib/docx/docxBuilder.test.ts index e00c2fa..46356fc 100644 --- a/test/lib/docx/docxBuilder.test.ts +++ b/test/lib/docx/docxBuilder.test.ts @@ -1,15 +1,14 @@ import { Document } from 'docx'; -import buildDocxContent from '../../../src/lib/docx/docxBuilder'; -import FlowParser from '../../../src/lib/flowParser'; -import { Flow } from '../../../src/types/flow'; +import { Flow } from '../../../src/types/metadata/flow'; import testFlow from '../testFlow.json'; +import DocxBuilder from '../../../src/lib/docx/docxBuilder'; +import { ReadableProcessMetadataConverter } from '../../../src/lib/converter/metadataConverter'; describe('lib/pdf/pdfBuilder', () => { it('buildPdfContent()', () => { - const fp = new FlowParser(testFlow as Flow, 'test_flow'); - const readableFlow = fp.createReadableProcess(); - const docxContent = buildDocxContent(readableFlow, 'en'); + const docxBuilder = new DocxBuilder('en'); + const docxContent = new ReadableProcessMetadataConverter(testFlow as Flow, 'test_flow').accept(docxBuilder); expect(docxContent instanceof Document).toBeTruthy(); }); }); diff --git a/test/lib/flowParser.test.ts b/test/lib/flowParser.test.ts deleted file mode 100644 index 0aa3906..0000000 --- a/test/lib/flowParser.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import testFlow from './testFlow.json'; -import FlowParser from '../../src/lib/flowParser'; -import { Flow } from '../../src/types/flow'; - -describe('lib/flowParser', () => { - let flowParser; - - beforeAll(() => { - flowParser = new FlowParser(testFlow as Flow, 'test_flow'); - }); - - it('supported flow', () => { - expect(flowParser.isSupportedFlow()).toBeTruthy(); - }); - - it('readable flow creation', () => { - const readableProcess = flowParser.createReadableProcess(); - expect(readableProcess.eventMatchingConditions).toBeFalsy(); - expect(readableProcess.actionGroups).toHaveLength(4); - }); -}); diff --git a/test/lib/flowUtils.test.ts b/test/lib/flowUtils.test.ts new file mode 100644 index 0000000..213f5b6 --- /dev/null +++ b/test/lib/flowUtils.test.ts @@ -0,0 +1,13 @@ +import testFlow from './testFlow.json'; +import { Flow } from '../../src/types/metadata/flow'; +import { isSupported, isProcess } from '../../src/lib/util/flowUtils'; + +describe('lib/flowUtils', () => { + it('isSupported', () => { + expect(isSupported(testFlow as Flow)).toBeTruthy(); + }); + + it('isProcess', () => { + expect(isProcess(testFlow as Flow)).toBeTruthy(); + }); +}); diff --git a/test/lib/json/jsonBuilder.test.ts b/test/lib/json/jsonBuilder.test.ts new file mode 100644 index 0000000..b0fb23b --- /dev/null +++ b/test/lib/json/jsonBuilder.test.ts @@ -0,0 +1,13 @@ +import { Flow } from '../../../src/types/metadata/flow'; + +import testFlow from '../testFlow.json'; +import JsonBuilder from '../../../src/lib/json/jsonBuilder'; +import { ReadableProcessMetadataConverter } from '../../../src/lib/converter/metadataConverter'; + +describe('lib/pdf/pdfBuilder', () => { + it('buildPdfContent()', () => { + const jsonBuilder = new JsonBuilder('en'); + const jsonContent = new ReadableProcessMetadataConverter(testFlow as Flow, 'test_flow').accept(jsonBuilder); + expect(jsonContent).toBeTruthy(); + }); +}); diff --git a/test/lib/pdf/pdfBuilder.test.ts b/test/lib/pdf/pdfBuilder.test.ts index ce3170b..c133e71 100644 --- a/test/lib/pdf/pdfBuilder.test.ts +++ b/test/lib/pdf/pdfBuilder.test.ts @@ -1,14 +1,13 @@ -import buildPdfContent from '../../../src/lib/pdf/pdfBuilder'; -import FlowParser from '../../../src/lib/flowParser'; -import { Flow } from '../../../src/types/flow'; +import { Flow } from '../../../src/types/metadata/flow'; import testFlow from '../testFlow.json'; +import PdfBuilder from '../../../src/lib/pdf/pdfBuilder'; +import { ReadableProcessMetadataConverter } from '../../../src/lib/converter/metadataConverter'; describe('lib/pdf/pdfBuilder', () => { it('buildPdfContent()', () => { - const fp = new FlowParser(testFlow as Flow, 'test_flow'); - const readableFlow = fp.createReadableProcess(); - const pdfContent = buildPdfContent(readableFlow, 'en'); + const pdfBuilder = new PdfBuilder('en'); + const pdfContent = new ReadableProcessMetadataConverter(testFlow as Flow, 'test_flow').accept(pdfBuilder); expect(pdfContent.content).toHaveLength(48); }); }); From 11153ae596807911c566ffefb2c6c5a114f4db43 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Thu, 14 May 2020 22:32:42 +0900 Subject: [PATCH 10/22] Add params for flow builder --- src/lib/converter/iteratableMetadata.ts | 6 ++++++ src/types/converter.ts | 5 ++++- src/types/metadata/flow.ts | 5 ++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/lib/converter/iteratableMetadata.ts b/src/lib/converter/iteratableMetadata.ts index 64a87ef..864c38e 100644 --- a/src/lib/converter/iteratableMetadata.ts +++ b/src/lib/converter/iteratableMetadata.ts @@ -13,6 +13,8 @@ export default function convertToIteratableMetadata(flow: Flow): IteratableFlow waits: toArray(flow.waits), start: flow.start, processMetadataValues: toArray(flow.processMetadataValues), + assignments: toArray(flow.assignments), + loops: toArray(flow.loops), decisions: toArray(flow.decisions), actionCalls: toArray(flow.actionCalls), recordLookups: toArray(flow.recordLookups), @@ -24,5 +26,9 @@ export default function convertToIteratableMetadata(flow: Flow): IteratableFlow toArray(flow.recordUpdates).length > 0 ? toArray(flow.recordUpdates).map(action => ({ ...action, actionType: 'recordUpdate' })) : [], + recordDeletes: + toArray(flow.recordDeletes).length > 0 + ? toArray(flow.recordDeletes).map(action => ({ ...action, actionType: 'recordDelete' })) + : [], }; } diff --git a/src/types/converter.ts b/src/types/converter.ts index 2ff99a6..427c873 100644 --- a/src/types/converter.ts +++ b/src/types/converter.ts @@ -7,15 +7,18 @@ export interface IteratableFlow { processType: string; label: string; description: string; - startElementReference: string; + startElementReference?: string; variables: Array; processMetadataValues: Array; formulas: any; decisions: Array; + assignments?: any; actionCalls?: Array; recordUpdates?: Array; recordCreates?: Array; recordLookups?: Array; + recordDeletes?: any; + loops?: any; waits: any; start?: any; } diff --git a/src/types/metadata/flow.ts b/src/types/metadata/flow.ts index ee622db..7024f0e 100644 --- a/src/types/metadata/flow.ts +++ b/src/types/metadata/flow.ts @@ -5,15 +5,18 @@ export interface Flow { processType: string; label: string; description: string; - startElementReference: string; + startElementReference?: string; variables: Variable | Array; processMetadataValues: Array; formulas: any; decisions: Decision | Array; + assignments: any; actionCalls?: ActionCall | Array; recordUpdates?: RecordUpdate | Array; recordCreates?: RecordCreate | Array; recordLookups?: RecordLookup | Array; + recordDeletes?: any; + loops?: any; waits: any; start?: any; } From 9b23467815135b6c2bc2509ac79b562a8f92e4d9 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Sat, 23 May 2020 12:29:48 +0900 Subject: [PATCH 11/22] Fix --- src/lib/util/flowUtils.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib/util/flowUtils.ts b/src/lib/util/flowUtils.ts index b8c9084..d5fa9db 100644 --- a/src/lib/util/flowUtils.ts +++ b/src/lib/util/flowUtils.ts @@ -4,10 +4,7 @@ export const SUPPORTED_PROCESS = ['Workflow', 'CustomEvent', 'InvocableProcess'] export const SUPPORTED_FLOW = ['AutoLaunchedFlow']; export function isSupported(flow: Flow) { - return ( - isProcess(flow) || - (SUPPORTED_FLOW.includes(this.flow.processType) && this.flow.start.recordTriggerType !== undefined) - ); + return isProcess(flow) || (SUPPORTED_FLOW.includes(flow.processType) && flow.start.recordTriggerType !== undefined); } export function isProcess(flow: Flow) { From acb9ee3c38427f003596e233edfa9e3a6b8ee69f Mon Sep 17 00:00:00 2001 From: shunkosa Date: Sat, 23 May 2020 12:31:37 +0900 Subject: [PATCH 12/22] Add dataValue --- src/types/metadata/processMetadataValue.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types/metadata/processMetadataValue.ts b/src/types/metadata/processMetadataValue.ts index 653ae7b..f0ea95f 100644 --- a/src/types/metadata/processMetadataValue.ts +++ b/src/types/metadata/processMetadataValue.ts @@ -7,6 +7,7 @@ export type ElementReferenceOrValue = RequireOne<{ stringValue?: string; numberValue?: string; booleanValue?: string; + dateValue?: string; elementReference?: string; }>; @@ -19,6 +20,7 @@ function implementsElementReferenceOrValue(arg: any): arg is ElementReferenceOrV arg.stringValue !== undefined || arg.numberValue !== undefined || arg.booleanValue !== undefined || + arg.dateValue !== undefined || arg.elementReference !== undefined ); } From b1ca911b38c2dd594019dd047dc7848c38d71ca1 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Wed, 5 Aug 2020 00:17:17 +0900 Subject: [PATCH 13/22] Add labels for flow start element --- src/config/locale/en.json | 12 ++++++++++++ src/config/locale/ja.json | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/config/locale/en.json b/src/config/locale/en.json index 1156459..ac3b889 100644 --- a/src/config/locale/en.json +++ b/src/config/locale/en.json @@ -8,6 +8,18 @@ "WHEN_THE_PROCESS_STARTS": "When the process starts", "WHEN_A_RECORD_IS_CREATED_OR_EDITED": "When a record is created or edited", "ONLY_WHEN_A_RECORD_IS_CREATED": "Only when a record is created", + "FLOW_TRIGGER_TYPE": "Flow Type", + "FLOW_TRIGGER_AUTO_LAUNCHED": "Auto Launched Flow", + "FLOW_TRIGGER_RECORD": "Record Change Flow", + "FLOW_TRIGGER_SCHEDULED": "Scheduled Flow", + "FLOW_TRIGGER_PLATFORM_EVENT": "Platform Event Flow", + "HEADER_FLOW_TRIGGER_RECORD": "Trigger the Flow When", + "FROW_TRIGGER_RECORD_CREATE_ONLY": "A record is created", + "FROW_TRIGGER_RECORD_UPDATE_ONLY": "A record is updated", + "FROW_TRIGGER_RECORD_CREATE_OR_UPDATE": "A record is created or updated", + "HEADER_FLOW_TRIGGER_RECORD_TYPE": "Run the Flow", + "FLOW_TRIGGER_BEFORE": "Before the record is saved", + "FLOW_TRIGGER_AFTER": "After the record is saved", "ACTION_GROUP": "Action Group", "CONDITION_NAME": "Condition Name", "CRITERIA_FOR_EXECUTING_ACTIONS": "Criteria for Execution Actions", diff --git a/src/config/locale/ja.json b/src/config/locale/ja.json index 4eded57..a2b07ee 100644 --- a/src/config/locale/ja.json +++ b/src/config/locale/ja.json @@ -8,6 +8,18 @@ "WHEN_THE_PROCESS_STARTS": "プロセスを開始", "WHEN_A_RECORD_IS_CREATED_OR_EDITED": "レコードを作成したときのみ", "ONLY_WHEN_A_RECORD_IS_CREATED": "レコードを作成または編集したとき", + "FLOW_TRIGGER_TYPE": "フローの種別", + "FLOW_TRIGGER_AUTOLAUNCHED": "自動起動", + "FLOW_TRIGGER_RECORD": "レコード変更フロー", + "FLOW_TRIGGER_SCHEDULED": "スケジュール済みフロー", + "FLOW_TRIGGER_PLATFORM_EVENT": "プラットフォームイベントフロー", + "HEADER_FLOW_TRIGGER_RECORD": "フローをトリガする条件", + "FROW_TRIGGER_RECORD_CREATE_ONLY": "レコードの作成時", + "FROW_TRIGGER_RECORD_UPDATE_ONLY": "レコードの更新時", + "FROW_TRIGGER_RECORD_CREATE_OR_UPDATE": "レコードの作成または更新時", + "HEADER_FLOW_TRIGGER_RECORD_TYPE": "フローを実行", + "FLOW_TRIGGER_BEFORE": "レコードが保存される前", + "FLOW_TRIGGER_AFTER": "レコードが保存された後", "ACTION_GROUP": "アクショングループ", "CONDITION_NAME": "条件名", "CRITERIA_FOR_EXECUTING_ACTIONS": "アクションの実行条件", From 92fe8eb5598201e70591aef20ebb02d80030a715 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Fri, 7 Aug 2020 10:04:42 +0900 Subject: [PATCH 14/22] WIP: Adding before flow support --- src/lib/converter/helper/readableFlow.ts | 196 +++++++++++++++--- src/lib/converter/helper/readableFlowStart.ts | 47 +++++ src/lib/json/jsonBuilder.ts | 5 +- src/lib/util/flowUtils.ts | 7 +- src/types/converter.ts | 70 +++++-- src/types/metadata/flow.ts | 75 ++++++- src/types/metadata/flowRecordAction.ts | 22 +- 7 files changed, 369 insertions(+), 53 deletions(-) create mode 100644 src/lib/converter/helper/readableFlowStart.ts diff --git a/src/lib/converter/helper/readableFlow.ts b/src/lib/converter/helper/readableFlow.ts index 77fa5a6..84d5e5b 100644 --- a/src/lib/converter/helper/readableFlow.ts +++ b/src/lib/converter/helper/readableFlow.ts @@ -1,48 +1,190 @@ import ReadableMetadata from '../readableMetadata'; -import { ReadableFlow, ReadableStart } from '../../../types/converter'; +import { + ReadableFlow, + ReadableAssignmentItem, + ReadableAssignment, + ReadableFlowBuilderItem, + ReadableLoop, + ReadableFlowDecisionRoute, + ReadableFlowDecision, +} from '../../../types/converter'; +import { + Flow, + FlowBuilderItem, + implementsAssignment, + Assignment, + AssignmentItem, + Loop, + implementsLoop, + implementsDecision, + Decision, +} from '../../../types/metadata/flow'; +import { toArray } from '../../util/arrayUtils'; +import ReadableFlowStartElement from './readableFlowStart'; export default class ReadableFlowMetadata extends ReadableMetadata { + flowBuilderItems: ReadonlyArray; + + visitedItemNameSet: Set; + + constructor(flow: Flow, name: string) { + super(flow, name); + this.flowBuilderItems = [ + ...this.flow.actionCalls, + ...this.flow.assignments, + ...this.flow.loops, + ...this.flow.decisions, + ...this.flow.recordLookups, + ...this.flow.recordCreates, + ...this.flow.recordUpdates, + ...this.flow.recordDeletes, + ]; + this.visitedItemNameSet = new Set(); + } + createReadableFlow(): ReadableFlow { + const start = new ReadableFlowStartElement(this.flow); + return { name: this.name, label: this.getLabel(), processType: this.getProcessType(), description: this.getDescription(), - start: this.getReadableFlowStart(), - elements: undefined, // this.getReadableFlowElements() + start: start.getReadableElement(), + elements: this.getReadableFlowElements([], this.getFirstElementName()), }; } - private getFlowTriggerType() { - if (this.flow.start.recordTriggerType) { - return 'FLOW_TRIGGER_RECORD'; + private getFirstElementName() { + return this.flow.start.connector ? this.flow.start.connector.targetReference : undefined; + } + + // Array + private getReadableFlowElements( + currentElements: Array, + targetReference: string + ): Array { + if (!targetReference) { + return currentElements; } - if (this.flow.start.schedule) { - return 'FLOW_TRIGGER_SCHEDULED'; + const nextElement = this.flowBuilderItems.find(e => e.name === targetReference); + if (nextElement) { + /* const readableNextElement = this.convertToReadableFlowElement(nextElement); + if (readableNextElement) { + currentElements.push(readableNextElement); + } + if (implementsAssignment(nextElement)) { + const nextReference = nextElement.connector ? nextElement.connector.targetReference : undefined; + this.getReadableFlowElements(currentElements, nextReference); + } + */ + this.visitedItemNameSet.add(nextElement.name); + let nextReference; + if (implementsLoop(nextElement)) { + nextReference = nextElement.noMoreValuesConnector + ? nextElement.noMoreValuesConnector.targetReference + : undefined; + currentElements.push(this.convertToReadableLoop(nextElement)); + } else if (implementsDecision(nextElement)) { + // nextReference = nextElement.defaultConnector ? nextElement.defaultConnector.targetReference : undefined; + currentElements.push(this.convertToReadableDecision(nextElement)); + } else { + nextReference = nextElement.connector ? nextElement.connector.targetReference : undefined; + currentElements.push({ label: nextElement.label, name: nextElement.name }); + } + + this.getReadableFlowElements(currentElements, nextReference); + } + return currentElements; + } + + private convertToReadableFlowElement(flowBuilderItem: FlowBuilderItem): ReadableFlowBuilderItem { + if (implementsAssignment(flowBuilderItem)) { + return this.convertToReadableAssingment(flowBuilderItem); } - return 'FLOW_TRIGGER_USER_OR_APPS'; - } - - private getFlowRecordTriggerType() { - switch (this.flow.start.recordTriggerType) { - case 'Create': - return 'FROW_TRIGGER_RECORD_CREATE_ONLY'; - case 'Update': - return 'FROW_TRIGGER_RECORD_UPDATE_ONLY'; - case 'CreateAndUpdate': - return 'FROW_TRIGGER_RECORD_CREATE_OR_UPDATE'; - default: - return undefined; + if (implementsLoop(flowBuilderItem)) { + return this.convertToReadableLoop(flowBuilderItem); } + return undefined; + } + + private convertToReadableDecision(decision: Decision): ReadableFlowDecision { + const defaultRoute: ReadableFlowDecisionRoute = { + name: decision.defaultConnectorLabel, + label: undefined, + elements: this.traceDecisionRouteElements([], decision.defaultConnector.targetReference), + }; + const rules = toArray(decision.rules); + const ruleRoutes: Array = []; + for (const rule of rules) { + if (rule.connector) { + ruleRoutes.push({ + name: rule.name, + label: rule.label, + elements: this.traceDecisionRouteElements([], rule.connector.targetReference), + }); + } + } + return { + type: 'decision', + name: decision.name, + label: decision.label, + routes: [defaultRoute, ...ruleRoutes], + }; + } + + private traceDecisionRouteElements( + routeElements: Array, + targetReference: string + ): Array { + if (!targetReference || this.visitedItemNameSet.has(targetReference)) { + return routeElements; + } + const nextElement = this.flowBuilderItems.find(e => e.name === targetReference); + if (nextElement) { + routeElements.push({ label: nextElement.label, name: nextElement.name }); + let nextReference; + if (implementsLoop(nextElement)) { + nextReference = nextElement.noMoreValuesConnector + ? nextElement.noMoreValuesConnector.targetReference + : undefined; + } else if (implementsDecision(nextElement)) { + nextReference = nextElement.defaultConnector ? nextElement.defaultConnector.targetReference : undefined; + } else { + nextReference = nextElement.connector ? nextElement.connector.targetReference : undefined; + } + this.traceDecisionRouteElements(routeElements, nextReference); + } + return routeElements; + } + + private convertToReadableAssingment(assignment: Assignment): ReadableAssignment { + const assignments: Array = toArray(assignment.assignmentItems).map( + item => ({ + reference: item.assignToReference, + operator: item.operator, + value: this.resolveValue(item.value), + }) + ); + return { + type: 'assignment', + name: assignment.name, + label: assignment.label, + description: assignment.description, + assignments, + }; } - private getReadableFlowStart(): ReadableStart { - const triggerType = this.getFlowTriggerType(); + private convertToReadableLoop(loop: Loop): ReadableLoop { + const elements = loop.nextValueConnector + ? this.getReadableFlowElements([], loop.nextValueConnector.targetReference) + : []; return { - triggerType, - object: this.flow.start.object, - recordTriggerType: this.getFlowRecordTriggerType(), - schedule: triggerType === 'FLOW_TRIGGER_SCHEDULED' ? this.flow.start.schedule : undefined, + type: 'loop', + name: loop.name, + label: loop.label, + description: loop.description, + elements, }; } } diff --git a/src/lib/converter/helper/readableFlowStart.ts b/src/lib/converter/helper/readableFlowStart.ts new file mode 100644 index 0000000..3a7ae84 --- /dev/null +++ b/src/lib/converter/helper/readableFlowStart.ts @@ -0,0 +1,47 @@ +import { ReadableStart } from '../../../types/converter'; + +export default class ReadableFlowStartElement { + flow; + + constructor(flow) { + this.flow = flow; + } + + getReadableElement(): ReadableStart { + const triggerType = this.getFlowTriggerType(); + return { + triggerType, // record, schedule, or auto-launch + object: this.flow.start.object, + recordTriggerType: this.getFlowRecordTriggerType(), // create, update, or both + schedule: triggerType === 'FLOW_TRIGGER_SCHEDULED' ? this.flow.start.schedule : undefined, + context: triggerType === 'FLOW_TRIGGER_RECORD' ? this.getRecordFlowContext() : undefined, // before or after + }; + } + + private getFlowTriggerType() { + if (this.flow.start.recordTriggerType) { + return 'FLOW_TRIGGER_RECORD'; + } + if (this.flow.start.schedule) { + return 'FLOW_TRIGGER_SCHEDULED'; + } + return 'FLOW_TRIGGER_USER_OR_APPS'; + } + + private getFlowRecordTriggerType() { + switch (this.flow.start.recordTriggerType) { + case 'Create': + return 'FROW_TRIGGER_RECORD_CREATE_ONLY'; + case 'Update': + return 'FROW_TRIGGER_RECORD_UPDATE_ONLY'; + case 'CreateAndUpdate': + return 'FROW_TRIGGER_RECORD_CREATE_OR_UPDATE'; + default: + return undefined; + } + } + + private getRecordFlowContext() { + return this.flow.start.triggerType === 'RecordBeforeSave' ? 'BEFORE_SAVE' : 'AFTER_SAVE'; + } +} diff --git a/src/lib/json/jsonBuilder.ts b/src/lib/json/jsonBuilder.ts index 718f376..a452eaf 100644 --- a/src/lib/json/jsonBuilder.ts +++ b/src/lib/json/jsonBuilder.ts @@ -11,8 +11,9 @@ export default class JsonBuilder extends DocumentBuilder { i18n = i18n(this.locale); buildFlowDocument(converter: ReadableFlowMetadataConverter) { - console.log(this.locale); - console.log(converter.readableMetadata); + // TODO: translate labels + console.log(`building json... ${this.i18n}`); + return converter.readableMetadata; } buildProcessDocument(converter: ReadableProcessMetadataConverter) { diff --git a/src/lib/util/flowUtils.ts b/src/lib/util/flowUtils.ts index d5fa9db..6f4cb58 100644 --- a/src/lib/util/flowUtils.ts +++ b/src/lib/util/flowUtils.ts @@ -4,7 +4,12 @@ export const SUPPORTED_PROCESS = ['Workflow', 'CustomEvent', 'InvocableProcess'] export const SUPPORTED_FLOW = ['AutoLaunchedFlow']; export function isSupported(flow: Flow) { - return isProcess(flow) || (SUPPORTED_FLOW.includes(flow.processType) && flow.start.recordTriggerType !== undefined); + return ( + isProcess(flow) || + (SUPPORTED_FLOW.includes(flow.processType) && + flow.start.recordTriggerType !== undefined && + flow.start.triggerType === 'RecordBeforeSave') + ); } export function isProcess(flow: Flow) { diff --git a/src/types/converter.ts b/src/types/converter.ts index 427c873..9b00f25 100644 --- a/src/types/converter.ts +++ b/src/types/converter.ts @@ -41,29 +41,29 @@ export interface ReadableFlow { label: string; description?: string; start: ReadableStart; - elements?: Array; -} - -export interface ReadableFlowElement { - name: string; - label: string; - description?: string; - type: string; - element: any; + elements?: Array; } export interface ReadableStart { triggerType: string; - recordTriggerType?: string; + recordTriggerType?: string; // update, create, both object?: string; schedule?: { startDate: string; startTime: string; frequency: string; }; + context?: 'BEFORE_SAVE' | 'AFTER_SAVE'; } -export interface ReadableAssignment { +export interface ReadableFlowElement { + type: string; + name: string; + label: string; + description?: string; +} + +export interface ReadableAssignment extends ReadableFlowElement { assignments: Array; } @@ -73,6 +73,21 @@ export interface ReadableAssignmentItem { value: string; } +export interface ReadableLoop extends ReadableFlowElement { + elements: Array; +} + +export interface ReadableFlowDecision extends ReadableFlowElement { + routes: Array; +} + +export interface ReadableFlowDecisionRoute { + name: string; + label: string; + rule?: Array; + elements: Array; +} + export interface ReadableActionGroup { decision: ReadableDecision; actions?: Array; @@ -80,8 +95,8 @@ export interface ReadableActionGroup { evaluatesNext?: boolean; } -export interface ReadableDecision { - label: string; +// TODO: Rename to ReadableProcessDecision +export interface ReadableDecision extends ReadableFlowElement { criteria: string; conditionLogic: string; conditions: Array; @@ -121,6 +136,33 @@ export interface ReadableWaitEventSummary { field?: string; } +export interface ReadableRecordLookup extends ReadableFlowElement { + object: string; + filterCondition: 'NONE' | 'MEET_ALL_CONDITIONS'; + filters?: Array; + sortBy: { + order: 'NONE' | 'ASC' | 'DSC'; + field: string; + }; + numberOfRecords: 'ONLY_FIRST' | 'ALL'; + output: { + method: 'ALL_FIELDS' | 'CHOOSE_FIELDS' | 'CHOOSE_AND_ASSIGN_FIELDS'; + fields?: Array; + assignments?: Array; + }; +} + +export interface OutputAssignment { + field: string; + reference: string; +} + +export interface ReadableFlowRecordLookupFilter { + field: string; + operator: string; + value: string; +} + export function implementsReadableFlow(arg: any): arg is ReadableFlow { return SUPPORTED_FLOW.includes(arg.processType); } @@ -128,3 +170,5 @@ export function implementsReadableFlow(arg: any): arg is ReadableFlow { export function implementsReadableProcess(arg: any): arg is ReadableProcess { return SUPPORTED_PROCESS.includes(arg.processType); } + +export type ReadableFlowBuilderItem = ReadableAssignment | ReadableDecision | ReadableLoop | ReadableRecordLookup; // | ReadableRecordCreate | ReadableRecordUpdate | ReadableRecordDelete ; diff --git a/src/types/metadata/flow.ts b/src/types/metadata/flow.ts index 7024f0e..a43a391 100644 --- a/src/types/metadata/flow.ts +++ b/src/types/metadata/flow.ts @@ -1,5 +1,5 @@ -import { ProcessMetadataValue } from './processMetadataValue'; -import { RecordCreate, RecordUpdate, RecordLookup } from './flowRecordAction'; +import { ProcessMetadataValue, ElementReferenceOrValue } from './processMetadataValue'; +import { Node, RecordCreate, RecordUpdate, RecordLookup, RecordDelete } from './flowRecordAction'; export interface Flow { processType: string; @@ -30,7 +30,9 @@ export interface ActionCall { actionType: string; name: string; label: string; - connector: any; + connector: { + targetReference: string; + }; processMetadataValues: any; inputParameters: any; } @@ -39,15 +41,74 @@ export function implementsActionCall(arg: any): arg is ActionCall { return !(arg.actionType === 'recordUpdate' || arg.actionType === 'recordCreate'); } -export interface Decision { - name: string; - label: string; - processMetadataValues?: ProcessMetadataValue; +export interface Decision extends Node { + processMetadataValues?: ProcessMetadataValue | Array; rules: any; defaultConnector: any; + defaultConnectorLabel: string; +} + +export function implementsDecision(arg: any): arg is Decision { + return arg !== undefined && arg.defaultConnector !== undefined; } export interface InputParamValue { stringValue?: string; elementReference?: string; } + +export interface Assignment extends Node { + assignmentItems: AssignmentItem | Array; + connector: { + targetReference: string; + }; +} + +export function implementsAssignment(arg: any): arg is Assignment { + return arg !== undefined && arg.assignmentItems !== undefined; +} +export interface AssignmentItem { + assignToReference: string; + operator: + | 'Add' + | 'AddAdStart' + | 'AddItem' + | 'Assign' + | 'AssignCount' + | 'RemoveAfterFirst' + | 'RemoveAll' + | 'RemoveBeforeFirst' + | 'RemoveFirst' + | 'RemovePosition' + | 'RemoveUncommon' + | 'Subtract'; + value: ElementReferenceOrValue; +} + +export interface Loop extends Node { + nextValueConnector: { + targetReference: string; + }; + noMoreValuesConnector: { + targetReference: string; + }; + collectionReference: string; + assignNextValueToReference: string; + iterationOrder: 'Asc' | 'Dsc'; +} + +export function implementsLoop(arg: any): arg is Loop { + return arg !== undefined && arg.collectionReference !== undefined; +} + +export type ProcessElement = Decision | ActionCall; + +export type FlowBuilderItem = + | Decision + | Assignment + | Loop + | ActionCall + | RecordLookup + | RecordCreate + | RecordUpdate + | RecordDelete; diff --git a/src/types/metadata/flowRecordAction.ts b/src/types/metadata/flowRecordAction.ts index 697c4cb..78e8cae 100644 --- a/src/types/metadata/flowRecordAction.ts +++ b/src/types/metadata/flowRecordAction.ts @@ -1,6 +1,6 @@ import { ProcessMetadataValue, ElementReferenceOrValue } from './processMetadataValue'; -interface Node { +export interface Node { name: string; label: string; locationX: string; @@ -11,28 +11,44 @@ export interface RecordCreate extends Node { processMetadataValues?: any; actionType?: string; assignRecordIdToReference?: string; - connector: any; faultConnector?: any; inputAssignments: any; inputReference?: string; object: string; storeOutputAutomatically?: boolean; + connector: { + targetReference: string; + }; } export interface RecordUpdate extends Node { processMetadataValues?: any; actionType?: string; - connector: any; faultConnector?: any; filters: RecordFilter | RecordFilter[]; inputAssignments: any; object: string; inputReference?: string; + connector: { + targetReference: string; + }; } export interface RecordLookup extends Node { processMetadataValues?: any; filters?: RecordFilter | RecordFilter[]; + connector: { + targetReference: string; + }; +} + +export interface RecordDelete extends Node { + filters?: RecordFilter | RecordFilter[]; + object: string; + inputReference?: string; + connector: { + targetReference: string; + }; } export interface RecordFilter { From 0dae05d3c446f6f08818aadef64111e68d6fc048 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Fri, 7 Aug 2020 12:15:58 +0900 Subject: [PATCH 15/22] Update types --- src/lib/converter/helper/actionParser.ts | 2 +- src/lib/converter/helper/readableFlow.ts | 2 +- src/lib/converter/helper/readableFlowStart.ts | 2 +- src/lib/converter/helper/readableProcess.ts | 8 +- src/lib/converter/iteratableMetadata.ts | 2 +- src/lib/converter/metadataConverter.ts | 3 +- src/lib/converter/readableMetadata.ts | 2 +- src/lib/docx/process/docxProcessFormatter.ts | 2 +- src/lib/docx/process/docxTableUtils.ts | 2 +- .../docx/process/startConditionFormatter.ts | 2 +- src/lib/pdf/flow/pdfFlowFormatter.ts | 2 +- src/lib/pdf/process/pdfProcessFormatter.ts | 6 +- .../pdf/process/startConditionFormatter.ts | 2 +- src/types/converter.ts | 174 ------------------ src/types/converter/flow.ts | 98 ++++++++++ src/types/converter/main.ts | 37 ++++ src/types/converter/process.ts | 65 +++++++ src/types/metadata/flow.ts | 2 +- 18 files changed, 220 insertions(+), 193 deletions(-) delete mode 100644 src/types/converter.ts create mode 100644 src/types/converter/flow.ts create mode 100644 src/types/converter/main.ts create mode 100644 src/types/converter/process.ts diff --git a/src/lib/converter/helper/actionParser.ts b/src/lib/converter/helper/actionParser.ts index ba275dd..e7d3b1d 100644 --- a/src/lib/converter/helper/actionParser.ts +++ b/src/lib/converter/helper/actionParser.ts @@ -2,7 +2,7 @@ import { ActionCall, InputParamValue } from '../../../types/metadata/flow'; import { RecordCreate, RecordUpdate, RecordFilter, RecordLookup } from '../../../types/metadata/flowRecordAction'; import { toArray } from '../../util/arrayUtils'; import { ProcessMetadataValue } from '../../../types/metadata/processMetadataValue'; -import { ReadableCondition, ReadableActionItem, ReadableActionItemParameter } from '../../../types/converter'; +import { ReadableCondition, ReadableActionItem, ReadableActionItemParameter } from '../../../types/converter/process'; const layout = require('./actionLayout.json'); diff --git a/src/lib/converter/helper/readableFlow.ts b/src/lib/converter/helper/readableFlow.ts index 84d5e5b..df0f571 100644 --- a/src/lib/converter/helper/readableFlow.ts +++ b/src/lib/converter/helper/readableFlow.ts @@ -7,7 +7,7 @@ import { ReadableLoop, ReadableFlowDecisionRoute, ReadableFlowDecision, -} from '../../../types/converter'; +} from '../../../types/converter/flow'; import { Flow, FlowBuilderItem, diff --git a/src/lib/converter/helper/readableFlowStart.ts b/src/lib/converter/helper/readableFlowStart.ts index 3a7ae84..6124f3c 100644 --- a/src/lib/converter/helper/readableFlowStart.ts +++ b/src/lib/converter/helper/readableFlowStart.ts @@ -1,4 +1,4 @@ -import { ReadableStart } from '../../../types/converter'; +import { ReadableStart } from '../../../types/converter/flow'; export default class ReadableFlowStartElement { flow; diff --git a/src/lib/converter/helper/readableProcess.ts b/src/lib/converter/helper/readableProcess.ts index 6ec7185..fcda4da 100644 --- a/src/lib/converter/helper/readableProcess.ts +++ b/src/lib/converter/helper/readableProcess.ts @@ -5,8 +5,8 @@ import { ReadableActionItem, ReadableScheduledActionSection, ReadableWaitEventSummary, - ReadableDecision, -} from '../../../types/converter'; + ReadableProcessDecision, +} from '../../../types/converter/process'; import { ActionCall, implementsActionCall, Decision } from '../../../types/metadata/flow'; import { getRecordLookupFilter, @@ -91,8 +91,8 @@ export default class ReadableProcessMetadata extends ReadableMetadata { }); } - private convertToReadableDecision(decision: Decision): ReadableDecision { - const readableDecision: ReadableDecision = { + private convertToReadableDecision(decision: Decision): ReadableProcessDecision { + const readableDecision: ReadableProcessDecision = { label: decision.rules.label, criteria: this.getActionExecutionCriteria(decision), conditionLogic: decision.rules.conditionLogic.toUpperCase(), diff --git a/src/lib/converter/iteratableMetadata.ts b/src/lib/converter/iteratableMetadata.ts index 864c38e..9e135da 100644 --- a/src/lib/converter/iteratableMetadata.ts +++ b/src/lib/converter/iteratableMetadata.ts @@ -1,6 +1,6 @@ import { toArray } from '../util/arrayUtils'; import { Flow } from '../../types/metadata/flow'; -import { IteratableFlow } from '../../types/converter'; +import { IteratableFlow } from '../../types/converter/main'; export default function convertToIteratableMetadata(flow: Flow): IteratableFlow { return { diff --git a/src/lib/converter/metadataConverter.ts b/src/lib/converter/metadataConverter.ts index 2a7bd0e..765c4f8 100644 --- a/src/lib/converter/metadataConverter.ts +++ b/src/lib/converter/metadataConverter.ts @@ -1,5 +1,6 @@ import { Flow } from '../../types/metadata/flow'; -import { ReadableProcess, ReadableFlow } from '../../types/converter'; +import { ReadableProcess } from '../../types/converter/process'; +import { ReadableFlow } from '../../types/converter/flow'; import ReadableFlowMetadata from './helper/readableFlow'; import ReadableProcessMetadata from './helper/readableProcess'; diff --git a/src/lib/converter/readableMetadata.ts b/src/lib/converter/readableMetadata.ts index 34599f6..71e7218 100644 --- a/src/lib/converter/readableMetadata.ts +++ b/src/lib/converter/readableMetadata.ts @@ -1,5 +1,5 @@ import { InputParamValue, Flow } from '../../types/metadata/flow'; -import { IteratableFlow } from '../../types/converter'; +import { IteratableFlow } from '../../types/converter/main'; import { RecordLookup } from '../../types/metadata/flowRecordAction'; import { implementsProcessMetadataValue, ProcessMetadataValue } from '../../types/metadata/processMetadataValue'; import { unescapeHtml } from '../util/stringUtils'; diff --git a/src/lib/docx/process/docxProcessFormatter.ts b/src/lib/docx/process/docxProcessFormatter.ts index 6f692ed..e5a8338 100644 --- a/src/lib/docx/process/docxProcessFormatter.ts +++ b/src/lib/docx/process/docxProcessFormatter.ts @@ -4,7 +4,7 @@ import { ReadableActionItem, ReadableActionItemParameter, ReadableWaitEventSummary, -} from '../../../types/converter'; +} from '../../../types/converter/process'; import i18n from '../../../config/i18n'; import StartConditionFormatter from './startConditionFormatter'; import { diff --git a/src/lib/docx/process/docxTableUtils.ts b/src/lib/docx/process/docxTableUtils.ts index 633e5ec..50b9bd6 100644 --- a/src/lib/docx/process/docxTableUtils.ts +++ b/src/lib/docx/process/docxTableUtils.ts @@ -1,5 +1,5 @@ import { Table, TableRow, TableCell, Paragraph, TextRun, BorderStyle } from 'docx'; -import { ReadableCondition, ReadableActionItemParameter } from '../../../types/converter'; +import { ReadableCondition, ReadableActionItemParameter } from '../../../types/converter/process'; const CELL_DEFAULT_MARGIN = { top: 20, diff --git a/src/lib/docx/process/startConditionFormatter.ts b/src/lib/docx/process/startConditionFormatter.ts index fff19e4..c8ce401 100644 --- a/src/lib/docx/process/startConditionFormatter.ts +++ b/src/lib/docx/process/startConditionFormatter.ts @@ -1,5 +1,5 @@ import { Paragraph } from 'docx'; -import { ReadableProcess } from '../../../types/converter'; +import { ReadableProcess } from '../../../types/converter/process'; import { createHorizontalHeaderTable, createProcessConditionTable } from './docxTableUtils'; export default class StartConditionFormatter { diff --git a/src/lib/pdf/flow/pdfFlowFormatter.ts b/src/lib/pdf/flow/pdfFlowFormatter.ts index b6fa020..70ee2ee 100644 --- a/src/lib/pdf/flow/pdfFlowFormatter.ts +++ b/src/lib/pdf/flow/pdfFlowFormatter.ts @@ -1,6 +1,6 @@ import i18n from '../../../config/i18n'; import { th } from '../../../style/text'; -import { ReadableFlow } from '../../../types/converter'; +import { ReadableFlow } from '../../../types/converter/flow'; export default class PdfProcessFormatter { flow: ReadableFlow; diff --git a/src/lib/pdf/process/pdfProcessFormatter.ts b/src/lib/pdf/process/pdfProcessFormatter.ts index 44f9353..5c9f8e8 100644 --- a/src/lib/pdf/process/pdfProcessFormatter.ts +++ b/src/lib/pdf/process/pdfProcessFormatter.ts @@ -2,11 +2,11 @@ import { th, h2, h3 } from '../../../style/text'; import i18n from '../../../config/i18n'; import { ReadableProcess, - ReadableDecision, + ReadableProcessDecision, ReadableCondition, ReadableActionItem, ReadableWaitEventSummary, -} from '../../../types/converter'; +} from '../../../types/converter/process'; import StartConditionFormatter from './startConditionFormatter'; import { toUpperSnakeCase } from '../../util/stringUtils'; @@ -76,7 +76,7 @@ export default class PdfProcessFormatter { return content; }; - private buildDecision(decision: ReadableDecision) { + private buildDecision(decision: ReadableProcessDecision) { const table = { unbreakable: true, table: { diff --git a/src/lib/pdf/process/startConditionFormatter.ts b/src/lib/pdf/process/startConditionFormatter.ts index b98458a..586f138 100644 --- a/src/lib/pdf/process/startConditionFormatter.ts +++ b/src/lib/pdf/process/startConditionFormatter.ts @@ -1,5 +1,5 @@ import { th } from '../../../style/text'; -import { ReadableProcess } from '../../../types/converter'; +import { ReadableProcess } from '../../../types/converter/process'; export default class StartConditionFormatter { flow: ReadableProcess; diff --git a/src/types/converter.ts b/src/types/converter.ts deleted file mode 100644 index 9b00f25..0000000 --- a/src/types/converter.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { SUPPORTED_FLOW, SUPPORTED_PROCESS } from '../lib/converter/helper/constants'; -import { ProcessMetadataValue } from './metadata/processMetadataValue'; -import { Variable, Decision, ActionCall } from './metadata/flow'; -import { RecordUpdate, RecordCreate, RecordLookup } from './metadata/flowRecordAction'; - -export interface IteratableFlow { - processType: string; - label: string; - description: string; - startElementReference?: string; - variables: Array; - processMetadataValues: Array; - formulas: any; - decisions: Array; - assignments?: any; - actionCalls?: Array; - recordUpdates?: Array; - recordCreates?: Array; - recordLookups?: Array; - recordDeletes?: any; - loops?: any; - waits: any; - start?: any; -} - -export interface ReadableProcess { - processType: string; - name: string; - label: string; - description?: string; - triggerType?: string; // only in trigger-based process - objectType: string; - eventType?: string; // only in platform event process - eventMatchingConditions?: Array; // only in platform event process - actionGroups: Array; -} - -export interface ReadableFlow { - processType: string; - name: string; - label: string; - description?: string; - start: ReadableStart; - elements?: Array; -} - -export interface ReadableStart { - triggerType: string; - recordTriggerType?: string; // update, create, both - object?: string; - schedule?: { - startDate: string; - startTime: string; - frequency: string; - }; - context?: 'BEFORE_SAVE' | 'AFTER_SAVE'; -} - -export interface ReadableFlowElement { - type: string; - name: string; - label: string; - description?: string; -} - -export interface ReadableAssignment extends ReadableFlowElement { - assignments: Array; -} - -export interface ReadableAssignmentItem { - reference: string; - operator: string; - value: string; -} - -export interface ReadableLoop extends ReadableFlowElement { - elements: Array; -} - -export interface ReadableFlowDecision extends ReadableFlowElement { - routes: Array; -} - -export interface ReadableFlowDecisionRoute { - name: string; - label: string; - rule?: Array; - elements: Array; -} - -export interface ReadableActionGroup { - decision: ReadableDecision; - actions?: Array; - scheduledActionSections?: Array; - evaluatesNext?: boolean; -} - -// TODO: Rename to ReadableProcessDecision -export interface ReadableDecision extends ReadableFlowElement { - criteria: string; - conditionLogic: string; - conditions: Array; - formulaExpression?: string; -} - -export interface ReadableCondition { - field: string; - operator: string; - type: string; - value: string; -} - -export interface ReadableActionItem { - label: string; - type: string; - details?: Array; - conditions?: Array; - params?: Array; -} - -export interface ReadableActionItemParameter { - field: string; - type: string; - value: string; -} - -export interface ReadableScheduledActionSection { - summary: ReadableWaitEventSummary; - actions?: Array; -} - -export interface ReadableWaitEventSummary { - offset: number; - unit: string; - isAfter: boolean; - field?: string; -} - -export interface ReadableRecordLookup extends ReadableFlowElement { - object: string; - filterCondition: 'NONE' | 'MEET_ALL_CONDITIONS'; - filters?: Array; - sortBy: { - order: 'NONE' | 'ASC' | 'DSC'; - field: string; - }; - numberOfRecords: 'ONLY_FIRST' | 'ALL'; - output: { - method: 'ALL_FIELDS' | 'CHOOSE_FIELDS' | 'CHOOSE_AND_ASSIGN_FIELDS'; - fields?: Array; - assignments?: Array; - }; -} - -export interface OutputAssignment { - field: string; - reference: string; -} - -export interface ReadableFlowRecordLookupFilter { - field: string; - operator: string; - value: string; -} - -export function implementsReadableFlow(arg: any): arg is ReadableFlow { - return SUPPORTED_FLOW.includes(arg.processType); -} - -export function implementsReadableProcess(arg: any): arg is ReadableProcess { - return SUPPORTED_PROCESS.includes(arg.processType); -} - -export type ReadableFlowBuilderItem = ReadableAssignment | ReadableDecision | ReadableLoop | ReadableRecordLookup; // | ReadableRecordCreate | ReadableRecordUpdate | ReadableRecordDelete ; diff --git a/src/types/converter/flow.ts b/src/types/converter/flow.ts new file mode 100644 index 0000000..ba15342 --- /dev/null +++ b/src/types/converter/flow.ts @@ -0,0 +1,98 @@ +import { SUPPORTED_FLOW } from '../../lib/converter/helper/constants'; +// WIP: import { RecordUpdate, RecordCreate, RecordLookup } from '../metadata/flowRecordAction'; + +/** + * Container for flow + */ +export interface ReadableFlow { + processType: string; + name: string; + label: string; + description?: string; + start: ReadableStart; + elements?: Array; +} + +export interface ReadableStart { + triggerType: string; + recordTriggerType?: string; // update, create, both + object?: string; + schedule?: { + startDate: string; + startTime: string; + frequency: string; + }; + context?: 'BEFORE_SAVE' | 'AFTER_SAVE'; +} + +export interface ReadableFlowElement { + type: string; + name: string; + label: string; + description?: string; +} + +export interface ReadableAssignment extends ReadableFlowElement { + assignments: Array; +} + +export interface ReadableAssignmentItem { + reference: string; + operator: string; + value: string; +} + +export interface ReadableLoop extends ReadableFlowElement { + elements: Array; +} + +export interface ReadableFlowDecision extends ReadableFlowElement { + routes: Array; +} + +export interface ReadableFlowDecisionRoute { + name: string; + label: string; + rule?: Array; + elements: Array; +} + +export interface ReadableCondition { + field: string; + operator: string; + type: string; + value: string; +} + +export interface ReadableRecordLookup extends ReadableFlowElement { + object: string; + filterCondition: 'NONE' | 'MEET_ALL_CONDITIONS'; + filters?: Array; + sortBy: { + order: 'NONE' | 'ASC' | 'DSC'; + field: string; + }; + numberOfRecords: 'ONLY_FIRST' | 'ALL'; + output: { + method: 'ALL_FIELDS' | 'CHOOSE_FIELDS' | 'CHOOSE_AND_ASSIGN_FIELDS'; + fields?: Array; + assignments?: Array; + }; +} + +export interface OutputAssignment { + field: string; + reference: string; +} + +export interface ReadableFlowRecordLookupFilter { + field: string; + operator: string; + value: string; +} + +export function implementsReadableFlow(arg: any): arg is ReadableFlow { + return SUPPORTED_FLOW.includes(arg.processType); +} + +export type ReadableFlowBuilderItem = ReadableAssignment | ReadableFlowDecision | ReadableLoop | ReadableRecordLookup; // | ReadableRecordCreate | ReadableRecordUpdate | ReadableRecordDelete ; diff --git a/src/types/converter/main.ts b/src/types/converter/main.ts new file mode 100644 index 0000000..af3c37c --- /dev/null +++ b/src/types/converter/main.ts @@ -0,0 +1,37 @@ +import { ProcessMetadataValue } from '../metadata/processMetadataValue'; +import { Variable, Decision, ActionCall } from '../metadata/flow'; +import { RecordUpdate, RecordCreate, RecordLookup, RecordDelete } from '../metadata/flowRecordAction'; + +/** + * Flow which has array parameters to find elements using prototype.array methods. + * (explicit array option is too redundant for a complex flow) + */ +export interface IteratableFlow { + processType: string; + label: string; + description: string; + startElementReference?: string; + variables: Array; + processMetadataValues: Array; + formulas: any; + decisions: Array; + assignments?: any; + actionCalls?: Array; + recordUpdates?: Array; + recordCreates?: Array; + recordLookups?: Array; + recordDeletes?: Array; + loops?: any; + waits: any; + start?: any; +} + +/** + * Base flow element + */ +export interface ReadableFlowElement { + type: string; + name: string; + label: string; + description?: string; +} diff --git a/src/types/converter/process.ts b/src/types/converter/process.ts new file mode 100644 index 0000000..8d25c55 --- /dev/null +++ b/src/types/converter/process.ts @@ -0,0 +1,65 @@ +import { SUPPORTED_PROCESS } from '../../lib/converter/helper/constants'; + +export interface ReadableProcess { + processType: string; + name: string; + label: string; + description?: string; + triggerType?: string; // only in trigger-based process + objectType: string; + eventType?: string; // only in platform event process + eventMatchingConditions?: Array; // only in platform event process + actionGroups: Array; +} + +export function implementsReadableProcess(arg: any): arg is ReadableProcess { + return SUPPORTED_PROCESS.includes(arg.processType); +} + +export interface ReadableActionGroup { + decision: ReadableProcessDecision; + actions?: Array; + scheduledActionSections?: Array; + evaluatesNext?: boolean; +} + +export interface ReadableProcessDecision { + label: string; + criteria: string; + conditionLogic: string; + conditions: Array; + formulaExpression?: string; +} + +export interface ReadableCondition { + field: string; + operator: string; + type: string; + value: string; +} + +export interface ReadableActionItem { + label: string; + type: string; + details?: Array; + conditions?: Array; + params?: Array; +} + +export interface ReadableActionItemParameter { + field: string; + type: string; + value: string; +} + +export interface ReadableScheduledActionSection { + summary: ReadableWaitEventSummary; + actions?: Array; +} + +export interface ReadableWaitEventSummary { + offset: number; + unit: string; + isAfter: boolean; + field?: string; +} diff --git a/src/types/metadata/flow.ts b/src/types/metadata/flow.ts index a43a391..e9da89f 100644 --- a/src/types/metadata/flow.ts +++ b/src/types/metadata/flow.ts @@ -42,7 +42,7 @@ export function implementsActionCall(arg: any): arg is ActionCall { } export interface Decision extends Node { - processMetadataValues?: ProcessMetadataValue | Array; + processMetadataValues?: ProcessMetadataValue; rules: any; defaultConnector: any; defaultConnectorLabel: string; From 5a268b1eb50eef83222669d42ea57b220efe8a08 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Fri, 7 Aug 2020 12:18:29 +0900 Subject: [PATCH 16/22] Use constant class --- src/lib/util/flowUtils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/util/flowUtils.ts b/src/lib/util/flowUtils.ts index 6f4cb58..816e56a 100644 --- a/src/lib/util/flowUtils.ts +++ b/src/lib/util/flowUtils.ts @@ -1,7 +1,5 @@ import { Flow } from '../../types/metadata/flow'; - -export const SUPPORTED_PROCESS = ['Workflow', 'CustomEvent', 'InvocableProcess']; -export const SUPPORTED_FLOW = ['AutoLaunchedFlow']; +import { SUPPORTED_PROCESS, SUPPORTED_FLOW } from '../converter/helper/constants'; export function isSupported(flow: Flow) { return ( From 63579d7a07e43d85d8a9db90541545b8f3634c15 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Fri, 7 Aug 2020 18:41:14 +0900 Subject: [PATCH 17/22] Move --- test/{lib => data}/testFlow.json | 48 ++++++++++++++++++++----- test/lib/converter/readableFlow.test.ts | 0 test/lib/docx/docxBuilder.test.ts | 2 +- test/lib/json/jsonBuilder.test.ts | 2 +- test/lib/pdf/pdfBuilder.test.ts | 2 +- test/lib/{ => util}/arrayUtils.test.ts | 2 +- test/lib/{ => util}/flowUtils.test.ts | 6 ++-- test/lib/{ => util}/stringUtils.test.ts | 2 +- 8 files changed, 47 insertions(+), 17 deletions(-) rename test/{lib => data}/testFlow.json (92%) create mode 100644 test/lib/converter/readableFlow.test.ts rename test/lib/{ => util}/arrayUtils.test.ts (81%) rename test/lib/{ => util}/flowUtils.test.ts (56%) rename test/lib/{ => util}/stringUtils.test.ts (83%) diff --git a/test/lib/testFlow.json b/test/data/testFlow.json similarity index 92% rename from test/lib/testFlow.json rename to test/data/testFlow.json index 2722de6..90198c2 100644 --- a/test/lib/testFlow.json +++ b/test/data/testFlow.json @@ -2,7 +2,10 @@ "$": { "xmlns": "http://soap.sforce.com/2006/04/metadata" }, "actionCalls": [ { - "processMetadataValues": { "name": "emailAlertSelection", "value": { "stringValue": "Email_to_Financial_Group" } }, + "processMetadataValues": { + "name": "emailAlertSelection", + "value": { "stringValue": "Email_to_Financial_Group" } + }, "name": "myRule_1_A1", "label": "Email to Financial Group", "locationX": "100", @@ -24,10 +27,16 @@ "actionName": "submit", "actionType": "submit", "connector": { "targetReference": "myDecision5" }, - "inputParameters": [{ "name": "objectId", "value": { "elementReference": "myVariable_current.Id" } }, { "name": "comment" }] + "inputParameters": [ + { "name": "objectId", "value": { "elementReference": "myVariable_current.Id" } }, + { "name": "comment" } + ] }, { - "processMetadataValues": { "name": "emailAlertSelection", "value": { "stringValue": "Email_to_Financial_Group" } }, + "processMetadataValues": { + "name": "emailAlertSelection", + "value": { "stringValue": "Email_to_Financial_Group" } + }, "name": "myRule_6_A2", "label": "Email to Financial Group Again", "locationX": "600", @@ -43,7 +52,11 @@ "label": "myWaitAssignment_myWait_myRule_1", "locationX": "0", "locationY": "0", - "assignmentItems": { "assignToReference": "cancelWaits", "operator": "Add", "value": { "stringValue": "myWait_myRule_1" } }, + "assignmentItems": { + "assignToReference": "cancelWaits", + "operator": "Add", + "value": { "stringValue": "myWait_myRule_1" } + }, "connector": { "targetReference": "myDecision" } }, { @@ -123,7 +136,11 @@ "rules": { "name": "myRule_6", "conditionLogic": "and", - "conditions": { "leftValueReference": "formula_myRule_6", "operator": "EqualTo", "rightValue": { "booleanValue": "true" } }, + "conditions": { + "leftValueReference": "formula_myRule_6", + "operator": "EqualTo", + "rightValue": { "booleanValue": "true" } + }, "connector": { "targetReference": "myRule_6_A1" }, "label": "closed Won?" } @@ -138,7 +155,11 @@ "rules": { "name": "myRule_9", "conditionLogic": "and", - "conditions": { "leftValueReference": "formula_myRule_9", "operator": "EqualTo", "rightValue": { "booleanValue": "true" } }, + "conditions": { + "leftValueReference": "formula_myRule_9", + "operator": "EqualTo", + "rightValue": { "booleanValue": "true" } + }, "label": "All OK" } }, @@ -147,7 +168,9 @@ "label": "myPostWaitDecision_myWaitEvent_myWait_myRule_1_event_0", "locationX": "0", "locationY": "0", - "defaultConnector": { "targetReference": "myWaitEvent_myWait_myRule_1_event_0_postWaitExecutionAssignment" }, + "defaultConnector": { + "targetReference": "myWaitEvent_myWait_myRule_1_event_0_postWaitExecutionAssignment" + }, "defaultConnectorLabel": "default", "rules": { "name": "myPostWaitRule_myWaitEvent_myWait_myRule_1_event_0", @@ -197,7 +220,11 @@ { "name": "myRule_1_pmetnullrule", "conditionLogic": "or", - "conditions": { "leftValueReference": "myVariable_old", "operator": "IsNull", "rightValue": { "booleanValue": "true" } }, + "conditions": { + "leftValueReference": "myVariable_old", + "operator": "IsNull", + "rightValue": { "booleanValue": "true" } + }, "connector": { "targetReference": "myRule_1_A1" }, "label": "Previously Met - Null" }, @@ -229,7 +256,10 @@ "expression": "TODAY() + 7" }, { - "processMetadataValues": { "name": "originalFormula", "value": { "stringValue": "180 + [Opportunity].CloseDate " } }, + "processMetadataValues": { + "name": "originalFormula", + "value": { "stringValue": "180 + [Opportunity].CloseDate " } + }, "name": "formula_7_myRule_6_A1_6831756980", "dataType": "Date", "expression": "180 + {!myVariable_current.CloseDate}" diff --git a/test/lib/converter/readableFlow.test.ts b/test/lib/converter/readableFlow.test.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/docx/docxBuilder.test.ts b/test/lib/docx/docxBuilder.test.ts index 46356fc..18b253b 100644 --- a/test/lib/docx/docxBuilder.test.ts +++ b/test/lib/docx/docxBuilder.test.ts @@ -1,7 +1,7 @@ import { Document } from 'docx'; import { Flow } from '../../../src/types/metadata/flow'; -import testFlow from '../testFlow.json'; +import testFlow from '../../data/testFlow.json'; import DocxBuilder from '../../../src/lib/docx/docxBuilder'; import { ReadableProcessMetadataConverter } from '../../../src/lib/converter/metadataConverter'; diff --git a/test/lib/json/jsonBuilder.test.ts b/test/lib/json/jsonBuilder.test.ts index b0fb23b..ad92dd4 100644 --- a/test/lib/json/jsonBuilder.test.ts +++ b/test/lib/json/jsonBuilder.test.ts @@ -1,6 +1,6 @@ import { Flow } from '../../../src/types/metadata/flow'; -import testFlow from '../testFlow.json'; +import testFlow from '../../data/testFlow.json'; import JsonBuilder from '../../../src/lib/json/jsonBuilder'; import { ReadableProcessMetadataConverter } from '../../../src/lib/converter/metadataConverter'; diff --git a/test/lib/pdf/pdfBuilder.test.ts b/test/lib/pdf/pdfBuilder.test.ts index c133e71..6cead6b 100644 --- a/test/lib/pdf/pdfBuilder.test.ts +++ b/test/lib/pdf/pdfBuilder.test.ts @@ -1,6 +1,6 @@ import { Flow } from '../../../src/types/metadata/flow'; -import testFlow from '../testFlow.json'; +import testFlow from '../../data/testFlow.json'; import PdfBuilder from '../../../src/lib/pdf/pdfBuilder'; import { ReadableProcessMetadataConverter } from '../../../src/lib/converter/metadataConverter'; diff --git a/test/lib/arrayUtils.test.ts b/test/lib/util/arrayUtils.test.ts similarity index 81% rename from test/lib/arrayUtils.test.ts rename to test/lib/util/arrayUtils.test.ts index 0812736..06d5928 100644 --- a/test/lib/arrayUtils.test.ts +++ b/test/lib/util/arrayUtils.test.ts @@ -1,4 +1,4 @@ -import { toArray } from '../../src/lib/util/arrayUtils'; +import { toArray } from '../../../src/lib/util/arrayUtils'; describe('lib/arrayUtils', () => { it('toArray()', () => { diff --git a/test/lib/flowUtils.test.ts b/test/lib/util/flowUtils.test.ts similarity index 56% rename from test/lib/flowUtils.test.ts rename to test/lib/util/flowUtils.test.ts index 213f5b6..61f5b5c 100644 --- a/test/lib/flowUtils.test.ts +++ b/test/lib/util/flowUtils.test.ts @@ -1,6 +1,6 @@ -import testFlow from './testFlow.json'; -import { Flow } from '../../src/types/metadata/flow'; -import { isSupported, isProcess } from '../../src/lib/util/flowUtils'; +import testFlow from '../../data/testFlow.json'; +import { Flow } from '../../../src/types/metadata/flow'; +import { isSupported, isProcess } from '../../../src/lib/util/flowUtils'; describe('lib/flowUtils', () => { it('isSupported', () => { diff --git a/test/lib/stringUtils.test.ts b/test/lib/util/stringUtils.test.ts similarity index 83% rename from test/lib/stringUtils.test.ts rename to test/lib/util/stringUtils.test.ts index 03d2c8f..ea5124d 100644 --- a/test/lib/stringUtils.test.ts +++ b/test/lib/util/stringUtils.test.ts @@ -1,4 +1,4 @@ -import { toUpperSnakeCase } from '../../src/lib/util/stringUtils'; +import { toUpperSnakeCase } from '../../../src/lib/util/stringUtils'; describe('lib/utils/stringUtils', () => { it('toUpperSnakeCase()', () => { From 1b9ad17b530da1aac7d857b746bf66680aa9647a Mon Sep 17 00:00:00 2001 From: shunkosa Date: Sat, 8 Aug 2020 21:36:30 +0900 Subject: [PATCH 18/22] Add record lookup --- src/lib/converter/helper/readableFlow.ts | 49 +++++++++++++++++------- src/types/converter/flow.ts | 9 +---- src/types/metadata/flowRecordAction.ts | 8 ++++ 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/lib/converter/helper/readableFlow.ts b/src/lib/converter/helper/readableFlow.ts index df0f571..045e6fb 100644 --- a/src/lib/converter/helper/readableFlow.ts +++ b/src/lib/converter/helper/readableFlow.ts @@ -7,6 +7,7 @@ import { ReadableLoop, ReadableFlowDecisionRoute, ReadableFlowDecision, + ReadableRecordLookup, } from '../../../types/converter/flow'; import { Flow, @@ -21,6 +22,7 @@ import { } from '../../../types/metadata/flow'; import { toArray } from '../../util/arrayUtils'; import ReadableFlowStartElement from './readableFlowStart'; +import { RecordLookup, implementsRecordLookup } from '../../../types/metadata/flowRecordAction'; export default class ReadableFlowMetadata extends ReadableMetadata { flowBuilderItems: ReadonlyArray; @@ -69,16 +71,8 @@ export default class ReadableFlowMetadata extends ReadableMetadata { } const nextElement = this.flowBuilderItems.find(e => e.name === targetReference); if (nextElement) { - /* const readableNextElement = this.convertToReadableFlowElement(nextElement); - if (readableNextElement) { - currentElements.push(readableNextElement); - } - if (implementsAssignment(nextElement)) { - const nextReference = nextElement.connector ? nextElement.connector.targetReference : undefined; - this.getReadableFlowElements(currentElements, nextReference); - } - */ this.visitedItemNameSet.add(nextElement.name); + // Get the details of element and find next. Elements inside loop and decision results are traced in each convert method. let nextReference; if (implementsLoop(nextElement)) { nextReference = nextElement.noMoreValuesConnector @@ -86,13 +80,11 @@ export default class ReadableFlowMetadata extends ReadableMetadata { : undefined; currentElements.push(this.convertToReadableLoop(nextElement)); } else if (implementsDecision(nextElement)) { - // nextReference = nextElement.defaultConnector ? nextElement.defaultConnector.targetReference : undefined; currentElements.push(this.convertToReadableDecision(nextElement)); } else { nextReference = nextElement.connector ? nextElement.connector.targetReference : undefined; - currentElements.push({ label: nextElement.label, name: nextElement.name }); + currentElements.push(this.convertToReadableFlowElement(nextElement)); } - this.getReadableFlowElements(currentElements, nextReference); } return currentElements; @@ -102,8 +94,8 @@ export default class ReadableFlowMetadata extends ReadableMetadata { if (implementsAssignment(flowBuilderItem)) { return this.convertToReadableAssingment(flowBuilderItem); } - if (implementsLoop(flowBuilderItem)) { - return this.convertToReadableLoop(flowBuilderItem); + if (implementsRecordLookup(flowBuilderItem)) { + return this.convertToReadableRecordLookup(flowBuilderItem); } return undefined; } @@ -187,4 +179,33 @@ export default class ReadableFlowMetadata extends ReadableMetadata { elements, }; } + + private convertToReadableRecordLookup(lookup: RecordLookup): ReadableRecordLookup { + return { + type: 'lookup', + name: lookup.name, + label: lookup.label, + object: lookup.object, + filterCondition: lookup.filters ? 'MEET_ALL_CONDITIONS' : 'NONE', + filters: this.getReadableFlowRecordLookupFilters(lookup.filters), + sortBy: { + order: lookup.sortOrder ? lookup.sortOrder.toUpperCase() : 'NONE', + }, + numberOfRecords: lookup.getFirstRecordOnly ? 'ONLY_FIRST' : 'ALL', + output: { + method: lookup.storeOutputAutomatically ? 'ALL_FIELDS' : undefined, + }, + }; + } + + getReadableFlowRecordLookupFilters = filters => { + if (!filters) { + return undefined; + } + return filters.map(f => ({ + field: f.field, + operator: f.operator, + value: this.resolveValue(f.value), + })); + }; } diff --git a/src/types/converter/flow.ts b/src/types/converter/flow.ts index ba15342..3700d3d 100644 --- a/src/types/converter/flow.ts +++ b/src/types/converter/flow.ts @@ -69,8 +69,8 @@ export interface ReadableRecordLookup extends ReadableFlowElement { filterCondition: 'NONE' | 'MEET_ALL_CONDITIONS'; filters?: Array; sortBy: { - order: 'NONE' | 'ASC' | 'DSC'; - field: string; + order: string; + field?: string; }; numberOfRecords: 'ONLY_FIRST' | 'ALL'; output: { @@ -80,11 +80,6 @@ export interface ReadableRecordLookup extends ReadableFlowElement { }; } -export interface OutputAssignment { - field: string; - reference: string; -} - export interface ReadableFlowRecordLookupFilter { field: string; operator: string; diff --git a/src/types/metadata/flowRecordAction.ts b/src/types/metadata/flowRecordAction.ts index 78e8cae..9494aca 100644 --- a/src/types/metadata/flowRecordAction.ts +++ b/src/types/metadata/flowRecordAction.ts @@ -36,6 +36,10 @@ export interface RecordUpdate extends Node { export interface RecordLookup extends Node { processMetadataValues?: any; + object: string; + getFirstRecordOnly?: boolean; + storeOutputAutomatically?: boolean; + sortOrder?: 'Asc' | 'Dsc'; filters?: RecordFilter | RecordFilter[]; connector: { targetReference: string; @@ -63,3 +67,7 @@ export function implementsRecordCreate(arg: any): arg is RecordCreate { export function implementsRecordUpdate(arg: any): arg is RecordUpdate { return arg.actionType === 'recordUpdate'; } + +export function implementsRecordLookup(arg: any): arg is RecordLookup { + return arg.storeOutputAutomatically || arg.outputReference || arg.assignNullValuesIfNoRecordFound; +} From 394cd931bb7c7f8c92866d109c8d8e57c46d26f0 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Sat, 8 Aug 2020 23:03:35 +0900 Subject: [PATCH 19/22] Add test for flow start element --- test/data/flow/after_save.json | 28 +++++++++++++++ test/data/flow/before_save_create.json | 28 +++++++++++++++ .../flow/before_save_create_and_update.json | 28 +++++++++++++++ test/data/flow/before_save_update.json | 28 +++++++++++++++ test/lib/converter/readableFlow.test.ts | 0 test/lib/converter/readableFlowStart.test.ts | 34 +++++++++++++++++++ 6 files changed, 146 insertions(+) create mode 100644 test/data/flow/after_save.json create mode 100644 test/data/flow/before_save_create.json create mode 100644 test/data/flow/before_save_create_and_update.json create mode 100644 test/data/flow/before_save_update.json delete mode 100644 test/lib/converter/readableFlow.test.ts create mode 100644 test/lib/converter/readableFlowStart.test.ts diff --git a/test/data/flow/after_save.json b/test/data/flow/after_save.json new file mode 100644 index 0000000..8ce3832 --- /dev/null +++ b/test/data/flow/after_save.json @@ -0,0 +1,28 @@ +{ + "fullName": "Start_Element", + "interviewLabel": "Start_Element {!$Flow.CurrentDateTime}", + "label": "Start_Element", + "processMetadataValues": [ + { + "name": "BuilderType", + "value": { + "stringValue": "LightningFlowBuilder" + } + }, + { + "name": "OriginBuilderType", + "value": { + "stringValue": "LightningFlowBuilder" + } + } + ], + "processType": "AutoLaunchedFlow", + "start": { + "locationX": "50", + "locationY": "50", + "object": "Case", + "recordTriggerType": "Update", + "triggerType": "RecordAfterSave" + }, + "status": "InvalidDraft" +} diff --git a/test/data/flow/before_save_create.json b/test/data/flow/before_save_create.json new file mode 100644 index 0000000..422e163 --- /dev/null +++ b/test/data/flow/before_save_create.json @@ -0,0 +1,28 @@ +{ + "fullName": "Start_Element", + "interviewLabel": "Start_Element {!$Flow.CurrentDateTime}", + "label": "Start_Element", + "processMetadataValues": [ + { + "name": "BuilderType", + "value": { + "stringValue": "LightningFlowBuilder" + } + }, + { + "name": "OriginBuilderType", + "value": { + "stringValue": "LightningFlowBuilder" + } + } + ], + "processType": "AutoLaunchedFlow", + "start": { + "locationX": "50", + "locationY": "50", + "object": "Case", + "recordTriggerType": "Create", + "triggerType": "RecordBeforeSave" + }, + "status": "InvalidDraft" +} diff --git a/test/data/flow/before_save_create_and_update.json b/test/data/flow/before_save_create_and_update.json new file mode 100644 index 0000000..10576f0 --- /dev/null +++ b/test/data/flow/before_save_create_and_update.json @@ -0,0 +1,28 @@ +{ + "fullName": "Start_Element", + "interviewLabel": "Start_Element {!$Flow.CurrentDateTime}", + "label": "Start_Element", + "processMetadataValues": [ + { + "name": "BuilderType", + "value": { + "stringValue": "LightningFlowBuilder" + } + }, + { + "name": "OriginBuilderType", + "value": { + "stringValue": "LightningFlowBuilder" + } + } + ], + "processType": "AutoLaunchedFlow", + "start": { + "locationX": "50", + "locationY": "50", + "object": "Case", + "recordTriggerType": "CreateAndUpdate", + "triggerType": "RecordBeforeSave" + }, + "status": "InvalidDraft" +} diff --git a/test/data/flow/before_save_update.json b/test/data/flow/before_save_update.json new file mode 100644 index 0000000..afa74c2 --- /dev/null +++ b/test/data/flow/before_save_update.json @@ -0,0 +1,28 @@ +{ + "fullName": "Start_Element", + "interviewLabel": "Start_Element {!$Flow.CurrentDateTime}", + "label": "Start_Element", + "processMetadataValues": [ + { + "name": "BuilderType", + "value": { + "stringValue": "LightningFlowBuilder" + } + }, + { + "name": "OriginBuilderType", + "value": { + "stringValue": "LightningFlowBuilder" + } + } + ], + "processType": "AutoLaunchedFlow", + "start": { + "locationX": "50", + "locationY": "50", + "object": "Case", + "recordTriggerType": "Update", + "triggerType": "RecordBeforeSave" + }, + "status": "InvalidDraft" +} diff --git a/test/lib/converter/readableFlow.test.ts b/test/lib/converter/readableFlow.test.ts deleted file mode 100644 index e69de29..0000000 diff --git a/test/lib/converter/readableFlowStart.test.ts b/test/lib/converter/readableFlowStart.test.ts new file mode 100644 index 0000000..7984b73 --- /dev/null +++ b/test/lib/converter/readableFlowStart.test.ts @@ -0,0 +1,34 @@ +import beforeSaveCreate from '../../data/flow/before_save_create.json'; +import beforeSaveUpdate from '../../data/flow/before_save_update.json'; +import beforeSaveCreateAndUpdate from '../../data/flow/before_save_create_and_update.json'; +import afterSave from '../../data/flow/after_save.json'; +import ReadableFlowStartElement from '../../../src/lib/converter/helper/readableFlowStart'; + +describe('lib/converter/readableFlowStart', () => { + it('Before save flow (create)', () => { + const start = new ReadableFlowStartElement(beforeSaveCreate).getReadableElement(); + expect(start.triggerType).toBe('FLOW_TRIGGER_RECORD'); + expect(start.recordTriggerType).toBe('FROW_TRIGGER_RECORD_CREATE_ONLY'); + expect(start.context).toBe('BEFORE_SAVE'); + }); + + it('Before save flow (update)', () => { + const start = new ReadableFlowStartElement(beforeSaveUpdate).getReadableElement(); + expect(start.triggerType).toBe('FLOW_TRIGGER_RECORD'); + expect(start.recordTriggerType).toBe('FROW_TRIGGER_RECORD_UPDATE_ONLY'); + expect(start.context).toBe('BEFORE_SAVE'); + }); + + it('Before save flow (create and update)', () => { + const start = new ReadableFlowStartElement(beforeSaveCreateAndUpdate).getReadableElement(); + expect(start.triggerType).toBe('FLOW_TRIGGER_RECORD'); + expect(start.recordTriggerType).toBe('FROW_TRIGGER_RECORD_CREATE_OR_UPDATE'); + expect(start.context).toBe('BEFORE_SAVE'); + }); + + it('After save flow', () => { + const start = new ReadableFlowStartElement(afterSave).getReadableElement(); + expect(start.triggerType).toBe('FLOW_TRIGGER_RECORD'); + expect(start.context).toBe('AFTER_SAVE'); + }); +}); From 0a4865fa304e138b09561c8ba4a17eba6726ca10 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Sat, 8 Aug 2020 23:03:44 +0900 Subject: [PATCH 20/22] Fix --- src/lib/converter/helper/readableFlow.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/converter/helper/readableFlow.ts b/src/lib/converter/helper/readableFlow.ts index 045e6fb..60de8bb 100644 --- a/src/lib/converter/helper/readableFlow.ts +++ b/src/lib/converter/helper/readableFlow.ts @@ -202,7 +202,7 @@ export default class ReadableFlowMetadata extends ReadableMetadata { if (!filters) { return undefined; } - return filters.map(f => ({ + return toArray(filters).map(f => ({ field: f.field, operator: f.operator, value: this.resolveValue(f.value), From 981fb5f36c8940816cfa36f8043f610bd1efdb1f Mon Sep 17 00:00:00 2001 From: shunkosa Date: Sat, 8 Aug 2020 23:04:21 +0900 Subject: [PATCH 21/22] Bypass variable replacement for flow builder --- src/lib/converter/readableMetadata.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/converter/readableMetadata.ts b/src/lib/converter/readableMetadata.ts index 71e7218..7938a70 100644 --- a/src/lib/converter/readableMetadata.ts +++ b/src/lib/converter/readableMetadata.ts @@ -4,15 +4,19 @@ import { RecordLookup } from '../../types/metadata/flowRecordAction'; import { implementsProcessMetadataValue, ProcessMetadataValue } from '../../types/metadata/processMetadataValue'; import { unescapeHtml } from '../util/stringUtils'; import convertToIteratableMetadata from './iteratableMetadata'; +import { isProcess } from '../util/flowUtils'; export default class ReadableMetadata { protected readonly flow: IteratableFlow; protected readonly name: string; + protected readonly isProcess: boolean; + constructor(flow: Flow, name: string) { this.flow = convertToIteratableMetadata(flow); this.name = name; + this.isProcess = isProcess(flow); } /** @@ -107,6 +111,7 @@ export default class ReadableMetadata { const key = Object.keys(value)[0]; // stringValue or elementReference if (key === 'elementReference') { if (!value[key].includes('.')) { + // reference to formula return this.getFormulaExpression(value[key]); } return this.replaceVariableNameToObjectName(value[key]); @@ -114,8 +119,9 @@ export default class ReadableMetadata { return value[key]; }; + // for process builder private replaceVariableNameToObjectName(string) { - if (!string.includes('.')) { + if (!string.includes('.') || !this.isProcess) { return string; } const variableName = string.split('.')[0]; From 308b31f62a760a910dd2af716633400efdb8ced6 Mon Sep 17 00:00:00 2001 From: shunkosa Date: Sat, 8 Aug 2020 23:09:14 +0900 Subject: [PATCH 22/22] Use constant for api version --- src/commands/flowdoc/docx/generate.ts | 2 +- src/commands/flowdoc/json/display.ts | 2 +- src/commands/flowdoc/json/generate.ts | 2 +- src/commands/flowdoc/pdf/generate.ts | 2 +- src/lib/converter/helper/constants.ts | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/commands/flowdoc/docx/generate.ts b/src/commands/flowdoc/docx/generate.ts index e25de38..a8ea996 100644 --- a/src/commands/flowdoc/docx/generate.ts +++ b/src/commands/flowdoc/docx/generate.ts @@ -10,11 +10,11 @@ import { } from '../../../lib/converter/metadataConverter'; import { isSupported, isProcess } from '../../../lib/util/flowUtils'; import DocxBuilder from '../../../lib/docx/docxBuilder'; +import { API_VERSION } from '../../../lib/converter/helper/constants'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('sfdx-flowdoc-plugin', 'messages'); -const API_VERSION = '48.0'; export default class Generate extends SfdxCommand { public static description = messages.getMessage('commandDescription'); diff --git a/src/commands/flowdoc/json/display.ts b/src/commands/flowdoc/json/display.ts index c1fe1cd..77ccf60 100644 --- a/src/commands/flowdoc/json/display.ts +++ b/src/commands/flowdoc/json/display.ts @@ -8,11 +8,11 @@ import { ReadableProcessMetadataConverter, ReadableFlowMetadataConverter, } from '../../../lib/converter/metadataConverter'; +import { API_VERSION } from '../../../lib/converter/helper/constants'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('sfdx-flowdoc-plugin', 'messages'); -const API_VERSION = '48.0'; export default class Display extends SfdxCommand { public static description = messages.getMessage('commandDescription'); diff --git a/src/commands/flowdoc/json/generate.ts b/src/commands/flowdoc/json/generate.ts index b8da388..a6b4c08 100644 --- a/src/commands/flowdoc/json/generate.ts +++ b/src/commands/flowdoc/json/generate.ts @@ -9,11 +9,11 @@ import { ReadableProcessMetadataConverter, ReadableFlowMetadataConverter, } from '../../../lib/converter/metadataConverter'; +import { API_VERSION } from '../../../lib/converter/helper/constants'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('sfdx-flowdoc-plugin', 'messages'); -const API_VERSION = '48.0'; export default class Generate extends SfdxCommand { public static description = messages.getMessage('commandDescription'); diff --git a/src/commands/flowdoc/pdf/generate.ts b/src/commands/flowdoc/pdf/generate.ts index be453ca..5caa3db 100644 --- a/src/commands/flowdoc/pdf/generate.ts +++ b/src/commands/flowdoc/pdf/generate.ts @@ -9,13 +9,13 @@ import { ReadableFlowMetadataConverter, } from '../../../lib/converter/metadataConverter'; import { isSupported, isProcess } from '../../../lib/util/flowUtils'; +import { API_VERSION } from '../../../lib/converter/helper/constants'; const Pdf = require('pdfmake'); Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('sfdx-flowdoc-plugin', 'messages'); -const API_VERSION = '48.0'; export default class Generate extends SfdxCommand { public static description = messages.getMessage('commandDescription'); diff --git a/src/lib/converter/helper/constants.ts b/src/lib/converter/helper/constants.ts index 766634c..d462080 100644 --- a/src/lib/converter/helper/constants.ts +++ b/src/lib/converter/helper/constants.ts @@ -1,2 +1,3 @@ export const SUPPORTED_PROCESS = ['Workflow', 'CustomEvent', 'InvocableProcess']; export const SUPPORTED_FLOW = ['AutoLaunchedFlow']; +export const API_VERSION = '49.0';