'use strict';
const rootRequire = require('rpcm-root-require');
const Constants = rootRequire('/platform-helpers/constants');
const ConnectorAction = rootRequire('/platform-helpers/connector-action');
/**
* A summary of an entire Trace record, up to the present.
* @hideconstructor
*/
class TraceSummary {
/**
* @param {array} traceEventArray the output of TraceHelper.generateTraceEventArray
* @param {object} cdhProfileData the CDH profile itself, to pull action info
*/
constructor (traceEventArray, cdhProfileData) {
/**
* An object, with numeric keys, starting at 1 (events are numbered in the order delivered by Trace). Each value is an instance of [TraceEvent]{@link module:TraceEvent}
*
* @type {array}
*/
this.events = {};
const summaryStringParts = [];
/**
* All the connector actions (including EventStream and AudienceStream, triggered, non-triggered, executed, delayed, queued). Each element is an instance of [ConnectorAction]{@link module:ConnectorAction}
*
* @type {array}
*/
this.allConnectorActions = [];
// handle connector actions
traceEventArray.forEach((entry, index) => {
const eventNumber = index + 1;
this.events[eventNumber] = entry;
Constants.EVENT_ACTION_TYPES.forEach((type) => {
if (Array.isArray(entry[type])) {
const actionsArray = this.generateConnectorActionEntry(type, entry[type], cdhProfileData, eventNumber);
actionsArray.forEach((action, index) => {
this.allConnectorActions.push(action);
});
const actionSummaryString = this.pushActionArrayToList(actionsArray, []);
summaryStringParts.push(` ${type}: \n ${JSON.stringify(actionSummaryString, null, 2)}`);
}
});
});
/**
* The initial AudienceStream Visitor Profile, upon Trace start (before the first event), an instance of [VisitorProfile]{@link module:traceHelper~VisitorProfile}
*
* For this to exist, the Trace needs to have actually received at least one event, the CDH profile needs have AudienceStream active, and there can't have been a Visitor ID collision within the AS Region.
*
* @type {object}
*/
this.firstProfile = traceEventArray && traceEventArray[0] && traceEventArray[0].visitor_profile_before;
/**
* The final AudienceStream Visitor Profile found in the trace (after the last Trace event), an instance of [VisitorProfile]{@link module:traceHelper~VisitorProfile}
*
* For this to exist, the Trace needs to have actually received at least one event, the CDH profile needs have AudienceStream active, and there can't have been a Visitor ID collision within the AS Region.
*
* @type {object}
*/
this.lastProfile = (traceEventArray && traceEventArray[traceEventArray.length - 1] && traceEventArray[traceEventArray.length - 1].visitor_profile_after) || {};
/**
* All the EventStream connector actions (including triggered, non-triggered, executed, etc.). Each element is an instance of [ConnectorAction]{@link module:ConnectorAction}
*
* @type {array}
*/
this.eventStreamActions = this.allConnectorActions.filter((action) => {
return action.connectorType === 'eventStream';
});
/**
* All the AudienceStream connector actions (including triggered, non-triggered, executed, etc.). Each element is an instance of [ConnectorAction]{@link module:ConnectorAction}
*
* @type {array}
*/
this.audienceStreamActions = this.allConnectorActions.filter((action) => {
return action.connectorType === 'audienceStream';
});
const keys = Object.keys(this.events);
/**
* A long string designed to give an overview of what happened in the Trace, event-by-event.
*
* Intended to be output as Markdown as part of the report to provide an overview and sanity check.
*
* @type {string}
* @example
* // this looks better rendered as Markdown (which is how it's expected to be viewed)
* ...
* ----
*
* ## Event 4 - button\_click\_increment
*
* Event VID : 017329504780001c1f002e96b46303079005507101274
*
* Event has spec : no
*
* URL : https://solutions.tealium.net/hosted/webdriver-testing/standard-integration-test.html
* Querystring : -
*
* Profile VID : 017329504780001c1f002e96b46303079005507101274
*
* Visit Count : 1
* Event Count : 5
*
*
* ### Badges
*
* After
* - Live On Site
*
*
* ### Connector Activity
*
* Executed
* - EventStream Success: 07b670a4-dc55-41cb-ee8c-1e1ec619e6aa - Webhooks - Postman Echo Send Event (webhook/send\_events) *
*
*
* ----
* ...
*/
this.summaryString = keys.map((key, index) => {
const event = this.events[key];
return this.getPrettyEventSummaryString(event, index);
}).join('\n');
}
/**
* Deduplicates and adds additional info to arrays of actions from the Trace
*
* @private
* @param {string} actionType the type of actions contained in the traceActionArray (executed_actions, triggered_actions, etc.)
* @param {array} traceActionArray an array of actions from a trace summary event
* @param {object} cdhProfileData the CDH profile JSON itself
* @param {number} eventNumber the current event number
*
* @returns {array} a deduplicated and enhanced array of actions, based on the traceActionArray
*/
generateConnectorActionEntry (actionType, traceActionArray, cdhProfileData, eventNumber) {
if (Array.isArray(traceActionArray)) {
const actions = [];
if (!traceActionArray) return actions;
traceActionArray.forEach((action, index) => {
action = new ConnectorAction(action, actionType, cdhProfileData, eventNumber, index);
actions.push(action);
});
return actions;
}
return [];
}
pushListToSummary (listName, listArray, summaryArray) {
const joiner = '\n - ';
if (Array.isArray(listArray) && listArray.length > 0) {
const escapedArray = listArray.map((entry) => {
return this.escapeForMarkdown(entry);
});
summaryArray.push(`${listName}${joiner}${escapedArray.sort().join(joiner)}\n`);
}
}
pushActionsToSummary (actionGroupTitle, actionArray, summaryArray) {
// const joiner = ' '
if (Array.isArray(actionArray) && actionArray.length > 0) {
summaryArray.push(`${actionGroupTitle}`);
actionArray.forEach((action) => {
let successString = '';
if (typeof action.is_success === 'boolean') successString = action.is_success ? 'Success' : 'Failure';
let connectorType = 'UNKNOWN!';
if (action.connectorType === 'audienceStream') connectorType = 'AudienceStream';
if (action.connectorType === 'eventStream') connectorType = 'EventStream';
summaryArray.push(` - ${connectorType} ${successString}: ${action.action} - ${this.escapeForMarkdown(action.connectorInfoFromProfile.name)} ${this.escapeForMarkdown(action.actionInfoFromProfile.name)} (${this.escapeForMarkdown(action.connectorInfoFromProfile.type)}/${this.escapeForMarkdown(action.actionInfoFromProfile.type)})`);
});
summaryArray.push('\n');
}
}
escapeForMarkdown (str) {
if (!str || typeof str.replace !== 'function') return str;
return str.replace(/_/g, '\\_').replace(/\[/g, '\\[').replace(/\]/g, '\\]');
}
getPrettyEventSummaryString (entry, index) {
const summaryStringParts = [];
const hasSpec = !!entry.event_spec_exists;
const validSpec = entry.event_spec_exists && entry.event_spec_is_valid;
summaryStringParts.push(`----\n\n## Event ${index + 1} - ${this.escapeForMarkdown(entry.trace_event_type)}\n`);
// summaryStringParts.push(`New Visitor: ${entry.is_new_visitor}\n`)
summaryStringParts.push(`Event VID : ${this.escapeForMarkdown(entry.visitor_id) || '-'}\n`);
// summaryStringParts.push(`tealium_event : ${escapeForMarkdown(entry.tealium_event) || '-'}\n`)
summaryStringParts.push(`Event has spec : ${hasSpec ? 'yes' : 'no'}`);
if (hasSpec) { summaryStringParts.push(`Event is valid : ${validSpec ? 'yes' : 'no'}`); }
if (hasSpec && !validSpec) { summaryStringParts.push(`Missing vars : ${entry.event_spec_missing_attributes.join(' , ')}`); }
summaryStringParts.push(`\nURL : ${this.escapeForMarkdown(entry.last_event_url) || '-'}`);
summaryStringParts.push(`Querystring : ${this.escapeForMarkdown(entry.last_event_querystring) || '-'}`);
summaryStringParts.push(`\nProfile VID : ${this.escapeForMarkdown(entry.visitor_profile_after._id) || '-'}\n`);
summaryStringParts.push(`Visit Count : ${entry.lifetime_visit_count || '-'}`);
summaryStringParts.push(`Event Count : ${entry.lifetime_event_count || '-'}\n`);
if (entry.badges_before || entry.badges_removed || entry.badges_added || entry.badges_after) {
summaryStringParts.push('\n### Badges\n');
// this.pushListToSummary('Before', entry.badges_before_pretty, summaryStringParts)
this.pushListToSummary('Removed', entry.badges_removed_pretty, summaryStringParts);
this.pushListToSummary('Added', entry.badges_added_pretty, summaryStringParts);
this.pushListToSummary('After', entry.badges_after_pretty, summaryStringParts);
}
if (entry.audiences_before || entry.audiences_left || entry.audiences_joined || entry.audiences_after) {
summaryStringParts.push('\n### Audiences\n');
// this.pushListToSummary('Before', entry.audiences_before_pretty, summaryStringParts)
this.pushListToSummary('Left', entry.audiences_left_pretty, summaryStringParts);
this.pushListToSummary('Joined', entry.audiences_joined_pretty, summaryStringParts);
this.pushListToSummary('After', entry.audiences_after_pretty, summaryStringParts);
}
if (entry.triggered_actions || entry.queued_delayed_actions || entry.queued_actions || entry.executed_actions || entry.not_triggered_actions) {
summaryStringParts.push('\n### Connector Activity\n');
this.pushActionsToSummary('Suppressed Actions', entry.not_triggered_actions, summaryStringParts);
this.pushActionsToSummary('Triggered', entry.triggered_actions, summaryStringParts);
this.pushActionsToSummary('Queued (Batched)', entry.queued_actions, summaryStringParts);
this.pushActionsToSummary('Delayed', entry.queued_delayed_actions, summaryStringParts);
this.pushActionsToSummary('Executed', entry.executed_actions, summaryStringParts);
}
return summaryStringParts.join('\n');
}
pushActionArrayToList (actionArray, summaryArray) {
summaryArray = summaryArray || [];
const summaryList = [];
actionArray.forEach((action) => {
summaryList.push(` ${this.escapeForMarkdown(action.action)} - ${this.escapeForMarkdown(action.connectorInfoFromProfile.name)} ${this.escapeForMarkdown(action.actionInfoFromProfile.name)} - ${this.escapeForMarkdown(action.actionInfoFromProfile.name)}`);
});
summaryArray.push(`${summaryList.join('\n')}`);
return summaryArray.join('');
}
}
module.exports = TraceSummary;