in libs/designer/src/lib/core/actions/bjsworkflow/serializer.ts [939:1364]
await serializeSubGraph(
subGraph,
[subGraphLocation, subGraphId, ...(subGraphDetail?.location ?? [])],
[subGraphLocation, subGraphId],
rootState,
subGraphDetail
)
);
}
} else if (subGraphs.length === 1) {
result = merge(
result,
await serializeSubGraph(
subGraphs[0],
[subGraphLocation, ...(subGraphDetail?.location ?? [])],
[subGraphLocation],
rootState,
subGraphDetail
)
);
}
}
}
return result;
};
const serializeSubGraph = async (
graph: WorkflowNode,
graphLocation: string[],
graphInputsLocation: string[],
rootState: RootState,
graphDetail?: SubGraphDetail
): Promise<Partial<LogicAppsV2.Action>> => {
const { id: graphId, children } = graph;
const result: Partial<LogicAppsV2.Action> = {};
const nestedNodes = children?.filter(isWorkflowOperationNode) ?? [];
const nestedActionsPromises = nestedNodes.map((nestedNode) =>
serializeOperation(rootState, nestedNode.id)
) as Promise<LogicAppsV2.OperationDefinition>[];
const nestedActions = await Promise.all(nestedActionsPromises);
const idReplacements = rootState.workflow.idReplacements;
safeSetObjectPropertyValue(
result,
graphLocation,
nestedActions.reduce((actions: LogicAppsV2.Actions, action: LogicAppsV2.OperationDefinition, index: number) => {
if (!isNullOrEmpty(action)) {
const actionId = nestedNodes[index].id;
actions[getRecordEntry(idReplacements, actionId) ?? actionId] = action;
return actions;
}
return actions;
}, {})
);
if (graphDetail?.inputs && graphDetail?.inputsLocation) {
const inputs = serializeParametersFromManifest(getOperationInputsToSerialize(rootState, graphId), { properties: graphDetail } as any);
safeSetObjectPropertyValue(result, [...graphInputsLocation, ...graphDetail.inputsLocation], inputs, true);
if (inputs?.agentParameterSchema?.required) {
safeSetObjectPropertyValue(
result,
[...graphInputsLocation, 'agentParameterSchema', 'required'],
inputs?.agentParameterSchema?.required
);
}
}
return result;
};
export const isWorkflowOperationNode = (node: WorkflowNode) =>
node.type === WORKFLOW_NODE_TYPES.OPERATION_NODE || node.type === WORKFLOW_NODE_TYPES.GRAPH_NODE;
//#endregion
//#region Settings Serialization
const serializeSettings = (
operationId: string,
settings: Settings,
nodeStaticResults: NodeStaticResults,
isTrigger: boolean,
rootState: RootState
): Partial<LogicAppsV2.Action | LogicAppsV2.Trigger> => {
const conditionExpressions = settings.conditionExpressions;
const conditions = conditionExpressions
? conditionExpressions.value?.filter((expression) => !!expression).map((expression) => ({ expression }))
: undefined;
const timeout = settings.timeout?.isSupported ? settings.timeout.value : undefined;
const count = settings.count?.isSupported ? settings.count.value : undefined;
const limit = timeout || count ? { count, timeout } : undefined;
const trackedProperties = settings.trackedProperties?.value;
return {
...optional('correlation', settings.correlation?.value),
...(settings.invokerConnection?.value?.enabled
? optional('isInvokerConnectionEnabled', settings.invokerConnection?.value?.enabled)
: {}),
...optional('conditions', conditions),
...optional('limit', limit),
...optional('operationOptions', getSerializedOperationOptions(operationId, settings, rootState)),
...optional('runtimeConfiguration', getSerializedRuntimeConfiguration(operationId, settings, nodeStaticResults, rootState)),
...optional('trackedProperties', trackedProperties),
...(getSplitOn(isTrigger, settings) ?? {}),
};
};
const getSerializedRuntimeConfiguration = (
operationId: string,
settings: Settings,
nodeStaticResults: NodeStaticResults,
rootState: RootState
): LogicAppsV2.RuntimeConfiguration | undefined => {
const runtimeConfiguration: LogicAppsV2.RuntimeConfiguration = {};
const isTrigger = isRootNodeInGraph(operationId, 'root', rootState.workflow.nodesMetadata);
const transferMode = settings.uploadChunk?.value?.transferMode;
const uploadChunkSize = settings.uploadChunk?.value?.uploadChunkSize;
const downloadChunkSize = settings.downloadChunkSize?.value;
const pagingItemCount = settings.paging?.value?.enabled ? settings.paging.value.value : undefined;
if (Object.keys(nodeStaticResults).length > 0) {
safeSetObjectPropertyValue(runtimeConfiguration, [Constants.SETTINGS.PROPERTY_NAMES.STATIC_RESULT], nodeStaticResults);
}
if (transferMode) {
safeSetObjectPropertyValue(
runtimeConfiguration,
[Constants.SETTINGS.PROPERTY_NAMES.CONTENT_TRANSFER, Constants.SETTINGS.PROPERTY_NAMES.TRANSFER_MODE],
transferMode
);
}
if (uploadChunkSize !== undefined) {
safeSetObjectPropertyValue(
runtimeConfiguration,
[Constants.SETTINGS.PROPERTY_NAMES.CONTENT_TRANSFER, Constants.SETTINGS.PROPERTY_NAMES.UPLOAD_CHUNK_SIZE],
uploadChunkSize
);
}
if (downloadChunkSize !== undefined) {
safeSetObjectPropertyValue(
runtimeConfiguration,
[Constants.SETTINGS.PROPERTY_NAMES.CONTENT_TRANSFER, Constants.SETTINGS.PROPERTY_NAMES.DOWNLOAD_CHUNK_SIZE],
downloadChunkSize
);
}
if (pagingItemCount !== undefined) {
safeSetObjectPropertyValue(
runtimeConfiguration,
[Constants.SETTINGS.PROPERTY_NAMES.PAGINATION_POLICY, Constants.SETTINGS.PROPERTY_NAMES.MINIMUM_ITEM_COUNT],
pagingItemCount
);
}
if (!isTrigger) {
if (!settings.sequential) {
const repetitions = settings.concurrency?.value?.enabled ? settings.concurrency.value.runs : undefined;
if (repetitions !== undefined) {
safeSetObjectPropertyValue(
runtimeConfiguration,
[Constants.SETTINGS.PROPERTY_NAMES.CONCURRENCY, Constants.SETTINGS.PROPERTY_NAMES.REPETITIONS],
repetitions
);
}
}
} else if (!settings.singleInstance) {
const runs = settings.concurrency?.value?.enabled ? settings.concurrency.value.runs : undefined;
const maximumWaitingRuns = settings.concurrency?.value?.enabled ? settings.concurrency.value.maximumWaitingRuns : undefined;
if (runs !== undefined) {
safeSetObjectPropertyValue(
runtimeConfiguration,
[Constants.SETTINGS.PROPERTY_NAMES.CONCURRENCY, Constants.SETTINGS.PROPERTY_NAMES.RUNS],
runs
);
safeSetObjectPropertyValue(
runtimeConfiguration,
[Constants.SETTINGS.PROPERTY_NAMES.CONCURRENCY, Constants.SETTINGS.PROPERTY_NAMES.MAXIMUM_WAITING_RUNS],
maximumWaitingRuns
);
}
}
const isSecureInputsSet = settings.secureInputs?.value;
const isSecureOutputsSet = settings.secureOutputs?.value;
if (isSecureInputsSet || isSecureOutputsSet) {
const secureData: LogicAppsV2.SecureData = {
properties: [
...(isSecureInputsSet ? [Constants.SETTINGS.SECURE_DATA_PROPERTY_NAMES.INPUTS] : []),
...(isSecureOutputsSet ? [Constants.SETTINGS.SECURE_DATA_PROPERTY_NAMES.OUTPUTS] : []),
],
};
safeSetObjectPropertyValue(runtimeConfiguration, [Constants.SETTINGS.PROPERTY_NAMES.SECURE_DATA], secureData);
}
const requestOptions = settings.requestOptions?.value;
if (requestOptions?.timeout) {
safeSetObjectPropertyValue(runtimeConfiguration, ['requestOptions'], requestOptions);
}
// TODO - Might need to add for Static results
return runtimeConfiguration && !Object.keys(runtimeConfiguration).length ? undefined : runtimeConfiguration;
};
const getSerializedOperationOptions = (operationId: string, settings: Settings, rootState: RootState): string | undefined => {
const originalDefinition = getRecordEntry(rootState.workflow.operations, operationId);
const originalOptions = originalDefinition?.operationOptions;
const deserializedOptions = isNullOrUndefined(originalOptions) ? [] : originalOptions.split(',').map((option) => option.trim());
updateOperationOptions(Constants.SETTINGS.OPERATION_OPTIONS.SINGLE_INSTANCE, true, !!settings.singleInstance, deserializedOptions);
updateOperationOptions(Constants.SETTINGS.OPERATION_OPTIONS.SEQUENTIAL, true, !!settings.sequential, deserializedOptions);
updateOperationOptions(
Constants.SETTINGS.OPERATION_OPTIONS.ASYNCHRONOUS,
!!settings.asynchronous?.isSupported,
!!settings.asynchronous?.value,
deserializedOptions
);
updateOperationOptions(
Constants.SETTINGS.OPERATION_OPTIONS.DISABLE_ASYNC,
!!settings.disableAsyncPattern?.isSupported,
!!settings.disableAsyncPattern?.value,
deserializedOptions
);
updateOperationOptions(
Constants.SETTINGS.OPERATION_OPTIONS.DISABLE_AUTOMATIC_DECOMPRESSION,
!!settings.disableAutomaticDecompression?.isSupported,
!!settings.disableAutomaticDecompression?.value,
deserializedOptions
);
updateOperationOptions(
Constants.SETTINGS.OPERATION_OPTIONS.SUPPRESS_WORKFLOW_HEADERS,
!!settings.suppressWorkflowHeaders?.isSupported,
!!settings.suppressWorkflowHeaders?.value,
deserializedOptions
);
updateOperationOptions(
Constants.SETTINGS.OPERATION_OPTIONS.SUPPRESS_WORKFLOW_HEADERS_ON_RESPONSE,
!!settings.suppressWorkflowHeadersOnResponse?.isSupported,
!!settings.suppressWorkflowHeadersOnResponse?.value,
deserializedOptions
);
updateOperationOptions(
Constants.SETTINGS.OPERATION_OPTIONS.REQUEST_SCHEMA_VALIDATION,
!!settings.requestSchemaValidation?.isSupported,
!!settings.requestSchemaValidation?.value,
deserializedOptions
);
updateOperationOptions(
Constants.SETTINGS.OPERATION_OPTIONS.FAILWHENLIMITSREACHED,
!!settings.shouldFailOperation?.isSupported,
!!settings.shouldFailOperation?.value,
deserializedOptions
);
return deserializedOptions.length ? deserializedOptions.join(', ') : undefined;
};
const updateOperationOptions = (
operationOption: string,
isOptionSupported: boolean,
isOptionSet: boolean,
existingOperationOptions: string[]
): void => {
if (isOptionSupported) {
const optionIndex = existingOperationOptions.findIndex((option) => equals(option, operationOption));
if (isOptionSet && optionIndex === -1) {
existingOperationOptions.push(operationOption);
}
if (!isOptionSet && optionIndex !== -1) {
existingOperationOptions.splice(optionIndex, 1);
}
}
};
const getRetryPolicy = (settings: Settings): LogicAppsV2.RetryPolicy | undefined => {
const retryPolicy = settings.retryPolicy?.value;
if (!retryPolicy) {
return undefined;
}
const retryPolicyType = retryPolicy.type && retryPolicy.type.toLowerCase();
switch (retryPolicyType) {
case Constants.RETRY_POLICY_TYPE.DEFAULT:
return undefined;
case Constants.RETRY_POLICY_TYPE.FIXED:
case Constants.RETRY_POLICY_TYPE.EXPONENTIAL:
return { ...retryPolicy, type: retryPolicyType };
case Constants.RETRY_POLICY_TYPE.NONE:
return { type: Constants.RETRY_POLICY_TYPE.NONE };
default:
throw new Error(`Unable to serialize retry policy with type ${retryPolicyType}`);
}
};
const setRetryPolicy = (inputs: any, nodeSettings: Settings): void => {
// Retry policy should always be in `inputs.retryPolicy`
// This should happen after the location swap to avoid moving it to a different location
const retryPolicy = getRetryPolicy(nodeSettings);
if (retryPolicy) {
if (!inputs?.inputs) {
inputs.inputs = {};
}
inputs.inputs.retryPolicy = retryPolicy;
}
};
const getSplitOn = (
isTrigger: boolean,
{ splitOn, splitOnConfiguration }: Settings
):
| {
splitOn: string;
splitOnConfiguration?: { correlation?: { clientTrackingId?: string } };
}
| undefined => {
if (!isTrigger || !splitOn?.value?.enabled) {
return undefined;
}
return {
splitOn: splitOn.value.value as string,
...(splitOnConfiguration ? { splitOnConfiguration } : {}),
};
};
/**
* Serializes the unit test definition based on the provided root state.
* @param {RootState} rootState The root state object containing the unit test data.
* @returns A promise that resolves to the serialized unit test definition.
*/
export const serializeUnitTestDefinition = async (rootState: RootState): Promise<UnitTestDefinition> => {
const { mockResults, assertions } = rootState.unitTest;
const { triggerMocks, actionMocks } = getTriggerActionMocks(mockResults);
return {
triggerMocks: triggerMocks,
actionMocks: actionMocks,
assertions: getAssertions(assertions),
};
};
/**
* Gets the node output operations based on the provided root state.
* @param {RootState} rootState The root state object containing the current designer state.
* @returns A promise that resolves to the serialized unit test definition.
*/
export const getNodeOutputOperations = (state: RootState) => {
const outputOperations: {
operationInfo: Record<string, NodeOperation>;
outputParameters: Record<string, NodeOutputs>;
} = {
operationInfo: state.operations.operationInfo,
outputParameters: state.operations.outputParameters,
};
return outputOperations;
};
/**
* Retrieves an array of Assertion objects based on the provided Assertion definitions.
* @param {Record<string, AssertionDefinition>} assertions - The Assertion definitions.
* @returns An array of Assertion objects.
*/
const getAssertions = (assertions: Record<string, AssertionDefinition>): Assertion[] => {
return Object.values(assertions).map((assertion) => {
const { name, description, assertionString } = assertion;
const assertionValueSegment = createTokenValueSegment(
{
title: assertionString,
key: assertionString,
tokenType: TokenType.FX,
type: Constants.SWAGGER.TYPE.STRING,
},
assertionString,
TokenType.FX
);
const castAsertionString = castValueSegments([assertionValueSegment], false, Constants.SWAGGER.TYPE.STRING, false);
return { name, description, assertionString: castAsertionString };
});
};
/**
* Parses the output of a workflow into a more structured format.
* @param {Record<string, ValueSegment[]>} outputs - The outputs of the workflow.
* @returns The parsed outputs in a more structured format.
*/
const parseOutputMock = (outputs: Record<string, ValueSegment[]>): Record<string, any> => {
const outputValues: Record<string, any> = {};
for (const [key, value] of Object.entries(outputs)) {
if (value && value.length > 0) {
outputValues[key] = value[0].value;
}
}
return unifyOutputs(outputValues);
};
/**
* Unifies the outputs by converting a dot-separated key-value object into a nested object.
* @param {Record<string, any>} outputs - The dot-separated key-value object to unify.
* @returns The unified object with nested properties.
*/
const unifyOutputs = (outputs: Record<string, any>): Record<string, any> => {
const result: Record<string, any> = {};
Object.keys(outputs).forEach((key) => {
const parts = key.split('.').filter((part) => part !== '$');
let currentLevel = result;
parts.forEach((part, index) => {
if (index === parts.length - 1) {
currentLevel[part] = outputs[key];
} else {
if (!currentLevel[part]) {