Name

global.AssessmentUtils

Description

Assessment Engine core utilities

Script

var AssessmentUtils = Class.create();

AssessmentUtils.prototype = {
  DefaultMaxOrder: 100,

  initialize: function() {

  },

  /**
  * This is a function to generate all necessary m2m records between this category and its assessable records.
  * This function is usually called after the category's filter gets changed.
  *
  * @param {GlideRecord} grCat - A glide record of the category
  * @param {Boolean} isDomainPluginActive - True if domain plugin is active.
  * @return {Array} m2mCatAsbleArr - A list of sysids of 'asmt_m2m_category_assessment' associated with this category
  */
  generateM2mCategoryAssessmentsIfNotExisted: function(grCat, isDomainPluginActive) {
  	var m2mCatAsbleArr = [];

  	if (grCat.table != '') {
  		var totalNumOfAsbleRrd = 0;
  		var aggrAsbleRrd = new GlideAggregate("asmt_assessable_record");
  		aggrAsbleRrd.addQuery("metric_type", grCat.metric_type);
  		aggrAsbleRrd.addAggregate('COUNT');
  		aggrAsbleRrd.query();
  		if (aggrAsbleRrd.next())
  			totalNumOfAsbleRrd = aggrAsbleRrd.getAggregate('COUNT');

  		// Here, we won't query source table until assessable records are generated for this assessment. This can improve the performance for new assessment category
  		if (totalNumOfAsbleRrd > 0) {
  			// Find all source records based on category's filter condition
  			var sourceIds = [];
  			var grSource = new GlideRecord(grCat.table);
  			grSource.addEncodedQuery(grCat.filter);
  			grSource.query();
  			while(grSource.next()) {
  				sourceIds.push(grSource.getUniqueValue());
  			}

  			var asbleRrdObj = {};// assessable record's sysid -> its domain
  			var grAsbleRrd = new GlideRecord("asmt_assessable_record");
  			grAsbleRrd.addQuery("metric_type", grCat.metric_type);
  			grAsbleRrd.addQuery("source_id", "IN", sourceIds);
  			grAsbleRrd.query();
  			while (grAsbleRrd.next()) {
  				if (isDomainPluginActive) {
  					asbleRrdObj[grAsbleRrd.getUniqueValue()] = grAsbleRrd.sys_domain;
  				} else {
  					asbleRrdObj[grAsbleRrd.getUniqueValue()] = "global";
  				}
  			}

  			for (var arId in asbleRrdObj) {
  				var grM2mCatAsble = new GlideRecord("asmt_m2m_category_assessment");
  				grM2mCatAsble.addQuery("category", grCat.getUniqueValue());
  				grM2mCatAsble.addQuery("assessment_record", arId);
  				grM2mCatAsble.query();
  				if (!grM2mCatAsble.next()) {
  					// Insert a new m2m record if not found
  					grM2mCatAsble.initialize();
  					grM2mCatAsble.assessment_record = arId;
  					grM2mCatAsble.category = grCat.getUniqueValue();
  					if (isDomainPluginActive) {
  						grM2mCatAsble.sys_domain = asbleRrdObj[arId];
  					}
  					grM2mCatAsble.insert();
  				}
  				m2mCatAsbleArr.push(grM2mCatAsble.getUniqueValue());
  			}
  		}
  	}
  	return m2mCatAsbleArr;
  },

  /**
  * This is a function to remove old m2m records between this category and its assessable records.
  * This function is usually called after the category's filter gets changed.
  *
  * @param {GlideRecord} grCat - A glide record of the category (old and previous)
  * @param {Array} m2mCatAsbleArr - A list of current sysids of asmt_m2m_category_assessment for this category
  */
  removeOldM2mCategoryAssessments: function(grCat, m2mCatAsbleArr) {
  	var grM2mCatAsble = new GlideRecord("asmt_m2m_category_assessment");
  	grM2mCatAsble.addQuery("category", grCat.getUniqueValue());
  	grM2mCatAsble.addQuery("sys_id", "NOT IN", m2mCatAsbleArr);
  	grM2mCatAsble.deleteMultiple();
  },

  // User can use UI actions on form only when they have admin role or when they are one of the survey owners.
  canUseUiAction: function(metricType) {
      if (gs.hasRole("admin") || gs.hasRole("survey_admin") || gs.hasRole("assessment_admin"))
          return true;

      if (gs.hasRole("survey_creator")) {
          if (metricType.isNewRecord())
              return true;
          var owners = metricType.getValue("survey_owners");
          if (!gs.nil(owners) && owners.indexOf(gs.getUserID()) !== -1)
              return true;
      }
      return false;
  },

  // Users can view scorecard UI page only when they have admin role or they are one of the survey owners.
  // Return type: boolean false or true
  canViewScoreCardPage: function(metricTypeId) {
      var metricType = new GlideRecord("asmt_metric_type");
      metricType.addQuery("sys_id", metricTypeId);
      metricType.query();
      if (metricType.next()) {
          if (gs.hasRole("admin") || gs.hasRole("survey_admin") || gs.hasRole("assessment_admin") || gs.hasRole("survey_reader"))
              return true;

          if (gs.hasRole("survey_creator")) {
              var owners = metricType.getValue("survey_owners");
              if (!gs.nil(owners) && owners.indexOf(gs.getUserID()) !== -1)
                  return true;
          }
      }
      return false;
  },

  getQueryCondition : function(current) {
  	var ids = [];
  	var metricId = current.getUniqueValue();
  	var typeId = current.metric_type + '';
  	var categoryId = current.category + '';
  	var method = current.metric_type.evaluation_method;
  	ids.push(current.getUniqueValue());
  
  	this.getValidDependencyMetrics(typeId, metricId, ids);
  	
  	var encodedQuery = '';
  	if (method != 'vdr_risk_asmt')
  		encodedQuery = 'category=' + categoryId + '^sys_idNOT IN' + ids.join(',');
  	else
  		encodedQuery = 'metric_type=' + typeId + '^sys_idNOT IN' + ids.join(',');

  	return encodedQuery;
  },
  
  getValidDependencyMetrics : function (typeId, metricId, ids) {
  	var metric = new GlideRecord('asmt_metric');
  	metric.addQuery('metric_type', typeId);
  	metric.addQuery('depends_on', metricId);
  	metric.query();
  	
  	while (metric.next()){
  		ids.push(metric.getUniqueValue());
  		this.getValidDependencyMetrics(typeId, metric.getUniqueValue(), ids);
  	}
  },

  // Generating assessable records for a particular metric type
  generateAssessableRecords: function(asmtMetricTypeSysId) {
      var metricTypeRecord = this._getAsmtMetricTypeRecord(asmtMetricTypeSysId);
      (new SNC.AssessmentCreation()).createAssessableRecords(metricTypeRecord);
  },

  // publishing a metric type 
  publish: function(asmtMetricTypeSysId) {
      var metricTypeRecord = this._getAsmtMetricTypeRecord(asmtMetricTypeSysId);
      metricTypeRecord.publish_state = "published";
      metricTypeRecord.update();

  },

  // setting user field in a metric type.
  setUserField: function(asmtMetricTypeSysId, feild) {
      var metricTypeRecord = this._getAsmtMetricTypeRecord(asmtMetricTypeSysId);
      metricTypeRecord.user_field = feild;
      metricTypeRecord.update();
  },

  // reset user field in a metric type.
  resetUserField: function(asmtMetricTypeSysId) {
      var metricTypeRecord = this._getAsmtMetricTypeRecord(asmtMetricTypeSysId);
      metricTypeRecord.user_field = "";
      metricTypeRecord.update();
  },

  // setting user field in a metric type.
  getUserField: function(asmtMetricTypeSysId) {
      var metricTypeRecord = this._getAsmtMetricTypeRecord(asmtMetricTypeSysId);
      return metricTypeRecord.user_field;
  },

  // setting metric type condition filter.
  setMetricTypeCondition: function(asmtMetricTypeSysId, metricCondition) {
      var metricTypeRecord = this._getAsmtMetricTypeRecord(asmtMetricTypeSysId);
      metricTypeRecord.condition = metricCondition;
      metricTypeRecord.update();
  },

  // getting metric type condition filter.
  getMetricTypeCondition: function(asmtMetricTypeSysId) {
      var metricTypeRecord = this._getAsmtMetricTypeRecord(asmtMetricTypeSysId);
      return metricTypeRecord.condition;
  },

  //set enforce condition to true
  setEnforceConditionTrue: function(asmtMetricTypeSysId) {
      var metricTypeRecord = this._getAsmtMetricTypeRecord(asmtMetricTypeSysId);
      metricTypeRecord.enforce_condition = true;
      metricTypeRecord.update();
  },

  // populating all the assessable records in a metric category from its metric type
  pullAssessableRecords: function(metricType, metricCategory) {

      var assmtRecord = new GlideRecord('asmt_assessable_record');
      assmtRecord.addQuery('metric_type', metricType);
      assmtRecord.query();
      while (assmtRecord.next()) {
          var asmtm2m = new GlideRecord('asmt_m2m_category_assessment');
          this._addRecord(asmtm2m, assmtRecord, metricCategory);

      }
  },

  deleteMetricCategoryAssessableRecords: function(metricCategory) {
      var asmtm2m = new GlideRecord('asmt_m2m_category_assessment');
      asmtm2m.addQuery('category', metricCategory);
      asmtm2m.query();
      asmtm2m.deleteMultiple();
  },

  // get all the category users of a metric category
  getCategoryUsers: function(metricCategorySysId) {
      var users = [];
      var assmtRecord = new GlideRecord('asmt_m2m_category_user');
      assmtRecord.addQuery('metric_category', metricCategorySysId);
      assmtRecord.query();
      while (assmtRecord.next()) {
          users.push(assmtRecord.getValue('user'));
      }
      return users;
  },

  // deletes category user for a metric category
  deleteCategoryUsers: function(metricCategorySysId, users) {
      var grr = new GlideRecord('asmt_m2m_category_user');
      grr.addQuery('metric_category', metricCategorySysId);
      if (users) {
          grr.addQuery('user', 'IN', users);
      }
      grr.query();
      grr.deleteMultiple();
  },

  // helper function to add assessable record from metric type to metric category
  _addRecord: function(asmtm2m, assmtRecord, metricCategory) {
      asmtm2m.addQuery('assessment_record', assmtRecord.getUniqueValue());
      asmtm2m.addQuery('category', metricCategory);
      asmtm2m.query();
      if (asmtm2m.getRowCount() == 0) {
          asmtm2m.initialize();
          asmtm2m.assessment_record = assmtRecord.getUniqueValue();
          asmtm2m.category = metricCategory;
          asmtm2m.insert();
      }
  },

  // helper fuction for returning glide record on metric type table.
  _getAsmtMetricTypeRecord: function(asmtMetricTypeSysId) {
      var metricTypeRecord = new GlideRecord('asmt_metric_type');
      metricTypeRecord.get(asmtMetricTypeSysId);
      return metricTypeRecord;
  },

  // Remove specific stakeholder
  deleteStakeholder: function(assessableRecordId, categoryUserId) {
      var stakeholder = new GlideRecord("asmt_m2m_stakeholder");
      stakeholder.addQuery("assessment_record", assessableRecordId);
      stakeholder.addQuery("category_user", categoryUserId);
      stakeholder.query();
      if (stakeholder.next())
          stakeholder.deleteRecord();
  },

  // Return a GlideRecord obj of m2m category user based on given metric category id and user id
  getM2mCategoryUserByCategoryAndUser: function(metricCategoryId, userId) {
      var catUser = new GlideRecord("asmt_m2m_category_user");
      catUser.addQuery("metric_category", metricCategoryId);
      catUser.addQuery("user", userId);
      catUser.query();
      return catUser;
  },

  // Check if a category user has been used in other assessable records
  ifThisCategoryUserUsedInOtherAssessableRecord: function(metricCategoryId, categoryUserId, assessableRecordId) {
      var count = 0;
      var catAsmt = new GlideRecord("asmt_m2m_category_assessment");
      catAsmt.addQuery("category", metricCategoryId);
      catAsmt.addEncodedQuery("assessment_record.sys_id!=" + assessableRecordId);
      catAsmt.query();
      while (catAsmt.next()) {
          var stakeholder = this.getM2mStakeholderByAssessableRecordAndCategoryUser(catAsmt.assessment_record, categoryUserId);
          if (stakeholder.hasNext()) {
              count += 1;
              break;
          }
      }
      return count > 0;
  },

  // Get assessor from assessable record
  // Return a sys id of user
  getAssessorFromAssessableRecord: function(assessableRecordId, userField) {
      if (gs.nil(userField))
          return "";

      var ar = new GlideRecord("asmt_assessable_record");
      ar.get(assessableRecordId);

      var sourceTable = ar.getValue("source_table");
      var sourceId = ar.getValue("source_id");
      if (!sourceTable || !sourceId)
          return "";

      var source = new GlideRecord(sourceTable);
      source.get(sourceId);
      return source.getElement(userField).toString();
  },

  // This method is used to find a map between assessable records and assessors based on "User Field".
  // Key: the sys id of assessable records; Value: user sys id.
  getMapOfAssessableRecordsAndUsers: function(metricType) {
      var assbleRecordAndUser = {};

      if (gs.nil(metricType.user_field))
          return assbleRecordAndUser;

      var ar = new GlideRecord("asmt_assessable_record");
      ar.addQuery("metric_type", metricType.sys_id);
      ar.query();
      while (ar.next()) {
          var source = new GlideRecord(ar.source_table);
          source.get(ar.source_id);
          var user_id = source.getElement(metricType.user_field).toString();
          assbleRecordAndUser[ar.sys_id] = user_id;
      }
      return assbleRecordAndUser;
  },

  // Return a GlideRecord obj of metric category based on given metric type sys id.
  getMetricCategoryByType: function(metricTypeId) {
      var metricCategory = new GlideRecord("asmt_metric_category");
      metricCategory.addQuery("metric_type", metricTypeId);
      metricCategory.query();
      return metricCategory;
  },

  // Return a GlideRecord obj of m2m category assessment based on given metric category sys id.
  getM2mCategoryAssessmentByCategory: function(metricCategoryId) {
      var catAsmt = new GlideRecord("asmt_m2m_category_assessment");
      catAsmt.addQuery("category", metricCategoryId);
      catAsmt.query();
      return catAsmt;
  },

  // Return a GlideRecord obj of m2m category user based on given metric category id and user id
  getCategoryUserByCategoryAndUser: function(metricCategoryId, userId) {
      var catUser = new GlideRecord("asmt_m2m_category_user");
      catUser.addQuery("metric_category", metricCategoryId);
      catUser.addQuery("user", userId);
      catUser.query();
      return catUser;
  },

  // Return a GlideRecord obj of m2m stakeholder by given assessable record syd id and category user sys id.
  getM2mStakeholderByAssessableRecordAndCategoryUser: function(assessableRecordId, CategoryUserId) {
      var stakeholder = new GlideRecord("asmt_m2m_stakeholder");
      stakeholder.addQuery("assessment_record", assessableRecordId);
      stakeholder.addQuery("category_user", CategoryUserId);
      stakeholder.query();
      return stakeholder;
  },

  // Create category user by metric category is and user id
  createCategoryUser: function(metricCategoryId, userId) {
      var catUser = new GlideRecord("asmt_m2m_category_user");
      catUser.addQuery("metric_category", metricCategoryId);
      catUser.addQuery("user", userId);
      catUser.query();
      if (!catUser.next()) {
          catUser.initialize();
          catUser.setValue("metric_category", metricCategoryId);
          catUser.setValue("user", userId);
          catUser.insert();
      }
      return catUser;
  },

  setSessionLanguage: function(languageId) {
      // Check if this language is valid or not
      // English must be valid by default
      if (languageId != 'en') {
          var sysLangs = new GlideRecord("sys_language");
          sysLangs.addActiveQuery();
          sysLangs.addQuery("id", languageId);
          sysLangs.query();
          if (!sysLangs.hasNext())
              return false;
      }

      GlideSession.get().getUser().setPreference("user.language", languageId);
      return true;
  },

  getAssessment: function(metricTypeId) {

      var surveyInfo = {};

      //Read the metric type info
      var metricType = new GlideRecord('asmt_metric_type');
      metricType.addQuery('sys_id', metricTypeId);
      metricType.query();
      metricType.next();
      surveyInfo.name = metricType.getValue('name');
      surveyInfo.description = metricType.getValue('name');
      surveyInfo.surveyId = metricTypeId;
      surveyInfo.assignToUsers = this.getAssignToUsers(metricTypeId);


      // Read all the metrics(questions)
      var metrics = [];
      var metricInfo = new GlideRecord('asmt_metric');
      metricInfo.addQuery('metric_type', metricTypeId);
      metricInfo.orderBy('order');
      metricInfo.query();

      while (metricInfo.next()) {
          var metric = {};
          metric.name = metricInfo.getValue('name');
          metric.datatype = metricInfo.getValue('datatype');
          metric.depends_on = metricInfo.getValue('depends_on');
          metric.sys_id = metricInfo.getValue('sys_id');
          metric.auto_gen = metricInfo.getValue('auto_gen');
          metric.scored = metricInfo.getValue('scored');
          metric.active = metricInfo.getValue('active');
          metric.hasResult = this.metricHasResult(metricInfo.getValue('sys_id'));

          var options = {};
          switch (metricInfo.getValue('datatype')) {
              case 'scale':
              case 'choice':
                  //Read all the answers (metric definitions)
                  options = [];
                  var answers = new GlideRecord('asmt_metric_definition');
                  answers.addQuery('metric', metricInfo.getValue('sys_id'));
                  answers.orderBy('order');
                  answers.query();
                  while (answers.next()) {
                      options.push({
                          "sys_id": answers.getValue('sys_id'),
                          "answer": answers.getValue('display')
                      });
                  }
                  break;

              case 'long':
              case 'percentage':
              case 'numericscale':
                  options = {
                      'min': metricInfo.getValue('min'),
                      'max': metricInfo.getValue('max')
                  };
                  break;

              case 'string':
                  options = {
                      'textSize': metricInfo.getValue('string_option')
                  };
                  break;

              case 'boolean':
                  if (metric.scored == 1) {
                      metric.datatype = 'attestation';
                      options = {
                          'correct_answer': metricInfo.getValue('correct_answer'),
                          'explain': this.getDependentExplainText(metric.sys_id)
                      };
                  }

                  break;

              case 'template':
                  options = {
                      'templateType': metricInfo.getValue('template')
                  };
                  break;

              default:
                  break;
          }

          metric.options = options;
          metrics.push(metric);
      }

      surveyInfo.metrics = metrics;
      return (new JSON()).encode(surveyInfo);
  },

  createAssessments: function(typeId, sourceId, userId, groupId) {
      return (new SNC.AssessmentCreation()).createAssessments(typeId, sourceId, userId, false, groupId);
  },

  // Get assessments for this type, source, user, and delete if they are not started, otherwise close
  removeAssessments: function(typeId, groupId, userId) {
      var gr = GlideRecord('asmt_assessment_instance');
      gr.addQuery('assessment_group', groupId);
      gr.addQuery('user', userId);
      gr.query();
      while (gr.next()) {
          if (gr.state == 'ready')
              this.deleteInstance(gr);
          else
              this.closeInstance(gr);
      }
  },

  // Close all assessments in this group.
  closeAssessments: function(groupId) {
      var gr = GlideRecord('asmt_assessment_instance');
      gr.addQuery('assessment_group', groupId);
      gr.query();
      while (gr.next())
          this.closeInstance(gr);
  },

  deleteInstance: function(grInst) {
      if (grInst.sys_id) { // Checking record and id are valid
          var delQuestions = GlideRecord('asmt_assessment_instance_question');
          delQuestions.addQuery('instance', grInst.sys_id);
          delQuestions.query();
          delQuestions.deleteMultiple();
      }
      grInst.deleteRecord();
  },

  closeInstance: function(grInst) {
      if (grInst.state != 'complete')
          grInst.state = 'canceled';
      if (grInst.due_date > gs.daysAgo(1))
          grInst.due_date = gs.daysAgo(1);
      grInst.update();
  },

  metricHasResult: function(metricId) {
      var metric = new GlideRecord('asmt_metric_result');
      metric.addQuery('metric', metricId);
      metric.setLimit(1);
      metric.query();
      return metric.hasNext();
  },

  getDependentExplainText: function(metricId) {
      var metric = new GlideRecord('asmt_metric');
      metric.addQuery('depends_on', metricId);
      metric.addQuery('auto_gen', true);
      metric.query();
      metric.next();
      return metric.getValue("name");
  },

  getAssignToUsers: function(metricTypeId) {
      var ctd_name = new GlideRecord('asmt_metric_type');
      ctd_name.get(metricTypeId);
      var name = ctd_name.getValue('name');

      var cat = new GlideRecord('asmt_metric_category');
      cat.addQuery('metric_type', metricTypeId);
      cat.addQuery('name', name);
      cat.orderBy('order');
      cat.query();
      cat.next();
      var catId = cat.sys_id;

      var users = [];
      var catUserGr = new GlideRecord('asmt_m2m_category_user');
      catUserGr.addQuery('metric_category', catId);
      catUserGr.orderBy('user');
      catUserGr.query();
      while (catUserGr.next()) {
          users.push({
              "user": catUserGr.getValue("user"),
              "name": catUserGr.getDisplayValue("user")
          });
      }
      return users;
  },

  getAllCategoryUsers: function(type_id) {
      var ids = [];
      var catUserGr = new GlideAggregate('asmt_m2m_category_user');
      catUserGr.addQuery('metric_category.metric_type', type_id);
      catUserGr.orderBy('user');
      catUserGr.setUnique(true);
      catUserGr.query();
      while (catUserGr.next())
          ids.push(catUserGr.getValue("user"));

      return ids.join(",");
  },

  getTypeFilter: function() {
      var view = gs.getSession().getClientData("asmt_view");
      if (view)
          return "evaluation_method=" + view;
      else
          return "";
  },

  checkRecord: function(current, metricType, byPassCheck) {
      if (!current.isValidRecord())
          return;

      var mt = new GlideRecord('asmt_metric_type');
      mt.get(metricType);
      var condition = mt.condition;

      var matched = false;
      if (byPassCheck)
          matched = true;
      else {
          var sourceRecord = new GlideRecord(current.getTableName());
          sourceRecord.addQuery('sys_id', current.sys_id);
          sourceRecord.addEncodedQuery(mt.condition + '');
          sourceRecord.setLimit(1);
          sourceRecord.query();
          matched = sourceRecord.hasNext();
      }

      var tables = this.getTableHierarchy(current);
      var gr = new GlideRecord("asmt_assessable_record");
      gr.addQuery("source_id", current.sys_id);
      gr.addQuery("source_table", "IN", tables.join());
      gr.addQuery("metric_type", metricType);
      gr.query();

      // if the metric type has enforce condition set to true and
      // there is a matching assessable record, delete it
      if (!matched) {
          if (mt.enforce_condition && gr.hasNext()) {
              gr.next();
              gr.deleteRecord();
          }
          return;
      }

      // if the source record matches the metric type condition
      // and there is no assessable record, create one
      if (matched && !gr.hasNext()) {
          gr.source_id = current.sys_id;
          gr.source_table = current.getTableName();
          gr.name = current.name;
          gr.metric_type = metricType;
          gr.insert();
      }
  },

  /* get an array of table hierarchy of the current record. 
   * For example, current is an incident GR, it will return ['incident', 'task']
   */
  getTableHierarchy: function(gr) {
      var tables = [];
      if (!gr || !gr.isValidRecord())
          return tables;

      tables.push(gr.getTableName()); // use an array to store the table names
      var sysDbObjectGr = new GlideRecord('sys_db_object');
      sysDbObjectGr.addQuery('name', gr.getTableName());
      sysDbObjectGr.addNotNullQuery('super_class');
      sysDbObjectGr.setLimit(1);
      sysDbObjectGr.query();
      if (sysDbObjectGr.next())
          this._getTableHierarchyHelper(sysDbObjectGr.super_class, tables);

      return tables;
  },

  _getTableHierarchyHelper: function(element, tables) {
      if (!element || !element.name)
          return;

      tables.push(element.name + '');
      if (!element.super_class.nil())
          this._getTableHierarchyHelper(element.super_class, tables);
  },

  checkDeleteRecord: function(current, metricType, showError) {
      var gr = new GlideRecord('asmt_metric_result');
      gr.addQuery('metric.metric_type', metricType);
      gr.addQuery('source_id', current.sys_id);
      gr.addQuery('source_table', current.getTableName());
      gr.setLimit(1);
      gr.query();
      if (gr.next()) {
          if (showError)
              gs.addErrorMessage(gs.getMessage('Items with related metric results cannot be deleted'));
          return false;
      }

      gr = new GlideRecord('asmt_category_result');
      gr.addQuery('metric_type', metricType);
      gr.addQuery('source_id', current.sys_id);
      gr.addQuery('source_table', current.getTableName());
      gr.setLimit(1);
      gr.query();
      if (gr.next()) {
          if (showError)
              gs.addErrorMessage(gs.getMessage('Items with related category results cannot be deleted'));
          return false;
      }

      gr = new GlideRecord('asmt_assessable_record');
      gr.addQuery('metric_type', metricType);
      gr.addQuery('source_id', current.sys_id);
      gr.addQuery('source_table', current.getTableName());
      gr.deleteMultiple();

      gr = new GlideRecord('asmt_assessment_instance_question');
      gr.addQuery('instance.metric_type', metricType);
      gr.addQuery('source_id', current.sys_id);
      gr.addQuery('source_table', current.getTableName());
      gr.deleteMultiple();
      return true;
  },

  domainsAreEqual: function(domain1, domain2) {
      if (this.domainNormalize(domain1) == this.domainNormalize(domain2))
          return true;
      return false;
  },

  domainNormalize: function(domainValue) {
      var domain = '';
      if ((domainValue === undefined) || domainValue.nil())
          return domain;
      if (domainValue.indexOf('global') >= 0)
          return domain;
      // When domainValue is Java string then rihno errors out because of
      // ambiguity. Java String has both replace(CharSequence,CharSequence)
      // and replace(String,String).
      var trimmedString = j2js(domainValue).replace(/^\s+|\s+$/g, ''); // trim
      return trimmedString === 'global' ? '' : trimmedString;
  },

  domainIsGlobal: function(domainValue) {
      return (this.domainNormalize(domainValue) == '');
  },

  defaultMatrix: function(current) {
      var gr = new GlideRecord("asmt_decision_matrix");
      gr.addQuery("isdefault", true);
      gr.addQuery("metric_type", "881df17dd7240100fceaa6859e610366");
      gr.query();
      if (!gr.hasNext()) {
          gr = new GlideRecord("asmt_decision_matrix");
          gr.addQuery("metric_type", "881df17dd7240100fceaa6859e610366");
          gr.query();
      }

      if (gr.next())
          return gr.sys_id.toString();
      else
          return null;
  },

  hasAssessmentRoles: function(roles) {
      var isAdmin = gs.hasRole('admin') || gs.hasRole('assessment_admin');
      if (isAdmin)
          return true;
      try {
          var Roles = roles.split(',');
          for (var i = 0; i < Roles.length; i++) {
              if (gs.hasRole(Roles[i]))
                  return true;
          }
      } catch (e) {
          gs.log('AssessmentUtils:hasAssessmentRoles: Exception: ' + e);
      }
      return false;
  },

  createStakeholders: function(categoryUserSys, assessmentSys) {
      var stakeholder = new GlideRecord("asmt_m2m_stakeholder");
      stakeholder.addQuery("category_user", categoryUserSys);
      stakeholder.addQuery("assessment_record", assessmentSys);
      stakeholder.query();
      if (!stakeholder.hasNext()) {
          stakeholder.initialize();
          stakeholder.category_user = categoryUserSys;
          stakeholder.assessment_record = assessmentSys;
          stakeholder.insert();
      }
  },

  getMetricTypeFilters: function(metricTypeId) {
      var metricType = new GlideRecord('asmt_metric_type');

      metricType.get(metricTypeId);
      var table = metricType.table + '';
      var condition = metricType.condition + '';
      var groupField = metricType.display_field + '';
      var filterTable = metricType.filter_table + '';
      var filterCondition = metricType.filter_condition + '';
      var showAllGroups = metricType.display_all_filters;

      return this.getFilters(metricTypeId, table, groupField, condition, filterTable, filterCondition, showAllGroups);
  },

  getFilters: function(metricTypeId, table, groupField, condition, filterTable, filterCondition, showAllGroups) {

      var uniqueGroups = {};
      if (filterTable == '')
          return [];

      var groupIsReference = false;
      if (table != filterTable)
          groupIsReference = true;
      else {
          var type = new GlideRecord(table);
          if (groupField !== 'none') {
              var refFieldType = type.getElement(groupField).getED().getInternalType();
              if (refFieldType == 'glide_list' || refFieldType == 'reference')
                  groupIsReference = true;
          }
      }

      if (!groupIsReference) {
          var gr1 = new GlideAggregate(table);
          gr1.addEncodedQuery(filterCondition);
          gr1.groupBy(groupField);
          gr1.query();
          while (gr1.next()) {
              var value = gr1[groupField] || '';
              uniqueGroups[value] = true;
          }
      } else if (showAllGroups == 'true' || showAllGroups == true) {
          var gr2 = new GlideRecord(filterTable);
          gr2.addEncodedQuery(filterCondition);
          gr2.query();
          uniqueGroups[''] = true;
          while (gr2.next())
              uniqueGroups[gr2.sys_id + ''] = true;
      } else {
          var acceptableValuesMap = {};
          var gr3 = new GlideRecord(filterTable);
          gr3.addEncodedQuery(filterCondition);
          gr3.query();
          while (gr3.next())
              acceptableValuesMap[gr3.sys_id + ''] = true;

          var gr4 = new GlideAggregate(table);

          var domain = this.getMetricDomain(metricTypeId);
          if (domain != null)
              gr4.addQuery('sys_domain', domain);
          if (condition)
              gr4.addEncodedQuery(condition);
          gr4.groupBy(groupField);
          gr4.query();

          while (gr4.next()) {
              var groupValues = gr4[groupField];
              if (!groupValues && !filterCondition) {
                  uniqueGroups[''] = true;
                  continue;
              }

              groupValues = groupValues.split(',');
              for (var i = 0; i < groupValues.length; i++) {
                  if (groupValues[i] in acceptableValuesMap)
                      uniqueGroups[groupValues[i]] = true;
              }
          }
      }

      var allOptions = [];
      for (var key in uniqueGroups) {
          if (key === undefined || key == null)
              continue;
          if (key == '') {
              allOptions.push({
                  display: '(empty)',
                  value: '(empty)'
              });
          } else if (!groupIsReference) {
              allOptions.push({
                  display: key,
                  value: key
              });
          } else
              allOptions.push(this._getReferenceOption(key, filterTable));
      }

      // Sort by display
      allOptions.sort(function(a, b) {
          if (a.display < b.display)
              return -1;
          else if (a.display > b.display)
              return 1;
          else
              return 0;
      });

      return allOptions;
  },

  _getReferenceOption: function(value, table) {
      var gr = new GlideRecord(table);
      gr.get(value);
      var display = gr.getDisplayValue();
      return {
          display: display,
          value: value
      };
  },

  getMetricDomain: function(id) {
      if (!GlidePluginManager.isActive('com.glide.domain'))
          return null;

      var gr = new GlideRecord('asmt_metric_type');
      gr.get(id);
      return gr.sys_domain + '';
  },

  // Survey Instance Table URL
  getAssessmentInstanceURL: function( /* String */ instance) {
      var gr = new GlideRecord("asmt_assessment_instance");
      var type = '';
      if (gr.get(instance)) {
          var asmtRec = new GlideRecord("asmt_metric_type");
          asmtRec.addQuery("sys_id", gr.getValue("metric_type"));
          asmtRec.query();
          if (asmtRec.next())
              type = asmtRec.getValue("sys_id");
      }
      var instanceURL = gs.getProperty("glide.servlet.uri");
      var overrideURL = gs.getProperty("glide.email.override.url");
      var url = "";
      if (this.redirectToPortal() == 'true') {
          if (asmtRec.allow_public)
              url = instanceURL + this.defaultServicePortal + '?id=public_survey&instance_id=' + instance;
          else
              url = instanceURL + this.defaultServicePortal + '?id=take_survey&instance_id=' + instance;
      } else {
          if (overrideURL)
              instanceURL = overrideURL;
          else
              instanceURL = instanceURL + "nav_to.do";

          url = instanceURL + '?uri=%2Fassessment_take2.do%3Fsysparm_assessable_type=' + type + '%26sysparm_assessable_sysid=' + instance;
      }

      return url;
  },

  // External Survey URL; includes processor that will auto create instance for
  // unassigned Survey/Assessments
  getAssessmentTypeURL: function( /* String */ type) {
      var url = gs.getProperty('glide.servlet.uri');
      var isSurveyPublic = false;
      var eval_method = "";
      var typeGr = new GlideRecord("asmt_metric_type");
      if (typeGr.get(type)) {
          isSurveyPublic = typeGr.allow_public;
          eval_method = typeGr.evaluation_method;
      }
      if (eval_method == "survey" && this.redirectToPortal() == "true") {
          if (isSurveyPublic)
              url += this.defaultServicePortal + '?id=public_survey&type_id=' + type;
          else
              url += this.defaultServicePortal + '?id=take_survey&type_id=' + type;
      } else {
          if (isSurveyPublic)
              url += 'assessment_take2.do?sysparm_assessable_type=' + type;
          else
              url += 'nav_to.do?uri=assessment_take2.do%3Fsysparm_assessable_type=' + type;
      }

      return url;
  },

  redirectToPortal: function() {
      var isServicePortalActive = pm.isActive("com.glide.service-portal") &&
          pm.isActive("com.glide.service-portal.survey");
      var emailRedirectProp = gs.getProperty("sn_portal_surveys.sp_survey.email_redirection", true);
      var hasDefaultPortal = false;
      var redirectToPortal = false;
      if (isServicePortalActive) {
          var gr = new GlideRecord('sp_portal');
          gr.addQuery('default', 'true');
          gr.query();
          if (gr.next()) {
              hasDefaultPortal = true;
              this.defaultServicePortal = gr.getValue('url_suffix');
          }
      }
      redirectToPortal = isServicePortalActive && hasDefaultPortal && emailRedirectProp;
      return redirectToPortal;
  },

  //Hide button "Send Invitations" if there is no user or metric or recipient list for the survey.
  hasUserAndMetric: function(metricType) {
      var countQuestions = 0;
      var countUsers = 0;
      var metricGr = new GlideRecord('asmt_metric');
      metricGr.addQuery('metric_type', metricType.sys_id);
      metricGr.setLimit(1);
      metricGr.query();
      countQuestions = metricGr.getRowCount();
      if (countQuestions == 0)
          return false;
      var gr = new GlideRecord('asmt_metric_category');
      gr.addQuery('metric_type', metricType.sys_id);
      gr.addJoinQuery('asmt_m2m_category_user', 'sys_id', 'metric_category');
      gr.setLimit(1);
      gr.query();
      countUsers = gr.getRowCount();

      // Check if we have recipients list(s).
      var hasRecipientList = false;
      if (this.isTCPluginActive()) {
          var gr_rl = new GlideRecord('asmt_m2m_recipientslist_survey');
          gr_rl.addQuery("metric_type", metricType.sys_id);
          gr_rl.query();
          if (gr_rl.next()) {
              hasRecipientList = true;
          }
      }
      return countUsers > 0 || hasRecipientList;
  },

  isTCPluginActive: function() {
      return pm.isActive("com.sn_publications");
  },

  // Hide Invite User & Publish if no active questions
  hasSurveyQuestions: function(metricType) {
      var gr = new GlideRecord('asmt_metric');
      gr.addQuery('category.metric_type', metricType.sys_id);
      gr.setLimit(1);
      gr.addActiveQuery();
      gr.query();
      return gr.hasNext();
  },

  createAssessment: function(surveyInfo, method, state, surveyId, editMode) {

      editMode = (editMode == 'true');
      var metricTypeId = '';
      try {

          var surveyInfoObj = (new JSONParser()).parse(surveyInfo);
          var survey_name = surveyInfoObj.name;
          var description = surveyInfoObj.description;
          var metrics = surveyInfoObj.metrics;

          var metricType = new GlideRecord('asmt_metric_type');

          var preview = metricType.get(surveyId);

          if (editMode || preview) {
              metricTypeId = surveyId;

              metricType.name = survey_name;
              metricType.evaluation_method = method;
              metricType.publish_state = state;
              metricType.description = description;
              metricType.update();

              this.clearMetrics(metricTypeId);

          } else {
              metricType = new GlideRecord('asmt_metric_type');
              metricType.name = survey_name;
              metricType.evaluation_method = method;
              metricType.publish_state = state;
              metricType.description = description;

              //This is the pre-generated id.
              if (surveyId)
                  metricType.setNewGuidValue(surveyId);

              //There is a business rule here to
              //create a default category if it does not exists.
              if (metricType.canCreate())
                  metricTypeId = metricType.insert();
          }

          var ctd_name = new GlideRecord('asmt_metric_type');
          ctd_name.get(metricTypeId);
          var name = ctd_name.getValue('name');

          //In a business rull, insert category if it does not exist.
          var defaultCategory = new GlideRecord('asmt_metric_category');
          defaultCategory.addQuery('metric_type', metricTypeId);
          defaultCategory.addQuery('name', name);
          defaultCategory.query();

          if (defaultCategory.next()) {

              for (var i = 0; i < metrics.length; i++) {

                  var metricInfo = metrics[i];

                  var metric = new GlideRecord('asmt_metric');

                  var metric_sys_id = metricInfo.sys_id;
                  if (metric_sys_id && editMode) {
                      metric.addQuery('sys_id', metric_sys_id);
                      metric.query();
                      metric.next();
                  } else {
                      metric.initialize();
                      metric.category = defaultCategory.sys_id;
                  }

                  if (metricInfo.active === "false")
                      metric.active = false;
                  else
                      metric.active = true;

                  metric.name = metricInfo.question;
                  metric.question = metricInfo.question;
                  metric.datatype = metricInfo.type;

                  metric.order = i * this.DefaultMaxOrder / 2 + this.DefaultMaxOrder;
                  metric.scale = "high";
                  var explain = "";
                  var correct_answer = "";

                  var options = metricInfo.options;

                  switch (metricInfo.type) {
                      case 'scale':
                      case 'choice':
                          metric.min = 0;
                          metric.max = options.length - 1;
                          break;

                      case 'long':
                      case 'percentage':
                      case 'numericscale':
                          metric.min = options.min;
                          metric.max = options.max;
                          break;

                      case 'string':
                          metric.string_option = options.textSize;
                          break;

                      case 'template':
                          var minMax = this.getTemplateMinMax(options.templateType);
                          metric.template = options.templateType;
                          metric.min = minMax.min;
                          metric.max = minMax.max;
                          break;

                          // datatype 'attestation' is only used on UI.
                      case 'attestation':
                          metric.datatype = 'boolean';
                          metric.correct_answer = options.correct_answer;
                          metric.mandatory = 1;
                          correct_answer = options.correct_answer;
                          explain = options.explain;

                      case 'checkbox':
                          metric.min = 0;
                          metric.max = 1;
                          break;

                      default:
                          break;
                  }

                  //clear the scored flag when user change from attestation to others.
                  if (metricInfo.type === 'attestation')
                      metric.scored = 1;


                  //update scored and mandatory when question type changes
                  if (metric.datatype != 'attestation' && metric.scored == true && metricInfo.type != 'attestation' && editMode) {
                      var grr = new GlideRecord('asmt_metric');
                      grr.get(metric.sys_id);
                      if (grr.getValue('datatype') == 'boolean' && grr.getValue('scored') == true) {
                          metric.scored = false;
                          metric.mandatory = false;
                      }
                  }

                  //insert asmt_metric
                  var metricId = '';
                  if (metric_sys_id && editMode) {
                      metric.update();
                      metricId = metric_sys_id;
                  } else {
                      metric.metric_type = metricTypeId;
                      metricId = metric.insert();
                  }

                  // update or add answers for Choice and Likert
                  if (metricInfo.type == 'scale' || metricInfo.type == 'choice') {
                      for (var k = 0; k < options.length; k++) {
                          var metricDef1 = new GlideRecord('asmt_metric_definition');

                          var metricDef_sys_id = options[k].sys_id;
                          if (metricDef_sys_id && editMode) {
                              metricDef1.addQuery('sys_id', metricDef_sys_id);
                              metricDef1.query();
                              metricDef1.next();
                          } else {
                              metricDef1.initialize();
                          }

                          metricDef1.display = options[k].answer;
                          metricDef1.value = k;
                          metricDef1.order = k + this.DefaultMaxOrder;
                          metricDef1.metric = metricId;

                          if (metricDef_sys_id && editMode) {
                              metricDef1.update();
                          } else {
                              //insert asmt_metric_definition
                              metricDef1.insert();
                          }
                      }
                  }

                  // add a dependent question.
                  if (method === 'attestation') {
                      explain = explain.trim();
                      if (!this.hasDependent(metricId))
                          this.addDependentQuestion(metricId, correct_answer, explain);
                      else
                          this.updateDependentQuestion(metricId, correct_answer, explain);
                  }

              }

              this.deleteEmptyMetrics(metricTypeId);
              this.deleteEmptyMetricDefs();
              this.deleteObsoleteDependents();

              this.setOrder(metricTypeId);
              return metricTypeId;
          }

      } catch (e) {
          gs.log('AssessmentUtils:createSurvey: Exception: ' + e);
      }
  },

  setOrder: function(metricTypeId) {
      var gr = new GlideRecord('asmt_metric');
      gr.addQuery('metric_type', metricTypeId);
      gr.orderBy('order');
      gr.query();
      while (gr.next()) {
          var order = gr.getValue('order');
          this.sortDependents(gr.getValue('sys_id'), order);
      }
  },

  sortDependents: function(metricId, order) {
      var gr = new GlideRecord('asmt_metric');
      gr.addQuery('depends_on', metricId);
      gr.orderByDesc('auto_gen');
      gr.orderBy('order');
      gr.query();
      while (gr.next()) {
          gr.setValue('order', ++order);
          gr.update();
      }
  },

  hasDependent: function(metricId) {
      var metric = new GlideRecord('asmt_metric');
      metric.addQuery('depends_on', metricId);
      metric.addQuery('auto_gen', true);
      metric.query();
      return metric.hasNext();
  },

  // add a single dependent question to the underlying attestation question.
  addDependentQuestion: function(metricId, correct_answer, explain) {
      var gr = new GlideRecord('asmt_metric');
      gr.get(metricId);

      var q = new GlideRecord("asmt_metric");
      q.initialize();
      q.name = explain;
      q.question = explain;
      q.string_option = 'wide';
      q.datatype = 'string';
      q.auto_gen = 1;
      q.mandatory = 1;

      q.method = 'assessment';
      q.metric_type = gr.getValue('metric_type');
      q.category = gr.getValue('category');
      q.depends_on = metricId;

      if (correct_answer == "0")
          q.displayed_when_yesno = 1;
      else
          q.displayed_when_yesno = 0;

      q.weight = 10;
      q.max_weight = 20;
      q.order = this.DefaultMaxOrder;
      if (gr.active)
          q.active = 1;
      else
          q.active = 0;

      q.insert();

  },

  // update dependent question in case the correct answer has changed.
  updateDependentQuestion: function(metricId, correct_answer, explain) {
      var gr = new GlideRecord("asmt_metric");
      gr.get(metricId);

      var q = new GlideRecord("asmt_metric");
      q.addQuery('depends_on', metricId);
      q.addQuery('auto_gen', true);
      q.query();
      q.next();
      if (correct_answer == "0")
          q.displayed_when_yesno = 1;
      else
          q.displayed_when_yesno = 0;
      q.name = explain;
      q.question = explain;
      q.mandatory = 1;
      if (gr.active)
          q.active = 1;
      else
          q.active = 0;
      q.update();
  },

  deleteEmptyMetrics: function(metricTypeId) {
      var metric = new GlideRecord('asmt_metric');
      metric.addQuery('metric_type', metricTypeId);
      metric.addQuery('name', '');
      metric.query();
      while (metric.next()) {
          //delete related answers
          this.deleteMetricDefs(metric.getValue('sys_id'));
          //delete dependent questions
          this.deleteDependentMetrics(metric.getValue('sys_id'));
          metric.deleteRecord();
      }
  },

  deleteObsoleteDependents: function(metricTypeId) {
      var metric = new GlideRecord('asmt_metric');
      metric.addQuery('metric_type', metricTypeId);
      metric.addQuery('auto_gen', 1);
      metric.query();
      while (metric.next()) {
          var dependsOn = metric.getValue('depends_on');
          if (!this.attestationMetricExists(dependsOn))
              metric.deleteRecord();
      }
  },

  attestationMetricExists: function(sys_id) {
      var metric = new GlideRecord('asmt_metric');
      metric.addQuery('sys_id', sys_id);
      metric.addQuery('scored', 1);
      metric.query();
      return metric.hasNext();
  },

  deleteDependentMetrics: function(metricId) {
      var metric = new GlideRecord('asmt_metric');
      metric.addQuery('depends_on', metricId);
      metric.deleteMultiple();
  },

  deleteMetricDefs: function(metricId) {
      var metricDef = new GlideRecord('asmt_metric_definition');
      metricDef.addQuery('metric', metricId);
      metricDef.deleteMultiple();
  },

  deleteEmptyMetricDefs: function() {
      var metricDef = new GlideRecord('asmt_metric_definition');
      metricDef.addQuery('metric', '');
      metricDef.deleteMultiple();
  },

  clearMetrics: function(metricTypeId) {
      var metric = new GlideRecord('asmt_metric');
      metric.addQuery('metric_type', metricTypeId);
      //TODO: leave the dependent questions alone for now.
      metric.addNullQuery('depends_on');
      metric.query();
      while (metric.next()) {
          this.clearMetricDefs(metric.getValue('sys_id'));
          metric.setValue('name', '');
          metric.setValue('auto_gen', 0);
          metric.update();
      }
  },

  clearMetricDefs: function(metricId) {
      var metricDef = new GlideRecord('asmt_metric_definition');
      metricDef.addQuery('metric', metricId);
      metricDef.query();
      while (metricDef.next()) {
          metricDef.setValue('metric', '');
          metricDef.update();
      }
  },

  removeAssessment: function(metricTypeId) {
      try {
          var metricType = new GlideRecord('asmt_metric_type');
          metricType.get(metricTypeId);
          metricType.deleteRecord();
      } catch (e) {
          gs.log('AssessmentUtils:createSurvey: Exception: ' + e);
      }
      return;
  },

  // Do case insensitive condition check
  conditionCheck: function(current, condition) {
      var filter = new GlideFilter(condition, '');
      filter.setCaseSensitive(false);
      filter.setEnforceSecurity(true);

      // check to see if current matches condition
      return filter.match(current, true);
  },

  getTemplateMinMax: function(template) {

      var gr = new GlideAggregate("asmt_template_definition");
      gr.addQuery("template", template);
      gr.addAggregate("MIN", "value");
      gr.addAggregate("MAX", "value");
      gr.groupBy('template');
      gr.query();

      gr.next();
      var min = gr.getAggregate("MIN", "value");
      var max = gr.getAggregate("MAX", "value");

      var result = {
          min: min,
          max: max
      };
      return result;
  },

  addSourcesToAssessment: function(sourceId, asmtId) {
      new SNC.AssessmentCreation().addSourcesToAssessment(sourceId, asmtId);
  },

  deleteMetricResults: function(record, metricTypeId) {
      var metric = new GlideRecord('asmt_metric');
      metric.addQuery('metric_type', metricTypeId);
      metric.query();
      while (metric.next()) {
          var amr = new GlideRecord('asmt_metric_result');
          amr.addQuery('metric', metric.sys_id + '');
          amr.addQuery('source_id', record.sys_id);
          amr.addQuery('source_table', record.getTableName());
          amr.query();
          amr.deleteMultiple();
      }
  },

  deleteCategoryResults: function(record, metricTypeId) {
      var catResult = new GlideRecord('asmt_category_result');
      catResult.addQuery('metric_type', metricTypeId);
      catResult.addQuery('source_id', record.sys_id);
      catResult.addQuery('source_table', record.getTableName());
      catResult.query();
      catResult.deleteMultiple();
  },

  //Util method to check for an integer. On server side js, it does not look like there is a common util method to do this
  isInteger: function(text) {
      if (text == null)
          return true;
      var validChars = "0123456789,-";
      return this._containsOnlyChars(validChars, text);
  },

  canDeleteMetric: function(metricGR) {
      var metricResultGR = new GlideRecord('asmt_metric_result');
      var canDelete = true;
      if (metricGR != null) {
          if (metricGR.datatype == 'attachment') {
              metricResultGR.addQuery('metric', metricGR.getUniqueValue());
              metricResultGR.query();
              var metricResultSysID = [];
              while (metricResultGR.next()) {
                  metricResultSysID.push(metricResultGR.getUniqueValue());
              }
              var attachmentGR = new GlideRecord('sys_attachment');
              attachmentGR.addQuery('table_name', 'asmt_metric_result');
              attachmentGR.addQuery('table_sys_id', 'IN', metricResultSysID.join());
              attachmentGR.query();
              if (attachmentGR.hasNext())
                  canDelete = false;
          } else {
              var encodedQuery = 'metric.datatypeINdate,datetime,string^string_valueISNOTEMPTY^metric=';
              encodedQuery += metricGR.getUniqueValue();
              encodedQuery += '^NQmetric.datatype=reference^reference_value!=-1^metric=';
              encodedQuery += metricGR.getUniqueValue();
              encodedQuery += '^NQmetric.datatypeINchoice,imagescale,scale,multiplecheckbox,long,numericscale,percentage,ranking,template,boolean^actual_value!=-1^metric=';
              encodedQuery += metricGR.getUniqueValue();
              encodedQuery += '^NQmetric.datatype=checkbox^metric=';
              encodedQuery += metricGR.getUniqueValue();
              metricResultGR.addEncodedQuery(encodedQuery);
              metricResultGR.query();
              if (metricResultGR.hasNext())
                  canDelete = false;
          }
      }
      return canDelete;
  },

  _containsOnlyChars: function(validChars, sText) {
      var IsNumber = true;
      var c;
      for (var i = 0; i < sText.length && IsNumber == true; i++) {
          c = sText.charAt(i);
          if (validChars.indexOf(c) == -1) {
              IsNumber = false;
          }
      }
      return IsNumber;
  },

  getInstanceLinkHTML: function(instanceGr) {
      var link = this.getAssessmentInstanceURL(instanceGr.sys_id);
      return this._getLinkHTML(instanceGr.getValue("metric_type"), link);
  },

  getTypeLinkHTML: function(typeGr) {
      var link = this.getAssessmentTypeURL(typeGr.sys_id);
      return this._getLinkHTML(typeGr.sys_id, link);
  },

  _getLinkHTML: function(typeId, link) {
      var html;
      var typeGr = new GlideRecord("asmt_metric_type");
      if (!typeGr.get(typeId))
          return "";
      var sample_metric_id = typeGr.getValue("sample_metric");
      if (GlideStringUtil.isEligibleSysID(sample_metric_id))
          html = '<img src="sys_attachment.do?sys_id=' + this._getSampleMetricImageId(sample_metric_id) + '"></img>';
      else
          html = gs.getMessage("Take me to the Survey");
      return '<a href="' + link + '">' + html + '</a>';
  },

  _getSampleMetricImageId: function(metricId) {
      var gr = new GlideRecord("sys_attachment");
      gr.addQuery("file_name", "sample_metric_image_" + metricId + ".png");
      gr.addQuery("table_name", "ZZ_YYasmt_metric_type");
      gr.orderByDesc("sys_updated_on");
      gr.setLimit(1);
      gr.query();
      if (!gr.next())
          return "";
      return gr.getUniqueValue();
  },

  processBankAction: function(action, from_list, to_record, include_dependent_questions) {
      var gr;
      if (action.indexOf("metric") >= 0) {
          this.addMetricsToCategory(from_list, to_record, include_dependent_questions);
          gr = new GlideRecord("asmt_metric_category");
      } else if (action.indexOf("category") >= 0) {
          if (AssessmentUtils.isChatSurvey(to_record) && this.hasExistingCategories(to_record)) {
              action = "invalid_category_add_chat_survey";
          } else {
              this.addCategoriesToType(from_list, to_record);
              gr = new GlideRecord("asmt_metric_type");
          }
      }
      gr.get(to_record);

      var resultObj = {
          msg: null,
          success: true
      };

      if (action == 'invalid_category_add_chat_survey') {
          resultObj.msg = gs.getMessage('For Chat Survey, all questions must belong to a single category');
          resultObj.success = false;
      }

      if (action == 'add_metric_from_bank')
          resultObj.msg = gs.getMessage('Added selected Metrics into current Category record');
      else if (action == 'add_category_from_bank')
          resultObj.msg = gs.getMessage('Added selected Categories into current Type record');
      else if (action == 'from_bank_add_metric')
          resultObj.msg = gs.getMessage('Added current Metric to {0} Category', GlideStringUtil.escapeHTML(gr.getDisplayValue("name")));
      else if (action == 'from_bank_add_category')
          resultObj.msg = gs.getMessage('Added current Category to {0} Type record', GlideStringUtil.escapeHTML(gr.getDisplayValue("name")));
      else if (action == 'add_metric_to_bank')
          resultObj.msg = gs.getMessage('Added current Metric to {0} Question Bank', GlideStringUtil.escapeHTML(gr.getDisplayValue("name")));
      else if (action == 'add_category_to_bank')
          resultObj.msg = gs.getMessage('Added current Category into Question Bank');

      return resultObj;
  },

  // check if has existing categories
  hasExistingCategories: function(metricTypeSysID) {
      var survey = new GlideRecord("asmt_metric_type");
      if (survey.get(metricTypeSysID)) {
          var category = new GlideRecord("asmt_metric_category");
          category.addQuery("metric_type", metricTypeSysID);
          category.setLimit(1);
          category.query();
          return category.hasNext();
      }
      return false;
  },

  // The argument can be GlideRecord or GlideElement
  isValidBankCategory: function(category) {
      return !!(category.qb_evaluation_method + "");
  },

  // The argument can be GlideRecord or GlideElement
  isValidBankMetric: function(metric) {
      var categoryGr = new GlideRecord("asmt_metric_category");
      if (!categoryGr.get(metric.category + ""))
          return false;
      return this.isValidBankCategory(categoryGr);
  },

  //This method is used for checking if there are any dependent questions for a given question
  hasDependentMetrics: function(metricsList) {
      var metric = new GlideRecord('asmt_metric');
      metric.addQuery('depends_on', 'IN', metricsList);
      metric.query();
      return metric.hasNext();
  },

  //This method is used for adding categories to metric type, basically creating new catgeories from given categories.
  addCategoriesToType: function(categoriesToAdd, typeID) {
      var gr_category = new GlideRecord("asmt_metric_category");
      var maxOrder = this.getMaxOrder("asmt_metric_category", "metric_type", typeID);
      for (var i = 0; i < categoriesToAdd.length; i++) {
          if (!gr_category.get(categoriesToAdd[i]))
              return;

          //Incrementing maxOrder everytime a new question is added.
          maxOrder += this.DefaultMaxOrder;
          gr_category.name = gr_category.getDisplayValue('name');
          if (typeID) {
              gr_category.qb_evaluation_method = '';
              gr_category.metric_type = typeID;
              gr_category.order = maxOrder;
          } else {
              gr_category.qb_evaluation_method = gr_category.getElement("metric_type.evaluation_method").toString();
              gr_category.metric_type = '';
          }
          gr_category.table = '';
          gr_category.filter = '';
          var newCatID = gr_category.insert();
          //copy the metrics under this category
          new CloneSurvey().cloneMetric('', '', categoriesToAdd[i], newCatID);
      }
  },

  //This method is used for getting a list of questions and adding them to a given category. If there are any dependencies, they will be copied over as well.
  addMetricsToCategory: function(metricsToAdd, categoryID, addDependencyFlag) {
      new SNC.NGAssessmentUtil.setAssessmentAction("clone");
      var hasDependent = false;
      var dependencyMap = {};
      var metricDefinitionMap = {};
      var maxOrder = this.getMaxOrder("asmt_metric", "category", categoryID);
      var gr = new GlideRecord('asmt_metric');
      gr.addQuery('depends_on', 'IN', metricsToAdd);
      gr.query();
      if (gr.hasNext())
          hasDependent = true;
      for (var i = 0; i < metricsToAdd.length; i++) {
          maxOrder += this.DefaultMaxOrder;
          gr.get(metricsToAdd[i]);
          gr.category = categoryID;
          gr.order = maxOrder;

          //Case 1: no dependent questions for any of the questions to be added to the bank
          if (!hasDependent || !addDependencyFlag) {
              gr = this.removeDependencies(gr);
              var newMetricSysID = gr.insert();
              if (gr.getValue('datatype') === 'imagescale')
                  metricDefinitionMap = new CloneSurvey().cloneMetricDefinition(metricsToAdd[i], newMetricSysID, true, metricDefinitionMap);
              else
                  metricDefinitionMap = new CloneSurvey().cloneMetricDefinition(metricsToAdd[i], newMetricSysID, false, metricDefinitionMap);
          } else {
              var newMetricId = gr.insert();
              dependencyMap[metricsToAdd[i]] = {
                  'newMetricId': newMetricId,
                  'depends_on': gr.depends_on + '',
                  'displayed_when': gr.displayed_when + '',
                  'correct_answer_choice': gr.correct_answer_choice + ''
              };
              if (gr.getValue("datatype") === "imagescale")
                  metricDefinitionMap = new CloneSurvey().cloneMetricDefinition(metricsToAdd[i], newMetricId, true, metricDefinitionMap);
              else
                  metricDefinitionMap = new CloneSurvey().cloneMetricDefinition(metricsToAdd[i], newMetricId, false, metricDefinitionMap);
              dependencyMap = this.checkDependency(metricsToAdd[i], dependencyMap, categoryID, metricsToAdd, metricDefinitionMap, {
                  'maxOrder': maxOrder
              });
          }
      }
      new CloneSurvey().updateDependencies(dependencyMap, metricDefinitionMap);
  },

  /*This is a recursive call for checking any dependencies of a given question and copying it to the new category
  currMetricID - the given metric to look for dependencies
  categoryID - the category in which to add the metrics to
  metricsToAdd - the list of metrics to add to Question bank
  metricDefinitionMap - this is used to update the metric definitions after copying
  */
  checkDependency: function(currMetricID, dependencyMap, categoryID, metricsToAdd, metricDefinitionMap, maxOrderObj) {
      //Get the current metric category and check if there are any dependent questions for this
      var currMetricGR = new GlideRecord('asmt_metric');
      currMetricGR.get(currMetricID);
      var gr = new GlideRecord('asmt_metric');
      gr.addQuery('category', currMetricGR.category);
      gr.addQuery('depends_on', currMetricID);
      gr.query();
      while (gr.next()) {
          //Since the order is always added to the end, get the maxOrder and add default order to it.
          maxOrderObj.maxOrder += this.DefaultMaxOrder;
          var oldMetricID = gr.getUniqueValue();
          if (metricsToAdd.indexOf(oldMetricID) >= 0)
              continue;
          gr.category = categoryID;
          if (maxOrderObj)
              gr.order = maxOrderObj.maxOrder;
          var newMetricId = gr.insert();
          // store dependency and correct answer info in metricMap
          dependencyMap[oldMetricID] = {
              'newMetricId': newMetricId,
              'depends_on': gr.depends_on + '',
              'displayed_when': gr.displayed_when + '',
              'correct_answer_choice': gr.correct_answer_choice + ''
          };
          if (gr.getValue("datatype") === "imagescale")
              metricDefinitionMap = new CloneSurvey().cloneMetricDefinition(oldMetricID, newMetricId, true, metricDefinitionMap);
          else
              metricDefinitionMap = new CloneSurvey().cloneMetricDefinition(oldMetricID, newMetricId, false, metricDefinitionMap);
          //Now repeat the same process, with the dependent question that was just found
          //oldMetricID - dependent of the current metric.
          dependencyMap = this.checkDependency(oldMetricID, dependencyMap, categoryID, metricsToAdd, metricDefinitionMap, maxOrderObj);
      }
      return dependencyMap;
  },

  //In the case where the dependent questions should not be copied over, remove the dependencies
  removeDependencies: function(gr) {
      gr.depends_on = '';
      gr.correct_answer = '';
      gr.displayed_when = '';
      gr.correct_answer_checkbox = '';
      gr.displayed_when_checkbox = '';
      gr.correct_answer_choice = '';
      gr.correct_answer_template = '';
      gr.displayed_when_template = '';
      gr.correct_answer_yesno = '';
      gr.displayed_when_yesno = '';
      return gr;
  },

  /*
   * Method to get the maxOrder of the current categories in metric type or current metrics in a category
   */
  getMaxOrder: function(tableName, fieldName, recordID) {
      var maxOrder = 0;
      //In certain cases, the recordID might be absent
      if (recordID) {
          var gr = new GlideRecord(tableName);
          gr.addQuery(fieldName, recordID);
          gr.orderByDesc("order");
          gr.setLimit(1);
          gr.query();
          if (gr.next())
              maxOrder = parseInt(gr.order);
      }
      return maxOrder;
  },

  /**
   * Get question label for a question instance. Replace the placeholder with the actual value from source record if there is any.
   */
  getQuestionLabel: function(instanceQuestionGr) {
      var PLACEHOLDER = "${param}";
      var PLACEHOLDER_REGEXP = /\$\{param\}/g;
      var metricGr = instanceQuestionGr.metric.getRefRecord();
      var metricQuestionLabel = metricGr.getDisplayValue("question");
      if (metricQuestionLabel.indexOf(PLACEHOLDER) == -1)
          return metricQuestionLabel;
      var sourceField = metricGr.getValue("source_field");
      var sourceTable = metricGr.metric_type.source_table + '';
      var triggerId = instanceQuestionGr.instance.trigger_id;
      if (!sourceField || !sourceTable || !triggerId)
          return metricQuestionLabel;
      var sourceRecord = new GlideRecord(sourceTable);
      if (!sourceRecord.get(triggerId) || !sourceRecord.canRead() || !sourceRecord.getElement(sourceField).canRead())
          return metricQuestionLabel;
      return metricQuestionLabel.replace(PLACEHOLDER_REGEXP, sourceRecord.getDisplayValue(sourceField));
  },

  isKioskSurvey: function(asmtId) {
      // check service portal survey plugin active
      if (!pm.isActive("com.glide.service-portal") || !pm.isActive("com.glide.service-portal.survey"))
          return false;

      // check metric type info
      var asmt = new GlideRecord('asmt_metric_type');
      if (!asmt.get(asmtId) || asmt.evaluation_method != 'survey' || !asmt.one_click_survey || !asmt.signature.nil())
          return false;

      // check metric count and info
      var metricCount = 0;
      var allowedDataType = 'imagescale, scale, boolean, numericscale, choice';
      var metric = new GlideRecord('asmt_metric');
      metric.addQuery('active', true);
      metric.addQuery('metric_type', asmtId);
      metric.query();
      while (metric.next()) {
          metricCount++;
          if (metricCount > 1 || metric.allow_add_info || allowedDataType.indexOf(metric.datatype + '') < 0)
              return false;
      }
      return true;
  },

  wipeMetricSourceFields: function(asmtId) {
      if (!asmtId)
          return false;
      var metric = new GlideRecord('asmt_metric');
      metric.addQuery('metric_type', asmtId);
      metric.addNotNullQuery('source_field');
      metric.setValue('source_field', 'NULL');
      metric.updateMultiple();
  },

  setAssessmentStats: function(currentRecord, recentSent, recentCompleted) {
      if (recentSent)
          this.setRecentSentStat(currentRecord);
      else if (recentCompleted)
          this.setRecentCompletedStat(currentRecord);
  },

  setRecentCompletedStat: function(asmtInstanceGR) {
      var user = asmtInstanceGR.user;
      var metricType = asmtInstanceGR.metric_type;
      var asmtInstance = asmtInstanceGR.sys_id;
      var recentFieldToSet = "recent_completed";

      if (!user || !metricType || !asmtInstance)
          return;

      if (this._shouldCollectStats(metricType)) {

          //Get All User Sources & SourceTbl for receiving the assessment
          var sourceSummary = this._getSourceForUser(asmtInstance, metricType);

          //Iterate through the Source and register the Recent Sent/Completed				
          for (var i = 0; i < sourceSummary.length; i++) {
              var srcDetails = sourceSummary[i];
              var src = srcDetails.source_id;
              var srcTbl = srcDetails.source_table;

              this._setAssmtStatsRecord(metricType, user, src, srcTbl, asmtInstance, recentFieldToSet);
          }
      }
  },

  setRecentSentStat: function(asmtInstQstnGR) {
      var user = asmtInstQstnGR.instance.user;
      var src = asmtInstQstnGR.source_id;
      var srcTbl = asmtInstQstnGR.source_table;
      var metricType = asmtInstQstnGR.metric.metric_type;
      var asmtInstance = asmtInstQstnGR.instance;
      var recentFieldToSet = "recent_sent";

      if (!user || !metricType || !asmtInstance || !src || !srcTbl)
          return;

      if (this._shouldCollectStats(metricType))
          this._setAssmtStatsRecord(metricType, user, src, srcTbl, asmtInstance, recentFieldToSet);
  },


  _setAssmtStatsRecord: function(metricType, user, src, srcTbl, asmtInstance, recentFieldToSet) {

      var asmtStatsGR = new GlideRecord("asmt_assessment_stat");
      asmtStatsGR.addQuery("metric_type", metricType);
      asmtStatsGR.addQuery("user", user);
      asmtStatsGR.addQuery("source_id", src);
      asmtStatsGR.addQuery("source_table", srcTbl);
      asmtStatsGR.query();

      if (asmtStatsGR.next()) {
          asmtStatsGR.setValue(recentFieldToSet, asmtInstance);
          asmtStatsGR.update();
      } else {
          asmtStatsGR.initialize();
          asmtStatsGR.setValue('user', user);
          asmtStatsGR.setValue('source_id', src);
          asmtStatsGR.setValue('source_table', srcTbl);
          asmtStatsGR.setValue('metric_type', metricType);
          asmtStatsGR.setValue(recentFieldToSet, asmtInstance);
          asmtStatsGR.insert();
      }
  },

  _shouldCollectStats: function(asmtType) {
      var eligAsmtStr = GlideProperties.get('com.snc.assessment.collect.stats', '').trim();
      return (eligAsmtStr.contains(asmtType));
  },

  _getSourceForUser: function(asmtInstance, assmtMetricType) {

      var srcs = [];

      var gr = new GlideAggregate("asmt_assessment_instance_question");
      gr.addQuery("instance", asmtInstance);
      gr.addQuery("metric.metric_type", assmtMetricType);
      gr.groupBy("source_id");
      gr.groupBy("source_table");
      gr.query();

      while (gr.next()) {
          var srcDetails = {};
          srcDetails.source_id = gr.getValue("source_id");
          srcDetails.source_table = gr.getValue("source_table");
          srcs.push(srcDetails);
      }

      return srcs;
  },

  updateTriggerConditionBR: function(triggerCondition) {
      var override = '';

      if (!triggerCondition.business_rule.nil()) {
          var pMgr = new GlidePluginManager();
          var isDomainSeparationActive = pMgr.isActive('com.glide.domain.msp_extensions.installer');
          if (isDomainSeparationActive) {
              var userDomain = gs.getUser().getDomainID();
              userDomain = userDomain || 'global'; // if user domain is null, use global
              if (userDomain != triggerCondition.business_rule.sys_domain) {
                  override = triggerCondition.business_rule + '';
                  triggerCondition.business_rule = '';
              }
          }
      }
      var escapeCondition = triggerCondition.condition.toString();
      escapeCondition = escapeCondition.replace(/[\'\"]/g, "\\'");
      var brCondition = "(new global.AssessmentUtils().conditionCheck(current, '" + escapeCondition + "'))";
      var br;

      if (!triggerCondition.business_rule.nil())
          br = triggerCondition.business_rule.getRefRecord();

      else {
          br = new GlideRecord('sys_script');
          br.initialize();
      }
      br.name = 'Auto assessment business rule';
      br.action_insert = true;
      br.action_update = true;
      br.active = true;
      br.when = 'after';
      br.execute_function = true;
      br.order = 300;
      br.collection = triggerCondition.table;
      if (brCondition.length <= 254) {
          br.condition = brCondition;
          br.script = "function onAfter(){ \n (new sn_assessment_core.AssessmentCreation()).conditionTrigger(current, '" + triggerCondition.sys_id + "'); \n }";
      } else {
          br.condition = "";
          br.when = "after";
          br.script = "function onAfter(){ \n if (" + brCondition + ") \n (new sn_assessment_core.AssessmentCreation()).conditionTrigger(current, '" + triggerCondition.sys_id + "'); \n }";
      }
      if (override)
          br.sys_overrides = override;
      if (triggerCondition.business_rule.nil()) {
          var brId = br.insert();
          triggerCondition.business_rule = brId;
          triggerCondition.setWorkflow(false);
          triggerCondition.update();
          triggerCondition.setWorkflow(true);
      } else
          br.update();
  },

  updateUserFieldBR: function(metricType) {
      var userField = metricType.getValue('user_field');
  	var override = null;

  	if (metricType.user_field_business_rule) {
  		var pMgr = new GlidePluginManager();
  		var isDomainSeparationActive = pMgr.isActive('com.glide.domain.msp_extensions.installer');
  		if (isDomainSeparationActive) {
  			var userDomain = gs.getUser().getDomainID();
  			userDomain = userDomain || 'global'; // if user domain is null, use global
  			if (userDomain != metricType.user_field_business_rule.sys_domain) {
  				override = metricType.getValue('user_field_business_rule');
  				metricType.user_field_business_rule = null;
  			}
  		}
  	}

      var br = new GlideRecord('sys_script');
      if (metricType.user_field_business_rule)
          br.get(metricType.getValue('user_field_business_rule'));
      else
          br.initialize();

      if (!metricType.getValue('user_field') && metricType.user_field_business_rule) {
          metricType.user_field_business_rule = null;
          br.deleteRecord();
      } else {
          br.name = 'Update category users';
          br.action_update = true;
          br.active = true;
          br.when = 'after';
          br.execute_function = true;
          br.order = 300;
          br.collection = metricType.getValue('table');
          br.advanced = true;

          var fieldCondition = userField + 'VALCHANGES^EQ';
          var previousRecUserField = "previous.getValue('" + userField + "')";
          var currentRecUserField = "current.getValue('" + userField + "')";

          br.condition = "(new global.AssessmentUtils().conditionCheck(current, '" + fieldCondition + "'))";
          br.script = "function onAfter() { \n" +
              "\t" + "var asmtUtil = new global.AssessmentUtils(); \n" +
              "\t" + "asmtUtil = asmtUtil.updateAssessableRecordCategoryUser(current.getUniqueValue(), '" + metricType.getUniqueValue() + "', " + previousRecUserField + ", " + currentRecUserField + "); \n" +
              "}";
  		
  		if (override)
  			br.sys_overrides = override;
          if (!metricType.user_field_business_rule) {
              var brSysId = br.insert();
              metricType.user_field_business_rule = brSysId;
              metricType.setWorkflow(false);
              metricType.update();
              metricType.setWorkflow(true);
          } else
              br.update();
      }
  },

  updateCategoryUsers: function(currMetricType, prevMetricType) {
      // In this case, we didn't update assessable records and we only updated assessors' user field, which means the relationships between metric category and assessable records are not changed; Only stackholders and category users need to be updated.

      // Get the old map of assessable records and users based on old value of user field
      var oldAssbleRecordAndUser = this.getMapOfAssessableRecordsAndUsers(prevMetricType);

      // Get the map of assessable records and users
      // e.g., key: assessable record sys id; value: user sys id
      var assbleRecordAndUser = this.getMapOfAssessableRecordsAndUsers(currMetricType);

      // Return if there is no assessable records in this assessment
      if (Object.keys(assbleRecordAndUser).length == 0)
          return;

      // Automatically generate category users for each metric category
      var metricCategory = this.getMetricCategoryByType(currMetricType.sys_id);
      while (metricCategory.next()) {
          // Find out assessable records from "asmt_m2m_category_assessment" table based on category id.
          var catAsmt = this.getM2mCategoryAssessmentByCategory(metricCategory.sys_id);
          while (catAsmt.next()) {
              // Add user to metric categories
              var oldUser = oldAssbleRecordAndUser[catAsmt.assessment_record];
              var newUser = assbleRecordAndUser[catAsmt.assessment_record];

              if (!oldUser && !newUser)
                  continue;

              this.updateCategoryUser(metricCategory.getUniqueValue(), catAsmt.getValue('assessment_record'), oldUser, newUser);
          }
      }
  },

  updateCategoryUser: function(metricCategorySysId, assessableRecordSysId, oldUser, newUser) {
  	// Get old category user that has been used in current assessable record and check if its used in other assessable records.
  	// If no, remove this category user; Then, create a new category user and stakeholder for current metric category and current assessable record.
  	if (oldUser) {
  		var catUsers = new GlideRecord("asmt_m2m_category_user");
  		catUsers.addQuery("metric_category", metricCategorySysId);
  		catUsers.addQuery("user", "IN", oldUser);
  		catUsers.query();
  		while (catUsers.next()) {
  			if (!this.ifThisCategoryUserUsedInOtherAssessableRecord(metricCategorySysId, catUsers.sys_id, assessableRecordSysId)) {
  				// Remove this category user since no assessable records use it
  				catUsers.deleteRecord();
 				} else {
 					// Since other assessable records are using this category user, we just need to remove the stakeholder from it.
 					this.deleteStakeholder(assessableRecordSysId, catUsers.sys_id);
       		}
       	}
       }
       var catUserSysIds = [];
       if (newUser) {
       	var newUserArr = newUser.split(",");
       	for (var i=0 ; i< newUserArr.length; i++) {
       		if (GlideStringUtil.isEligibleSysID(newUserArr[i])) {
       			var newCatUser = this.createCategoryUser(metricCategorySysId, newUserArr[i]);
       			catUserSysIds.push(newCatUser.sys_id + "");
       		}
       	}
      }
     	// Automatically assign/create stakeholder for assessable record
     	// Find stakeholder based on assessable record id and category user id from "asmt_m2m_stakeholder" table.
     	for (var i = 0 ; i< catUserSysIds.length; i++) {
     		this.createStakeholders(catUserSysIds[i], assessableRecordSysId);
     	}
  },

  updateAssessableRecordCategoryUser: function(asmtTableRecSysId, metricTypeRecSysId, oldUser, newUser) {
      var ar = new GlideRecord("asmt_assessable_record");
      ar.addQuery("metric_type", metricTypeRecSysId);
      ar.addQuery("source_id", asmtTableRecSysId);
      ar.query();
      if (ar.next()) {
          var catAsmt = new GlideRecord("asmt_m2m_category_assessment");
          catAsmt.addQuery("assessment_record", ar.getUniqueValue());
          catAsmt.query();
          while (catAsmt.next()) {
              this.updateCategoryUser(catAsmt.getValue("category"), ar.getUniqueValue(), oldUser, newUser);
          }
      }
  },
  
  filterSurvey: function(gr) {
      var extensionPoints = new GlideScriptedExtensionPoint().getExtensions('global.FilterSurveys');
  	for (var i = 0; i < extensionPoints.length; ++i) {
  			var point = extensionPoints[i];
  			if (point.filter(gr)) {
  				return true;
  			}
  	}
  	return false;
  },

  getMySurveys: function() {
      var gr = new GlideRecord("asmt_assessment_instance");
      gr.addQuery("metric_type.active", true);
      gr.addQuery("due_date", '>=', new GlideDateTime().getLocalDate().getValue());
      gr.addQuery("metric_type.publish_state", "published");
      gr.addQuery("preview", false);
      gr.addQuery("user", gs.getUserID());
      var sub = gr.addQuery("state", "ready");
      sub.addOrCondition("state", "wip");
      var sub1 = sub.addOrCondition("state", "complete");
      sub1.addCondition("metric_type.allow_retake", "true");
      gr.orderByDesc('state');
      gr.orderBy('due_date');
      gr.orderBy('sys_created_on');
      gr.query();
      return gr;
  },

  getMyFilteredSurveys: function() {
  	var gr = this.getMySurveys();
  	var surveys = [];
  	while (gr.next()) {
  		if (!this.filterSurvey(gr))
  			surveys.push(gr.getUniqueValue());
  	}
  	return surveys;
  },
  
  invokeExtensionPoints : function(instance) {
  	var className = instance.metric_type.sys_class_name + '';
  	if(instance.state + '' === 'complete' && className === 'change_risk_asmt')
  		this._invokeExtensionPoints(instance, 'global.RunPostAssessment');
  },

  _invokeExtensionPoints: function(instance, extension) {
  	var extensionPoints = new GlideScriptedExtensionPoint().getExtensions(extension);
  		for (var i = 0; i < extensionPoints.length; ++i)
  				extensionPoints[i].run(instance);
  },

  updateSurveyMasterItem: function() {
  	var gr = new GlideRecord('sys_sg_master_item');
  	gr.setWorkflow(false);
  	gr.get('c79af305ebc23010e0ef83c45e52280d');
  	gr.use_view_config = false;
  	gr.item_view = '8c4abfc1ebc23010e0ef83c45e5228b7'; // set legacy item view 
  	gr.update();
  },

  processCollisionCheck: function() {
  	var gcd = GlideCollisionDetector.get();
  	if (gs.nil(gcd)) {
  		gs.info("Survey Mobile: Error in initiating GlideCollisionDetector.");
  		return;
  	}
  	var gr = new GlideRecord("sys_sg_master_item");
  	if (gr.get("c79af305ebc23010e0ef83c45e52280d") && !gcd.containsKey(gr.sys_update_name + "")) {
  		var itemView = new GlideRecord("sys_sg_item_view");
  		if (itemView.get("8c4abfc1ebc23010e0ef83c45e5228b7") && gcd.containsKey(itemView.sys_update_name + "")) {
  			this.updateSurveyMasterItem();
  			return;
  		}
  		var uiStyleIds = "272580d9ebc23010e0ef83c45e5228b4,bb8cc8ddebc23010e0ef83c45e5228d3,7581fc5753623010c59dddeeff7b125c,49c10c1f53223010c59dddeeff7b12b4,043b4c9debc23010e0ef83c45e52282d,638fbfcb53223010c59dddeeff7b12c5,58d508d9ebc23010e0ef83c45e522820,14bcc8ddebc23010e0ef83c45e5228dc";
  		var styleGR = new GlideRecord("sys_sg_ui_style");
  		styleGR.addQuery('sys_id', 'IN', uiStyleIds);
  		styleGR.query();
  		while(styleGR.next()) {
  			if (gcd.containsKey(styleGR.sys_update_name + "")) {
  				this.updateSurveyMasterItem();
  				break;
  			}
  		}
  	}
  },
  setAnonymizeResponse:  function(qID) {
  	var qstRecord = new GlideRecord("asmt_assessment_instance_question");
  	if (qstRecord.get(qID) && qstRecord.instance.metric_type.anonymize) {
  		qstRecord.setWorkflow(false);
  		qstRecord.autoSysFields(false);
  		qstRecord.setValue("sys_updated_by", "guest");
  		qstRecord.setValue("sys_created_by", "guest");
  		qstRecord.update();
  	}
  },

  type: 'AssessmentUtils'
};

AssessmentUtils.isChatSurvey = function(surveySysID) {
  var survey = new GlideRecord("asmt_metric_type");
  if (survey.get(surveySysID))
      return (survey.getValue("chat_survey") === "1" && survey.getValue("evaluation_method") === "survey");
  return false;
};

Sys ID

ca4033c1d7110100fceaa6859e610326

Offical Documentation

Official Docs: