Name

sn_em_ai.EvtMgmtJsonToEventsProcessor

Description

No description available

Script

var EvtMgmtJsonToEventsProcessor = Class.create();
EvtMgmtJsonToEventsProcessor.prototype = {
  initialize: function() {
      this.alertUtilsSnc = new global.EvtMgmtAlertUtilsSNC();
      this.logAnalyticsJsonProcessor = new EvtMgmtProcessLogAnalyticsJson();
      this.incidentSysId = "";
      this.DEFAULT_CLASSIFICATION = "IT";

      this.SOURCES = {
          NONE: '0',
          LOG_ANALYTICS: '1',
      };

      this.ALERT_EXTRA_DATA = {
          TABLE: 'em_alert_extra_data',

          FIELDS: {
              RAW_JSON: 'raw_json',
              SOURCE: 'source',
          },
      };
  },

  type: 'EvtMgmtJsonToEventsProcessor',

  /**
   * Extracts and creates new alerts from a given JSON.
   * @param source - string or number that identifies the source of the JSON file
   * @param json - the json to parse and extract events from
   * @param sysId - the sys_id value of the extracted json
   * @returns {string} - empty string on success, or a string with failure message on failure
   */
  process: function(source, json, sysId) {
      this.processTimestamp = new GlideDateTime().getNumericValue(); // Consistent timestamp to capture process start time
      this.jsonSource = '';
      this.msgKeyPrefix = "";
      this.source = source;
      this.jsonSysId = sysId; // sys_id of the json record itself, where it's stored in em_extra_data_json
      this.jsonId = json.incident && json.incident.sysId ? json.incident.sysId : this.jsonSysId; // sysId as it is defined for the incident in the json object
      this.incidentSysId = json.incident.sysId;
      var processedJson = {};
      source = this.getValidSourceValue(source);

      if (sysId && this.isNotEmptyObject(json)) {
          switch (source) {
              case this.SOURCES.LOG_ANALYTICS:
                  this.jsonSource = 'Log Analytics';
                  this.msgKeyPrefix = "LOG_ANALYTICS";
                  this.activeProcessor = this.logAnalyticsJsonProcessor;
                  break;
              default:
                  return gs.getMessage('Invalid or unsupported JSON source');
          }
          processedJson = this.activeProcessor.process(json);
      }

      // Check for valid processJson
      if (this.isEmptyObject(processedJson)) {
          return gs.getMessage('EvtMgmtJsonToEventsProcessor - Failure occurred while processing JSON with sys_id: {0}', [sysId]);
      }

      // Test the given against the expected "processedJson" structure -
      // should have the 'tree' key, with values of non-empty object
      if (!this.isValidProcessedJson(processedJson)) {
          return gs.getMessage('EvtMgmtJsonToEventsProcessor - Processed JSON with sys_id: {0} is missing expected tree key or its content', [sysId]);
      }

      // Build tree and tree nodes based on given tree
      var tree = new Tree(source, sysId);
      tree.build(processedJson.tree);
      this.rootId = tree.getRoot().getId();

      // Modify, alter and change the built tree before the creation of events from it
      if (this.activeProcessor && (typeof this.activeProcessor.modifyTreeBeforeCreatingEvents === 'function')) {
          this.activeProcessor.modifyTreeBeforeCreatingEvents(tree);
      }

      // Create events from the built tree
      this.createEvents(tree);

      return '';
  },

  /**
   * Creates events for every given event-object in the given treeNodesArray.
   * @param tree: An array of TreeNodes objects. Each object describes holds data about an event.
   */
  createEvents: function(tree) {
      var treeNode, event, EmEvent;
      var treeNodesArray = tree.toArray();

      for (var i = 0; i < treeNodesArray.length; i++) {
          treeNode = treeNodesArray[i];

          if (treeNode.shouldBeCreatedAsEvent()) {
              event = this.getCustomizedEvent(treeNode);
              EmEvent = new EvtMgmtEmEvent(event);
              EmEvent.createEvent();
          }
      }
  },

  /**
   * Test the given against the expected "processedJson" structure -
   * @param processedJson: should have the 'tree' key, with value of non-empty object
   */
  isValidProcessedJson: function(processedJson) {
      return this.isNotEmptyObject(processedJson) && processedJson.hasOwnProperty('tree') && this.isNotEmptyObject(processedJson.tree);
  },

  /**
   * Returns an object with the structure of event for a given treeNode
   * @param treeNode
   * @returns An object describing a new event in em_event
   */
  getCustomizedEvent: function(treeNode) {
      var event = treeNode.getEventData();

      // Event fields values
      event.source = this.jsonSource;
      event.event_class = this.jsonSource;
      event.message_key = String(treeNode.getMessageKey(this.msgKeyPrefix, this.processTimestamp, this.rootId));
      event.time_of_event = String(treeNode.getManipulatedTimeOfEvent(this.activeProcessor));
      event.bucket = this.alertUtilsSnc.calculateBucket(this.jsonId, this.DEFAULT_CLASSIFICATION);

      // Values that will go into additional_info
      event.sn_root_id = String(this.rootId);
      event.sn_hla_group_id = this.incidentSysId;
      event.sn_json_id = String(this.jsonSysId);
      event.sn_source = String(this.jsonSource);

      event.sn_source_original_id = String(treeNode.getId());
      event.sn_original_time = String(treeNode.getOriginalTime());

      event.sn_source_parent_id = String(treeNode.getParentId());
      event.sn_parent_message_key = String(treeNode.getParentMessageKey(this.msgKeyPrefix, this.processTimestamp, this.rootId));

      event.sn_level = String(treeNode.getLevel());

      event.sn_partofGOGgroup = String(treeNode.isGog());
      event.sn_my_group_type = String(treeNode.getMyGroupType(this.activeProcessor));
      event.sn_parent_group_type = String(treeNode.getParentGroupType(this.activeProcessor));

      // Call hook, for adding more data to the event
      if (this.activeProcessor.addToEvent && (typeof this.activeProcessor.addToEvent === 'function')) {
          this.activeProcessor.addToEvent(event, treeNode);
      }

      // insert extra data into em_alert_extra_data and get its sys_id
      event.sn_extra_data = this.insertExtraData(treeNode.getExtraData(this.activeProcessor));

      return event;

  },

  /**
   * Inserts the given extra data values into em_alert_extra_data
   * Returns the sys_id of the inserted record
   * @param extraDta
   * @returns string
   */
  insertExtraData: function(extraData) {
      var gr = new GlideRecord(this.ALERT_EXTRA_DATA.TABLE);
      gr.initialize();
      gr.setValue(this.ALERT_EXTRA_DATA.FIELDS.RAW_JSON, this.jsonSysId);
      gr.setValue(this.ALERT_EXTRA_DATA.FIELDS.SOURCE, this.source);

      for (var field in extraData) {
          if (extraData.hasOwnProperty(field) && (typeof extraData[field] !== 'undefined')) {
              fieldValue = extraData[field];
              gr.setValue(field, fieldValue);
          }
      }

      return gr.insert();
  },

  /**
   * Returns source value as stored in the database
   * @param source
   * @returns number
   */
  getValidSourceValue: function(source) {
      var sources = this.SOURCES;
      if (sources.hasOwnProperty(source.toUpperCase())) {
          return sources[source.toUpperCase()];
      } else {
          for (var name in sources) {
              if (sources.hasOwnProperty(name) && (parseInt(sources[name], 10) === parseInt(source, 10))) {
                  return String(parseInt(source, 10));
              }
          }
      }
      return 0;
  },

  /**
   * Returns true if a given object does not exist, is not an object or has no keys
   * @param object
   * @returns {boolean}
   */
  isEmptyObject: function(object) {
      return !object || (object.constructor !== Object) || (Object.keys(object).length === 0);
  },

  /**
   * Returns true if a given object has keys (1 or more)
   * @param object
   * @returns {boolean}
   */
  isNotEmptyObject: function(object) {
      return !this.isEmptyObject(object);
  },
};

/**
* Tree "Class" - Returns a Tree instance
* @returns TreeNode instance
*/
function Tree() {
  /**
   * PROPERTIES
   */
  this.tree = {};
  this.TOP_LEVEL = 1;

  /**
   * METHODS
   */
  // Init the tree and build it
  this.build = function(treeRoot) {
      var treeRootNode = new TreeNode();
      treeRootNode.build(treeRoot);
      this.buildTree(treeRootNode, 1);
  };

  this.buildTree = function(treeNode, level) {
      treeNode = this.flattenNodes(treeNode);
      this.addTreeNodeToLevel(treeNode, level);

      if (treeNode.hasChildren()) {
          var children = treeNode.getChildTreeNodes();
          for (var i = 0; i < children.length; i++) {
              children[i].setParent(treeNode);
              this.buildTree(children[i], level + 1);
          }
      } else if (!treeNode.getParent()) {
          treeNode.setGog(false);
      }
  };

  this.addTreeNodeToLevel = function(treeNode, level) {
      if (this.tree.hasOwnProperty(level) === false) {
          this.tree[level] = [];
      }
      treeNode.setLevel(level);
      this.tree[level].push(treeNode);
  };

  /**
   * Returns the treeNode that's at the top of the tree
   * @returns {*}
   */
  this.getRoot = function() {
      if (this.tree.hasOwnProperty(this.TOP_LEVEL) && (this.tree[this.TOP_LEVEL] instanceof Array) && this.tree[this.TOP_LEVEL].length) {
          return this.tree[this.TOP_LEVEL][0];
      }
  };

  /**
   * Flattens tree nodes when there's a node with only one child (merges the parent and the child)
   * @param treeNode
   */
  this.flattenNodes = function(treeNode) {
      while (treeNode.getChildCount() === 1) {
          var treeNodeChild = treeNode.getChildAt(0);

          // Link parent field if exists, before replacement
          treeNodeChild.setParent(treeNode.getParent());
          treeNodeChild.flatten();

          treeNode = treeNodeChild;
      }
      // If flattened and remained without parent or children then it's not gog
      if (!treeNode.hasParent() && !treeNode.hasChildren()) {
          treeNode.setGog(false);
      }
      return treeNode;
  };

  // Create and return an array of event object that are ordered by their levels in the tree
  this.toArray = function() {
      var treeNodesArray = [],
          eventsInLevel;

      var levels = Object.keys(this.tree);
      if (levels.length) {
          levels.sort();
          for (var i = 0; i < levels.length; i++) {
              eventsInLevel = this.tree[levels[i]];
              treeNodesArray = treeNodesArray.concat(eventsInLevel);
          }
      }

      return treeNodesArray;
  };
}

/**
* TreeNode "Class" - Returns a TreeNode instance
* @returns TreeNode instance
*/
function TreeNode() {
  /**
   * PROPERTIES
   */
  this.node = undefined;
  this.meta_data = {
      parent: undefined,
      children: [],
      level: 0,
      flattenCount: 0,
      inGroupOfGroups: true,
      originalTime: undefined,
      createAsEvent: true,
  };

  /**
   * METHODS
   */
  // Init the tree-node and build it
  this.build = function(node) {
      this.node = node;
      this.meta_data.originalTime = node.time_of_event;
      this.setChildTreeNodes();
      this.setExtraData();
  };

  this.setChildTreeNodes = function() {
      var children = [],
          childTreeNode, childNode;
      if (this.node.children && this.node.children instanceof Array && this.node.children.length) {
          for (var i = 0; i < this.node.children.length; i++) {
              childNode = this.node.children[i];
              childTreeNode = new TreeNode();
              childTreeNode.build(childNode);
              children.push(childTreeNode);
          }
      }
      delete this.node.children;
      this.meta_data.children = children;
  };

  /**
   * Returns the "node" field of the event that is saved on the treeNode (i.e., on "this.node")
   */
  this.getEventNode = function() {
      return this.node.node;
  };

  this.getId = function() {
      return this.node.id ? String(this.node.id) : '';
  };

  this.getParentId = function() {
      return this.hasParent() ? this.getParent().getId() : '';
  };

  this.getMessageKey = function(prefix, suffix, rootId) {
      var treeNodeId = this.getId();

      // Add parent id from node id if it isn't contained in it
      if (this.getParentId() && (treeNodeId.includes(this.getParentId()) === false)) {
          treeNodeId = this.getParentId() + '_' + treeNodeId;
      }

      if (treeNodeId.includes(rootId) === false) {
          treeNodeId = rootId + '_' + treeNodeId;
      }

      return prefix + '_' + this.getLevel() + '_' + treeNodeId + '_' + suffix;
  };

  this.getParentMessageKey = function(prefix, suffix, rootId) {
      return this.hasParent() ? this.getParent().getMessageKey(prefix, suffix, rootId) : '';
  };

  this.getChildTreeNodes = function() {
      return this.meta_data.children;
  };

  this.getChildAt = function(index) {
      if (this.hasChildren() && this.meta_data.children[index]) {
          return this.meta_data.children[index];
      }
  };

  this.setLevel = function(level) {
      this.meta_data.level = level;
  };

  // Returns original treenode level (before flattening, if occured)
  this.getLevel = function() {
      return parseInt(this.meta_data.level, 10);
  };

  this.setGog = function(isGog) {
      this.meta_data.inGroupOfGroups = isGog;
  };

  this.isGog = function() {
      return this.meta_data.inGroupOfGroups;
  };

  this.getParent = function() {
      return this.meta_data.parent;
  };

  this.setParent = function(treeNode) {
      this.meta_data.parent = treeNode;
  };

  this.hasParent = function() {
      return typeof this.meta_data.parent !== 'undefined';
  };

  this.getOriginalTime = function() {
      var originalTime = parseInt(Number(this.meta_data.originalTime), 10);
      var defaultOnError = new GlideDateTime();
      return originalTime || defaultOnError.getNumericValue();
  };

  this.setExtraData = function() {
      this.meta_data.extraData = this.node.extraData;
      delete this.node.extraData;
  };

  this.getExtraData = function(activeProcessor) {
      // Add more key-values to extra data based on TreeNode data
      if (activeProcessor.manipulateExtraData && (typeof activeProcessor.manipulateExtraData === 'function')) {
          activeProcessor.manipulateExtraData(this);
      }
      return this.meta_data.extraData;
  };

  this.getValueFromExtraData = function(field) {
      return this.isFieldInExtraData(field) ? this.meta_data.extraData[field] : undefined;
  };

  this.isFieldInExtraData = function(field) {
      return this.meta_data && this.meta_data.extraData && this.meta_data.extraData.hasOwnProperty(field);
  };

  this.addToExtraData = function(key, value) {
      if (key && ((value != undefined) || value != null)) {
          this.meta_data.extraData[key] = value;
      }
  };

  this.removeFromExtraData = function(fields) {
      if (Array.isArray(fields) && fields.length) {
          for (var i = 0; i < fields.length; i++) {
              var field = fields[i];
              delete this.meta_data.extraData[field];
          }
      }
  };

  // This function is mandatory for the correct creation of GOG alerts
  this.getManipulatedTimeOfEvent = function(activeProcessor) {
      if (activeProcessor.getManipulatedTimeOfEvent && (typeof activeProcessor.getManipulatedTimeOfEvent === 'function')) {
          return activeProcessor.getManipulatedTimeOfEvent(this);
      } else {
          gs.error('EvtMgmtJsonToEventsProcessor - expecting "getManipulatedTimeOfEvent" function in active processor');
          return '';
      }
  };

  // This function is mandatory for the correct creation of GOG alerts
  this.getMyGroupType = function(activeProcessor) {
      if (activeProcessor.getGroupType && (typeof activeProcessor.getGroupType === 'function')) {
          return activeProcessor.getGroupType(this);
      } else {
          gs.error('EvtMgmtJsonToEventsProcessor - expecting "getGroupType" function in active processor');
          return '';
      }
  };

  this.getParentGroupType = function(activeProcessor) {
      if (this.hasParent()) {
          return this.getParent().getMyGroupType(activeProcessor);
      }
      return '';
  };

  /**
   * Whether this treeNode should be created as event or not.
   * Defaults to true, i.e. every treeNode will be created as an event, unless the
   * active processor has a function named "shouldBeCreatedAsEvent" that determines 
   * whether the treeNode needs to become an event (by returning true), or not (by returning false).
   */
  this.shouldBeCreatedAsEvent = function() {
      return this.meta_data.createAsEvent;
  };

  /**
   * Sets CreateAsEvent to the given argument.
   * When the argument is false, this treeNode will not be created as an event.
   * 
   * @param isCreateAsEvent - boolean
   */
  this.setCreateAsEvent = function(isCreateAsEvent) {
      this.meta_data.createAsEvent = String(isCreateAsEvent) === 'true';
  };

  // Returns FINAL treenode level (AFTER flattening, if occurred)
  this.getActualTreeNodeLevel = function() {
      if (!this.isGog()) {
          return '';
      } else if (this.hasParent()) {
          return this.getParent().getActualTreeNodeLevel() + 1;
      }
      var treeNodeLevel = this.getLevel();
      if (this.isFlattened()) {
          treeNodeLevel += this.getFlattenCount();
      }
      return treeNodeLevel;
  };

  /**
   * Increases the flatten count of a node (the times it was overridden/merged with its parents)
   */
  this.flatten = function() {
      this.meta_data.flattenCount++;
  };

  this.getFlattenCount = function() {
      return this.meta_data.flattenCount;
  };

  /**
   * Returns true if the treeNode was flattened (got overridden/merged with its parent)
   * @returns {boolean}
   */
  this.isFlattened = function() {
      return this.getFlattenCount() > 0;
  };

  this.hasChildren = function() {
      return this.getChildCount() > 0;
  };

  this.getChildCount = function() {
      return this.getChildTreeNodes().length;
  };

  /**
   * Returns raw node data that has the event fields
   * @returns Object that includes event fields, e.g. { description: '...', metric_name: '...' }
   */
  this.getEventData = function() {
      return JSON.parse(JSON.stringify(this.node));
  };
}

Sys ID

f2af19d0b74310107c038229ce11a9a3

Offical Documentation

Official Docs: