Name

global.CMDBDuplicateTemplateUtils

Description

Utility class for De-Duplication Templates to add/remove De-Duplication Tasks

Script

var CMDBDuplicateTemplateUtils = Class.create();
CMDBDuplicateTemplateUtils.prototype = {
  RECONCILE_DUPLICATE_TASK: 'reconcile_duplicate_task',
  RECONCILE_DUPLICATE_TEMPLATE_RUN: 'reconcile_duplicate_template_run',
  RECONCILE_DUPLICATE_TEMPLATE: 'reconcile_duplicate_template',
  TEMPLATE_NOT_EXIST: 'template_not_exist',
  UNPUBLISHED_TEMPLATE: 'unpublished_template',
  ONGOING_TEMPLATE_RUN: 'ongoing_template_run',
  TEMPLATE_TASK_NOT_COMPATIBLE: 'template_task_not_compatible',
  TEMPLATE_ALREADY_EXISTS: 'template_already_exists',
  UPDATE_FAILED: 'update_failed',
  INVALID_STATE: 'invalid_state',
  BATCH_SIZE: 100,
  OPEN_STATE: 1,
  WORK_IN_PROGRESS: 2,

  initialize: function() {
      this.IS_DOMAIN_SUPPORT_ACTIVE = GlidePluginManager.isActive('com.glide.domain.msp_extensions.installer');
  },

  /*
  	This function adds the provided tasks to the template in batches of 100. It will overwrite any
  	existing template values, but log the previous value for each template.

  	@param templateId sys_id of the template
  	@param taskIds array of the task id sys_ids to be added to the template
  */
  addTasksToTemplate: function(templateId, taskIds) {
      var result = {
          success: [],
          failed: [],
          skipped: [],
          summary: ''
      };

      // Do not add the tasks to the template if they are empty/invalid
      if (!Array.isArray(taskIds)) {
          result.summary = 'CMDBDuplicateTemplateUtils: Failed to add de-duplication task(s) to template. Invalid/Empty task ids.';
          return result;
      }

      var templateGr = this._getTemplate(templateId);
      if (!templateGr) {
          var templateDoesNotExistMessage = 'Failed to add de-duplication task(s) because template ' + templateId + ' does not exist';
          gs.error('CMDBDuplicateTemplateUtils: ' + templateDoesNotExistMessage);
          result.failed = this._populateResult(taskIds, templateDoesNotExistMessage, this.INVALID_TEMPLATE);
          result.summary = templateDoesNotExistMessage;
          return result;
      }

      if (!this._isPublishedTemplate(templateId)) {
          var unPublishedTemplateMessage = 'Cannot add de-duplication task(s) to template ' + templateId + ' because the template is not published. Publish the template before adding tasks';
          result.skipped = this._populateResult(taskIds, unPublishedTemplateMessage, this.UNPUBLISHED_TEMPLATE);
          result.summary = unPublishedTemplateMessage;
          return result;
      }

      if (this._isOngoingTemplateRun(templateId)) {
          var ongoingTemplateRunMessage = 'Cannot add de-duplication task(s) to template ' + templateId + ' because there is already an ongoing run either in Draft/Ready/Running/Cancel Requested state';
          result.skipped = this._populateResult(taskIds, ongoingTemplateRunMessage, this.ONGOING_TEMPLATE_RUN);
          result.summary = ongoingTemplateRunMessage;
          return result;
      }

      // Check if the all tasks can be added to template
      var canAddTasksToTemplateResult = sn_cmdb.DuplicateTemplate.canAddTasksToTemplate(taskIds, templateId);
      var templateTaskNotCompatibleFailedMessage = 'Failed adding de-duplication task(s) to template ' + templateId + ' due to sn_cmdb.DuplicateTemplate.canAddTasksToTemplate API errors';
      var templateTaskNotCompatibleSkippedMessage = 'Skipped adding de-duplication task(s) to template ' + templateId + ' because the template-task class hierarchy does not match';
      result.failed = this._populateResult(canAddTasksToTemplateResult.failed, templateTaskNotCompatibleFailedMessage, this.TEMPLATE_TASK_NOT_COMPATIBLE);
      result.skipped = this._populateResult(canAddTasksToTemplateResult.skipped, templateTaskNotCompatibleSkippedMessage, this.TEMPLATE_TASK_NOT_COMPATIBLE);

      var domain = templateGr.getValue('sys_domain');

      // Only process the tasks that can be added to the template
      for (var i = 0; i < canAddTasksToTemplateResult.success.length; i += this.BATCH_SIZE) {
          var chunk = canAddTasksToTemplateResult.success.slice(i, i + this.BATCH_SIZE);
          var batchResult = this._processBatchAddTasks(templateId, chunk, domain);
          result.success = result.success.concat(batchResult.success);
          result.failed = result.failed.concat(batchResult.failed);
          result.skipped = result.skipped.concat(batchResult.skipped);
      }

      gs.info('CMDBDuplicateTemplateUtils: Added ' + result.success.length + ', Skipped ' + result.skipped.length + ', Failed ' + result.failed.length + ' tasks');
      return result;
  },

  /*
      This function removes the template from the provided tasks to the template in batches of 100.

      @param taskIds array of the task id sys_ids to be removed from the template
  */
  removeTemplateFromTasks: function(taskIds) {
      var result = {
          success: 0,
          failed: 0,
          skipped: 0
      };
      // Do not remove the tasks from the template if they are empty/invalid
      if (!Array.isArray(taskIds)) {
          gs.info('CMDBDuplicateTemplateUtils: Failed to remove template from de-duplication task(s). Invalid/Empty task ids.');
          return result;
      }

      for (var i = 0; i < taskIds.length; i += this.BATCH_SIZE) {
          var chunk = taskIds.slice(i, i + this.BATCH_SIZE);
          var batchResult = this._processBatchRemoveTemplateFromTasks(chunk);
          result.success += batchResult.success;
          result.failed += batchResult.failed;
          result.skipped += batchResult.skipped;
      }
      gs.info('CMDBDuplicateTemplateUtils: Removed ' + result.success + ', Skipped ' + result.skipped + ', Failed ' + result.failed + ' template from tasks');
      return result;
  },

  _processBatchAddTasks: function(templateId, chunk, domain) {
      var currentDomain = GlideSession.get().getCurrentDomainID();
      try {
          if (this.IS_DOMAIN_SUPPORT_ACTIVE) {
              // Change the domain to the template domain
              GlideSession.get().setDomainID(domain);
          }

          var gr = new GlideRecord(this.RECONCILE_DUPLICATE_TASK);
          gr.addQuery('sys_id', 'IN', chunk);
          gr.addQuery('state', this.OPEN_STATE);
          gr.query();
      } finally {
          if (this.IS_DOMAIN_SUPPORT_ACTIVE) {
              // Change the domain back to current domain
              GlideSession.get().setDomainID(currentDomain);
          }
      }

      var resultTaskIds = [];
      var skippedTaskIds = [];
      var failedTaskIds = [];
      // Tasks that will be either skipped, failed
      var openTaskIds = [];

      while (gr.next()) {
          var currentTemplate = gr.getValue('template');
          var taskId = gr.getValue('sys_id');

          /**
           * Follow the below steps if the task already has a template:
           * 1. If the template is same as template we are trying to add, skip the update
           * 2. Else if the template is not in valid state i.e. there is an ongoing run, skip the update
           * 3. Else, log a message about changing templates from previous to current template
           */
          if (currentTemplate) {
              if (currentTemplate == templateId) {
                  var alreadyHasTemplateMessage = 'Task ' + taskId + ' already has the current template ' + currentTemplate;
                  gs.info('CMDBDuplicateTemplateUtils: Task ' + alreadyHasTemplateMessage);
                  resultTaskIds.push(this._populateResult0(taskId, alreadyHasTemplateMessage, this.TEMPLATE_ALREADY_EXISTS));
                  openTaskIds.push(taskId);
                  continue;
              } else if (this._isOngoingTemplateRun(currentTemplate)) {
                  var existingTemplateMessage = 'Task ' + taskId + ' has an existing template ' + currentTemplate + ' with ongoing run either in Draft/Ready/Running state. Cannot update template while it is running';
                  gs.info('CMDBDuplicateTemplateUtils: Task ' + existingTemplateMessage);
                  skippedTaskIds.push(this._populateResult0(taskId, existingTemplateMessage, this.ONGOING_TEMPLATE_RUN));
                  openTaskIds.push(taskId);
                  continue;
              } else {
                  gs.info('CMDBDuplicateTemplateUtils: Task ' + taskId + ' has an existing template. Updating template from ' + currentTemplate + ' to ' + templateId);
              }
          }

          gr.setValue('template', templateId);
          if (gr.update()) {
              resultTaskIds.push(this._populateResult0(taskId, '', ''));
          } else {
              var failedMessage = 'Failed to add task ' + taskId + ' to template ' + templateId + ' because there were update errors.';
              gs.info('CMDBDuplicateTemplateUtils: Failed to add task ' + failedMessage);
              failedTaskIds.push(this._populateResult0(taskId, failedMessage, this.UPDATE_FAILED));
          }
          openTaskIds.push(taskId);
      }

      // We only selected the open tasks from the chunk. Some of them were success, others were skipped or failed
      // Add the remaining set of tasks which did not get selected because they were not open to the skippedTaskIds
      // Other reason could be because of domain difference between template and the tasks
      var arrayUtil = new ArrayUtil();
      var invalidStateTaskIds = arrayUtil.diff(chunk, openTaskIds);
      var message = 'Skip adding task to template ' + templateId + ' because it is not in Open state or task domain does not match with template.';
      skippedTaskIds = skippedTaskIds.concat(this._populateResult(invalidStateTaskIds, message, this.INVALID_STATE));

      return {
          success: resultTaskIds,
          failed: failedTaskIds,
          skipped: skippedTaskIds
      };
  },

  _processBatchRemoveTemplateFromTasks: function(chunk) {
      var gr = new GlideRecord(this.RECONCILE_DUPLICATE_TASK);
      gr.addQuery('sys_id', 'IN', chunk);
      gr.addQuery('state', '!=', this.WORK_IN_PROGRESS);
      gr.query();

      var resultTaskIds = [];
      var skippedTaskIds = [];
      var failedTaskIds = [];

      while (gr.next()) {
          var currentTemplate = gr.getValue('template');
          var taskId = gr.getValue('sys_id');

          /**
           * 1. If the task does not have a template, skip the update
           * 2. If the task has a template that is not in valid state i.e. there is an ongoing run, skip the update
           */
          if (!currentTemplate) {
              gs.info('CMDBDuplicateTemplateUtils: Task ' + taskId + ' does not have a template. Skip removing template');
              skippedTaskIds.push(taskId);
              continue;
          } else if (this._isOngoingTemplateRun(currentTemplate)) {
              gs.info('CMDBDuplicateTemplateUtils: Task ' + taskId + ' has an existing template ' + currentTemplate + ' with ongoing run either in Draft/Ready/Running state. Cannot remove template from task while it is running');
              skippedTaskIds.push(taskId);
              continue;
          }

          gr.setValue('template', '');
          if (gr.update()) {
              resultTaskIds.push(taskId);
          } else {
              gs.info('CMDBDuplicateTemplateUtils: Failed to remove template from task ' + taskId + ' because there were update errors.');
              failedTaskIds.push(taskId);
          }
      }

      // We only selected the open tasks from the chunk. Some of them were success, others were skipped or failed
      // Add the remaining set of tasks which did not get selected because they were not open to the skippedTaskIds
      var arrayUtil = new ArrayUtil();
      skippedTaskIds = skippedTaskIds.concat(arrayUtil.diff(chunk, resultTaskIds.concat(failedTaskIds).concat(skippedTaskIds)));

      return {
          success: resultTaskIds.length,
          failed: failedTaskIds.length,
          skipped: skippedTaskIds.length
      };
  },

  /**
   * Check if the template is published
   */
  _isPublishedTemplate: function(templateId) {
      var gr = new GlideRecord(this.RECONCILE_DUPLICATE_TEMPLATE);
      if (gr.get(templateId)) {
          return gr.getValue('state') == 'published';
      }
      return false;
  },

  /**
   * Check if the template exists and there is no run scheduled in draft/ready/running
   */
  _isOngoingTemplateRun: function(templateId) {
      var gr = new GlideRecord(this.RECONCILE_DUPLICATE_TEMPLATE_RUN);
      gr.addQuery('template', templateId);
      gr.addQuery('state', 'IN', 'draft,ready,running,cancel_requested');
      gr.setLimit(1);
      gr.query();

      return gr.hasNext();
  },

  /**
   * Get the template
   */
  _getTemplate: function(templateId) {
      var templateGr = new GlideRecord(this.RECONCILE_DUPLICATE_TEMPLATE);
      if (!templateGr.get(templateId)) {
          return null;
      }
      return templateGr;
  },

  _populateResult: function(taskIds, message, messageType) {
      var result = [];
      for (taskId in taskIds) {
          result.push(this._populateResult0(taskId, message, messageType));
      }
      return result;
  },

  _populateResult0: function(taskId, message, messageType) {
      var resultObj = {};
      resultObj.taskId = taskId;
      resultObj.message = message;
      resultObj.messageType = messageType;

      return resultObj;
  },

  type: 'CMDBDuplicateTemplateUtils'
};

Sys ID

ed8f3d8373256110dc50c3ed8ff6a724

Offical Documentation

Official Docs: