DigitalConsumer.ISS/src/mixins/analytics-mixin.js
2026-07-20 14:53:42 -04:00

642 lines
26 KiB
JavaScript

import {
areAllSessionCookiesSet,
getDeviceIdValue,
getSessionIdValue,
getSessionKeyValue,
getUserIdValue,
regenerateUserId,
regenerateDeviceId,
setSessionIdIfUnset,
setSessionKeyIfUnset,
updateSessionIdCookie
} from '@/helpers/cookie-helper';
import applicationConfig from '@/constants/application-config';
import queryStrings from '@/constants/query-strings';
import { experimentSettings } from '@/constants/experiments';
import {
analyticsPageEvents,
GaCategories,
GaActions,
GaLabels,
GaEvents,
ValueToLogTypes
} from '@/constants/analytics';
import { getPriceOfLineItems } from '@/helpers/price-calculator';
import { getAvailableLineItems, getCartTotal, getSubtotal } from '@/helpers/cart-helper';
import { getRecalPartNumbers, isRecalOrder } from "@/helpers/recal-helper";
import coverageStatuses from '@/constants/coverage-statuses';
import coverageType from '@/constants/coverage-type';
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
import { useMainStore } from '@/store';
import BailoutCode from '@/constants/bailoutCode';
function pushToDataLayerIfDefined(data) {
if (window.dataLayer !== undefined) {
window.dataLayer.push(data);
}
}
function getValueToLog(value, valueToLogType) {
if (valueToLogType != null && valueToLogType === ValueToLogTypes.LAST_5) {
return value.slice(-5);
}
return value;
}
export default {
methods: {
getPageNameByQueryString() {
const params = new URLSearchParams(location.search);
if (params.has(queryStrings.ISS_PAGE)) {
return params.get(queryStrings.ISS_PAGE);
}
return '';
},
async validateSession() {
const emptySessionId = '00000000-0000-0000-0000-000000000000';
if (this.noSession()) {
await this.initSession();
}
const sessionId = getSessionIdValue();
if (sessionId && sessionId !== emptySessionId) {
updateSessionIdCookie();
}
},
async logPageView(pageEvent) {
const store = useMainStore();
const issConfig = store.issConfig;
const currentPageName = this.getPageNameByQueryString();
// await this.validateSession();
const submittedOrder = store.getSubmittedOrder();
const hasSubmittedOrder = store.hasSubmittedOrder();
const order = hasSubmittedOrder ? submittedOrder : store.order;
// Use parentaccount on order, use parentaccount on issconfig as fallback.
const parentAccountNumber = (order.parentAccountNumber ?? issConfig.parentAccountNumber);
const payload = {
userId: getUserIdValue(),
sessionKey: getSessionKeyValue(),
pageName: currentPageName,
referralSequenceNumber: order.referralSequenceNumber,
referralNumber: order.referralNumber,
parentAccountNumber: parentAccountNumber,
sessionId: getSessionIdValue(),
action: '',
event: pageEvent,
shouldUseSessionId: false,
experimentsForUser: store.applicationUser.experiments
};
await store.logPageView(payload);
},
async logCustomEvent(category, action, label, value) {
const store = useMainStore();
const issConfig = store.issConfig;
const currentPageName = this.getPageNameByQueryString();
// await this.validateSession();
const submittedOrder = store.getSubmittedOrder();
const hasSubmittedOrder = store.hasSubmittedOrder();
const order = hasSubmittedOrder ? submittedOrder : store.order;
// Use parentaccount on order, use parentaccount on issconfig as fallback.
const parentAccountNumber = (order.parentAccountNumber ?? issConfig.parentAccountNumber);
const payload = {
userId: getUserIdValue(),
sessionKey: getSessionKeyValue(),
pageName: currentPageName,
referralSequenceNumber: order.referralSequenceNumber,
referralNumber: order.referralNumber,
parentAccountNumber: parentAccountNumber,
sessionId: getSessionIdValue(),
category,
action,
label,
value,
shouldUseSessionId: false,
experimentsForUser: store.applicationUser.experiments
};
await store.logCustomEvent(payload);
},
async pushEventToGA(category, action, label, pushToLogApp = false, valueToLogType = null, value = undefined) {
const currentPageName = this.getPageNameByQueryString();
const labelToLog = getValueToLog(label, valueToLogType);
const eventToBePushed = {
event: GaEvents.GENERIC_EVENT,
category,
action,
label: labelToLog,
value: value,
path: `/iss/?${queryStrings.ISS_PAGE}=${currentPageName}`
};
pushToDataLayerIfDefined(eventToBePushed);
if (pushToLogApp) {
await this.logCustomEvent(category, action, labelToLog, value);
}
},
pushGenericObjectToGA(object) {
pushToDataLayerIfDefined(object);
},
async pushPageViewToGA() {
const currentPageName = this.getPageNameByQueryString();
const pageViewEvent = {
event: GaEvents.PAGE_VIEW_EVENT,
pagePath: `/iss/?${queryStrings.ISS_PAGE}=${currentPageName}`,
pageTitle: currentPageName
};
pushToDataLayerIfDefined(pageViewEvent);
await this.logPageView(analyticsPageEvents.ENTRY);
},
pushOrderToDataLayer() {
// helper check for if an object is defined (but maybe falsey)
const isDefined = (x) => x !== null && x !== undefined;
const store = useMainStore();
// Get correct order object
const hasSubmittedOrder = store.hasSubmittedOrder();
const submittedOrder = store.getSubmittedOrder();
const order = hasSubmittedOrder ? submittedOrder : store.order;
const deviceId = getDeviceIdValue();
const sid = getSessionIdValue();
// Begin assembling payload for data layer
const payload = {};
payload.appName = "ISS";
payload.siteType = store.issConfig.siteType;
payload.pageName = this.getPageNameByQueryString();
payload.deviceId = deviceId;
payload.sessionId = sid;
payload.clientName = store.issConfig.clientName;
payload.lossCause = order.policy?.damageCause ?? "";
// Service Zip
if (
order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE &&
isDefined(order.serviceLocation.zipCode)
) {
payload.serviceZipCode = order.serviceLocation.zipCode;
} else if (
isDefined(order.serviceLocation.appointmentType) &&
order.serviceLocation.appointmentType !== AppointmentTypeStrings.MOBILE &&
isDefined(order.serviceLocation.provider.address.zipCode)
) {
payload.serviceZipCode = order.serviceLocation.provider.address.zipCode;
} else {
payload.serviceZipCode = "";
}
// Damage Type
if (isDefined(order.damage.isRepair)) {
payload.damageType = order.damage.isRepair ? "repair" : "replace";
} else {
payload.damageType = "";
}
// Account Type - always insurance for ISS
payload.accountType = "insurance";
// Promo Codes
const promos = order.lineItems.promos ?? [];
if (promos.length === 0) {
payload.promoCodes = "";
} else {
const promoCodes = promos.map((promo) => promo.promoCode);
const promoString = promoCodes.reduce((prev, next) => `${prev},${next}`);
payload.promoCodes = promoString;
}
// Vehicle info
if (isDefined(order.vehicle.year)) {
// Ensure cast to string.
payload.vehicleYear = `${order.vehicle.year}`;
} else {
payload.vehicleYear = "";
}
if (isDefined(order.vehicle.make)) {
payload.vehicleMake = order.vehicle.make;
} else {
payload.vehicleMake = "";
}
if (isDefined(order.vehicle.model)) {
payload.vehicleModel = order.vehicle.model;
} else {
payload.vehicleModel = "";
}
if (isDefined(order.vehicle.style)) {
payload.vehicleStyle = order.vehicle.style;
} else {
payload.vehicleStyle = "";
}
if (isDefined(order.vehicle.carId)) {
payload.vehicleCarId = order.vehicle.carId;
} else {
payload.vehicleCarId = "";
}
if (isDefined(order.vehicle.vinRequired)) {
payload.vehicleIsVinRequired = order.vehicle.vinRequired;
} else {
payload.vehicleIsVinRequired = "";
}
// Glass pieces
const glass = order.damage.glassToReplace ?? [];
if (glass.length === 0) {
payload.glassToReplace = "";
} else {
const glassNames = glass.map((g) => `${g.glassLocation}/${g.glassName}`);
const glassString = glassNames.reduce((prev, next) => `${prev},${next}`);
payload.glassToReplace = glassString;
}
// EON
if (order.eon) {
payload.eon = order.eon;
} else {
payload.eon = "";
}
// Work Order Id
if (order.workOrderId) {
const parsedId = parseInt(order.workOrderId);
if (!isNaN(parsedId)) {
payload.workOrderId = parsedId;
} else {
payload.workOrderId = "";
}
} else {
payload.workOrderId = "";
}
// Provider Ctu
if (isDefined(order.serviceLocation.zipCodeCtu)) {
payload.providerCtu = order.serviceLocation.zipCodeCtu;
} else {
payload.providerCtu = "";
}
// Work Order Number
if (order.workOrderNumber) {
payload.orderNumber = order.workOrderNumber;
} else {
payload.orderNumber = "";
}
// Unverified (no price or deductible displayed)
if (!store.isVerified) {
payload.priceSubTotal = "";
payload.priceTotal = "";
}
// Deductible case (no price is displayed, only deductible)
else if (
store.isVerified &&
(typeof store.currentDeductible === 'number' && store.currentDeductible >= 0) &&
!store.isITAC &&
!store.isNoComp
) {
payload.priceSubTotal = "";
payload.priceTotal = "";
// ITAC or NoComp (where cash price is shown)
} else if (store.isVerified && (store.isITAC || store.isNoComp)) {
const subtotal = getPriceOfLineItems(getAvailableLineItems(order)).toFixed(2);
payload.priceSubTotal = parseFloat(subtotal);
const total = getCartTotal(order).toFixed(2);
payload.priceTotal = parseFloat(total);
} else {
payload.priceSubTotal = "";
payload.priceTotal = "";
}
// Cash Quote or Cash Price Sub Total
payload.cashPriceSubTotal = getPriceOfLineItems(getAvailableLineItems(order)).toString();
// Recalibration
payload.isRecalibrationOnOrder = isRecalOrder(order.lineItems);
// Appointment Type
if (isDefined(order.serviceLocation.appointmentType)) {
payload.appointmentType = order.serviceLocation.appointmentType;
} else {
payload.appointmentType = "";
}
payload.isInsuranceVerified = store.isVerified;
payload.insuranceCompanyName = store.issConfig.clientName ?? "";
if (!store.isVerified) {
payload.isInsuranceItac = "";
payload.isInsuranceNoComp = "";
} else {
payload.isInsuranceItac = store.isITAC ?? "";
payload.isInsuranceNoComp = store.isNoComp ?? "";
}
if (
store.isITAC ||
store.isNoComp ||
!store.isVerified
) {
payload.insuranceDeductible = "";
} else {
payload.insuranceDeductible = store.currentDeductible ?? "";
}
pushToDataLayerIfDefined(payload);
},
pushExperimentsToDataLayer() {
const { experiments } = useMainStore().applicationUser;
experiments?.forEach((exp) => {
// Set Google Dimension Index based on experiment settings.
let googleDimensionIndex = 99;
if (exp.settings[experimentSettings.GOOGLE_CUSTOM_DIMENSION_INDEX] !== undefined) {
googleDimensionIndex = exp.settings[experimentSettings.GOOGLE_CUSTOM_DIMENSION_INDEX];
}
// Create object with dimension index and value.
const experimentWithDimension = {
[`experimentId_${googleDimensionIndex}`]: exp.universeId,
[`variationId_${googleDimensionIndex}`]: exp.variationId,
[`experimentName_${googleDimensionIndex}`]: exp.universeName,
[`variationName_${googleDimensionIndex}`]: exp.variationName,
[`customDimension_${googleDimensionIndex}`]:
`${exp.universeId}_${exp.variationId}_${exp.universeName}_${exp.variationName}`
};
// Push to the data layer with the Google Custom Dimension Index.
pushToDataLayerIfDefined(experimentWithDimension);
});
},
prependActionToMethod(object, method, actionToPrepend) {
const baseMethodName = method.name.startsWith('bound ')
? method.name.substring(6)
: method.name;
const baseMethod = object[baseMethodName];
object[baseMethodName] = function () {
actionToPrepend.apply(this, arguments);
return baseMethod.apply(object, arguments);
};
},
logDigitalConsumer() {
const store = useMainStore();
const currentPageName = this.getPageNameByQueryString();
const universes = store.applicationUser.experiments;
const deviceId = getDeviceIdValue();
const sid = getSessionIdValue();
const variationNames = universes
.filter((item) => item.universeName === experimentSettings.CONCEPT_FUNNEL)
.map((item) => item.variationName)
.filter(Boolean); // removes undefined/null
const conceptVariation = variationNames.length > 0 ? variationNames[0] : "";
const isConceptExposed = universes.find(
(item) => item.universeName === experimentSettings.CONCEPT_FUNNEL
)?.isExposed;
const submittedOrder = store.getSubmittedOrder();
const hasSubmittedOrder = store.hasSubmittedOrder();
const order = hasSubmittedOrder ? submittedOrder : store.order;
var payload = {
deviceId: deviceId,
sessionId: sid,
actionName: `Browser page:${currentPageName}`,
referralSequenceNumber: order.referralSequenceNumber,
referralNumber: order.referralNumber,
workOrderId: order.workOrderId,
workOrderNumber: order.workOrderNumber,
conceptVariation: conceptVariation,
isConceptExposed: isConceptExposed,
};
store.logDigitalConsumer(payload);
},
// This sends session data to the session logging endpoint on the analytics service.
// From there, it uses aws kinesis data stream, DigitalConsumer-Session-Data-Stream,
// and aws FireHose stream, DigitalConsumer-Session-Firehose, to push data to an
// S3 bucket, safelite-dev-digitalconsumer-session-data-us-east-2/1.
// This bucket data is then picked up by snowflake for analytics use.
pushIssSessionData() {
const store = useMainStore();
const submittedOrder = store.getSubmittedOrder();
const hasSubmittedOrder = store.hasSubmittedOrder();
const order = hasSubmittedOrder ? submittedOrder : store.order;
const issConfig = store.issConfig;
const applicationUser = store.applicationUser;
// Bailout info for analytics. We use combined string to send to analytics - BailoutCode_BailoutString - ex: 15_ApiError
const bailoutCode = store.bailoutCode;
let bailoutCombinedString = "";
try {
if (bailoutCode !== null && bailoutCode !== undefined) {
const bailoutString =
Object.keys(BailoutCode).find((key) => BailoutCode[key] === bailoutCode) ?? "";
bailoutCombinedString = bailoutString ? `${bailoutCode}_${bailoutString}` : `${bailoutCode}`;
}
} catch (error) {
// Do not let error here stop logging.
}
// Use parentaccount on order, use parentaccount on issconfig as fallback.
const parentAccountNumber = (order.parentAccountNumber ?? issConfig.parentAccountNumber).toString();
// NOTE: ISS does not support early bird times.
const isEarlyBird = false; /* order?.lineItems?.supportingItems?.find(
(lineItem) => lineItem.partType == partTypeStrings.EARLY_BIRD
);
*/
// NOTE: No promo codes on ISS at the moment.
const promoCodes = ""; /* Array.isArray(order?.lineItems?.promos)
? order?.lineItems?.promos
.map((item) => item.promoCode)
.filter((code) => code)
.join(", ")
: null;
*/
const glassProducts = order?.damage?.glassToReplace?.map(
(part) => `${part.glassLocation}-${part.glassName}`
);
const format = (part) => `${part?.partNumber}-${part?.partType ? part.partType : ""}`;
const mapParts = (arr) => (arr ?? []).map(format);
const glassParts = mapParts(order?.lineItems?.glassParts);
const supportingItems = mapParts(order?.lineItems?.supportingItems);
const vaps = mapParts(order?.lineItems?.vaps);
const childParts = [].concat(
...(order?.lineItems?.glassParts ?? []).map((gp) => mapParts(gp?.childParts))
);
const allParts = [...glassParts, ...supportingItems, ...vaps, ...childParts];
if (applicationUser?.pageData?.quote?.servicePackageSelected) {
allParts.push(applicationUser.pageData.quote.servicePackageSelected);
}
var appointment = `${order?.schedule?.date ?? ""} ${order?.schedule?.startTime ?? ""}`;
var currentPageName = this.getPageNameByQueryString();
/*
// add query strings to the page name for debugging. on the vehicle page, if from an external link, pull it from the stash
if (currentPageName === "vehicle") {
if (!window.location.search) {
if (store.getters.externalParameterState?.qsStash) {
currentPageName += `${store.getters.externalParameterState.qsStash}`;
}
} else {
currentPageName += `${window.location.search}`;
}
} else {
if (window.location.search) {
currentPageName += `${window.location.search}`;
}
}
*/
var sessionData = {};
sessionData.currentPage = currentPageName;
sessionData.sid = getSessionIdValue();
sessionData.deviceId = getDeviceIdValue();
sessionData.issSessionId = applicationUser?.savedSessionId;
sessionData.skey = getSessionKeyValue().toString();
sessionData.userId = getUserIdValue();
sessionData.carId = order?.vehicle?.carId;
sessionData.vehicleYear = order?.vehicle?.year;
sessionData.vehicleMake = order?.vehicle?.make;
sessionData.vehicleModel = order?.vehicle?.model;
sessionData.vehicleStyle = order?.vehicle?.style;
sessionData.hasVin = order?.vehicle?.vin ? true : false;
sessionData.cashOrInsuranceAccountType = "Insurance";
sessionData.damageType = order?.damage?.isRepair ? "Repair" : "Replace";
sessionData.productType = allParts;
sessionData.eon = order?.eon;
sessionData.referralNumber = order?.referralNumber;
sessionData.referralSequenceNumber = order?.referralSequenceNumber;
sessionData.referralDate = order?.referralDate;
sessionData.workOrderNumber = order?.workOrderNumber;
sessionData.workOrderId = order?.workOrderId;
sessionData.isPia = store.isPayInAdvance;
sessionData.piaType = (store.isPayInAdvance ? order?.payment?.paymentMethod : null);
sessionData.parentAccountNumber = parentAccountNumber;
sessionData.billToAccountNumber = issConfig.billToAccountNumber;
sessionData.settledTenderAmount = order?.payment?.nextGenSettledAmount;
sessionData.recalRequired = store.hasRecalibrationPart;
sessionData.recalType = getRecalPartNumbers(order?.lineItems?.glassParts);
sessionData.serviceZipCode = order?.serviceLocation?.provider?.address?.zipCode
? order?.serviceLocation?.provider?.address?.zipCode
: order?.serviceLocation?.zipCode;
sessionData.providerCtu = order?.serviceLocation?.provider?.address?.zipCodeCtu
? order?.serviceLocation?.provider?.address?.zipCodeCtu
: order?.serviceLocation?.zipCodeCtu;
sessionData.appointmentDate = appointment;
sessionData.serviceType = order?.serviceLocation?.appointmentType;
sessionData.promoCodes = promoCodes;
sessionData.hasTechnicianNotes = order?.contactInfo?.notesForTechnician ? true : false;
sessionData.paymentMethod = order?.payment?.paymentMethod;
sessionData.isTextingOptedIn = order?.contactInfo?.requestTextUpdates ? true : false;
sessionData.isEarlyBird = isEarlyBird;
sessionData.insuranceCo = issConfig.clientName;
sessionData.deductible = store.currentDeductible ?? 0;
sessionData.isVerified = store.isVerified;
sessionData.coverageStatus = coverageStatuses.mapToApi(order?.insuranceCoverage.coverageStatus);
sessionData.coverageSubStatus = coverageType.mapToApi(order?.insuranceCoverage.coverageType);
sessionData.isNoComp = store.isNoComp;
sessionData.isItac = store.isITAC;
sessionData.subTotalPrice = getSubtotal(order).toString();
sessionData.totalPrice = getCartTotal(order).toString();
sessionData.cashPriceSubTotal = getSubtotal(order).toString();
sessionData.userAgent = navigator.userAgent;
sessionData.bailoutCodeValue = bailoutCombinedString;
sessionData.vinRequired = order?.vehicle?.vinRequired ?? false;
store.logIssSessionData(sessionData);
},
async initSession() {
regenerateDeviceId();
regenerateUserId();
const deviceId = getDeviceIdValue(); // cookieNames.DXDEV
const userId = getUserIdValue(); // cookieNames.ISS_USER_ID
const sid = getSessionIdValue(); // cookieNames.SESSION_ID
// const skey = getSessionKeyValue();
const referrer = applicationConfig.CURRENT_ENVIRONMENT !== 'Localhost' ? document.referrer : null;
const store = useMainStore();
const clientTag = store.issConfig.clientTag;
const siteType = store.issConfig.siteType;
const payload = {
clientTag,
siteType: siteType,
deviceId,
referrer,
sessionId: sid,
userId,
userAgent: navigator.userAgent
};
const response = await useMainStore().initializeSession(payload);
if (response?.data) {
if (response?.data.sessionKey) {
setSessionKeyIfUnset(response.data.sessionKey);
}
if (response?.data.sessionId) {
setSessionIdIfUnset(response.data.sessionId);
}
}
},
noSession() {
return !areAllSessionCookiesSet();
}
},
computed: {
analyticsPageEvents() {
return analyticsPageEvents;
},
GaCategories() {
return GaCategories;
},
GaActions() {
return GaActions;
},
GaLabels() {
return GaLabels;
},
ValueToLogTypes() {
return ValueToLogTypes;
}
}
};