await serializeSubGraph()

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]) {