platform-helpers/visitor-profile-beautifier.js

const lodash = require('lodash');
const objectHelper = require('./object-helper');

/**
 *
 * @static
 * @requires lodash
 */

class VisitorProfileBeautifier {
  /**
     *
     * @static
     * @param {*} visitorProfile
     * @param {*} cdhConfiguration
     * @returns {Object} Adds an additional lot of properties with the attribute names
     */
  static beautify = (visitorProfile, cdhConfiguration) => {
    visitorProfile = lodash.cloneDeep(visitorProfile);

    // badges and audiences are arrays, so the logic is different than the rest (which are objects)
    // UPDATE 11/11/2022: recently it seems at least some profiles use an object for badges
    if (Array.isArray(visitorProfile.badges)) {
      visitorProfile.badges_pretty = this.renameArrayFromMap(visitorProfile.badges, cdhConfiguration.visitorAttributes, 'name');
    } else {
      Object.keys(visitorProfile.badges || []).forEach(id => {
        visitorProfile.badges_pretty = visitorProfile.badges_pretty || [];
        visitorProfile.badges_pretty.push(cdhConfiguration.visitorAttributes[id].name);
      });
    }

    // first make sure the audiences are actually raw (varies within the trace output)
    visitorProfile.audiences = this.renameArrayFromMap(visitorProfile.audiences, cdhConfiguration.reverseAudiences, 'id');
    // then make sure the pretty one is pretty
    visitorProfile.audiences_pretty = this.renameArrayFromMap(visitorProfile.audiences, cdhConfiguration.audiences, 'name');

    const conditionalOptions = [
      'dates', 'properties', 'metrics', 'property_sets', 'metric_sets', 'flags',
      'funnels', 'sequences', 'property_lists', 'metric_lists', 'flag_lists', 'secondary_ids'
    ];
    conditionalOptions.forEach((el) => {
      visitorProfile = this.conditionalAddPretty(visitorProfile, cdhConfiguration, el);
    });

    // for funnels, also rename any attributes that were captured in the 'snapshot'
    if (typeof visitorProfile.funnels_pretty === 'object') {
      const funnels = Object.keys(visitorProfile.funnels_pretty);
      funnels.forEach((funnelName) => {
        const funnel = visitorProfile.funnels_pretty[funnelName];
        const funnelSteps = Object.keys(funnel);
        funnelSteps.forEach((stepName) => {
          // the 'completed' key can't have a snapshot, it's just a boolean - the rest should
          if (stepName !== 'completed') {
            // event AND visit/visitor attributes can be captured
            visitorProfile.funnels_pretty[funnelName][stepName].snapshot = this.renameObjectKeysFromMap(visitorProfile.funnels_pretty[funnelName][stepName].snapshot, cdhConfiguration.visitorAttributes, 'name');
            visitorProfile.funnels_pretty[funnelName][stepName].snapshot = this.renameObjectKeysFromMap(visitorProfile.funnels_pretty[funnelName][stepName].snapshot, cdhConfiguration.eventAttributes, 'name');
          }
        });
      });
    }

    // for sequences, also rename any attributes that were captured in the 'snapshot'
    if (typeof visitorProfile.sequences_pretty === 'object') {
      const sequences = Object.keys(visitorProfile.sequences_pretty);
      sequences.forEach((sequenceName, index) => {
        const sequence = visitorProfile.sequences_pretty[sequenceName];
        sequence.forEach((entry, index) => {
          // event AND visit/visitor attributes can be captured
          visitorProfile.sequences_pretty[sequenceName][index].snapshot = this.renameObjectKeysFromMap(visitorProfile.sequences_pretty[sequenceName][index].snapshot, cdhConfiguration.visitorAttributes, 'name');
          visitorProfile.sequences_pretty[sequenceName][index].snapshot = this.renameObjectKeysFromMap(visitorProfile.sequences_pretty[sequenceName][index].snapshot, cdhConfiguration.eventAttributes, 'name');
        });
      });
    }

    return objectHelper.sortKeysByName(visitorProfile);
  };

  /**
     *
     * @param {*} visitorProfile
     * @param {*} keyName
     * @returns {Object}
     */
  static conditionalAddPretty = (visitorProfile, cdhConfiguration, keyName) => {
    const prettyKey = `${keyName}_pretty`;
    if (typeof visitorProfile[keyName] === 'object') {
      visitorProfile[prettyKey] = this.renameObjectKeysFromMap(visitorProfile[keyName], cdhConfiguration.visitorAttributes, 'name');
    }
    return visitorProfile;
  };

  /**
     *
     * @param {*} inputObject
     * @param {*} map
     * @param {*} field
     * @returns {Object}
     */
  static renameObjectKeysFromMap = (inputObject, map, field) => {
    const rawObject = lodash.cloneDeep(inputObject);
    if (typeof rawObject !== 'object') return rawObject;
    const idArray = Object.keys(rawObject);
    const prettyObject = {};
    idArray.forEach((id) => {
      let prettyName = map[id] && map[id][field];
      if (prettyName === undefined || prettyName === false) prettyName = id; // fall back to ID if missing from map
      prettyObject[prettyName] = rawObject[id];
    });
    return prettyObject;
  };

  /**
     *
     * @param {*} inputIdArray
     * @param {*} map
     * @param {*} field
     * @returns {Object}
     */
  static renameArrayFromMap (inputIdArray, map, field) {
    const idArray = lodash.cloneDeep(inputIdArray);
    if (typeof idArray !== 'object' || typeof idArray.map !== 'function') return idArray;
    return idArray.map((id) => {
      let mapped = map[id] && map[id][field];
      if (mapped === undefined || mapped === false) mapped = id; // fall back to ID if missing from map
      return mapped;
    });
  }
}
module.exports = VisitorProfileBeautifier;