Name

global.NLUCloneModel

Description

Clone a selected model in the currect application scope. It clones model along with all the artificats like intent, utterances, entity, vocabulary etc.

Script

var NLUCloneModel = Class.create();

(function() {

  var constants = NLUConstants.constants;
  var tables = NLUConstants.tables;
  var stateTypes = NLUConstants.MODEL_STATE_TYPES;

  var getIntentFilter = function(filter) {
      return filter && Array.isArray(filter.intents) &&
          'sys_idIN' + filter.intents.join(',');
  };

  var getUtteranceFilter = function(filter) {
      var utteranceFilter = null;
      if (filter && Array.isArray(filter.intents))
          utteranceFilter = 'intentIN' + filter.intents.join(',');
      if (filter && Array.isArray(filter.utterances))
          utteranceFilter = (utteranceFilter ? utteranceFilter + '^' : '') + 'sys_idIN' + filter.utterances.join(',');

      return utteranceFilter;
  };

  NLUCloneModel.canCloneModel = function(modelId) {
      return !NLUModel.isInCloneOrTranslation(modelId);
  };

  NLUCloneModel.prototype = {

      type: 'NLUCloneModel',

      initialize: function(modelId, scope) {
          this.modelId = modelId;
          this.nluModel = new NLUModel(modelId);

          this.tgtProps = {
              model: '',
              sys_scope: scope || gs.getCurrentApplicationId()
          };

          this.vocabPriIdsMap = {};
          this.intentPriIdsMap = {};
          this.intentIdsMap = {};
          this.entityPriIdsMap = {};
          this.entityIdsMap = {};
          this.utterancePriIdsMap = {};
      },

      /*
      	This will return the map of srcId -> tgtId.
      	priMap contains srcPriId -> tgtPriId,
  		srcTgtMap contains srcId -> tgtId
      */
      createRecords: function(srcGr, priMap, getProps, srcTgtMap) {
          var srcPriMap = {}; // this is map of srcId -> srcPriId
          var thisObj = this;
          var srcTgtIdMap = NLUImportUtil.createRecords(srcGr, function(gr) {
              var primaryValue = NLUImportUtil.getPrimaryValue(gr);
              srcPriMap[gr.getUniqueValue()] = primaryValue;
              var newProps = NLUHelper.cloneDeep(thisObj.tgtProps);
              if (getProps) NLUHelper.extend(newProps, getProps(gr));
              if (priMap[primaryValue]) { //only primary mappings are needed to update primaryField here.
                  var primaryField = NLUImportUtil.FIELDS_DATA[gr.getTableName()].primaryField;
                  newProps[primaryField] = priMap[primaryValue];
              }
              return newProps;
          }, false);

          for (var srcId in srcTgtIdMap) {
              var srcPriId = srcPriMap[srcId];
              if (srcPriId && !(srcPriId in priMap)) {
                  priMap[srcPriId] = srcTgtIdMap[srcId]; // add entry for srcPriId -> tgtPriId
              }
          }
          if (srcTgtMap) NLUHelper.extend(srcTgtMap, srcTgtIdMap);
          return srcTgtIdMap;
      },

      getModelName: function(gr, newProps, isSecondary) {
          var displayName = (newProps.display_name ? newProps.display_name : this.getAUniqueName(gr.getValue('display_name')));
          if (!isSecondary) {
              return displayName;
          }
          return displayName + ' ' + NLUModel.getLanguage(gr).toUpperCase();
      },

      createModels: function(srcModelGr, newProps, isSecondary) {
          var thisObj = this;
          var modelIdsMap = NLUImportUtil.createRecords(srcModelGr, function() {
              return {
                  sys_scope: thisObj.tgtProps.sys_scope,
                  display_name: thisObj.getModelName(srcModelGr, newProps, isSecondary),
                  description: newProps.description,
                  primary_model: newProps.primary_model
              };
          });
          Object.keys(modelIdsMap).forEach(function(srcModelId) {
              var tgtModelId = modelIdsMap[srcModelId];
              NLUModel.updateModelStatus(tgtModelId, stateTypes.cloning);
          });
          return modelIdsMap;
      },

      createIntentEntities: function(getEntityProps, srcIntentIds) {
          var srcPriMap = {};
          var newEntityIdsMap = {};
          var thisObj = this;
          NLUIntent.forEachIntentEntityMap(srcIntentIds, function(srcIntentId, srcRelationship, srcEntityGr) {
              var primaryValue = NLUImportUtil.getPrimaryValue(srcEntityGr);
              srcPriMap[srcEntityGr.getUniqueValue()] = primaryValue;

              var entityId = srcEntityGr.getUniqueValue();
              // Create entity:
              var tgtEntityId = thisObj.entityIdsMap[entityId] || newEntityIdsMap[entityId];
              if (!tgtEntityId) {
                  tgtEntityId = NLUImportUtil.createRecord(tables.SYS_NLU_ENTITY, srcEntityGr, getEntityProps && getEntityProps(srcEntityGr), false);
                  newEntityIdsMap[entityId] = tgtEntityId;
              }
              // Create m2m record for intent-entity:
              new NLUIntent(thisObj.intentIdsMap[srcIntentId]).createIntentEntityMap(tgtEntityId, srcRelationship);
          });

          for (var srcId in newEntityIdsMap) {
              var srcPriId = srcPriMap[srcId];
              if (srcPriId && !(srcPriId in this.entityPriIdsMap)) {
                  this.entityPriIdsMap[srcPriId] = newEntityIdsMap[srcId]; // add entry for srcPriId -> tgtId
              }
          }
          return NLUHelper.extend(this.entityIdsMap, newEntityIdsMap);
      },

      createUtterances: function(srcUtteranceGr) {
          var thisObj = this;
          var utteranceIdsMap = this.createRecords(srcUtteranceGr, this.utterancePriIdsMap, function(gr) {
              return {
                  intent: thisObj.intentIdsMap[gr.intent],
                  sys_scope: thisObj.tgtProps.sys_scope
              };
          });

          var annotationGr = new GlideRecord(tables.M2M_SYS_NLU_UTTERANCE_ENTITY);
          annotationGr.addQuery('utterance', 'IN', Object.keys(utteranceIdsMap));
          annotationGr.query();
          NLUImportUtil.createRecords(annotationGr, function(gr) {
              return {
                  entity: thisObj.entityIdsMap[gr.entity],
                  utterance: utteranceIdsMap[gr.utterance],
                  annotation: gr.annotation,
                  sys_scope: thisObj.tgtProps.sys_scope
              };
          });
      },

      /*
      - filter is optional filter, which can be used to clone specific data from source model. 
      - format: 
      {
      	intents: ['intent1', 'intent2'],
      	entities: ['ent1', 'ent2'],
      	... 
      }
      */
      cloneModel: function(srcModelId, tgtModelId, filter) {
          this.tgtProps.model = tgtModelId;
          var thisObj = this;
          var nluModel = new NLUModel(srcModelId);
          var tgtModel = new NLUModel(tgtModelId);
          var isEntitiesEnabledInPrimary = NLUParloIntegrator.isEntitiesEnabled(this.tgtPriLanguage, tgtModel.getVersion());
          this.createRecords(nluModel.getVocabulary(), this.vocabPriIdsMap);
          this.createRecords(nluModel.getModelEntities(), isEntitiesEnabledInPrimary ? this.entityPriIdsMap : {}, null, this.entityIdsMap);

          // Skip if filter is provided and intents key exists, but it's null / empty array: 
          var skipIntents = filter && ('intents' in filter) && (!filter.intents || filter.intents.length == 0);
          if (!skipIntents) {
              this.createRecords(nluModel.getIntents(getIntentFilter(filter)), this.intentPriIdsMap, function(gr) {
                  var result = {};
                  if (gr.origin.nil() || NLUIntent.getGRById(gr.getValue('origin')) === null)
                      result.origin = gr.getUniqueValue();
                  
                  return result;
              }, this.intentIdsMap);
              var srcIntentIds = (filter && filter.intents) ? filter.intents : nluModel.getIntentIds();
              this.createIntentEntities(function(gr) {
                  var result = NLUHelper.cloneDeep(thisObj.tgtProps);
                  if (isEntitiesEnabledInPrimary && thisObj.entityPriIdsMap[NLUImportUtil.getPrimaryValue(gr)]) {
                      var primaryField = NLUImportUtil.FIELDS_DATA[gr.getTableName()].primaryField;
                      result[primaryField] = thisObj.entityPriIdsMap[NLUImportUtil.getPrimaryValue(gr)];
                  }
                  if (gr.model.nil() && result.model) delete result.model;
                  return result;
              }, srcIntentIds);
              this.createUtterances(nluModel.getUtterances(getUtteranceFilter(filter)));
          }
          NLUModel.updateModelStatus(tgtModelId, stateTypes.draft);

          // clone corresponding default test set from srcModelId to tgtModelId
          if (global.NLUSystemUtil.isNluAdvancedPluginActive() && typeof sn_nlu_workbench.NLUBatchTestSet.cloneTestSetForModel === 'function') {
              sn_nlu_workbench.NLUBatchTestSet.cloneTestSetForModel(srcModelId, tgtModelId);
          }
      },

      cloneGroupAsync: function(modelIdsMapStr, srcPriModelId, tgtPriModelId) {
          //clone primary model
          var modelIdsMap = JSON.parse(modelIdsMapStr);
          this.cloneModel(srcPriModelId, tgtPriModelId);
          this.tgtPriLanguage = new NLUModel(tgtPriModelId).getGR().getValue('language');
          // Clone secondary models:
          var srcModels = Object.keys(modelIdsMap);
          for (var i = 0, len = srcModels.length; i < len; i++) {
              var modelId = srcModels[i];
              this.cloneModel(modelId, modelIdsMap[modelId]);
          }
      },

      cloneGroup: function(opts) {
          var result = {};
          try {
              var srcPriModelGr = NLUModel.getModelsGr(this.modelId, [opts.model_props.language]);
              var obj = this.createModels(srcPriModelGr, opts.model_props);
              var srcPriModelId = Object.keys(obj)[0];
              var tgtPriModelId = obj[srcPriModelId];
              var srcSecModelsGr = NLUModel.getModelsGr(this.modelId, opts.secondary_languages);

              var modelIdsMap = this.createModels(srcSecModelsGr, {
                  primary_model: tgtPriModelId,
                  display_name: opts.model_props.display_name
              }, true);

              var tgtModels = Object.keys(modelIdsMap).map(function(srcModelId) {
                  return modelIdsMap[srcModelId];
              });
              tgtModels.push(tgtPriModelId);
              var script = "new NLUCloneModel('" + this.modelId + "', '" + this.tgtProps.sys_scope + "').cloneGroupAsync('" + JSON.stringify(modelIdsMap) + "', '" + srcPriModelId + "', '" + tgtPriModelId + "')";

              NLUWorkbenchGlobalScript.scheduleScript(script, tables.SYS_NLU_MODEL, this.modelId);

              result.sysIds = tgtModels;
              result.status = 'success';
              result.message = gs.getMessage('Creating copy of {0}', opts.model_props.display_name);

          } catch (e) {
              result.status = 'failure';
              result.message = e.message;
          }

          return result;
      },

      cloneSingle: function(modelProps) {
          var result = {};
          if (!modelProps) modelProps = {};
          try {
              var srcPriModelGr = NLUModel.getModelsGr(this.modelId, modelProps.language ? [modelProps.language] : []);
              var obj = this.createModels(srcPriModelGr, modelProps);
              var srcPriModelId = Object.keys(obj)[0];
              var newModelId = obj[srcPriModelId];
              this.cloneModel(srcPriModelId, newModelId, modelProps.filter);
              result.sysId = newModelId;
              result.status = 'success';
              result.message = gs.getMessage('The model has been successfully copied.');
          } catch (e) {
              result.status = 'failure';
              result.message = e.message;
          }
          return result;
      },

      clone: function(opts) {
          this.validateModel();
          if (opts.secondary_languages)
              return this.cloneGroup(opts);
          return this.cloneSingle(opts.model_props);
      },

      trimSuffixIfPresent: function(modelName) {
          var pos = modelName.search(constants.COPY_SUFFIX_REGEX);
          if (pos > -1) {
              return modelName.substring(0, pos);
          }
          if (modelName.length > 33) return modelName.substring(0, 33);
          return modelName;
      },

      getAUniqueName: function(modelName) {
          var name = this.trimSuffixIfPresent(modelName);
          var gr = new GlideRecord(tables.SYS_NLU_MODEL);
          gr.addQuery('display_name', 'LIKE', name + constants.COPY_NAME_SUFFIX + '%');
          gr.query();
          var sequence = 0;
          while (gr.next()) {
              var n = gr.display_name;
              if (constants.COPY_SUFFIX_REGEX.test(n)) {
                  var num = parseInt(n.match(constants.COPY_SUFFIX_REGEX)[2]);
                  if (num > sequence)
                      sequence = num;
              }
          }
          return this.getCloneModelName(name, sequence);
      },

      getCloneModelName: function(name, sequence) {
          var cloneModelName;
          sequence++;
          if (constants.COPY_SUFFIX_REGEX.test(name)) {
              cloneModelName = name.replace(constants.COPY_SUFFIX_REGEX, function(match, p1) {
                  return p1 + sequence;
              });
          } else {
              cloneModelName = name + constants.COPY_NAME_SUFFIX + sequence;
          }
          return (cloneModelName.length <= constants.MODEL_NAME_LENGTH ? cloneModelName : this.trimModelName(cloneModelName));
      },

      trimModelName: function(modelName) {
          var trimLength = modelName.length - constants.MODEL_NAME_LENGTH;
          var pos = modelName.search(constants.COPY_SUFFIX_REGEX);
          if (trimLength > 0) {
              var name = modelName.substring(0, pos - trimLength) + modelName.substring(pos);
              var gr = new GlideRecord(tables.SYS_NLU_MODEL);
              if (gr.get('display_name', modelName)) {
                  return this.getAUniqueName(name);
              }
              return name;
          }
          return modelName;
      },

      copySysEntityState: function(targetModelId) {
          var gr = new GlideRecord(tables.M2M_SYS_NLU_MODEL_SYS_ENTITY);
          gr.addQuery('model', targetModelId);
          var jq = gr.addJoinQuery(tables.M2M_SYS_NLU_MODEL_SYS_ENTITY, 'sys_entity', 'sys_entity');
          jq.addCondition('model', this.modelId);
          jq.addCondition('active', false);
          gr.query();
          while (gr.next()) {
              gr.setValue('active', false);
              gr.update();
          }
      },

      copyVocabulary: function(targetModelId) {
          var fields = ['name', 'type', 'pattern', 'related_terms', 'model'];
          var gr = (new NLUModel(this.modelId)).getVocabulary();
          while (gr.next()) {
              gr.setValue('model', targetModelId);
              NLUSystemUtil.copyRecordFromGR('sys_nlu_vocabulary', fields, gr);
          }
      },

      validateModel: function() {
          var modelGr = this.nluModel.getGR();
          if (modelGr.getValue('category') === 'virtual_agent' && !global.NLUModelCategoryUtil.isVAEnabled()) throw new Error(gs.getMessage("Install Glide Virtual Agent plugin to enable cloning virtual agent models."));
      }

  };
})();

Sys ID

301d83af5f62330064f1e1cf2f731378

Offical Documentation

Official Docs: