File

libs/nx/src/executors/shared/df-editor-server/update-dynamic-form-config.ts

Index

Properties

Properties

buildingBlockId
Type string
config
Type DynamicFormConfiguration
pageId (Optional)
Type string
Description

When provided, the block is looked up inside that page's blocks. When omitted, the block is looked up inside frame.globalSidebar.tabs[].blocks instead, since the global sidebar is not page-scoped.

import type {
  BuildingBlockConfiguration,
  DynamicFormBBConfiguration,
  DynamicFormConfiguration,
  Frame,
  PageConfiguration
} from '@allianz/taly-core/schemas';
import type { ApplicationJsonSchema, PagesJsonSchema } from '@allianz/taly-sdk';
import {
  getJsonFilePathInDirectory,
  getJsonFilePathsInDirectory,
  SPLIT_JOURNEY_PATHS
} from '@allianz/taly-sdk/node';
import { parse, stringify } from 'comment-json';
import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';

interface DfEditRequest {
  /**
   * When provided, the block is looked up inside that page's `blocks`. When omitted,
   * the block is looked up inside `frame.globalSidebar.tabs[].blocks` instead, since
   * the global sidebar is not page-scoped.
   */
  pageId?: string;
  buildingBlockId: string;
  config: DynamicFormConfiguration;
}

interface ConfigDirectories {
  configDirectory: string;
  baseConfigDirectory?: string;
}

/**
 * Updates the dynamic form configuration for a building block identified by `id`
 * directly in the source configuration file(s) on disk.
 *
 * Supports classic journeys (pages.json), split journeys (pages/*.json),
 * and multi-tenant journeys (tenant pages + base pages fallback).
 *
 * When `pageId` is omitted the block is treated as a global sidebar block and
 * looked up under `frame.globalSidebar.tabs[].blocks` in `pages.json(c)` (classic)
 * or `application.json(c)` (split / multi-tenant).
 *
 * Uses `comment-json` to preserve JSONC comments when writing back.
 */
export function updateDynamicFormConfig(
  { configDirectory, baseConfigDirectory }: ConfigDirectories,
  { pageId, buildingBlockId, config }: DfEditRequest
): void {
  // For multi-tenant: try tenant first, then fall back to base
  if (baseConfigDirectory) {
    const updatedInTenant = tryUpdateInDirectory(configDirectory, pageId, buildingBlockId, config);
    if (updatedInTenant) return;

    const updatedInBase = tryUpdateInDirectory(
      baseConfigDirectory,
      pageId,
      buildingBlockId,
      config
    );
    if (updatedInBase) return;

    throw new Error(
      `${notFoundLocation(
        buildingBlockId,
        pageId
      )} in tenant config "${configDirectory}" or base config "${baseConfigDirectory}".`
    );
  }

  const updated = tryUpdateInDirectory(configDirectory, pageId, buildingBlockId, config);
  if (updated) return;

  throw new Error(
    `${notFoundLocation(buildingBlockId, pageId)} in config directory "${configDirectory}".`
  );
}

function notFoundLocation(buildingBlockId: string, pageId: string | undefined): string {
  return pageId
    ? `Building block "${buildingBlockId}" not found in page "${pageId}"`
    : `Building block "${buildingBlockId}" not found in the configuration`;
}

/**
 * Tries to find and update the building block in the relevant config file(s) within a directory.
 * Routes to either page-block or global-sidebar lookup based on whether a pageId was given.
 * Returns true if the building block was found and updated.
 */
function tryUpdateInDirectory(
  configDirectory: string,
  pageId: string | undefined,
  blockId: string,
  newConfig: DynamicFormConfiguration
): boolean {
  if (pageId === undefined) {
    return tryUpdateInGlobalSidebar(configDirectory, blockId, newConfig);
  }

  // Classic journey: single pages.json(c) at the root
  const pagesFilePath = getJsonFilePathInDirectory(configDirectory, 'pages');
  if (pagesFilePath) {
    return updateBlockInClassicPagesFile(pagesFilePath, pageId, blockId, newConfig);
  }

  // Split journey: individual page files in pages/ subdirectory
  const pagesDir = join(configDirectory, SPLIT_JOURNEY_PATHS.FOLDERS.PAGES);
  const pageFiles = getJsonFilePathsInDirectory(pagesDir);
  for (const pageFile of pageFiles) {
    if (updateBlockInSplitPageFile(pageFile, pageId, blockId, newConfig)) {
      return true;
    }
  }

  return false;
}

/**
 * Tries to find and update a global sidebar block. The `frame.globalSidebar` config lives in
 * `pages.json(c)` for classic journeys and in `application.json(c)` for split/multi-tenant journeys.
 */
function tryUpdateInGlobalSidebar(
  configDirectory: string,
  blockId: string,
  newConfig: DynamicFormConfiguration
): boolean {
  const pagesFilePath = getJsonFilePathInDirectory(configDirectory, 'pages');
  if (pagesFilePath) {
    return updateGlobalSidebarBlockInFile<PagesJsonSchema>(pagesFilePath, blockId, newConfig);
  }

  const applicationFilePath = getJsonFilePathInDirectory(
    configDirectory,
    SPLIT_JOURNEY_PATHS.FILES.APPLICATION
  );
  if (applicationFilePath) {
    return updateGlobalSidebarBlockInFile<ApplicationJsonSchema>(
      applicationFilePath,
      blockId,
      newConfig
    );
  }

  return false;
}

/**
 * Updates a dynamic form block inside `frame.globalSidebar.tabs[].blocks` of the given file.
 * Works for both classic (`pages.json(c)`) and split/multi-tenant (`application.json(c)`)
 * journeys, since both share the same `frame` shape.
 */
function updateGlobalSidebarBlockInFile<T extends { frame?: Frame }>(
  filePath: string,
  blockId: string,
  newConfig: DynamicFormConfiguration
): boolean {
  const content = readAndParse<T>(filePath);
  const tabs = content.frame?.globalSidebar?.tabs;
  if (!tabs) return false;

  for (const tab of tabs) {
    if (updateBlockInBlocksArray(tab.blocks, blockId, newConfig)) {
      writeBack(filePath, content);
      return true;
    }
  }

  return false;
}

/**
 * Updates a dynamic form block in a classic pages.json(c) file.
 * The file has a top-level `pages` array, each with a `blocks` array.
 */
function updateBlockInClassicPagesFile(
  filePath: string,
  pageId: string,
  blockId: string,
  newConfig: DynamicFormConfiguration
): boolean {
  const content = readAndParse<PagesJsonSchema>(filePath);
  if (!content.pages) return false;

  const page = content.pages.find((p) => p.id === pageId);
  if (!page) return false;

  if (updateBlockInBlocksArray(page.blocks, blockId, newConfig)) {
    writeBack(filePath, content);
    return true;
  }

  return false;
}

/**
 * Updates a dynamic form block in a split page file.
 * Each file is a single page with a top-level `blocks` array.
 */
function updateBlockInSplitPageFile(
  filePath: string,
  pageId: string,
  blockId: string,
  newConfig: DynamicFormConfiguration
): boolean {
  const content = readAndParse<PageConfiguration>(filePath);
  if (!content.blocks || content.id !== pageId) return false;

  if (updateBlockInBlocksArray(content.blocks, blockId, newConfig)) {
    writeBack(filePath, content);
    return true;
  }

  return false;
}

/**
 * Searches for a block by id in the blocks array and updates its `form` property.
 * Only updates blocks that have a `form` property (dynamic form building blocks).
 * Returns true if a matching block was found and updated.
 */
function updateBlockInBlocksArray(
  blocks: (BuildingBlockConfiguration | DynamicFormBBConfiguration)[] | undefined,
  blockId: string,
  newConfig: DynamicFormConfiguration
): boolean {
  if (!blocks) return false;

  const block = blocks.find((b) => b.id === blockId);
  if (!block || !isDynamicFormBbConfig(block)) return false;

  block.form = newConfig;
  return true;
}

function isDynamicFormBbConfig(
  block: BuildingBlockConfiguration | DynamicFormBBConfiguration
): block is DynamicFormBBConfiguration {
  return Boolean((block as DynamicFormBBConfiguration).form);
}

function readAndParse<T>(filePath: string): T {
  const raw = readFileSync(filePath, 'utf8');
  return parse(raw) as T;
}

function writeBack(filePath: string, content: unknown): void {
  writeFileSync(filePath, stringify(content, null, 2) + '\n', 'utf8');
}

results matching ""

    No results matching ""