Name

sn_ace.ACEAppBuilderUtil

Description

No description available

Script

var ACEAppBuilderUtil = Class.create();
ACEAppBuilderUtil.prototype = {
  initialize: function() {
      this.CONSTANTS = new ACEAppBuilderMetaData();
      this.TABLE = this.CONSTANTS.TABLES;
      this.UXF_TABLE = this.CONSTANTS.UXF_TABLES;
      this.GLOBAL = new global.ACEAppBuilderGlobalScopedUtil();
      this.CREATE_DEMO_DATA = false;
      this.demoUtils = new ACEDemoDataUtils();
      this.AIS = this.CONSTANTS.AIS;
      this.ALL_CONTENT_BLOCK_TYPES = this.getAllContentBlockTypes(this.TABLE.TYPE);
      this.EXPERIENCE_ID = null;
      this.VIRTUAL_GR = null;
      this.gr_counter = 0;
      this.CAN_READ = this.hasReadAccess();
      this.DEFAULT_FETCH_MODE = 'dfs';
      this.SUPPORTED_FETCH_MODES = ['dfs', 'bfs'];
  },
  _getVirtualGrInstance: function(cbId) {
      if (!cbId) return null;
      var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      if (gr.get(cbId)) {
          return gr;
      }
      return null;
  },
  _cachedScriptEvaluator: function(grInstance, scriptField, scriptValue, context) {
      try {
          if (!grInstance || !scriptValue) return context ? context : true;
          grInstance.setValue(scriptField, scriptValue);
          var evaluator = new GlideScopedEvaluator();
          if (context) {
              evaluator.putVariable('context', context);
              evaluator.evaluateScript(grInstance, scriptField, null);
          } else {
              evaluator.putVariable('answer', null);
              evaluator.putVariable('current', grInstance);
              evaluator.evaluateScript(grInstance, scriptField, null);
              return evaluator.getVariable('answer');
          }
      } catch (e) {
          gs.log('ACEAppBuilder: Exception executing script of cached content block, ' + e);
      }
  },
  _scriptEvaluator: function(grInstance, context, fieldName, pageParams) {
      try {
          if (!fieldName || !grInstance.getValue(fieldName))
              return context;

          var evaluator = new GlideScopedEvaluator();
          evaluator.putVariable('context', context);
          evaluator.putVariable('pageParams', pageParams);
          evaluator.evaluateScript(grInstance, fieldName, null);
      } catch (e) {
          gs.error('ACEAppBuilder: Exception executing script of content block, ' + e.toString());
      }
  },
  _evaluatePostScript: function(grInstance, context, pageParams) {
      this._scriptEvaluator(grInstance, context, 'post_script', pageParams);
  },
  _evaluateLazyScript: function(grInstance, context, pageParams) {
      this._scriptEvaluator(grInstance, context, 'lazy_script');
  },
  _visibilityEvaluator: function(grInstance, pageParams) {
      try {
          if (!grInstance.getValue('visibility'))
              return true;

          var evaluator = new GlideScopedEvaluator();
          evaluator.putVariable('answer', null);
          evaluator.putVariable('current', grInstance);
          evaluator.putVariable('pageParams', pageParams);
          evaluator.evaluateScript(grInstance, 'visibility', null);
          return evaluator.getVariable('answer');
      } catch (e) {
          gs.error('ACEAppBuilder: Exception executing script of content block, ' + e.toString());
          return false;
      }
  },
  _getValuePairsFromClonedRecord: function(table, sysId, fields) {
      try {
          if (!table || !sysId || !this.CAN_READ) return {};
          var valuePairs = {};
          var gr = new GlideRecord(table);
          if (gr.get(sysId)) {
              fields.forEach(function(field) {
                  valuePairs[field] = gr.getValue(field);
              });
              return valuePairs;
          }
          return {};
      } catch (e) {
          return {};
      }
  },
  _grRecordCreator: function(table, valuePairs, cloneInfo) {
      try {
          var clonedValuePairs = {},
              field;
          if (cloneInfo && cloneInfo.sys_id && Array.isArray(cloneInfo.fields) && cloneInfo.fields.length > 0) {
              clonedValuePairs = this._getValuePairsFromClonedRecord(table, cloneInfo.sys_id, cloneInfo.fields);
          }
          var gr = new GlideRecord(table);
          gr.initialize();
          gr.sys_scope = gs.getCurrentApplicationId();
          for (field in clonedValuePairs) {
              gr[field] = clonedValuePairs[field];
          }
          for (field in valuePairs) {
              if (field == 'props_details' && table == this.TABLE.CONTENT_BLOCK) {
                  for (var i in valuePairs.props_details) {
                      gr.variable[i] = valuePairs.props_details[i];
                  }
              } else
                  gr[field] = valuePairs[field];
          }

          return gr.insert();
      } catch (e) {
          gs.error("ACEAppBuilderUtil: Error in Glide Record Creation: " + e.toString());
          return null;
      }

  },
  _updateMacroponentProp: function(macroponentId, field, element, inputValueProp, newValue) {
      try {
          var _self = this;
          var gr = new GlideRecord(this.UXF_TABLE.MACROPONENT);
          if (gr.get(macroponentId)) {
              var fieldData = gr.getValue(field);
              fieldData = JSON.parse(fieldData);
              if (Array.isArray(fieldData)) {
                  for (var i = 0; i < fieldData.length; i++) {
                      var dataBroker = fieldData[i];
                      if (dataBroker.elementId == element) {
                          dataBroker.inputValues[inputValueProp].value = newValue.toString();

                          var input = {};
                          input[field] = JSON.stringify(fieldData, null, 1);
                          _self.GLOBAL.updateRecord(_self.UXF_TABLE.MACROPONENT, gr.getUniqueValue(), input);
                      }
                  }
              }
              return true;
          }
      } catch (e) {
          return null;
      }
  },
  createPage: function(appConfigId, name, aceAppSysId, template, pageId) {
      var screenTypeId = this.GLOBAL._grRecordCreator(this.UXF_TABLE.SCREEN_TYPE, {
          'name': name
      }, {
          'sys_id': this._getConstantsForPage(template).SCREEN_TYPE,
          'fields': this.CONSTANTS.CLONE_FIELDS.SCREEN_TYPE
      });
      var macroponentId = this._createMacroponent(name, template);


      var screenId = this._createScreen(macroponentId, appConfigId, screenTypeId, name, template);
      var appRouteId = this._createRoute(appConfigId, screenTypeId, name, template);
      // TODO: Have segregation between methods that create UXF artifacts and ACE artifacts
      if (pageId) {
          if (!this._updateMacroponentProp(macroponentId, 'data', this.CONSTANTS.BASE_UXF_RECORDS.SECTIONS_DATA_BROKER_NAME, 'page_route', pageId)) {
              gs.addErrorMessage(gs.getMessage('Please update the macroponent prop value via UIB'));
          }
          // FIXME: Remove references to hardcoded template names
          if (template != 'Home')
              this._createContentBlocks(pageId, this.CONSTANTS.DEFAULT_CONTENT_BLOCKS);

      }

      return {
          route: appRouteId,
          screenType: screenTypeId,
          screen: screenId,
          macroponent: macroponentId
      };
  },
  _createRoute: function(appConfig, screenType, name, template) {
      var cloneInfo = {
          'sys_id': this._getConstantsForPage(template).ROUTE,
          'fields': this.CONSTANTS.CLONE_FIELDS.APP_ROUTE
      };

      var payload = {
          'name': name,
          'app_config': appConfig,
          'screen_type': screenType,
          'route_type': name.toLowerCase()
      };

      return this.GLOBAL._grRecordCreator(this.UXF_TABLE.APP_ROUTE, payload, cloneInfo);
  },
  _cloneClientScripts: function(baseMacroponentId, newMacroponentId) {
      var clientScriptMaps = [];
      try {
          var _self = this;
          var gr = new GlideRecord(_self.UXF_TABLE.CLIENT_SCRIPT);
          gr.addQuery('macroponent', baseMacroponentId);
          gr.query();
          while (gr.next()) {
              var newClientScriptId = this.GLOBAL._grRecordCreator(_self.UXF_TABLE.CLIENT_SCRIPT, {
                  'macroponent': newMacroponentId,
                  'name': gr.getValue('name'),
                  'type': gr.getValue('type'),
                  'script': gr.getValue('script'),
              });
              clientScriptMaps.push({
                  'findId': gr.getValue('sys_id'),
                  'replaceWithId': newClientScriptId
              });
          }
          return clientScriptMaps;
      } catch (e) {
          return clientScriptMaps;
      }

  },
  _replaceAll: function(str, find, replace) {
      return str.replace(new RegExp(find, 'g'), replace);
  },
  _createMacroponent: function(name, template) {
      var _self = this;
      var cloneInfo = {
          'sys_id': _self._getConstantsForPage(template).MACROPONENT,
          'fields': _self.CONSTANTS.CLONE_FIELDS.MACROPONENT
      };
      var macroponentId = this.GLOBAL._grRecordCreator(this.UXF_TABLE.MACROPONENT, {
          'name': name
      }, cloneInfo);
      var clientScriptMaps = _self._cloneClientScripts(this._getConstantsForPage(template).MACROPONENT, macroponentId);
      //postCleanUp
      var gr = new GlideRecord(_self.UXF_TABLE.MACROPONENT);
      if (gr.get(macroponentId)) {
          var postUpdateComposition = {
              'composition': gr.getValue('composition'),
              'data': gr.getValue('data'),
              'state_properties': gr.getValue('state_properties'),
              'internal_event_mappings': gr.getValue('internal_event_mappings'),
          };
          clientScriptMaps.forEach(function(clientScriptMap) {
              postUpdateComposition.composition = _self._replaceAll(postUpdateComposition.composition, clientScriptMap.findId, clientScriptMap.replaceWithId);
              postUpdateComposition.data = _self._replaceAll(postUpdateComposition.data, clientScriptMap.findId, clientScriptMap.replaceWithId);
              postUpdateComposition.state_properties = _self._replaceAll(postUpdateComposition.state_properties, clientScriptMap.findId, clientScriptMap.replaceWithId);
              postUpdateComposition.internal_event_mappings = _self._replaceAll(postUpdateComposition.internal_event_mappings, clientScriptMap.findId, clientScriptMap.replaceWithId);
          });
          //gr.update();
          _self.GLOBAL.updateRecord(_self.UXF_TABLE.MACROPONENT, macroponentId, postUpdateComposition);
      }

      return macroponentId;
  },
  _createScreen: function(macroponent, appConfig, screenType, name, template) {
      var cloneInfo = {
          'sys_id': this._getConstantsForPage(template).SCREEN,
          'fields': this.CONSTANTS.CLONE_FIELDS.UX_SCREEN
      };
      var payload = {
          'name': name,
          'macroponent': macroponent,
          'app_config': appConfig,
          'screen_type': screenType
      };

      return this.GLOBAL._grRecordCreator(this.UXF_TABLE.SCREEN, payload, cloneInfo);
  },
  _getConstantsForPage: function(name) {
      switch (name) {
          case 'Home':
              return this.CONSTANTS.BASE_UXF_RECORDS.HOME_PAGE;
          case 'Settings':
              return this.CONSTANTS.BASE_UXF_RECORDS.SETTINGS_PAGE;
          case 'Search':
              return this.CONSTANTS.BASE_UXF_RECORDS.SEARCH_PAGE;
          case 'List':
              return this.CONSTANTS.BASE_UXF_RECORDS.LIST_PAGE;
          case 'Record':
              return this.CONSTANTS.BASE_UXF_RECORDS.RECORD_PAGE;
          case 'Content Block':
              return this.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK;
          default:
              return this.CONSTANTS.BASE_UXF_RECORDS.BLANK_PAGE;
      }
  },
  _createContentBlocks: function(pageId, contentBlocks) {
      var _self = this;

      function createContentBlock(pageId, block, parent) {
          try {
              var payload = JSON.parse(JSON.stringify(block));
              payload.type = block.type.id;
              payload.parent = parent;
              payload.document_table = _self.TABLE.PAGE;
              payload.document_id = pageId;
              var blockId = _self._grRecordCreator(_self.TABLE.CONTENT_BLOCK, payload);

              block.children && block.children.forEach(function(child) {
                  createContentBlock(pageId, child, blockId);
              });
          } catch (e) {
              gs.error('ACEAppBuilderUtil: error creating content blocks: ' + e.toString());
          }
      }

      contentBlocks.forEach(function(block) {
          createContentBlock(pageId, block, null);
      });
  },
  addPageToExperience: function(pageName, pageTemplate, path, aceAppSysId) {
      var pageId = this._grRecordCreator(this.TABLE.PAGE, {
          'name': pageName,
          'ace_app': aceAppSysId,
          'template': pageTemplate,
          'path': path
      });
      return pageId;
  },
  createExperience: function(name, urlPath, landingPath, aceAppSysId, createDemoData, current) {
      var _self = this;
      if (createDemoData) this.CREATE_DEMO_DATA = true;
      var appConfig = this.GLOBAL._grRecordCreator(_self.UXF_TABLE.APP_CONFIG, {
          'name': name,
          'landing_path': landingPath
      });
      var experience = this.GLOBAL._grRecordCreator(_self.UXF_TABLE.PAGE_REGISTRY, {
          'title': name,
          'admin_panel_table': _self.UXF_TABLE.APP_CONFIG,
          'admin_panel': appConfig,
          'path': urlPath,
          'parent_app': _self.CONSTANTS.BASE_UXF_RECORDS.PARENT_APP,
          'root_macroponent': _self.CONSTANTS.BASE_UXF_RECORDS.APP_SHELL_UI
      });
      var pagePropertyPayload = {
          'page': experience,
          'name': 'ace_application_id',
          'type': 'string',
          'value': aceAppSysId,
          'description': 'ACE Record id for this Experience'
      };
      var chromeHeaderPagePropertyPayload = {
          'page': experience,
          'name': 'chrome_header',
          'type': 'json',
          'value': JSON.stringify(_self.CONSTANTS.CHROME_HEADER_VALUE),
      };
      this.GLOBAL._grRecordCreator(_self.UXF_TABLE.PAGE_PROPERTY, pagePropertyPayload);
      this.GLOBAL._grRecordCreator(_self.UXF_TABLE.PAGE_PROPERTY, chromeHeaderPagePropertyPayload);

      // Update the current variable directly here with required info
      var context = this._createFactories(appConfig, aceAppSysId, name);
      if (current) {
          current.app_config = appConfig;
          current.context = JSON.stringify(context);
          current.update();
      }

      var shippedPages = _self.CONSTANTS.DEFAULT_PAGES;
      shippedPages.forEach(function(pageInfo) {
          _self.addPageToExperience(pageInfo.name, pageInfo.template, pageInfo.path, aceAppSysId);
      });
      return appConfig;
  },

  constructNavMenuItems: function(menuItems, parentId) {
      var _self = this;
      menuItems.forEach(function(menuItem) {
          menuItem.label = menuItem.title;
          menuItem.id = menuItem.block_id;
          _self.constructNavMenuItems(menuItem.children);
      });
  },
  /*_getPageIdFromRoute: function(aceAppSysId, pageRoute) {
      if (!aceAppSysId || !pageRoute) return null;
      var gr = new GlideRecord(this.TABLE.PAGE);
      gr.addQuery('name', pageRoute);
      gr.addQuery('ace_app', aceAppSysId);
      gr.query();
      if (gr.next()) {
          return gr.getValue('sys_id');
      }
      return null;
  },*/
  _buildLink: function(gr) {
      var link = '';
      switch (gr.getValue('link_type')) {
          case 'guided_setup':
              link = '/$guided_setup.do#/content/' + gr.getValue('guided_setup_id');
              var focus_id = gr.getValue('foucus_id');
              if (focus_id != '') {
                  link += '?focus=' + focus_id;
              }
              break;
          case 'record':
              link = '/' + gr.getValue('list_table') + '.do?sys_id=' + gr.getValue('record');
              break;
          case 'list':
              link = '/' + gr.getValue('list_table') + '_list.do?sysparm_query=' + gr.getValue('list_query');
              break;
          case 'custom':
          default:
              link = gr.getValue('link_url');
      }
      return link;
  },
  _getPluginsInfo: function(plugins) {
      if (!plugins) return [];

      var pluginList = plugins.split(',');
      return pluginList.map(function(plugin) {
          var grPlugin = new GlideRecord('v_plugin');
          grPlugin.get(plugin);

          return {
              "id": plugin,
              "label": grPlugin.getValue('name'),
              "selected": true,
              "type": "plugin",
              "use_demo_data": false
          };
      });
  },
  _areAllPluginsActive: function(plugins) {
      if (!plugins) return false;

      var pluginList = plugins.split(',');
      return !pluginList.some(function(plugin) {
          var grPlugin = new GlideRecord('v_plugin');
          grPlugin.get(plugin);
          return grPlugin.getValue('active') !== 'active';
      });
  },
  _mapCards: function(registry) {
      var mappedCards = [];
      try {
          registry.card_order.forEach(function(parentCardId) {
              var screens = [];
              screens.push(registry.definitions[parentCardId]);
              registry.maps[parentCardId].forEach(function(childCardId) {
                  screens.push(registry.definitions[childCardId]);
              });
              mappedCards.push(screens);
          });

          return mappedCards;
      } catch (e) {
          return mappedCards;
      }
  },
  _parseSysProperties: function(propsSysIdList) {
      var props = [];
      try {
          var gr = new GlideRecord('sys_properties');
          gr.addQuery('sys_id', 'IN', propsSysIdList);
          gr.query();
          while (gr.next()) {
              props.push({
                  'name': gr.getValue('name'),
                  'value': gr.getValue('value') == 'true',
                  'description': gr.getValue('description')
              });
          }
          return props;
      } catch (e) {
          return props;
      }
  },
  syncSysProperties: function(propsList) {
      var _self = this;
      if (!propsList) return false;
      if (!Array.isArray(propsList)) return false;
      if (propsList.length == 0) return false;
      try {
          propsList.forEach(function(property) {
              if (_self.GLOBAL._getSysProperty(property.name) != property.value.toString())
                  _self.GLOBAL._updateSysProperty(property.name, property.value);
          });
          return true;
      } catch (e) {
          return false;
      }
  },
  _getChartConfig: function(gr) {
      try {
          var config = JSON.parse(gr.getValue('chart_config'));
          config.chart_type = gr.getValue('chart_type');
          return config;
      } catch (e) {
          return {
              "chart_type": "donut",
              "title": "Basic Chart",
              "table": "problem",
              "query": "active=true"
          };
      }
  },
  _isParentChildTypeEqual: function(gr) {
      if (!gr.parent) return false;
      return gr.parent.category.toString() == gr.category.toString() &&
          gr.parent.type.toString() == gr.type.toString();
  },
  _getGlideVarsForType: function(type) {
      var variables = [];
      var gr = new GlideRecord(this.TABLE.VARIABLE);
      gr.addQuery('model', type);
      gr.query();
      while (gr.next()) {
          variables.push({
              'element': gr.getValue('element'),
              'type': gr.getValue('internal_type')
          });
      }
      return variables;
  },
  _setGlideVars: function(table, recordId, glideVariableObject) {
      try {
          var gr = new GlideRecord(table);
          if (gr.get(recordId)) {
              for (var gVariable in glideVariableObject) {
                  gr.variable[gVariable] = glideVariableObject[gVariable];
              }
              return gr.update();
          }
          return false;
      } catch (e) {
          return false;
      }
  },
  _getContentBlocksRowCountOnly: function(customQuery) {
      try {
          return this._getContentBlocks('', customQuery).length;
      } catch (e) {
          return 0;
      }
  },
  getAllContentBlockTypes: function(table) {
      var types = {},
          _self = this;
      var gr = new GlideRecord(table);
      gr.query();
      while (gr.next()) {
          var typeId = gr.getUniqueValue();
          types[typeId] = {
              'group': gr.category.toString(),
              'name': gr.name.toString(),
              'id': typeId,
              'route': gr.route.toString(),
              'props': _self._getGlideVarsForType(typeId)
          };
      }
      return types;
  },
  _getDetailedType: function(typeSysId) {
      return this.ALL_CONTENT_BLOCK_TYPES[typeSysId];
  },
  _variableScriptEvaluator: function(gr, cbScriptVariables, cbData) {
      var _self = this;
      try {
          var evaluator = new GlideScopedEvaluator();
          evaluator.putVariable('pageId', cbScriptVariables.pageId);
          evaluator.putVariable('pageParams', cbScriptVariables.pageParams);
          var aceVarGr = new GlideRecord(this.TABLE.VARIABLE);
          aceVarGr.addQuery('model', cbData.type.id);
          aceVarGr.query();
          if (aceVarGr.next()) {
              var sysVarGr = new GlideRecord('sys_variable_value');
              sysVarGr.addQuery('document_key', cbData.block_id);
              sysVarGr.addQuery('variable', aceVarGr.getValue('sys_id'));
              sysVarGr.query();
              try {
                  if (sysVarGr.next())
                      return evaluator.evaluateScript(sysVarGr, 'value', null);
              } catch (e) {
                  gs.error('Error while evaluating Script: ' + e.toString());
              }
          }
      } catch (e) {
          gs.error('Error while evaluating Script: ' + e.toString());
      }
      return [];
  },
  _evaluateCBScriptVariables: function(gr, cbData, cbScriptVariables) {
      var _self = this;
      var typeObject = cbData.type;
      if (typeObject && typeObject.props && Array.isArray(typeObject.props) > 0) {
          typeObject.props.forEach(function(propItem) {
              if (propItem.type === "script")
                  cbData.props_details[propItem.element] = _self._variableScriptEvaluator(gr, cbScriptVariables, cbData);
          });
      }
  },
  _sanitize: function(value, parseBoolean) {
      if (value == null || value == undefined) return '';
      if (parseBoolean) {
          if (value.toString() == 'true') return '1';
          if (value.toString() == 'false') return '0';
      }
      return value.toString();
  },
  /* dataSource =  GlideRecord instance [OR] Cached Data*/
  _getContentBlockInfo: function(dataSource, cbId, pageParams) {
      var _self = this;
      try {
          var typeObject = _self._getDetailedType(_self._sanitize(dataSource.type));
          //id and label property is required for content tree component
          var contentBlockInfo = {
              'block_id': cbId,
              'id': cbId,
              'parent_block_id': dataSource.parent.toString(),
              'active': dataSource.active.toString() === 'true' ? true : false,
              'name': _self._sanitize(dataSource.name),
              'label': _self._sanitize(dataSource.name),
              'title': _self._sanitize(dataSource.title),
              'subtitle': _self._sanitize(dataSource.subtitle),
              'order': _self._sanitize(dataSource.order),
              'type': typeObject,
              'lazyLoad': _self._sanitize(dataSource.lazy_load, true),
              'children': [],
              'props_details': {},
              'props_details_display': {},
              'pageParams': pageParams
          };
          if (typeObject && typeObject.props && Array.isArray(typeObject.props) > 0) {
              typeObject.props.forEach(function(propItem) {
                  contentBlockInfo.props_details[propItem.element] = dataSource.variable[propItem.element].toString();
                  if (propItem.type == 'reference') {
                      var dictionary = new GlideRecord('sys_dictionary');
                      dictionary.addQuery('sys_id', _self._sanitize(dataSource.variable[propItem.element]));
                      dictionary.query();
                      if (dictionary.next()) {
                          contentBlockInfo.props_details_display[propItem.element] = dictionary.getValue('element');
                      }
                  }
              });
          }
          return contentBlockInfo;
      } catch (e) {
          gs.error('ACEAppBuilderUtil: Error while fetching content block info: ' + e.toString());
          return contentBlockInfo;
      }
  },
  _getExperienceIdFromCBId: function(cbId) {
      if (!cbId) return null;
      var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      if (gr.get(cbId)) {
          return gr.getValue('ace_config');
      }
      return null;
  },
  _getExperienceIdFromCBQuery: function(cbQuery) {
      if (!cbQuery || !cbQuery.document_table || !cbQuery.document_id) return null;
      var _self = this;
      switch (cbQuery.document_table) {
          case _self.TABLE.PAGE:
              var gr = new GlideRecord(_self.TABLE.PAGE);
              if (gr.get(cbQuery.document_id)) {
                  return gr.getValue('ace_app');
              }
              return null;
          case _self.TABLE.APP_CONFIG:
              return cbQuery.document_id;
          default:
              return null;
      }
  },
  _buildCachedContentBlocks: function(experienceId, cbQuery, parentId, cbScriptVariables) {
      var cachedContentBlocks = [],
          _self = this;

      //check for user roles access sn_ace.ace_user
      if (!this.CAN_READ) return cachedContentBlocks;

      //attach the parent query to the existing query. NOTE: For cached content, parent == '' IS_NOT_EQUAL parent == null
      if (!cbQuery) cbQuery = {};
      cbQuery['parent'] = parentId;
      cbQuery['active'] = '1';
      var cachedCBs = this.GLOBAL.getContentBlocksFromCache(experienceId, cbQuery);
      if (!cachedCBs) return null;

      //Try creating a Virtual GlideRecord instance using the parentId required for GlideScopeEvaluator. Re-use this for evaluation to reduce queries.
      var virtualGr = _self.VIRTUAL_GR || _self._getVirtualGrInstance(parentId);
      if (!_self.VIRTUAL_GR && virtualGr) _self.VIRTUAL_GR = virtualGr;

      cachedCBs.forEach(function(cachedCB) {
          var cbId = cachedCB.sys_id;

          //Re-try the Virtual GR creation again with cbId (since parentId is null) and register it in app level
          virtualGr = _self.VIRTUAL_GR || _self._getVirtualGrInstance(cbId);
          if (!_self.VIRTUAL_GR && virtualGr) _self.VIRTUAL_GR = virtualGr;

          if (_self._cachedScriptEvaluator(virtualGr, 'visibility', cachedCB.visibility, null)) {
              var cachedCBData = _self._getContentBlockInfo(cachedCB, cbId);
              _self._evaluateCBScriptVariables(virtualGr, cachedCBData, cbScriptVariables);
              cachedCBData.children = _self._buildCachedContentBlocks(experienceId, cbQuery, cbId, cbScriptVariables);
              _self._cachedScriptEvaluator(virtualGr, 'post_script', cachedCB.post_script, cachedCBData);
              cachedContentBlocks.push(cachedCBData);
          }
      });
      return cachedContentBlocks;
  },
  _getContentBlocksFromCache: function(parentId, cbQuery, cbScriptVariables) {
      try {
          //get and register the experience id in app level to reduce queries
          var experienceId = this.EXPERIENCE_ID || this._getExperienceIdFromCBId(parentId) || this._getExperienceIdFromCBQuery(cbQuery);
          if (!experienceId) return null;
          if (!this.EXPERIENCE_ID) this.EXPERIENCE_ID = experienceId;

          //try to fetch content blocks from cache
          var cachedContentBlocks = this._buildCachedContentBlocks(experienceId, cbQuery, parentId, cbScriptVariables);

          //if not present, try to build the cache for this experience
          if (!cachedContentBlocks || (Array.isArray(cachedContentBlocks) && cachedContentBlocks.length == 0)) {
              this.GLOBAL.buildExperienceCache(experienceId);

              //retry fetching the content blocks from the recenly built cache
              cachedContentBlocks = this._buildCachedContentBlocks(experienceId, cbQuery, parentId, cbScriptVariables);
          }

          return cachedContentBlocks;
      } catch (e) {
          return null;
      }
  },
  _getContentBlocks: function(parentId, customQuery, cbScriptVariables) {
      var contentBlocks = [];
      cbScriptVariables = cbScriptVariables || {};

      //fetch and return from cache if present
      // if (this.GLOBAL._isCacheEnabled()) {
      //   var cachedContentBlocks = this._getContentBlocksFromCache(parentId, customQuery, cbScriptVariables);
      //   if (cachedContentBlocks) return cachedContentBlocks;
      // }

      //if no cached content blocks present, use GlideRecord to query from table
      var _self = this;
      try {
          this.gr_counter += 1;
          var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
          gr.addQuery('parent', parentId);
          gr.addQuery('active', true);
          for (var q in customQuery) {
              gr.addQuery(q, customQuery[q]);
          }
          gr.orderBy('order');
          gr.query();
          while (gr.next()) {
              if (!_self._visibilityEvaluator(gr, cbScriptVariables.pageParams)) continue;
              var cbId = gr.getUniqueValue();
              var cbData = _self._getContentBlockInfo(gr, cbId, cbScriptVariables.pageParams);

              // content block variable script evaluator
              _self._evaluateCBScriptVariables(gr, cbData, cbScriptVariables);
              cbData.children = _self._getContentBlocks(cbId, customQuery, cbScriptVariables);

              //post script evaluator
              _self._evaluatePostScript(gr, cbData, cbScriptVariables.pageParams);

              contentBlocks.push(cbData);
          }
      } catch (e) {
          gs.error('ACEAppBuilderUtil: Error while fetching content blocks: ' + e.toString());
      }
      return contentBlocks;
  },
  _getFetchMode: function(cbQuery) {

      //Experience level System Property value (ace.content_blocks.fetch.mode.<EXPERIENCE ID>)

      var experienceId = this._getExperienceIdFromCBQuery(cbQuery);
      if (!experienceId) return this.DEFAULT_FETCH_MODE;
      var mode = gs.getProperty('ace.content_blocks.fetch.mode.' + experienceId, 'dfs');
      mode = mode.toLowerCase();
      if (this.SUPPORTED_FETCH_MODES.indexOf(mode) != -1) return mode;
      return this.DEFAULT_FETCH_MODE;

  },
  getContentBlocks: function(pageId, pageParams) {
      var _self = this;
      if (!this.CAN_READ) return [];
      var cbQuery = {
          'document_table': _self.TABLE.PAGE,
          'document_id': pageId
      };
      var cbPageParams = {
          'pageId': pageId,
          'pageParams': pageParams
      };
      var fetchMode = this._getFetchMode(cbQuery);
      gs.info('Admin Experience Framework Content Blocks fetching mode: ' + fetchMode);
      if (fetchMode === this.DEFAULT_FETCH_MODE) {
          return this._getContentBlocks(null, cbQuery, cbPageParams);
      }
      return this._getContentBlocksBFS(null, cbQuery, cbPageParams);
  },

  getAllContentBlocksBFS: function() {
      return this._getContentBlocksBFS(null, {}, {});
  },

  _appendContentBlockschildren: function(contentBlocksMap, cbId, cbContextObj) {
      if (!contentBlocksMap[cbId]) return [];
      var result = [];
      contentBlocksMap[cbId].forEach(function(cbChildId) {
          if (cbContextObj[cbChildId]) {
              result.push(JSON.parse(JSON.stringify(cbContextObj[cbChildId])));
              //delete cbContextObj[cbChildId];   
          }
      });
      return result;
  },
  _processContentBlocksFromIds: function(contentBlocksMap, contentBlockParentIds, cbContextObj, cbScriptVariables, cbLevel, nextCBLevel) {
      var _self = this;
      this.gr_counter += 1;
      var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      if (!contentBlockParentIds) gr.addQuery('parent', '');
      else gr.addQuery('parent', 'IN', contentBlockParentIds.toString());
      gr.orderBy('order');
      gr.query();
      while (gr.next()) {
          var cbId = gr.getUniqueValue().toString();
          if (cbLevel[nextCBLevel] && cbLevel[nextCBLevel].indexOf(cbId) == -1) continue;
          var cbData = _self._getContentBlockInfo(gr, cbId, cbScriptVariables.pageParams);
          // content block variable script evaluator  
          _self._evaluateCBScriptVariables(gr, cbData, cbScriptVariables);
          //append children   
          cbData.children = _self._appendContentBlockschildren(contentBlocksMap, cbId, cbContextObj);
          //post script evaluator 
          _self._evaluatePostScript(gr, cbData, cbScriptVariables.pageParams);
          cbContextObj[cbId] = cbData;
      }
  },
  _buildContentBlockTreeFromLevels: function(contentBlocksMap, cbLevel, cbScriptVariables) {
      //loop through last-but-one level to 0  
      var _self = this;
      var cbContextObj = {};
      var contentBlocks = [];
      for (var level = (cbLevel.length - 2); level >= 0; level--) {
          var contentBlockParentIds = cbLevel[level];
          _self._processContentBlocksFromIds(contentBlocksMap, contentBlockParentIds, cbContextObj, cbScriptVariables, cbLevel, (level + 1));
      }
      _self._processContentBlocksFromIds(contentBlocksMap, '', cbContextObj, cbScriptVariables, cbLevel, 0);
      //if only root level content blocks are present 
      if (!contentBlockParentIds) contentBlockParentIds = cbLevel[0];
      contentBlockParentIds.forEach(function(contentBlockParentId) {
          contentBlocks.push(cbContextObj[contentBlockParentId]);
      });
      return contentBlocks;
  },
  _buildContentBlockLevels: function(cbMap, cbIds, cbLevelObject, level) {
      if (!cbLevelObject[level]) cbLevelObject[level] = [];
      var _self = this;
      cbIds.forEach(function(blockId) {
          cbLevelObject[level].push(blockId);
          if (cbMap[blockId] && cbMap[blockId].length > 0) {
              _self._buildContentBlockLevels(cbMap, cbMap[blockId], cbLevelObject, (level + 1));
          }
      });
  },
  _getValidCBIds: function(base, blacklist) {
      var validIds = [];
      base.forEach(function(cbId) {
          if (blacklist.indexOf(cbId) == -1) validIds.push(cbId);
      });
      return validIds;
  },
  _visibilityCheck: function(cbLevels, cbLevelIndex, blacklistedCBIds, cbScriptVariables) {
      if (cbLevelIndex == cbLevels.length && cbLevelIndex >= 0) {
          return cbLevels;
      }
      this.gr_counter += 1;
      var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      gr.addQuery('active', true);
      if (cbLevelIndex < 0) {
          gr.addQuery('sys_id', 'IN', cbLevels[0]);
      } else {
          var validIds = this._getValidCBIds(cbLevels[cbLevelIndex], blacklistedCBIds);
          if (validIds.length === 0) return cbLevels;
          gr.addQuery('parent', 'IN', validIds.toString());
      }
      gr.orderBy('order');
      gr.query();
      var cbs = [];
      while (gr.next()) {
          var cbId = gr.getUniqueValue().toString();
          if (!this._visibilityEvaluator(gr, cbScriptVariables.pageParams)) {
              blacklistedCBIds.push(cbId);
          } else {
              cbs.push(cbId);
          }
      }
      if (cbs.length)
          cbLevels[cbLevelIndex + 1] = cbs;
      return this._visibilityCheck(cbLevels, (cbLevelIndex + 1), blacklistedCBIds, cbScriptVariables);
  },
  /** 
   * BFS Approach:    
   * Step 1: Get ALL the content blocks of a page orderby 'order' 
   * Step 2: Build the parent-child sys_id relationship map. Example: 
   *      {   
   *          '_root': ['cb_id#1', 'cb_id#2', 'cb_id#3'], 
   *          'cb_id#1': ['cb_id#1.child#1', 'cb_id#1.child#2', 'cb_id#1.child#3'],   
   *          'cb_id#2': ['cb_id#2.child#1', 'cb_id#2.child#2'],  
   *          'cb_id#3': ['cb_id#3.child#1', 'cb_id#3.child#2', 'cb_id#3.child#3', 'cb_id#3.child#4'],    
   *          'cb_id#1.child#1': ['cb_id#1.child#1.child#1', ...],    
   *          ... 
   *      }   
   * Step 3: Build the step-level hierarchy of the map. Example:  
   *      [   
   *          ['cb_id#1', 'cb_id#2', 'cb_id#3'],  
   *          ['cb_id#1.child#1', 'cb_id#1.child#2', 'cb_id#1.child#3', 'cb_id#2.child#1', 'cb_id#2.child#2', 'cb_id#3.child#1', 'cb_id#3.child#2', 'cb_id#3.child#3', 'cb_id#3.child#4'],    
   *          ['cb_id#1.child#1.child#1', ...],   
   *          ... 
   *      ]   
   * Step 4: Using a top-down approach evaluate the visibility script and modify the step-level hierarchy.    
   *      Example: For, 'cb_id#2' = false 
   *      [   
   *          ['cb_id#1', 'cb_id#3'], 
   *          ['cb_id#1.child#1', 'cb_id#1.child#2', 'cb_id#1.child#3', 'cb_id#3.child#1', 'cb_id#3.child#2', 'cb_id#3.child#3', 'cb_id#3.child#4'],  
   *          ['cb_id#1.child#1.child#1', ...],   
   *          ... 
   *      ]   
   * Step 5: Using a bottom-up approach evaluate the post script and build the Content Block contract 
   */
  _getContentBlocksBFS: function(parentId, customQuery, cbScriptVariables) {
      try {
          var contentBlocksMap = {
              '_root': []
          };
          this.gr_counter += 1;
          //Step 1,2: get all content blocks and build the parent-child relationship map of their sys_ids 
          var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
          for (var q in customQuery) {
              gr.addQuery(q, customQuery[q]);
          }

          if (parentId) {
              gr.addQuery('parent', parentId);
          }
          gr.orderBy('order');
          gr.query();
          while (gr.next()) {
              var blockId = gr.getUniqueValue();
              var parentBlockId = gr.getValue('parent');
              if (!parentBlockId) contentBlocksMap._root.push(blockId);
              else {
                  if (!contentBlocksMap[parentBlockId]) contentBlocksMap[parentBlockId] = [];
                  contentBlocksMap[parentBlockId].push(blockId);
              }
          }
          var contentBlocksLevel = [];
          //Step 3: build a 2D array of the step-level hierarchy  
          this._buildContentBlockLevels(contentBlocksMap, contentBlocksMap._root, contentBlocksLevel, 0);
          //Step 4: modify the same 2D array of the step-level hierarchy after removing visible false items   
          contentBlocksLevel = this._visibilityCheck(contentBlocksLevel, -1, [], cbScriptVariables);
          //Step 5: build the content block contract  
          var contentBlocks = this._buildContentBlockTreeFromLevels(contentBlocksMap, contentBlocksLevel, cbScriptVariables);
          return contentBlocks;
      } catch (e) {
          gs.error('ACEAppBuilderUtil: Error while fetching content blocks: ' + e.toString());
          return [];
      }
  },
  getContentBlockPayload: function(cbId, pageParams, includeChildBlocks) {
      var cbData = {};
      var cbScriptVariables = {
          'pageParams': pageParams
      };
      if (!this.CAN_READ) return cbData;
      var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      if (cbId && gr.get(cbId)) {
          // TODO: Should we check for visibility in lazy load again?


          cbData = this._getContentBlockInfo(gr, cbId, pageParams);
          this._evaluateCBScriptVariables(gr, cbData, cbScriptVariables);

          if (includeChildBlocks) {
              // content block variable script evaluator
              cbData.children = this._getContentBlocks(cbId, {}, cbScriptVariables);

              //post script evaluator
              _self._evaluatePostScript(gr, cbData, pageParams);
          }

          this._evaluateLazyScript(gr, cbData, pageParams);
      }
      return cbData;
  },
  _getUrlPath: function(urlPath, route) {
      return '/now' + '/' + urlPath + '/' + route.toLowerCase();
  },
  getMenuItems: function(aceAppId, moduleSysId, routeParams) {
      var _self = this;
      var menuItems = [];
      try {
          menuItems = _self._getContentBlocks(moduleSysId, {
              'document_table': _self.TABLE.APP_CONFIG,
              'document_id': aceAppId,
              'type': _self.CONSTANTS.CONTENT_BLOCK_TYPES.NAV
          }, {
              pageParams: routeParams
          });

          _self.constructNavMenuItems(menuItems);
          return menuItems;
      } catch (e) {
          return menuItems;
      }
  },

  getFieldsAndParams: function(routeId) {
      var routeGr = new GlideRecord('sys_ux_app_route');
      var result = {
          fields: [],
          params: []
      };
      if (routeGr.get(routeId)) {
          var fields = routeGr.getValue('fields') || '';
          result.fields = fields.split(',').filter(function(field) {
              return field;
          }).map(function(field) {
              return {
                  name: field
              };
          });
          var params = routeGr.getValue('optional_parameters') || '';
          result.params = params.split(',').filter(function(param) {
              return param;
          }).map(function(param) {
              return {
                  name: param
              };
          });
      }
      return result;
  },

  getModulePath: function(urlPath, modulePath, routeId, page_parameters) {
      var _self = this;
      var path = _self._getUrlPath(urlPath, modulePath);
      var uxfPageParams = _self.getFieldsAndParams(routeId);
      if (page_parameters) {
          page_parameters = JSON.parse(page_parameters);
          uxfPageParams.fields.concat(uxfPageParams.params).forEach(function(param) {
              var name = param.name;
              if (page_parameters[name])
                  path = path + '/' + page_parameters[name].toString();
          });
      }
      return path;
  },

  getModules: function(aceAppId, urlPath, routeParams) {
      var _self = this;
      if (!this.CAN_READ) return [];
      var modules = this._getContentBlocks(null, {
          'document_table': this.TABLE.APP_CONFIG,
          'document_id': aceAppId,
          'type': this.CONSTANTS.CONTENT_BLOCK_TYPES.MODULE
      });

      //massage modules blocks to support UI
      //TODO: Make UI in sync with content_blocks contract
      modules.forEach(function(module) {
          module.isActive = false;
          var containedPages = module.props_details.contained_pages;
          module.props_details.contained_pages = containedPages ? _self.getContainedPagesRoutes(containedPages) : containedPages;
          module.menu = _self.getMenuItems(aceAppId, module.block_id, routeParams);
          // Module Page Route Should be atken from prop details
          var pageId = module.props_details.ace_page;
          var gr = new GlideRecord(_self.TABLE.PAGE);
          if (pageId && gr.get(pageId)) {
              module.page_route = gr.getValue('path');
              module.path = _self.getModulePath(urlPath, module.page_route, gr.route.toString(), module.props_details.page_parameters);
          } else if (module.props_details.link) module.path = module.props_details.link;
          else gs.addErrorMessage(gs.getMessage("Please provide input for either acepage or link fields"));
      });

      return modules;

  },

  getContainedPagesRoutes: function(containedPages) {
      var _self = this;
      var contained_pages_routes = [];
      var ace_page_gr = new GlideRecord(_self.TABLE.PAGE);
      ace_page_gr.addQuery('sys_id', 'IN', containedPages);
      ace_page_gr.query();
      while (ace_page_gr.next()) {
          contained_pages_routes.push(ace_page_gr.getValue('path'));
      }
      return contained_pages_routes;
  },

  getURLPath: function(uxAppConfigId) {
      var url_path = '';
      var sys_ux_page_registry_gr = new GlideRecord(this.CONSTANTS.UXF_TABLES.PAGE_REGISTRY);
      sys_ux_page_registry_gr.addQuery('admin_panel', uxAppConfigId);
      sys_ux_page_registry_gr.query();
      if (sys_ux_page_registry_gr.next()) {
          url_path = sys_ux_page_registry_gr.getValue('path');
      }
      return url_path;
  },

  getAceAppData: function(aceAppConfig, routeParams) {
      var _self = this;
      var onLoadData = {
          'modules': []
      };
      try {
          var gr = new GlideRecord(this.TABLE.APP_CONFIG);
          if (gr.get(aceAppConfig)) {
              onLoadData.modules = _self.getModules(aceAppConfig, _self.getURLPath(gr.getValue('app_config')), routeParams);
              onLoadData.isSearchConfigured = _self.fetchSearchConfig(aceAppConfig) ? true : false;
          }
          return onLoadData;
      } catch (e) {
          return onLoadData;
      }
  },
  _createDemoBlocksInPage: function(pageId) {
      var _self = this;
      this._createContentBlocks(pageId, this.demoUtils.getDemoTemplate());
  },
  _createDemoData: function(pageName, pageId, moduleId) {
      // TODO: Add this demo data while creating the app
      if (!this.CREATE_DEMO_DATA) return;
  },

  _createFactories: function(appConfigId, aceAppSysId, appName) {
      var contentOutput = this.createPage(appConfigId, appName + ' - ACE Content Factory', aceAppSysId, 'Content Factory');
      var containerOutput = this.createPage(appConfigId, appName + ' - ACE Container Factory', aceAppSysId, 'Container Factory');

      return this._patchFactories(contentOutput.macroponent, containerOutput.macroponent);
  },

  _deleteUXFPage: function(routeId) {
      // TODO: Replace this method with UXF apis in app builder
      var routeGr = new GlideRecord(this.UXF_TABLE.APP_ROUTE);
      if (!routeGr.get(routeId)) return;

      var screenTypeGr = new GlideRecord(this.UXF_TABLE.SCREEN_TYPE);
      if (!screenTypeGr.get(routeGr.getValue('screen_type'))) return;

      var screenGr = new GlideRecord(this.UXF_TABLE.SCREEN);
      screenGr.addQuery('screen_type', routeGr.getValue('screen_type'));
      screenGr.query();
      while (screenGr.next()) {
          this.GLOBAL.deleteRecord(this.UXF_TABLE.SCREEN, screenGr.getUniqueValue());
      }

      var macroponentGr = new GlideRecord(this.UXF_TABLE.MACROPONENT);
      if (!macroponentGr.get(screenGr.getValue('macroponent'))) return;

      this.GLOBAL.deleteRecord(this.UXF_TABLE.APP_ROUTE, routeGr.getUniqueValue());
      this.GLOBAL.deleteRecord(this.UXF_TABLE.SCREEN_TYPE, screenTypeGr.getUniqueValue());
      this.GLOBAL.deleteRecord(this.UXF_TABLE.MACROPONENT, macroponentGr.getUniqueValue());
  },
  _createContentBlock: function(configId, inputDocId, templateId, documentTable, searchPageId) {
      var _self = this;
      var cbGr = new GlideRecord(_self.TABLE.CONTENT_BLOCK);
      cbGr.addQuery('document_id', templateId);
      cbGr.addQuery('document_table', documentTable);
      cbGr.query();
      while (cbGr.next()) {
          var props_details = {};
          var props = _self._getGlideVarsForType(cbGr.type.toString());
          if (props && Array.isArray(props) > 0) {
              props.forEach(function(propItem) {
                  props_details[propItem.element] = cbGr.variable[propItem.element].toString();
              });
          }
          var cbId = _self._grRecordCreator(_self.TABLE.CONTENT_BLOCK, {
              name: cbGr.getValue('name'),
              title: cbGr.getValue('title'),
              subtitle: cbGr.getValue('subtitle'),
              order: cbGr.getValue('order'),
              type: cbGr.type,
              parent: cbGr.parent,
              post_script: cbGr.getValue('post_script'),
              visibility: cbGr.getValue('visibility'),
              active: cbGr.getValue('active'),
              ace_config: configId,
              document_id: inputDocId,
              document_table: documentTable
          });
          if (searchPageId && cbGr.getValue('name') === _self.CONSTANTS.SEARCH_CONTENT_BLOCK) {
              props_details['page'] = searchPageId;
          }
          _self._setGlideVars(_self.TABLE.CONTENT_BLOCK, cbId, props_details);
      }
  },
  createContentBlock: function(configId, pageId) {
      var _self = this;
      if (!this.CAN_READ) return;
      var baseGr = new GlideRecord(_self.TABLE.PAGE);
      if (pageId && baseGr.get(pageId)) {
          var templatePageId = baseGr.getValue('template');
          _self._createContentBlock(configId, pageId, templatePageId, _self.TABLE.PAGE);
      }
  },
  _patchPage: function(aceAppId, pageId, macroponentId, screenType) {
      var context = {};
      if (!this.CAN_READ) return null;
      var gr = new GlideRecord(this.TABLE.APP_CONFIG);
      if (gr.get(aceAppId)) {
          context = JSON.parse(gr.getValue('context'));
      }
      if (!this._updateMacroponentProp(macroponentId, 'data', this.CONSTANTS.BASE_UXF_RECORDS.SECTIONS_DATA_BROKER_NAME, 'page_route', pageId)) {
          //code logic
      }

      // FIXME: there are multiple updates to macroponent here.. Consolidate it into one
      try {

          // Update content block factory sysIds in each page composition
          var macroGr = new GlideRecord(this.UXF_TABLE.MACROPONENT);
          if (macroGr.get(macroponentId)) {
              var composition = macroGr.getValue('composition');
              var newComposition = this._replaceAll(composition, this.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT, context.content_block_id);

              this.GLOBAL.updateRecord(this.UXF_TABLE.MACROPONENT, macroponentId, {
                  composition: newComposition
              });
          }

          // Update route field on ace_page record if screenType is supplied
          if (screenType) {
              var routeGr = new GlideRecord(this.UXF_TABLE.APP_ROUTE);
              routeGr.addQuery('screen_type', screenType);
              routeGr.query();

              if (routeGr.next()) {
                  var pageGr = new GlideRecord(this.TABLE.PAGE);
                  if (pageGr.get(pageId) && pageGr.canWrite()) {
                      pageGr.setValue('route', routeGr.getUniqueValue());
                      pageGr.update();
                  }
              }
          }

          // FIXME: These record updates maynot work from a different scope. Try to use Global update script include

      } catch (e) {
          return null;
      }
  },

  updateAppConfigForSubPagesRoutes: function(screenType, contentBlockId) {
      var _self = this;
      var screen = new GlideRecord(this.UXF_TABLE.SCREEN);
      screen.get('screen_type', screenType);
      var appConfig = screen.app_config;

      var route_gr = new GlideRecord(this.UXF_TABLE.APP_ROUTE);
      route_gr.addQuery('parent_macroponent', contentBlockId);
      route_gr.addQuery('app_config', this.CONSTANTS.BASE_UXF_RECORDS.ACE_BASE_EXPERIENCE_APP_CONFIG);
      route_gr.query();
      while (route_gr.next()) {
          var route_id = route_gr.sys_id;
          this.GLOBAL.updateRecord(_self.UXF_TABLE.APP_ROUTE, route_id, {
              'app_config': appConfig
          });
      }
  },

  getContentBlockSubPages: function(appName) {
      var self = this;
      var sub_pages_list = [];
      var clone_macroponent_routes = self.CONSTANTS.CLONE_ROUTES;
      var content_block_routes_gr = new GlideRecord(self.UXF_TABLE.APP_ROUTE);
      content_block_routes_gr.addQuery('app_config', self.CONSTANTS.BASE_UXF_RECORDS.ACE_BASE_EXPERIENCE_APP_CONFIG);
      content_block_routes_gr.addQuery('parent_macroponent', self.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT);
      content_block_routes_gr.query();
      while (content_block_routes_gr.next()) {
          var cloneMacroponent = clone_macroponent_routes.some(function(route) {
              return route === content_block_routes_gr.getValue('route_type');
          });
          var screen_gr = new GlideRecord(self.UXF_TABLE.SCREEN);
          screen_gr.get('screen_type', content_block_routes_gr.getValue('screen_type'));
          sub_pages_list.push({
              MACROPONENT: screen_gr.getValue('macroponent'),
              SCREEN: screen_gr.getValue('sys_id'),
              SCREEN_TYPE: content_block_routes_gr.getValue('screen_type'),
              NAME: appName + " - " + content_block_routes_gr.getValue('name'),
              ROUTE: content_block_routes_gr.getValue('route_type'),
              CLONE_MACROPONENT: cloneMacroponent
          });
      }
      return sub_pages_list;
  },

  _cloneMacroponentForSubpage: function(baseMacroponentId, subPageName, appContentBlockId) {
      var self = this;
      var content_block = self._getConstantsForPage('Content Block');
      var macroponentCloneInfo = {
          'sys_id': baseMacroponentId,
          'fields': self.CONSTANTS.CLONE_FIELDS.MACROPONENT
      };

      // create new subpage macroponent
      var newMacroponentId = self.GLOBAL._grRecordCreator(self.UXF_TABLE.MACROPONENT, {
          'name': subPageName
      }, macroponentCloneInfo);
      var macroponentGr = new GlideRecord(self.UXF_TABLE.MACROPONENT);
      if (!macroponentGr.get(newMacroponentId)) {
          return;
      }

      // patch it with base composition
      self.GLOBAL.updateRecord(self.UXF_TABLE.MACROPONENT, newMacroponentId, {
          'composition': self._replaceAll(macroponentGr.getValue('composition'), content_block.MACROPONENT, appContentBlockId)
      });

      // ensure success
      macroponentGr = new GlideRecord(self.UXF_TABLE.MACROPONENT);
      if (!macroponentGr.get(newMacroponentId)) {
          return;
      }

      return macroponentGr;
  },

  _updateNewMacroponentWithScripts: function(newMacroponentGr, clientScriptMaps, newMacroponentId) {
      var self = this;
      var postUpdate = {
          'composition': newMacroponentGr.getValue('composition'),
          'data': newMacroponentGr.getValue('data'),
          'state_properties': newMacroponentGr.getValue('state_properties'),
          'internal_event_mappings': newMacroponentGr.getValue('internal_event_mappings'),
      };

      clientScriptMaps.forEach(function(clientScriptMap) {
          postUpdate.composition = self._replaceAll(
              postUpdate.composition,
              clientScriptMap.findId,
              clientScriptMap.replaceWithId
          );
          postUpdate.data = self._replaceAll(
              postUpdate.data,
              clientScriptMap.findId,
              clientScriptMap.replaceWithId
          );
          postUpdate.state_properties = self._replaceAll(
              postUpdate.state_properties,
              clientScriptMap.findId,
              clientScriptMap.replaceWithId
          );
          postUpdate.internal_event_mappings = self._replaceAll(
              postUpdate.internal_event_mappings,
              clientScriptMap.findId,
              clientScriptMap.replaceWithId
          );
      });

      self.GLOBAL.updateRecord(self.UXF_TABLE.MACROPONENT, newMacroponentId, postUpdate);
  },

  _createScreenTypeForSubPage: function(subPage) {
      var self = this;
      var screenTypeCloneInfo = {
          'sys_id': subPage.SCREEN_TYPE,
          'fields': self.CONSTANTS.CLONE_FIELDS.SCREEN_TYPE
      };
      var newScreenTypeId = self.GLOBAL._grRecordCreator(self.UXF_TABLE.SCREEN_TYPE, {
              'name': subPage.NAME
          },
          screenTypeCloneInfo);

      return newScreenTypeId;
  },

  _createScreenForSubPage: function(subPage, newMacroponentId, appConfig, newScreenTypeId, appContentBlockId) {
      var self = this;
      var screenCloneInfo = {
          'sys_id': subPage.SCREEN,
          'fields': self.CONSTANTS.CLONE_FIELDS.UX_SCREEN
      };

      var screenPayload = {
          'name': subPage.NAME,
          'macroponent': newMacroponentId,
          'app_config': appConfig,
          'screen_type': newScreenTypeId,
          'parent_macroponent': appContentBlockId
      };

      self.GLOBAL._grRecordCreator(self.UXF_TABLE.SCREEN, screenPayload, screenCloneInfo);
  },

  _patchScreenCollectionToRoute: function(subPage, appContentBlockId, appConfig, newScreenTypeId) {
      var self = this;
      var route_gr = new GlideRecord(self.UXF_TABLE.APP_ROUTE);
      route_gr.addQuery('route_type', subPage.ROUTE);
      route_gr.addQuery('parent_macroponent', appContentBlockId);
      route_gr.addQuery('app_config', appConfig);
      route_gr.query();
      if (route_gr.next()) {
          var route_id = route_gr.sys_id;
          self.GLOBAL.updateRecord(self.UXF_TABLE.APP_ROUTE, route_id, {
              'screen_type': newScreenTypeId
          });
      }
  },

  _cloneMacroponentAndScripts: function(baseMacroponent, subPageName, appContentBlockId) {
      var self = this;

      // MACROPONENT CREATION
      var newMacroponentGr = self._cloneMacroponentForSubpage(baseMacroponent, subPageName, appContentBlockId);
      if (!newMacroponentGr) {
          return;
      }
      var newMacroponentSysId = newMacroponentGr.getValue('sys_id');

      // CLONE CLIENT SCRIPTS
      var clientScriptMaps = self._cloneClientScripts(baseMacroponent, newMacroponentSysId);

      // POST UPDATE MACROPONENT
      self._updateNewMacroponentWithScripts(newMacroponentGr, clientScriptMaps, newMacroponentSysId);

      return newMacroponentSysId;
  },

  cloneSubPages: function(appName, appConfig, appContentBlockId, subPages) {
      // TODO: appName is not used in this method but do not remove it here unless you are going to refactor the
      // various callers using this method signature through UIB/UX tranform script, etc.
      var self = this;
      try {
          subPages.forEach(function(subPage) {
              if (!subPage) {
                  return;
              }

              // OPTIONAL MACROPONENT CREATION
              var newMacroponentId = subPage.MACROPONENT; // initialize to base macroponent
              if (subPage.CLONE_MACROPONENT) {
                  newMacroponentId = self._cloneMacroponentAndScripts(subPage.MACROPONENT, subPage.NAME, appContentBlockId);
              }

              //SCREEN_TYPE CREATION
              var newScreenTypeId = self._createScreenTypeForSubPage(subPage);

              //SCREEN CREATION
              self._createScreenForSubPage(subPage, newMacroponentId, appConfig, newScreenTypeId, appContentBlockId);

              // PATCH SCREEN COLLECTION TO ROUTE
              self._patchScreenCollectionToRoute(subPage, appContentBlockId, appConfig, newScreenTypeId);
          });
      } catch (e) {
          gs.error("ACEAppBuilderUtil: Failed to create content block subpages: " + e.toString());
      }
  },

  fixMissingSubPages: function(aceBaseCbSubpages, appCbSubpages, appContentBlockId, appConfigSysId) {
      try {

          var self = this;
          var appSubpagesMap = {};
          for (var i = 0; i < appCbSubpages.length; i++) {
              appSubpagesMap[appCbSubpages[i].ROUTE] = appCbSubpages[i];
          }

          var subpagesWithMissingMacroponent = aceBaseCbSubpages.filter(function(baseSubpage) {
              var appSubpage = appSubpagesMap[baseSubpage.ROUTE];
              if (!appSubpage) {
                  return false;
              }

              // for subpages that are flagged to have their macroponents cloned,
              // if both base and app subpage point to the same macroponent, then the macroponent is missing
              return appSubpage.CLONE_MACROPONENT && baseSubpage.MACROPONENT === appSubpage.MACROPONENT;
          });

          subpagesWithMissingMacroponent.forEach(function(baseSubpage) {
              var newMacroponentId = self._cloneMacroponentAndScripts(baseSubpage.MACROPONENT, baseSubpage.NAME, appContentBlockId);
              var appSubpage = appSubpagesMap[baseSubpage.ROUTE];
              appSubpage.MACROPONENT = newMacroponentId;
              self.syncScreenWithBase(baseSubpage, appSubpage, appConfigSysId);
          });
      } catch (e) {
          gs.error('ACEAppBuilderUtil: Failed to fix missing content block subpages: ' + e.toString());
      }
  },

  getPropertiesPayload: function(propertyIds) {
      var supportedTypes = this.CONSTANTS.SUPPORTED_PROPERTIES_TYPES;
      return this.GLOBAL.getPropertiesPayload(propertyIds, supportedTypes);
  },
  getControlCardQuery: function(table) {
      //Example query: name=incident^internal_type=string^ORinternal_type=string_full_utf8^ORinternal_type=integer^ORinternal_type=choice^ORinternal_type=boolean
      var query = 'name=' + table + '^';
      var supportedFields = this.CONSTANTS.SUPPORTED_PROPERTIES_TYPES;
      query += supportedFields.map(function(f, index) {
          return (index > 0 ? '^OR' : '') + 'internal_type=' + f;
      }).join('');
      return query;
  },
  getAnalyticsCardQuery: function(tableName) {

      var query = 'name=' + tableName + '^';
      var tableGr = new GlideRecord('sys_db_object');
      tableGr.addQuery('name', tableName);
      tableGr.query();
      if (tableGr.next()) {
          var superClass = tableGr.getValue('super_class');
          if (superClass) {
              var superClassTableGr = new GlideRecord('sys_db_object');
              if (superClassTableGr.get(superClass)) {
                  query += 'OR' + 'name=' + superClassTableGr.getValue('name') + '^';
              }
          }
      }
      query += 'internal_type!=collection^ORinternal_type=NULL';
      return query;

  },

  getControl: function(contentBlockPayload) {
      var result = [];
      var _self = this;
      try {
          contentBlockPayload.forEach(function(control) {
              if (!control.props_details) {
                  return;
              }

              var controlType = control.props_details.control_type;

              if (controlType === 'button') {
                  var buttonLabel = control.props_details.button_label;
                  var buttonEventName = control.props_details.button_event_name;

                  var payload = {
                      'title': control.title,
                      'subtitle': control.subtitle,
                      'type': control.props_details.control_type,
                      'button_data': {
                          label: buttonLabel,
                          event_name: buttonEventName
                      }
                  };

                  result.push(payload);
              } else if (controlType === 'record') {
                  if (control.props_details.table && control.props_details.record && control.props_details.field) {
                      var table = control.props_details.table;
                      var recordId = control.props_details.record;
                      var fieldName = control.props_details.field;
                      var helperText = control.props_details.helper_text;
                      var placeHolderText = control.props_details.placeholder_text || '';
                      var isDisabled = (control.props_details.disabled === true);
                      var payload = {
                          'title': control.title,
                          'subtitle': control.subtitle,
                          'type': control.props_details.control_type,
                          'record_data': {}
                      };

                      if (fieldName) {
                          payload.record_data.field_name = fieldName;
                          var recordDetails = _self.GLOBAL.getRecord(table, recordId, [payload.record_data.field_name]);
                          if (recordDetails[payload.record_data.field_name] != undefined) {
                              payload.record_data.field_type = recordDetails.__internal_type;
                              payload.record_data.sys_id = recordId;
                              payload.record_data.table = table;
                              payload.record_data.old_value = recordDetails[payload.record_data.field_name];
                              payload.record_data.read_only = isDisabled || !(recordDetails.__canWrite && recordDetails.__isInSelectedScope);
                              if (helperText) {
                                  payload.record_data.helper_text = helperText;
                              }

                              if (placeHolderText) {
                                  payload.record_data.placeholder_text = placeHolderText;
                              }

                              if (recordDetails.__choices)
                                  payload.record_data.choices = recordDetails.__choices;
                          }
                      }
                      result.push(payload);
                  }
              }
          });
          return result;
      } catch (e) {
          return result;
      }
  },

  _updateRecord: function(table, recordId, input) {
      try {
          return this.GLOBAL.updateRecord(table, recordId, input);
      } catch (e) {
          gs.error('ACEAppBuilderUtil: Error: ' + e.toString());
          return false;
      }
  },
  setControl: function(payload) {
      try {
          var updated = true,
              _self = this;
          for (var recordId in payload) {
              var record = payload[recordId];
              if (record.new_value != record.old_value) {
                  var input = {};
                  input[record.field_name] = record.new_value;
                  updated = _self._updateRecord(record.table, recordId, input) && updated;
              }
          }
          return updated;
      } catch (e) {
          return false;
      }
  },
  createAiSearchConfig: function(appConfigId, searchConfigBlockId) {
      var searchSourceId, searchProfileId, searchApplicationId, appName, searchContextContentBlockId;
      //fetching app name
      if (!this.CAN_READ) return 3;
      var gr = new GlideRecord(this.TABLE.APP_CONFIG);
      if (gr.get(appConfigId)) {
          appName = gr.getValue('name');
      }

      if (this.GLOBAL._isAisEnabled()) {
          var dataSource = this.GLOBAL._grGetKeyDataFromTable('ais_datasource', this.AIS.searchIndex, 'source');

          if (!this.GLOBAL.AISAlreadyExists(appName)) {
              if (dataSource != null) {
                  searchSourceId = this.GLOBAL._grRecordCreator('ais_search_source', {
                      'name': appName + this.AIS.source,
                      'datasource': dataSource,
                      'condition': 'ace_config=' + appConfigId + ''
                  }, null, true);
              }
              if (searchSourceId != null) {
                  searchProfileId = this.GLOBAL._grRecordCreator('ais_search_profile', {
                      'label': appName + this.AIS.profile
                  }, null, true);

                  //map search source to profile
                  this.GLOBAL._grRecordCreator('ais_search_profile_ais_search_source_m2m', {
                      'profile': searchProfileId,
                      'search_source': searchSourceId
                  });

                  //publishing search profile
                  var searchProfileName = this.GLOBAL._grGetKeyDataFromTable('ais_search_profile', searchProfileId, 'name');
                  this.GLOBAL._aisPublishProfile(searchProfileName);
              }

              //creating search application
              if (searchProfileId != null) {
                  searchApplicationId = this.GLOBAL._grRecordCreator('sys_search_context_config', {
                      'name': appName + this.AIS.application,
                      'search_profile': searchProfileId,
                      'genius_results_limit': 3
                  }, null, true);
              }
              // creating search context block
              if (searchApplicationId != null) {
                  searchContextContentBlockId = this._grRecordCreator(this.TABLE.CONTENT_BLOCK, {
                      'type': this.CONSTANTS.CONTENT_BLOCK_TYPES.SEARCH_CONTEXT,
                      'name': appName + ' - Default Search Context',
                      'ace_config': appConfigId,
                      'parent': searchConfigBlockId,
                      'order': 10,
                      'document_id': appConfigId,
                      'document_table': this.TABLE.APP_CONFIG
                  });
                  this._setGlideVars(this.TABLE.CONTENT_BLOCK, searchContextContentBlockId, {
                      'search_application': searchApplicationId,
                      'evam_configuration': this.AIS.evam_config
                  });
              }

              if (searchContextContentBlockId != null) {
                  return 2;
              }

          } else {
              return 1;
          }

      } else {
          //zing search configuration
          return 0;
      }
      return 3;
  },

  fetchRouteSearchResult: function(aceAppId, contentBlockId) {
      var _self = this;
      if (!this.CAN_READ) return null;
      var contentBlockGr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      if (contentBlockGr.get(contentBlockId)) {
          if (contentBlockGr.getValue('document_table') == this.TABLE.PAGE) {
              var pageId = contentBlockGr.getValue('document_id');
              var gr = new GlideRecord(this.TABLE.PAGE);
              if (gr.get(pageId)) {
                  var routeId = gr.getValue('route');
                  var route = this.GLOBAL._grGetKeyDataFromTable(_self.UXF_TABLE.APP_ROUTE, routeId, 'route_type');
                  return getURL(aceAppId, route);
              }
          } else {
              return getURL(aceAppId, '');
          }
      }

      function getURL(aceAppId, route) {
          var gr = new GlideRecord(_self.TABLE.APP_CONFIG);
          var url = '';
          if (gr.get(aceAppId)) {
              url = '/now/' + _self.getURLPath(gr.getValue('app_config')) + '/' + route;
          }

          return url;
      }
  },

  fetchSearchConfig: function(aceAppId) {
      var _self = this;

      function getURL(route, contentBlockId) {
          var gr = new GlideRecord(_self.TABLE.APP_CONFIG);
          var url = '';
          if (gr.get(aceAppId)) {
              url = '/now/' + _self.getURLPath(gr.getValue('app_config')) + '/' + route + '/' + contentBlockId;
          }

          return url;
      }

      var result = {};
      if (!this.CAN_READ) return result;
      var searchConfigBlocks = this._getContentBlocks('', {
          'document_table': this.TABLE.APP_CONFIG,
          'document_id': aceAppId,
          'type': this.CONSTANTS.CONTENT_BLOCK_TYPES.SEARCH_CONFIG
      });
      if (searchConfigBlocks.length > 0) {
          var pageId = searchConfigBlocks[0].props_details.page;
          var gr = new GlideRecord(this.TABLE.PAGE);
          if (gr.get(pageId)) {
              var routeId = gr.getValue('route');
              var route = this.GLOBAL._grGetKeyDataFromTable(_self.UXF_TABLE.APP_ROUTE, routeId, 'route_type');
              result = {
                  page_route: route,
                  mode: searchConfigBlocks[0].props_details.mode,
                  sys_id: searchConfigBlocks[0].block_id,
                  url: getURL(route, searchConfigBlocks[0].block_id)
              };
          }
          return result;
      }
  },
  getSearchContexts: function(aceAppId, searchConfigContentBlockId) {
      var result = [];
      if (!this.CAN_READ) return result;
      var searchContexts = this._getContentBlocks(searchConfigContentBlockId, {
          'ace_config': aceAppId,
          'type': this.CONSTANTS.CONTENT_BLOCK_TYPES.SEARCH_CONTEXT
      });
      searchContexts.forEach(function(context) {
          if (context.props_details.evam_configuration && context.props_details.search_application) {
              result.push({
                  'search_config': context.props_details.search_application,
                  'evam_config': context.props_details.evam_configuration
              });
          }
      });
      return result;
  },
  activateSearchConfig: function(aceAppId, searchConfigContentBlockId) {
      var result = this.getSearchContexts(aceAppId, searchConfigContentBlockId);
      if (result.length > 0) {
          var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
          if (gr.get(searchConfigContentBlockId) && gr.canWrite()) {
              gr.setValue('active', true);
              gr.update();
              return true;
          }
      }
      return false;
  },


  getSubPageRoutes: function(appConfig, contentBlockId, appName) {

      var self = this;
      var routes = [];
      var clone_macroponent_routes = self.CONSTANTS.CLONE_ROUTES;
      var contentBlockGr = new GlideRecord(self.UXF_TABLE.APP_ROUTE);
      contentBlockGr.addQuery('app_config', appConfig);
      contentBlockGr.addQuery('parent_macroponent', contentBlockId);
      contentBlockGr.query();
      while (contentBlockGr.next()) {
          var cloneMacroponent = clone_macroponent_routes.some(function(route) {
              return route === contentBlockGr.getValue('route_type');
          });
          var screen_gr = new GlideRecord(self.UXF_TABLE.SCREEN);
          screen_gr.addQuery('screen_type', contentBlockGr.getValue('screen_type'));
          screen_gr.orderBy('sys_created_on');
          screen_gr.setLimit(1);
          screen_gr.query();
          while (screen_gr.next()) {
              var screen_id = screen_gr.getValue('sys_id');
          }
          screen_gr.get('screen_type', contentBlockGr.getValue('screen_type'));
          routes.push({
              MACROPONENT: screen_gr.getValue('macroponent'),
              SCREEN: screen_id,
              SCREEN_TYPE: contentBlockGr.getValue('screen_type'),
              NAME: appName + " - " + contentBlockGr.getValue('name'),
              ROUTE: contentBlockGr.getValue('route_type'),
              ROUTE_ID: contentBlockGr.getValue('sys_id'),
              VIEWPORT_ID: contentBlockGr.getValue('parent_macroponent_composition_element_id'),
              CLONE_MACROPONENT: cloneMacroponent
          });
      }
      return routes;

  },


  getClientScriptDetails: function(macroponentSysId) {

      var client_scripts = [];
      var client_script_gr = new GlideRecord('sys_ux_client_script');
      client_script_gr.addQuery('macroponent', macroponentSysId);
      client_script_gr.query();
      while (client_script_gr.next()) {
          client_scripts.push({
              sys_id: client_script_gr.getValue('sys_id').toString(),
              name: client_script_gr.getValue('name').toString(),
              script: client_script_gr.getValue('script').toString(),
              macroponent_id: macroponentSysId
          });
      }
      return client_scripts;

  },

  getAppConfigDetails: function(aceAppConfig) {
      if (!this.CAN_READ) return {};
      var app_config_gr = new GlideRecord('sn_ace_app_config');
      if (app_config_gr.get(aceAppConfig)) {
          return {
              app_config: app_config_gr.getValue('app_config').toString(),
              context: JSON.parse(app_config_gr.getValue('context')),
              app_name: app_config_gr.getValue('name')
          };
      }
  },

  syncNewSubPages: function(appName, aceBaseCbRoutes, appCbRoutes, appConfig) {

      var self = this;
      var content_block_id = appConfig.context.content_block_id;
      var routes_diff = aceBaseCbRoutes.filter(function(acebase_route) {
          return !appCbRoutes.some(function(route) {
              return acebase_route.ROUTE === route.ROUTE;
          });
      });

      routes_diff.forEach(function(route) {
          var cloneInfo = {
              'sys_id': route.ROUTE_ID,
              'fields': self.CONSTANTS.CLONE_FIELDS.APP_ROUTE
          };
          var payload = {
              'name': route.NAME,
              'app_config': appConfig.app_config,
              'parent_macroponent': content_block_id,
              'parent_macroponent_composition_element_id': route.VIEWPORT_ID

          };
          self.GLOBAL._grRecordCreator(self.UXF_TABLE.APP_ROUTE, payload, cloneInfo);
      });

      self.cloneSubPages(appName, appConfig.app_config, content_block_id, routes_diff);

  },

  syncClientScriptsWithBase: function(aceBaseCbSubpage, appCbSubpage) {

      var self = this;
      var ace_base_client_scripts = self.getClientScriptDetails(aceBaseCbSubpage.MACROPONENT);
      var app_client_scripts = self.getClientScriptDetails(appCbSubpage.MACROPONENT);

      app_client_scripts.forEach(function(app_client_script) {
          var ace_base_client_script = ace_base_client_scripts.filter(function(ace_base_client_script) {
              return ace_base_client_script.name === app_client_script.name;
          });
          if (ace_base_client_script.length > 0) {
              self.GLOBAL.updateRecord(self.UXF_TABLE.CLIENT_SCRIPT, app_client_script.sys_id, {
                  'script': ace_base_client_script[0].script
              });
          }
      });

      //Fetch client scripts that are part of a subpage in ace base content block and do not exist in app's content block su page
      var client_script_diff = ace_base_client_scripts.filter(function(ace_base_client_script) {
          return !app_client_scripts.some(function(app_client_script) {
              return ace_base_client_script.name === app_client_script.name;
          });
      });

      if (client_script_diff.length > 0) {
          var macroponent = {};
          var macroponent_gr = new GlideRecord(self.UXF_TABLE.MACROPONENT);
          if (macroponent_gr.get(appCbSubpage.MACROPONENT)) {
              macroponent.composition = macroponent_gr.getValue('composition'),
                  macroponent.data = macroponent_gr.getValue('data'),
                  macroponent.state_properties = macroponent_gr.getValue('state_properties'),
                  macroponent.internal_event_mappings = macroponent_gr.getValue('internal_event_mappings');
          }
          client_script_diff.forEach(function(client_script) {
              var client_script_id = self.GLOBAL._grRecordCreator(self.UXF_TABLE.CLIENT_SCRIPT, {
                  'macroponent': appCbSubpage.MACROPONENT,
                  'name': client_script.name,
                  'script': client_script.script,
              });
              macroponent.composition = self._replaceAll(macroponent.composition, client_script.sys_id, client_script_id);
              macroponent.data = self._replaceAll(macroponent.data, client_script.sys_id, client_script_id);
              macroponent.state_properties = self._replaceAll(macroponent.state_properties, client_script.sys_id, client_script_id);
              macroponent.internal_event_mappings = self._replaceAll(macroponent.internal_event_mappings, client_script.sys_id, client_script_id);
          });
          self.GLOBAL.updateRecord(self.UXF_TABLE.MACROPONENT, appCbSubpage.MACROPONENT, {
              'composition': macroponent.composition,
              'data': macroponent.data,
              'state_properties': macroponent.state_properties,
              'internal_event_mappings': macroponent.internal_event_mappings,
          });
      }

  },

  syncMacroponentWithBase: function(aceBaseCbSubpage, appCbSubpage, appConfig) {


      var self = this;
      var macroponent = {};
      var ace_base_macroponent_gr = new GlideRecord(self.UXF_TABLE.MACROPONENT);
      if (ace_base_macroponent_gr.get(aceBaseCbSubpage.MACROPONENT)) {
          macroponent.composition = ace_base_macroponent_gr.getValue('composition'),
              macroponent.data = ace_base_macroponent_gr.getValue('data'),
              macroponent.layout = ace_base_macroponent_gr.getValue('layout'),
              macroponent.properties = ace_base_macroponent_gr.getValue('props'),
              macroponent.state_properties = ace_base_macroponent_gr.getValue('state_properties'),
              macroponent.internal_event_mappings = ace_base_macroponent_gr.getValue('internal_event_mappings'),
              macroponent.dispatched_events = ace_base_macroponent_gr.getValue('dispatched_events'),
              macroponent.handled_events = ace_base_macroponent_gr.getValue('handled_events');
      }

      macroponent.composition = self._replaceAll(macroponent.composition, self.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT, appConfig.context.content_block_id);

      var ace_base_client_scripts = self.getClientScriptDetails(aceBaseCbSubpage.MACROPONENT);
      var app_client_scripts = self.getClientScriptDetails(appCbSubpage.MACROPONENT);

      app_client_scripts.forEach(function(app_client_script) {
          var client_script = ace_base_client_scripts.filter(function(ace_base_client_script) {
              return app_client_script.name === ace_base_client_script.name;
          });

          if (client_script.length > 0) {
              macroponent.composition = self._replaceAll(
                  macroponent.composition,
                  client_script[0].sys_id,
                  app_client_script.sys_id
              );
              macroponent.data = self._replaceAll(
                  macroponent.data,
                  client_script[0].sys_id,
                  app_client_script.sys_id
              );
              macroponent.state_properties = self._replaceAll(
                  macroponent.state_properties,
                  client_script[0].sys_id,
                  app_client_script.sys_id
              );
              macroponent.internal_event_mappings = self._replaceAll(
                  macroponent.internal_event_mappings,
                  client_script[0].sys_id,
                  app_client_script.sys_id
              );
          }
      });

      self.GLOBAL.updateRecord(self.UXF_TABLE.MACROPONENT, appCbSubpage.MACROPONENT, {
          'composition': macroponent.composition,
          'data': macroponent.data,
          'layout': macroponent.layout,
          'props': macroponent.properties,
          'state_properties': macroponent.state_properties,
          'internal_event_mappings': macroponent.internal_event_mappings,
          'dispatched_events': macroponent.dispatched_events,
          'handled_events': macroponent.handled_events
      });

  },

  syncRouteWithBase: function(aceBaseCbSubpage, appCbSubpage, appConfig) {

      var self = this;
      var route = {};
      var ace_base_route_gr = new GlideRecord(self.UXF_TABLE.APP_ROUTE);
      if (ace_base_route_gr.get(aceBaseCbSubpage.ROUTE_ID)) {
          route.name = ace_base_route_gr.getValue('name'),
              route.fields = ace_base_route_gr.getValue('fields'),
              route.optional_parameters = ace_base_route_gr.getValue('optional_parameters'),
              route.parent_macroponent_composition_element_id = ace_base_route_gr.getValue('parent_macroponent_composition_element_id');
      }


      self.GLOBAL.updateRecord(self.UXF_TABLE.APP_ROUTE, appCbSubpage.ROUTE_ID, {
          'name': appConfig.app_name + "-" + route.name,
          'parent_macroponent': appConfig.context.content_block_id,
          'fields': route.fields,
          'optional_parameters': route.optional_parameters,
          'parent_macroponent_composition_element_id': route.parent_macroponent_composition_element_id,
          'screen_type': appCbSubpage.SCREEN_TYPE,
          'app_config': appConfig.app_config,

      });
  },

  syncScreenWithBase: function(aceBaseCbSubpage, appCbSubpage, appConfig) {

      var self = this;
      var screen = {};
      var ace_base_screen_gr = new GlideRecord(self.UXF_TABLE.SCREEN);
      if (ace_base_screen_gr.get(aceBaseCbSubpage.SCREEN)) {
          var content_block_routes_gr = new GlideRecord(self.UXF_TABLE.APP_ROUTE);
          content_block_routes_gr.addQuery('app_config', self.CONSTANTS.BASE_UXF_RECORDS.ACE_BASE_EXPERIENCE_APP_CONFIG);
          content_block_routes_gr.addQuery('parent_macroponent', self.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT);
          content_block_routes_gr.addQuery('screen_type', ace_base_screen_gr.getValue('screen_type'));
          content_block_routes_gr.query();
          if (!content_block_routes_gr.next())
              return;

          screen.name = appConfig.app_name + ' - ' + content_block_routes_gr.getValue('name');
          screen.macroponent_config = ace_base_screen_gr.getValue('macroponent_config');
          screen.event_mappings = ace_base_screen_gr.getValue('event_mappings');
          screen.page_definition = (appCbSubpage.CLONE_MACROPONENT) ? appCbSubpage.MACROPONENT : aceBaseCbSubpage.MACROPONENT;
      }

      self.GLOBAL.updateRecord(self.UXF_TABLE.SCREEN, appCbSubpage.SCREEN, {
          'name': screen.name,
          'screen_type': appCbSubpage.SCREEN_TYPE,
          'macroponent': screen.page_definition,
          'parent_macroponent': appConfig.context.content_block_id,
          'macroponent_config': screen.macroponent_config,
          'event_mappings': screen.event_mappings,
          'app_config': appConfig.app_config

      });
  },

  hardSyncWithBase: function(aceAppConfig) {

      var self = this;
      var app_config = self.getAppConfigDetails(aceAppConfig);
      var app_name = app_config.app_name;
      var app_content_block_id = app_config.context.content_block_id;

      var ace_base_cb_subpages = self.getSubPageRoutes(self.CONSTANTS.BASE_UXF_RECORDS.ACE_BASE_EXPERIENCE_APP_CONFIG, self.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT, app_name);
      var app_cb_subpages = self.getSubPageRoutes(app_config.app_config, app_content_block_id, app_name);

      self.syncNewSubPages(app_name, ace_base_cb_subpages, app_cb_subpages, app_config);

      app_cb_subpages.forEach(function(subpage) {
          var ace_base_cb_subpage = ace_base_cb_subpages.filter(function(acebase_subpage) {
              return acebase_subpage.ROUTE === subpage.ROUTE;
          });

          if (ace_base_cb_subpage.length > 0) {
              if (subpage.CLONE_MACROPONENT) {
                  self.syncMacroponentWithBase(ace_base_cb_subpage[0], subpage, app_config);
                  self.syncClientScriptsWithBase(ace_base_cb_subpage[0], subpage);
              }
              self.syncRouteWithBase(ace_base_cb_subpage[0], subpage, app_config);
              self.syncScreenWithBase(ace_base_cb_subpage[0], subpage, app_config);

          }
      });

      self.fixMissingSubPages(ace_base_cb_subpages, app_cb_subpages, app_content_block_id, app_config.app_config);

      //sync content block macroponent and client scripts
      self.syncMacroponentWithBase({
          MACROPONENT: self.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT
      }, {
          MACROPONENT: app_content_block_id
      }, app_config);
      self.syncClientScriptsWithBase({
          MACROPONENT: self.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT
      }, {
          MACROPONENT: app_content_block_id
      });
  },

  hasReadAccess: function() {
      return gs.hasRole('sn_ace.ace_user');
  },
  type: 'ACEAppBuilderUtil'
};

Sys ID

a36948529d373410f8776ab43f397e99

Offical Documentation

Official Docs: