Name

sn_cd.cd_ContentProcessor

Description

API to process content such as emails and push notification

Script

var cd_ContentProcessor = Class.create();
cd_ContentProcessor.prototype = {
  initialize: function() {},

  /** Process content for a content type
   * @param contentType string content type value e.g. 'email'
   */
  processContent: function(contentType, waitDuration, retryCount) {
      if (gs.nil(contentType)) {
          gs.error("processContent : Empty content type. Exiting ");
          return;
      }

      var grContentVisibility = new GlideRecord('sn_cd_content_visibility');
      grContentVisibility.addActiveQuery();
      grContentVisibility.addNotNullQuery('when_to_process');
      grContentVisibility.addNullQuery('approvers').addOrCondition('state', 'published');
      grContentVisibility.addQuery("notification_status", "pending");

      if (cd_CommonConstants.NOTIFICATIONS_TYPE_EMAIL == contentType)
          grContentVisibility.addQuery('content.content_type.value', cd_CommonConstants.NOTIFICATIONS_TYPE_EMAIL);
      else if (cd_CommonConstants.NOTIFICATIONS_TYPE_PUSH == contentType)
          grContentVisibility.addQuery('content.content_type.value', cd_CommonConstants.NOTIFICATIONS_TYPE_PUSH);
      else if (cd_CommonConstants.NOTIFICATIONS_TYPE_TEAMS == contentType) {
          grContentVisibility.addNotNullQuery('when_to_process').addCondition('when_to_process', '<=', new GlideDateTime());
          grContentVisibility.addQuery('content.content_type.value', cd_CommonConstants.NOTIFICATIONS_TYPE_TEAMS);
      } else {
          gs.error("processContent : Invalid or unsupported content type : " + contentType + ". Exiting");
          return;
      }

      grContentVisibility.orderBy('when_to_process');
      grContentVisibility.query();

      if (!grContentVisibility.hasNext()) {
          gs.error("processContent : No Content to schedule. Exiting ");
          return;
      }

      if (cd_CommonConstants.NOTIFICATIONS_TYPE_EMAIL == contentType)
          this.processEmail(grContentVisibility);
      else if (cd_CommonConstants.NOTIFICATIONS_TYPE_PUSH == contentType)
          this.processPush(grContentVisibility);
      else if (cd_CommonConstants.NOTIFICATIONS_TYPE_TEAMS == contentType)
          new sn_cd.cd_ContentTeamsProcessor().processTeams(grContentVisibility, waitDuration, retryCount);
  },

  /** Process email for content visibility audience
   * @param grContentVisibility GlideRecord [sn_cd_content_visibility] Content visibility record to retrieve audience from
   */
  processEmail: function(grContentVisibility) {
      var EMAIL_EVENT_NAME = 'sn_cd.notification.content_email';
      this._processContentTypeAndRaiseEvent(grContentVisibility, cd_CommonConstants.NOTIFICATIONS_TYPE_EMAIL, EMAIL_EVENT_NAME);
  },

  /** Process push for content visibility audience
   * @param grContentVisibility GlideRecord [sn_cd_content_visibility] Content visibility record to retrieve audience from
   */
  processPush: function(grContentVisibility) {
      var PUSH_EVENT_NAME = 'sn_cd.notification.content_push';
      this._processContentTypeAndRaiseEvent(grContentVisibility, cd_CommonConstants.NOTIFICATIONS_TYPE_PUSH, PUSH_EVENT_NAME);
  },

  _processContentTypeAndRaiseEvent: function(grContentVisibility, contentType, eventName) {
      var MAX_RUN_TIME = this._getNumberProperty("sn_cd.notification.max_run_time", 600);
      var MAX_RECIPIENTS = this._getNumberProperty("sn_cd.notification.max_recipients", 50000);
      var MAX_USERS_PER_EVENT = this._getNumberProperty("sn_cd.notification.max_users_per_event", 1000);
      var MAX_EVENTS = this._getNumberProperty("sn_cd.notification.max_events", 50);
      var EVENT_STAGGER = this._getNumberProperty("sn_cd.notification.event_stagger", 60);
      var cd_Audience = new sn_cd.cd_Audience();
      var cd_Translations = new sn_cd.cd_ContentTranslations();
      var gdtStart = new GlideDateTime();
      var gdtEnd = new GlideDateTime();
      gdtEnd.addSeconds(MAX_RUN_TIME);
      var eventCount = 0;
      while (eventCount < MAX_EVENTS &&
          gdtEnd.after(new GlideDateTime()) &&
          grContentVisibility.next()) {
          if ((grContentVisibility.getValue('use_adhoc_users') == true && gs.nil(grContentVisibility.getValue('users'))) || (grContentVisibility.getValue('use_adhoc_users') != true && gs.nil(grContentVisibility.getValue('audience'))))
              continue;
          var grContentNotification = grContentVisibility.content.getRefRecord();
          if (!grContentNotification.isValidRecord() || !grContentNotification.active) {
              grContentVisibility.setValue("notification_status", "error");
              grContentVisibility.setValue("active", false);
              grContentVisibility.update();
              gs.warn("Script - " + this.type + ": Failed to find active content for schedule " + grContentVisibility.getUniqueValue());
              continue;
          }
          var recipients;
          if (grContentVisibility.getValue('use_adhoc_users') == true) {
              var systemLanguage = gs.getProperty('glide.sys.language');
              var grUsers = new GlideRecord('sys_user');
              grUsers.addEncodedQuery('sys_idIN' + grContentVisibility.getValue('users'));
              grUsers.query();
              recipients = {};
              while (grUsers.next()) {
                  var userSysId = grUsers.getUniqueValue();
                  var userLang = grUsers.getValue('preferred_language') || systemLanguage;
                  if (!recipients.hasOwnProperty(userLang))
                      recipients[userLang] = [];
                  recipients[userLang].push(userSysId);
              }
          } else
              recipients = cd_Audience.getAudienceByLanguage(grContentVisibility.getValue('audience'));

          var recipientStats = this._getRecipientCounts(recipients, MAX_USERS_PER_EVENT);
          // Filter out content visibilities that can never process due to exceeding maximums
          if (recipientStats[1] > MAX_RECIPIENTS || recipientStats[0] > MAX_EVENTS) {
              grContentVisibility.setValue("notification_status", "max_exceeded");
              grContentVisibility.setValue("active", false);
              grContentVisibility.update();
              continue;
          }
          // Skip if content visibility will exceed leftover event allotment. NOTE: It's possible that every next content visibility exceeds the leftover
          if (eventCount + recipientStats[0] > MAX_EVENTS)
              continue;
          // Mark completed_on before queueing events to prevent duplicate emails if transaction times out
          grContentVisibility.setValue("notification_status", "sent");
          grContentVisibility.setValue("active", false);
          grContentVisibility.completed_on = new GlideDateTime();
          grContentVisibility.update();
          // Chunk and stagger events
          var gdtWhenToProcess = new GlideDateTime(grContentVisibility.when_to_process);
          // Adjust this time to now if in the past
          if (gdtWhenToProcess.before(gdtStart))
              gdtWhenToProcess = new GlideDateTime();

          for (var languagePreference in recipients) {
              var eventGlideRecord = contentType == cd_CommonConstants.NOTIFICATIONS_TYPE_PUSH ? grContentVisibility : grContentNotification;
              var eventParameter = contentType == cd_CommonConstants.NOTIFICATIONS_TYPE_PUSH ? "" : JSON.stringify(cd_Translations.getTranslatedEmailContent(grContentNotification, languagePreference));
              for (var i = 0; i < recipients[languagePreference].length && MAX_USERS_PER_EVENT > 0;) {
                  var recipientsChunk = recipients[languagePreference].slice(i, i + MAX_USERS_PER_EVENT);
                  gs.eventQueueScheduled(eventName, eventGlideRecord, recipientsChunk, eventParameter, gdtWhenToProcess);
                  eventCount++;
                  gdtWhenToProcess.addSeconds(EVENT_STAGGER);
                  i += recipientsChunk.length;
              }
          }
      }
  },

  /** Return a number value for a given property
   * @param propName String Name of a property
   * @param defaultValue Number The default value to use for a property
   * @return Number The number value for a given property
   */
  _getNumberProperty: function(propName, defaultValue) {
      var propValue = Number(gs.getProperty(propName, defaultValue));
      if (isNaN(propValue)) {
          gs.warn("Script - " + this.type + ": Overriding invalid property value for " + propName);
          propValue = defaultValue;
      }
      return propValue;
  },

  /** Return several counts including events to create, number of users, number of languages
   * @param notificationRecipients object Map of {languageCode : [list of users]}
   * @return Array an array of integers such that [eventCount, userCount, languageCount]
   */
  _getRecipientCounts: function(notificationRecipients, max_users) {
      var eventsToQueue = 0;
      var userCount = 0;
      var languageCount = 0;
      for (var language in notificationRecipients) {
          eventsToQueue += Math.ceil(notificationRecipients[language].length / max_users);
          userCount += notificationRecipients[language].length;
          languageCount++;
      }
      return [eventsToQueue, userCount, languageCount];
  },

  type: 'cd_ContentProcessor'
};

Sys ID

25b0adfb0b6303008cd6e7ae37673a65

Offical Documentation

Official Docs: