sfdx-hardis
Version:
Swiss-army-knife Toolbox for Salesforce. Allows you to define a complete CD/CD Pipeline. Orchestrate base commands and assist users with interactive wizards
373 lines • 16.5 kB
JavaScript
import * as yaml from 'js-yaml';
import moment from 'moment';
import * as fs from 'fs';
import path from 'path';
const FIELDS_WITH_VALUES_TO_FORMAT = [
"actionType",
"fieldType",
"inputsOnNextNavToAssocScrn",
"processType",
"recordTriggerType",
"triggerType",
"regionContainerType",
"runInMode",
"type"
];
const FIELDS_WITH_VALUES_TO_FORMAT_ENUM = {
"status": {
"Draft": "⚠️ Draft",
"Inactive": "⚠️ Inactive",
"InvalidDraft": "⚠️ Invalid Draft"
}
};
const FIELDS_PREFERRED_ORDER_START = [
"type",
"object",
"processType",
"triggerType",
"recordTriggerType",
"label",
"status",
"actionType",
"actionName",
"dataType",
"objectType"
];
const FIELDS_PREFERRED_ORDER_END = [
"connector",
"nextNode",
"noMoreValuesConnector",
"conditionLogic",
"filterLogic",
];
const FIELDS_WITH_COLUMN_CENTERED = [
"dataType",
"objectType",
"operator",
"isCollection",
"isInput",
"isOutput",
"rightValue",
"startDate",
"startTime",
"value"
];
export function simplifyNode(flowNode) {
const nodeCopy = Object.assign({}, flowNode);
for (const nodeKey of Object.keys(nodeCopy)) {
if (["locationX", "locationY"].includes(nodeKey)) {
delete nodeCopy[nodeKey];
}
else if (nodeCopy[nodeKey] === null || nodeCopy[nodeKey] === undefined) {
delete nodeCopy[nodeKey];
}
}
return nodeCopy;
}
export function flowNodeToMarkdown(flowNodeIn, allProperties) {
const flowNode = Object.assign({}, flowNodeIn);
delete flowNode["name"];
const additionalTables = [];
// Properties that can be found on multiple flow node types
handleprocessMetadataValues(flowNode, allProperties);
handleInputParameters(flowNode, allProperties);
const conditionsTable = handleConditions(flowNode, allProperties);
additionalTables.push(conditionsTable);
const filterItemsTable = handleFilterItems(flowNode, allProperties);
additionalTables.push(filterItemsTable);
const inputAssignmentsTable = handleInputAssignments(flowNode, allProperties);
additionalTables.push(inputAssignmentsTable);
const assignmentItemsTable = handleAssignmentItems(flowNode, allProperties);
additionalTables.push(assignmentItemsTable);
const scheduledPathsTable = handleScheduledPaths(flowNode, allProperties);
additionalTables.push(scheduledPathsTable);
// Special case of decisions
if (flowNode.type === "decisions") {
const rules = getElementAsArray(flowNode, "rules");
delete flowNode.rules;
delete flowNode.rules2;
for (const rule of rules) {
const ruleNode = Object.assign({}, rule);
delete ruleNode.name;
delete ruleNode.label;
const ruleConditionsTable = handleConditions(ruleNode, allProperties);
const ruleTable = buildGenericMarkdownTable(ruleNode, ["allFields"], `#### Rule ${rule.name} (${rule.label})`, allProperties);
additionalTables.push(...[ruleTable, mdEndSection(ruleConditionsTable)]);
}
}
// Special case of transforms
else if (flowNode.type === "transforms") {
const transformValues = flowNode.transformValues || {};
const transformValueActions = getElementAsArray(transformValues, "transformValueActions");
delete flowNode.transformValues;
const transformsTable = buildCustomMarkdownTable(transformValueActions, ["transformType", "value", "outputFieldApiName"], "#### Transform actions", allProperties);
additionalTables.push(transformsTable);
}
else if (flowNode.type === "screens") {
handleFields(flowNode, allProperties, "", additionalTables);
}
else if (flowNode.type === "start") {
delete flowNode.type;
}
// Build final markdown for Node
let table = buildGenericMarkdownTable(flowNode, ["allFields"], "", allProperties);
for (const additionalTable of additionalTables) {
if (additionalTable !== "") {
table += additionalTable + "\n\n";
}
}
return mdEndSection(table);
}
function handleFields(flowNode, allProperties, parentField = "", additionalTables) {
const fields = getElementAsArray(flowNode, "fields");
delete flowNode.fields;
for (const field of fields) {
const fieldNode = Object.assign({}, field);
const fieldName = "" + (field.name || field.objectFieldReference);
delete fieldNode.name;
if (parentField) {
fieldNode.parentField = parentField;
allProperties.push(parentField);
}
handleInputParameters(fieldNode, allProperties);
const fieldsBefore = getElementAsArray(fieldNode, "fields");
delete fieldNode.fields;
const fieldTable = buildGenericMarkdownTable(fieldNode, ["allFields"], `#### ${fieldName}`, allProperties);
// Handle recursive loop
if (fieldsBefore) {
fieldNode.name = fieldName;
fieldNode.fields = fieldsBefore;
handleFields(fieldNode, allProperties, fieldName, additionalTables);
additionalTables.push(...[mdEndSection(fieldTable)]);
}
}
}
function handleConditions(ruleNode, allProperties) {
const conditions = getElementAsArray(ruleNode, "conditions");
if (conditions.length === 0) {
return "";
}
let id = 0;
const conditionsValues = conditions.map((item) => {
id++;
return {
conditionId: id,
leftValueReference: item.leftValueReference,
operator: stringifyOperator(item.operator),
rightValue: (item.operator === "IsNull" && item.rightValue === "false") ? "<!-- -->" : stringifyValue(item.rightValue, "", allProperties)
};
});
delete ruleNode.conditions;
/* let descriptiveLine = "";
if (ruleNode.conditionLogic) {
descriptiveLine += "\n\nConditions logic: **" + ruleNode.conditionLogic + "**\n\n";
delete ruleNode.conditionLogic;
} */
return buildCustomMarkdownTable(conditionsValues, ["conditionId", "leftValueReference", "operator", "rightValue"], "", allProperties);
}
function handleInputAssignments(flowNode, allProperties) {
const inputAssignmentsItems = getElementAsArray(flowNode, "inputAssignments");
if (inputAssignmentsItems.length === 0) {
return "";
}
const inputAssignmentsItemsValues = inputAssignmentsItems.map((item) => {
return {
field: item.field,
value: stringifyValue(item.value, item.field, allProperties)
};
});
delete flowNode.inputAssignments;
return buildCustomMarkdownTable(inputAssignmentsItemsValues, ["field", "value"], "#### Input Assignments", allProperties);
}
export function handleSchedule(flowNode, allProperties) {
const scheduleItems = getElementAsArray(flowNode, "schedule");
if (scheduleItems.length === 0) {
return "";
}
const scheduleItemsValues = scheduleItems.map((item) => {
const startDateFormatted = moment(item.startDate).format("ll");
const startTimeFormatted = item?.startTime?.endsWith("Z") ? item.startTime.slice(0, 5) : item.startTime;
return {
frequency: item.frequency,
startDate: !startDateFormatted.includes("Invalid") ? startDateFormatted : item.startDate,
startTime: !startTimeFormatted.includes("Invalid") ? startTimeFormatted : item.startTime,
};
});
delete flowNode.schedule;
return buildCustomMarkdownTable(scheduleItemsValues, ["frequency", "startDate", "startTime"], "#### Schedules", allProperties);
}
export function handleFilterItems(flowNode, allProperties) {
const filterItems = getElementAsArray(flowNode, "filters");
if (filterItems.length === 0) {
return "";
}
let id = 0;
const filterItemsValues = filterItems.map((item) => {
id++;
return {
filterId: id,
field: item.field,
operator: stringifyOperator(item.operator),
value: item.operator === "IsNull" ? "<!-- -->" : stringifyValue(item.value, item.field, allProperties)
};
});
delete flowNode.filters;
let descriptiveLine = "";
if (flowNode.filterLogic) {
descriptiveLine += " (logic: **" + flowNode.filterLogic + "**)";
delete flowNode.filterLogic;
}
return buildCustomMarkdownTable(filterItemsValues, ["filterId", "field", "operator", "value"], "#### Filters" + descriptiveLine, allProperties);
}
function handleAssignmentItems(flowNode, allProperties) {
const assignmentItems = getElementAsArray(flowNode, "assignmentItems");
if (assignmentItems.length === 0) {
return "";
}
const assignmentItemsValues = assignmentItems.map((item) => {
const value = item?.value?.elementReference || stringifyValue(item.value, "", allProperties);
return {
assignToReference: item.assignToReference,
operator: stringifyOperator(item.operator),
value: stringifyValue(value, "", allProperties)
};
});
delete flowNode.assignmentItems;
return buildCustomMarkdownTable(assignmentItemsValues, ["assignToReference", "operator", "value"], "#### Assignments", allProperties);
}
export function handleScheduledPaths(flowNode, allProperties) {
const scheduledPaths = getElementAsArray(flowNode, "scheduledPaths");
delete flowNode.scheduledPaths;
if (scheduledPaths.length === 0) {
return "";
}
return buildCustomMarkdownTable(scheduledPaths, ["label", "name", "offsetNumber", "offsetUnit", "recordField", "timeSource", "connector"], "#### Scheduled Paths", allProperties);
}
export function handleInputParameters(flowNode, allProperties) {
const inputParameters = getElementAsArray(flowNode, "inputParameters");
for (const inputParam of inputParameters) {
const inputParamName = `${inputParam.name} (input)`;
flowNode[inputParamName] = stringifyValue(inputParam.value, inputParam.name, allProperties);
}
delete flowNode.inputParameters;
}
export function handleprocessMetadataValues(flowNode, allProperties) {
const metadataValues = {};
const processMetadataValues = getElementAsArray(flowNode, "processMetadataValues");
for (const processMetadataValue of processMetadataValues) {
const inputParamName = `${processMetadataValue.name} (PM)`;
flowNode[inputParamName] = stringifyValue(processMetadataValue.value, processMetadataValue.name, allProperties);
metadataValues[processMetadataValue.name] = flowNode[inputParamName];
}
delete flowNode.processMetadataValues;
return metadataValues;
}
export function buildGenericMarkdownTable(item, fields, title = "", allProperties) {
if (fields[0] === "allFields") {
fields = Object.keys(item);
// Reorder fields according to preferences
const fieldOrderFromStart = FIELDS_PREFERRED_ORDER_START.slice().reverse();
for (const field of fieldOrderFromStart) {
if (fields.includes(field)) {
fields.splice(fields.indexOf(field), 1);
fields.unshift(field);
}
}
for (const field of FIELDS_PREFERRED_ORDER_END) {
if (fields.includes(field)) {
fields.splice(fields.indexOf(field), 1);
fields.push(field);
}
}
}
// Add link to Apex class doc if existing
if (item?.actionType === "apex" && item.actionName && fs.existsSync(path.join("docs", "apex", `${item.actionName}.md`))) {
item.actionName = `[${item.actionName}](../apex/${item.actionName}.md)`;
}
// Add link to SObject doc if existing
if (item?.sobjectType && fs.existsSync(path.join("docs", "objects", `${item.sobjectType}.md`))) {
item.sobjectType = `[${item.sobjectType}](../objects/${item.sobjectType}.md)`;
}
let table = title ? `${title}\n\n` : '';
table += `|<!-- -->|<!-- -->|\n|:---|:---|\n`;
for (const field of fields) {
if (item[field] !== undefined && item[field] !== null) {
table += `|${prettifyFieldName(field)}|${stringifyValue(item[field], field, allProperties)}|\n`;
}
}
return table + "\n\n";
}
export function buildCustomMarkdownTable(items, fields, title = "", allProperties) {
let table = title ? `${title}\n\n` : '';
table += "|" + fields.map(field => prettifyFieldName(field)).join("|") + "|\n";
table += "|" + fields.map(field => FIELDS_WITH_COLUMN_CENTERED.includes(field) ? ":--:" : ":-- ").join("|") + " |\n";
for (const item of items) {
const fieldValues = fields.map(field => stringifyValue(item[field], field, allProperties));
table += "|" + fieldValues.join("|") + "|\n";
}
return table + "\n\n";
}
export function stringifyOperator(operatorIn) {
return prettifyFieldName(operatorIn);
}
export function stringifyValue(valueIn, field, allProperties) {
const valueType = typeof valueIn;
// String
let valueStringified = valueType === "string" ?
valueIn.split("\n").join("<br/>") :
// String value
(valueType === "object" && valueIn.stringValue && Object.keys(valueIn).length === 1) ?
valueIn.stringValue :
// Boolean value
(valueType === "object" && (valueIn.booleanValue !== undefined) && Object.keys(valueIn).length === 1) ?
valueIn.booleanValue :
// Number value
(valueType === "object" && valueIn.numberValue && Object.keys(valueIn).length === 1) ?
valueIn.numberValue :
// Target reference
(valueType === "object" && valueIn.targetReference && Object.keys(valueIn).length === 1) ?
valueIn.targetReference :
// Element reference
(valueType === "object" && valueIn.elementReference && Object.keys(valueIn).length === 1) ?
valueIn.elementReference :
// Element reference
(valueType === "object" && field === 'template' && valueIn?.name) ?
valueIn.name :
// Undefined or empty array or empty object
(valueType === "undefined" || (Array.isArray(valueIn) && valueIn.length === 0) || (valueType === "object" && Object.keys(valueIn).length === 0)) ?
'<!-- -->' :
// Default YAML for array & object
(Array.isArray(valueIn) || valueType === "object") ?
yaml.dump(valueIn).replace(/"/gm, "").replace(/^(\s+)/gm, match => ' '.repeat(match.length)).split("\n").join("<br/>") :
// Default
String(valueIn).split("\n").join("<br/>");
// Final updates if necessary
if (allProperties.includes(valueStringified)) {
valueStringified = `[${valueStringified}](#${valueStringified.toLowerCase()})`;
}
else if (FIELDS_WITH_VALUES_TO_FORMAT_ENUM[field] && FIELDS_WITH_VALUES_TO_FORMAT_ENUM[field][valueStringified]) {
valueStringified = FIELDS_WITH_VALUES_TO_FORMAT_ENUM[field][valueStringified];
}
else if (FIELDS_WITH_VALUES_TO_FORMAT.includes(field)) {
valueStringified = prettifyFieldName(valueStringified);
if (field === "type" && valueStringified.endsWith("s")) {
valueStringified = valueStringified.slice(0, -1);
}
}
else {
valueStringified = valueStringified === "true" ? "✅" : valueStringified === "false" ? "⬜" : valueStringified;
}
return valueStringified;
}
export function prettifyFieldName(field) {
return field.replace(/([A-Z])/g, " $1").replace(/^./, str => str.toUpperCase()).replace("( P M)", "(PM)").replace("S Object", "SObject");
}
export function mdEndSection(sectionString) {
if (!sectionString)
return sectionString + "\n\n___\n\n";
return sectionString;
}
export function getElementAsArray(node, key) {
return Array.isArray(node[key]) ? node[key] : typeof node[key] === "object" ? [node[key]] : [];
}
//# sourceMappingURL=nodeFormatUtils.js.map