Name

sn_irm_shared_cmn.IRMTableSchemaUtils

Description

Utility class to register, and process IRM tables schema updates.

Script

var IRMTableSchemaUtils = Class.create();
IRMTableSchemaUtils.CONSTANTS = {};
IRMTableSchemaUtils.CONSTANTS.OPERATIONS = {
  OPERATION_UPDATE: 'UPDATE',
};

/**
* Utility class to register, and process IRM tables schema updates.
*/
IRMTableSchemaUtils.prototype = {
  initialize: function(scope) {
      this.scope = scope;
      this.OPERATION_UPDATE = IRMTableSchemaUtils.CONSTANTS.OPERATIONS.OPERATION_UPDATE;
      this.QUERYS = {
          AND: '^',
          EMPTY_QUERY: 'name=NULL^element=NULL',
          NEW_QUERY: '^NQ',
      };
      this.TABLES = {
          SYS_CHOICE: 'sys_choice',
          SYS_DB_OBJECT: 'sys_db_object',
          SYS_DICTIONARY: 'sys_dictionary',
      };
      this.currentElement = null;
      this.logger = {
          info: gs.info,
          debug: gs.debug,
          warn: gs.warn,
          error: gs.error,
      };
      this.glideUtils = new sn_irm_shared_cmn.IRMGlideUtils({
          logger: this.logger
      });
      this.operations = {};
  },

  /**
   * A utility class to process table operations using a builder pattern.
   * @private
   * @param {string} tableName - table name to process operations for.
   * @param {string} operation - operation name.
   * @returns {IRMTableOperationProcessor} processor - mini-class to process table operations.
   */
  IRMTableOperationProcessor: function(tableName, operation) {
      var self = this;

      return {
          registerElementUpdate: self._registerElementUpdate(tableName, operation),
          registerChoiceUpdate: self._registerChoiceUpdate(tableName, operation),
      };
  },

  /**
   * Get update operation configurations for a table.
   * @private
   * @param {string} tableName - table name
   * @returns {Object}
   */
  _getTableUpdateConfigurations: function(tableName) {
      this.operations[tableName] = this.operations[tableName] || {};
      this.operations[tableName][this.OPERATION_UPDATE] = this.operations[tableName][this.OPERATION_UPDATE] || {};
      this.operations[tableName][this.OPERATION_UPDATE].elements =
          this.operations[tableName][this.OPERATION_UPDATE].elements || {};
      this.operations[tableName][this.OPERATION_UPDATE].overrides =
          this.operations[tableName][this.OPERATION_UPDATE].overrides || {};
      return this.operations[tableName][this.OPERATION_UPDATE];
  },

  /**
   * Get all table elements registered for update operation.
   * @private
   * @param {string} tableName - table name
   * @returns {Object}
   */
  _getTableUpdateElements: function(tableName) {
      return this._getTableUpdateConfigurations(tableName).elements;
  },

  /**
   * Get configurations for a table element registered for update operation.
   * @private
   * @param {string} tableName - table name
   * @param {string} element - element name
   * @returns {Object}
   */
  _getTableUpdateElementConfigurations: function(tableName, element) {
      var currentElements = this._getTableUpdateElements(tableName);
      currentElements[element] = currentElements[element] || {};
      currentElements[element].choices = currentElements[element].choices || {};
      currentElements[element].overrides = currentElements[element].overrides || {};

      return currentElements[element];
  },

  /**
   * Get all configurations for element's choices registered for an update operation.
   * @private
   * @param {string} tableName - table name.
   * @param {string} element - element name.
   * @returns {Object}
   */
  _getTableUpdateElementChoicesConfigurations: function(tableName, element) {
      var currentElement = this._getTableUpdateElementConfigurations(tableName, element);

      return currentElement.choices;
  },

  /**
   * Get element's choice configurations.
   * @private
   * @param {string} tableName - table name.
   * @param {string} element - element name.
   * @param {string} choice - choice name.
   * @returns {Object}
   */
  _getTableUpdateElementChoiceConfigurations: function(tableName, element, choice) {
      var currentChoices = this._getTableUpdateElementChoicesConfigurations(tableName, element);
      currentChoices[choice] = currentChoices[choice] || {};
      currentChoices[choice].overrides = currentChoices[choice].overrides || {};

      return currentChoices[choice];
  },

  /**
   * Get all update operation overrides registered for an element.
   * @private
   * @param {string} tableName - table name.
   * @param {string} element - element name.
   * @returns {Object}
   */
  _getTableUpdateElementOverrides: function(tableName, element) {
      var currentElement = this._getTableUpdateElementConfigurations(tableName, element);

      return currentElement.overrides;
  },

  /**
   * Get all update operation overrides registered for an element's choice.
   * @private
   * @param {string} tableName - table name.
   * @param {string} element - element name.
   * @param {string} choice - choice name.
   * @returns {Object}
   */
  _getTableUpdateElementChoiceOverrides: function(tableName, element, choice) {
      var currentChoice = this._getTableUpdateElementChoiceConfigurations(tableName, element, choice);

      return currentChoice.overrides;
  },

  /**
   * Get all update operation overrides registered for a table.
   * @private
   * @param {string} tableName - table name.
   * @returns {Object}
   */
  _getTableUpdateOverrides: function(tableName) {
      return this._getTableUpdateConfigurations(tableName).overrides;
  },

  /**
   * Set table update overrides.
   * @private
   * @param {string} tableName - table name
   * @param {TableUpdateOverrides} overrides - Update overrides to apply.
   */
  _setTableUpdateOverrides: function(tableName, overrides) {
      var currentOverrides = this._getTableUpdateOverrides(tableName);
      currentOverrides = IRMCoreUtils.objectAssign(currentOverrides, overrides);
  },

  /**
   * Set element update overrides.
   * @private
   * @param {string} tableName - table name
   * @param {string} element - element name
   * @param {ElementUpdateOverrides} overrides - Update overrides to apply.
   */
  _setTableUpdateElementOverrides: function(tableName, element, overrides) {
      this.currentElement = element;
      var currentOverrides = this._getTableUpdateElementOverrides(tableName, element);
      IRMCoreUtils.objectAssign(currentOverrides, overrides);
  },

  /**
   * Set element's choice update overrides.
   * @private
   * @param {string} tableName - table name
   * @param {string} element - element name
   * @param {string} choice - choice name
   * @param {ChoiceUpdateOverrides} overrides - Update overrides to apply.
   */
  _setTableUpdateElementChoiceOverrides: function(tableName, element, choice, overrides) {
      var currentOverrides = this._getTableUpdateElementChoiceOverrides(tableName, element, choice);
      IRMCoreUtils.objectAssign(currentOverrides, overrides);
  },

  /**
   * Get all element's choices registered for update operation.
   * @param {string} table - table name.
   * @param {string} element - element name.
   * @returns {string[]} choices
   */
  getRegisteredChoices: function(tableName, element) {
      var choices = this._getTableUpdateElementChoicesConfigurations(tableName, element);

      return Object.keys(choices);
  },

  /**
   * Get all tables registered for schema changes.
   * @returns {string[]} tables
   */
  getRegisteredTables: function() {
      return Object.keys(this.operations);
  },

  /**
   * Get all elements registered for schema changes.
   * @param {string} table - table name to get elements for.
   * @returns {string[]} Fields in table
   */
  getRegisteredElements: function(table) {
      var elements = this._getTableUpdateElements(table);

      return Object.keys(elements);
  },

  /**
   * A method that gets all SysChoice records for choices registered for schema changes.
   * @returns {GlideRecord} choiceRecords
   */
  getChoiceRecords: function() {
      var self = this;
      var AND = self.QUERYS.AND;
      var EMPTY_QUERY = self.QUERYS.EMPTY_QUERY;
      var NEW_QUERY = self.QUERYS.NEW_QUERY;
      var filteredTables = self.getRegisteredTables().filter(function(table) {
          return Object.keys(self._getTableUpdateConfigurations(table).elements || {}).length;
      });
      var query = filteredTables.reduce(function(acc, cur, index) {
          var fields = self.getRegisteredElements(cur);
          var filteredFields = fields.filter(function(element) {
              return Object.keys(self._getTableUpdateElementConfigurations(cur, element).choices || {}).length;
          });

          if (index > 0 && filteredFields.length > 0) {
              acc += NEW_QUERY;
          }

          acc += filteredFields.reduce(function(filter, element, _index) {
              var values = self.getRegisteredChoices(cur, element);
              if (_index > 0) {
                  filter += NEW_QUERY;
              }

              filter += 'name=' + cur + AND + 'element=' + element + AND + 'valueIN' +
                  values + AND + 'language=en';

              return filter;
          }, '');

          return acc;
      }, '') || EMPTY_QUERY;

      var choiceRecords = new GlideRecord(self.TABLES.SYS_CHOICE);
      choiceRecords.addEncodedQuery(query);
      choiceRecords.query();

      return choiceRecords;
  },

  /**
   * A method that gets all SysDBObject records for tables registered for schema changes.
   * @returns {GlideRecord} tableRecords
   */
  getTableRecords: function() {
      var self = this;
      var tables = self.getRegisteredTables();
      var filteredTables = tables.filter(function(table) {
          var overrides = self._getTableUpdateOverrides(table);

          return overrides.hasOwnProperty('label');
      });

      var tableRecords = new GlideRecord(self.TABLES.SYS_DB_OBJECT);
      tableRecords.addQuery('sys_scope.scope', this.scope);
      tableRecords.addQuery('name', 'IN', filteredTables.join(','));
      tableRecords.query();

      return tableRecords;
  },

  /**
   * A method that gets all SysDictionary records for elements registered for schema changes.
   * @returns {GlideRecord} elementRecords
   */
  getElementRecords: function() {
      var self = this;
      var AND = self.QUERYS.AND;
      var EMPTY_QUERY = self.QUERYS.EMPTY_QUERY;
      var NEW_QUERY = self.QUERYS.NEW_QUERY;
      var filteredTables = this.getRegisteredTables().filter(function(table) {
          return Object.keys(self._getTableUpdateConfigurations(table).elements || {}).length;
      });

      var query = filteredTables.reduce(function(acc, cur, index) {
          var fields = self.getRegisteredElements(cur);
          var filteredFields = fields.filter(function(element) {
              return Object.keys(self._getTableUpdateElementConfigurations(cur, element).overrides || {}).length;
          });
          if (index > 0) {
              acc += NEW_QUERY;
          }

          acc += 'name=' + cur + AND + 'elementIN' + filteredFields;

          return acc;
      }, '') || EMPTY_QUERY;
      var elementRecords = new GlideRecord(self.TABLES.SYS_DICTIONARY);
      elementRecords.addQuery('sys_scope.scope', this.scope);
      elementRecords.addEncodedQuery(query);
      elementRecords.query();

      return elementRecords;
  },

  /**
   * Get table (SysDBObject) schema updates.
   * @param {GlideRecord} record - SysDBObject record to get schema changes for.
   * @returns {Object} changes - change for record
   */
  getTableUpdateSchemaChanges: function(record) {
      var self = this;
      var tableName = record.getValue('name');
      var overrides = self._getTableUpdateOverrides(tableName);
      var safeChanges = {};
      Object.keys(overrides).forEach(function(override) {
          if (override === 'label') {
              safeChanges[override] = overrides[override];
          } else {
              // eslint-disable-next-line sonarjs/no-duplicate-string, max-len
              self.logger.warn('Failed to register "' + override + '" change for "' + tableName + '". "' + override + ': change is not allowed.');
          }
      });

      return safeChanges;
  },

  /**
   * Get element (SysDictionary) schema updates.
   * @param {GlideRecord} record - record for get elements schema changes.
   * @returns {Object} change for elements
   */
  getTableUpdateElementSchemaChanges: function(record) {
      var self = this;
      var tableName = record.getValue('name');
      var element = record.getValue('element');
      var overrides = this._getTableUpdateElementOverrides(tableName, element);
      var safeChanges = {};
      Object.keys(overrides).forEach(function(override) {
          if (override !== 'name') {
              safeChanges[override] = overrides[override];
          } else {
              // eslint-disable-next-line sonarjs/no-duplicate-string, max-len
              self.logger.warn('Failed to register "' + override + '" change for "' + tableName + '/' + element + '". "' + override + '" change is not allowed.');
          }
      });

      return safeChanges;
  },

  /**
   * Get choice (SysChoice) schema updates.
   * @param {GlideRecord} record - record for get elements schema changes.
   * @returns {Object} change for elements
   */
  getTableUpdateElementChoiceSchemaChanges: function(record) {
      var self = this;
      var tableName = record.getValue('name');
      var element = record.getValue('element');
      var choice = record.getValue('value');
      var safeChanges = {};
      var overrides = this._getTableUpdateElementChoiceOverrides(tableName, element, choice);
      Object.keys(overrides).forEach(function(override) {
          if (override === 'label') {
              safeChanges[override] = overrides[override];
          } else {
              // eslint-disable-next-line sonarjs/no-duplicate-string, max-len
              self.logger.warn('Failed to register "' + override + '" change for "' + tableName + '/' + element + '/' + choice + '". "' + override + '" change is not allowed.');
          }
      });

      return safeChanges;
  },

  /**
   * A method to process the registered table schema operations.
   * @param {string} operation - Operation to process.
   * @param {Function} processor - method to perform updates on SysDBObject GlideRecord
   */
  processTableOperation: function(operation, processor) {
      var self = this;
      var tableRecord = this.getTableRecords();
      while (tableRecord.next()) {
          var errors = this.validateSchemaUpdate(tableRecord);
          if (errors.length > 0) {
              for (var i = 0; i < errors.length; i++) {
                  self.logger.error(errors[i]);
              }

              continue;
          }

          var changes = this.getTableUpdateSchemaChanges(tableRecord);
          var updateResult = processor(tableRecord, changes);
          if (updateResult.updated) {
              self.logger.info('Table update result is : ' + updateResult.updated);
          } else {
              self.logger.warn('Table update result is : ' + updateResult.updated);
          }
      }
  },

  /**
   * A method to process the registered elements schema updates
   * @param {Function} processor method to perform updates on SysDBObject GlideRecord
   * @param {string} operation - Operation to process.
   */
  processElementOperation: function(operation, processor) {
      var self = this;
      var currentElementRecord = this.getElementRecords();
      while (currentElementRecord.next()) {
          var errors = this.validateSchemaUpdate(currentElementRecord);
          if (errors.length > 0) {
              for (var i = 0; i < errors.length; i++) {
                  self.logger.error(errors[i]);
              }

              continue;
          }

          var changes = this.getTableUpdateElementSchemaChanges(currentElementRecord);
          var updateResult = processor(currentElementRecord, changes);
          self.logger.info('element update result is : ' + updateResult.updated);
      }
  },

  /**
   * A method to process the registered elements schema updates
   * @param {Function} processor method to perform updates on SysDBObject GlideRecord
   * @param {string} operation - Operation to process.
   */
  processChoiceOperation: function(operation, processor) {
      var self = this;
      var choiceRecords = this.getChoiceRecords();
      while (choiceRecords.next()) {
          var errors = this.validateSchemaUpdate(choiceRecords);
          if (errors.length > 0) {
              for (var i = 0; i < errors.length; i++) {
                  self.logger.error(errors[i]);
              }

              continue;
          }

          var changes = this.getTableUpdateElementChoiceSchemaChanges(choiceRecords);
          // if (!changes) continue
          var updateResult = processor(choiceRecords, changes);
          self.logger.info('element update result is : ' + updateResult.updated);
      }
  },

  /**
   * Wrapper function for registering choice updates.
   * @param {string} tableName table name.
   * @param {string} operation operation name.
   * @returns {Function}
   */
  _registerChoiceUpdate: function(tableName, operation) {
      var self = this;

      /**
       * Function to register choice update operations.
       * @param {string} choice - choice name.
       * @param {Object} overrides - choice record overrides.
       * @returns {IRMTableOperationProcessor} processor
       */
      return function(choice, overrides) {
          if (operation !== self.OPERATION_UPDATE) {
              // eslint-disable-next-line max-len
              self.logger.error('Failed to register "' + operation + ' " operation on Choice "' + tableName + '/' + self.currentElement + '/' + choice + '". Choice updates can only be registered for "' + self.OPERATION_UPDATE + '" table operation.');

              return self.IRMTableOperationProcessor(tableName, operation);
          }

          if (!self.currentElement) {
              // eslint-disable-next-line max-len
              self.logger.error('Failed to register "' + operation + ' " operation on Choice "' + tableName + '/' + self.currentElement + '/' + choice + '". Choice updates can only registered after an element update operation.');

              return self.IRMTableOperationProcessor(tableName, operation);
          }

          self._setTableUpdateElementChoiceOverrides(tableName, self.currentElement, choice, overrides);

          return self.IRMTableOperationProcessor(tableName, operation);
      };
  },

  /**
   * Wrapper function for registering element updates.
   * @param {string} tableName table name.
   * @param {string} operation operation name.
   * @returns {Function}
   */
  _registerElementUpdate: function(tableName, operation) {
      var self = this;

      /**
       * Function to register element update operations.
       * @param {string} element - element name.
       * @param {Object} overrides - element record overrides.
       * @returns {IRMTableOperationProcessor} processor
       */
      return function(element, overrides) {
          if (operation !== self.OPERATION_UPDATE) {
              // eslint-disable-next-line max-len
              self.logger.error('Failed to register "' + operation + ' " operation on Element "' + tableName + '/' + element + '". Element updates can only be registered for "' + self.OPERATION_UPDATE + '" table operation.');

              return self.IRMTableOperationProcessor(tableName, operation);
          }

          self._setTableUpdateElementOverrides(tableName, element, overrides);

          return self.IRMTableOperationProcessor(tableName, operation);
      };
  },

  /**
   * Register schema changes operations on a table.
   * @param {string} tableName - table name.
   * @param {string} operation - operation name.
   * @param {Object} overrides - table record overrides.
   * @returns {IRMTableOperationProcessor} processor
   */
  registerTableOperation: function(tableName, operation, overrides) {
      var self = this;
      self.currentElement = null;
      if (operation === self.OPERATION_UPDATE) {
          self._setTableUpdateOverrides(tableName, overrides);

          return self.IRMTableOperationProcessor(tableName, operation);
      }

      // eslint-disable-next-line max-len
      self.logger.error('Failed to register "' + operation + ' " operation on table "' + tableName + '". "' + operation + '" is an invalid operation.');

      return self.IRMTableOperationProcessor(tableName, operation);
  },

  reportChanges: function() {},

  /**
   * Get an Object with Schema changes validation rules.
   * @returns {ValidationRules}
   */
  _getValidationRules: function() {
      var self = this;

      return {
          validateCustomization: function(record) {
              var updateName;
              if (record.getTableName() === 'sys_choice') {
                  var tableName = record.getValue('name');
                  var element = record.getValue('element');
                  updateName = ['sys_choice', tableName, element].join('_');
              } else {
                  updateName = record.getValue('sys_update_name');
              }
              var isRecordCustomized = self.glideUtils.isRecordCustomized(updateName);
              if (isRecordCustomized) {
                  // eslint-disable-next-line max-len
                  return 'Unable to update "' + record.getDisplayValue() + ' (' + updateName + ')". This file has been customized.';
              }

              return null;
          },
      };
  },

  /**
   * Check if the registered schema updates are valid for a record.
   * @param {GlideRecord} record - record to use for validations.
   * @returns {string[]} errors - Array of validation error messages.
   */
  validateSchemaUpdate: function(record) {
      var errors = [];
      var validationRules = this._getValidationRules();
      var rulesKeys = Object.keys(validationRules);
      for (var i = 0; i < rulesKeys.length; i++) {
          var key = rulesKeys[i];
          var rule = validationRules[key];
          var errorMessage = rule(record);
          if (errorMessage) {
              errors.push(errorMessage);
              break;
          }
      }

      return errors;
  },

  type: 'IRMTableSchemaUtils',
};

Sys ID

91a863d9535221108288ddeeff7b1205

Offical Documentation

Official Docs: