Skip to content

Menu builder plugin

Overview

The menu builder plugin is a complex plugin that aggregates multiple menu calls into a single menu config that is stored in the platform and exposed via the SDK. It executes multiple API calls based on the users context (authResponse). The plugin makes use of config files to map menu items and has information about menu items for clients who are geo-linked. Based on the current client region , it will configure the correct URL to go to.

shell
npm install --save @investec/plugins-menu-builder

Environment

The plugin makes use of environments and an object that is exposed to the platform for other params to be injected into it. The object is defined as follows:

ts
import { ReplaySubject } from 'rxjs';
import { IAuthResponse, IMenuConfig } from '@investec-plugins/menu-builder';

export const menuBuilder = { activeEnvironment : 'staging',updateAppState : new ReplaySubject<IMenuConfig>(1),currentMenuState: new ReplaySubject<IMenuConfig>(1),authResponse: new ReplaySubject<IAuthResponse>(1),clearState: new ReplaySubject<void>(1) }

INFO

The exposed menu object menuBuilder is also used to send back data as it receives new data to and from the platform via events updateAppState : new ReplaySubject<IMenuConfig>(1).

The following shows the environment config:

ts
export const environment = {
  production:{
    activeEnv: 'production',
    privateVault:'/cxt-data-vault.secure.investec.com/v1/privatevault/menu',
    ukExecClient: 'https://ctr-lcg-ecp.secure.investec.com/menu',
    ibsag: '/cxt-wealth-ibsag.secure.investec.com/v1/menu/ibsag'
  },
  beta: {
    activeEnv: 'beta',
    privateVault:'/cxt-data-vault.secure.investec.com/v1/privatevault/menu',
    ukExecClient: 'https://ctr-lcg-ecp.secure.investec.com/menu',
    ibsag: '/cxt-wealth-ibsag.secure.investec.com/v1/menu/ibsag'
  },
  staging:{
    activeEnv: 'staging',
    privateVault:'/cxt-data-vault-nonprod.secure.investec.com/v1/privatevault/menu',
    ukExecClient: 'https://ctr-lcg-ecp-nonprod.secure.investec.com/menu',
    ibsag: '/cxt-wealth-ibsag-nonprod.secure.investec.com/v1/menu/ibsag'
  },
  test:{
    activeEnv: 'test',
    privateVault:'/cxt-data-vault-nonprod.secure.investec.com/v1/privatevault/menu',
    ukExecClient:'https://ctr-lcg-ecp-nonprod.secure.investec.com/menu',
    ibsag: '/cxt-wealth-ibsag-nonprod.secure.investec.com/v1/menu/ibsag'
  }
}

Configuration

The plugin makes use of various configs to map menu items. The configs are stored in the menu-builder/src/config folder. The following code snippet shows the mapping of when menu calls are being made based on the client context (auth flags).

ts
export function MenuUrlMap (){
  const menus = [
      {property: 'PrivateBankZA', url: '/za/pb/menu', region: 'ZA'},
      {property: 'PrivateBankZA', url: '/za/pb/menu/business', region: 'ZA'},
      {property: 'PrivateBankUK', url: '/uk/pb/menu', region: 'UK'},
      {property: 'PrivateBankCI', url: '/ci/pb/menu', region: 'CI'},
      {property: 'PrivateBankMU', url: '/mu/pb/menu', region: 'MU'},
      {property: 'BusinessBankingZa', url: '/za/icb/menu/business', region: 'ZA'},
      {property: 'WealthAndInvestmentZA', url: '/za/wi/menu', region: 'ZA'},
      {property: 'WealthAndInvestmentUK', url: '/uk/wi/menu', region: 'UK'},
      {property: 'WealthAndInvestmentGU', url: '/ci/wi/menu', region: 'CI'},
      {property: 'PfmTargetMarket', url: '/pfm/menu', region: 'Default'},
      {property: 'WealthAndInvestmentClick', url: '/uk/ci/menu', region: 'UK'},
      {property: 'LifeTargetMarket', url: '/life/menu', region: 'Default'},
      {property: 'PasswordResetJourneyAllowed', url: '/quickpass/menu', region: 'Default'},
      {property: 'IntermediaryBanking', url: '/za/ib/menu/intermediary', region: 'ZA'},
      {property: 'CibUkIntermediaryBanking', url: '/uk/ib/menu', region: 'UK'},
      {property: 'Ibsag', url:  environment[menuBuilder.activeEnvironment as keyof typeof environment].ibsag, region: 'CH'},
      {property: 'PrivateVault', url:  environment[menuBuilder.activeEnvironment as keyof typeof environment].privateVault, region: 'Default'}
    ];
  
  return {menus: menus};
}

export function AdditionalClientProfileFlagsMenuUrlMap() {
  return {
    menus: [
      {
        property: 'UKExecutiveClientPlatform',
        url: environment[menuBuilder.activeEnvironment as keyof typeof environment].ukExecClient,
        region: 'UK'
      }
    ]
  };
}

Additionally, the plugin makes use of the MenuItemMap that contains the mapping of menu items to the correct URL based on the client region. An extract of that would look like this:

json
{
    "menuId": "3",
    "redirectUrl": {
      "menuUrlZA": "/pc-za/payments-sa/transaction-history",
      "menuUrlUK": "/io-uk/pbuk/dashboard/transactions",
      "menuUrlCI": "/content/investec/ibci/en/transaction-history.html",
      "menuUrlMU": "/pc-mu-wpaas/pbmu/transactions"
    },
    "analyticsQueryString": ""
  }

The region is tied to the last two letters of the "menuUrl" property in the redirectUrls object.

Implementation Guide

The plugin exposes a main function that gets called to set up the menus based on the client context. It will use the updateAppState event to send back the menu config to the platform as the data comes back from the apis. The following code shows the main function that gets called:

ts

function checkMenuRegion() {
  if(authRes){
    if (authRes?.HIPI === 'SA') {
      return 'za';
    } else if (authRes?.HIPI === 'MAU') {
      return 'mu';
    } else if (authRes?.PrivateBankUK || authRes?.WealthAndInvestmentUK) {
      return 'uk';
    } else if (authRes?.Ibsag) {
      return 'ch';
    } else {
      return 'ci';
    }
  } else {
    return undefined
  }

}

export function setupMenu() : Promise<boolean>{
  const menuConfig : IMenuConfig = {mainMenu:[...defaultMenu],mainMenuWithoutChildren:[],callResultMap:[],hasError:false,currentMenuRegion: checkMenuRegion()}

  numOfCalls = 0;

  return new Promise(resolve => {

    promiseResponse = resolve;

    const callMap: Array<ICallResultMapModel> = [];
    MenuUrlMap().menus.forEach(value => {
      if (authRes) {
        if (Object.entries(authRes).find(([key, val]) => key === value.property)?.[1]) {
          let urlFound = false;
          for (let i = 0; i < menuConfig.callResultMap.length; i++) {
            const callResultMapElement = menuConfig.callResultMap[i];
            if (value.url === callResultMapElement.url) {
              urlFound = true;
              if (!callResultMapElement.result) {
                callMap.push({ url: value.url, result: callResultMapElement.result, region: value.region })
              }
              break;
            }
          }

          if (!urlFound) {
            callMap.push({ url: value.url, result: false, region: value.region })
          }
        }
      }
    })
    callMap.forEach(value => {
      aggregateMenuItems(value.url, value.region, menuConfig)
    })
    const AdditionalClientProfileFlagsCallMap: Array<ICallResultMapModel> = [];
    AdditionalClientProfileFlagsMenuUrlMap().menus.forEach(value => {
      if (authRes) {
        if (authRes.ClientProfileFlags) {
          if (authRes.ClientProfileFlags[value.property as keyof typeof authRes.ClientProfileFlags]) {
            let urlFound = false;
            for (let i = 0; i < menuConfig.callResultMap.length; i++) {
              const callResultMapElement = menuConfig.callResultMap[i];
              if (value.url === callResultMapElement.url) {
                urlFound = true;
                if (!callResultMapElement.result) {
                  AdditionalClientProfileFlagsCallMap.push({ url: value.url, result: callResultMapElement.result, region: value.region })
                }
                break;
              }
            }

            if (!urlFound) {
              AdditionalClientProfileFlagsCallMap.push({ url: value.url, result: false, region: value.region })
            }
          }
        }

      }
    })
    AdditionalClientProfileFlagsCallMap.forEach(value => {
      aggregateClientProfileFlagsMenuItems(value.url, value.region, menuConfig)
    })
  })
}

WARNING

Changes to the menu config will have a direct impact on the platform and multiple features that use the config via the SDK. Please ensure that you have tested the changes thoroughly before deploying.