DigitalConsumer.FixMyGlass/src/mixins/analytics-mixin.js
2025-05-06 13:04:42 -04:00

826 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { storeActions } from "@/constants/store-actions";
import {
getDeviceIdValue,
getSessionIdValue,
getSessionKeyValue,
getUserIdValue,
regenerateDeviceId,
regenerateUserId,
refreshSessionExpiration,
areAllSessionCookiesSet,
setSessionIdIfUnset,
setSessionKeyIfUnset,
} from "@/helpers/heritage-integration/cookie-helper";
import { queryStrings } from "@/constants/query-strings";
import { experimentSettings } from "@/constants/experiments";
import {
analyticsPageEvents,
GaCategories,
GaActions,
GaLabels,
GaEvents,
ValueToLogTypes,
} from "@/constants/analytics";
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin";
import { applicationConfig } from "../constants/application-config";
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import router from "@/router/index.js";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { Variables } from "../constants/analytics";
export default {
methods: {
getPageName() {
return getPageNameByQueryString();
},
async logPageView(pageEvent) {
const currentPageName = getPageNameByQueryString();
await this.validateSession();
const submittedOrder = baseMixin.methods.getSubmittedOrder();
const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder();
const hasSubmittedOrderAtConfirmationPage =
hasSubmittedOrder && currentPageName?.toLowerCase() == fmgPageValues.CONFIRMATION;
const refSequenceNum = hasSubmittedOrderAtConfirmationPage
? submittedOrder.referralSequenceNumber
: store.getters.order.referralSequenceNumber;
const parentAccountNum = hasSubmittedOrderAtConfirmationPage
? submittedOrder.payment.parentAccountNumber
: store.getters.order.payment.parentAccountNumber;
var payload = {
userId: getUserIdValue(),
sessionKey: getSessionKeyValue(),
pageName: currentPageName,
sessionId: getSessionIdValue(),
action: "",
event: pageEvent,
shouldUseSessionId: false,
experimentsForUser: store.getters.applicationUser.experiments,
referralSequenceNumber: refSequenceNum,
parentAccountNumber: parentAccountNum,
};
await baseMixin.methods.dispatchStoreAction(storeActions.LOG_PAGE_VIEW, payload, false);
},
async logCustomEvent(category, action, label, value) {
const currentPageName = getPageNameByQueryString();
await this.validateSession();
const refSequenceNum =
store.getters.order.referralSequenceNumber ||
store.getters.submittedOrder?.referralSequenceNumber;
var payload = {
userId: getUserIdValue(),
sessionKey: getSessionKeyValue(),
pageName: currentPageName,
sessionId: getSessionIdValue(),
category: category,
action: action,
label: label,
value: value,
shouldUseSessionId: false,
experimentsForUser: store.getters.applicationUser.experiments,
referralSequenceNumber: refSequenceNum,
parentAccountNumber: store.getters.order.payment.parentAccountNumber,
};
await baseMixin.methods.dispatchStoreAction(
storeActions.LOG_CUSTOM_EVENT,
payload,
false
);
},
async pushEventToGA(category, action, label, pushToLogApp = false, valueToLogType = null) {
const currentPageName = getPageNameByQueryString();
const labelToLog = getValueToLog(label, valueToLogType);
const eventToBePushed = {
event: GaEvents.GENERIC_EVENT,
category: category,
action: action,
label: labelToLog,
value: undefined,
path: `/fmg/?${queryStrings.FMG_PAGE}=${currentPageName}`,
};
pushToDataLayerIfDefined(eventToBePushed);
if (pushToLogApp) {
await this.logCustomEvent(category, action, labelToLog, undefined);
}
},
async pushVariableToDataLayer(data) {
pushToDataLayerIfDefined(data);
},
async pushPageViewToGA() {
const currentPageName = getPageNameByQueryString();
const pageViewEvent = {
event: GaEvents.PAGE_VIEW_EVENT,
pagePath: `/fmg/?${queryStrings.FMG_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;
// Get correct order object
const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder();
const submittedOrder = baseMixin.methods.getSubmittedOrder();
const order = hasSubmittedOrder ? submittedOrder : store.getters.order;
// Begin assembling payload for data layer
const payload = {};
// 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
if (isDefined(order.payment.isInsurance)) {
payload.accountType = order.payment.isInsurance ? "insurance" : "cash";
} else {
payload.accountType = "";
}
// 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 = "";
}
// 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 = "";
}
// Pricing
// Only fire for completed orders?
const lineItems = order.lineItems ?? {};
const combinedLineItems = [
...(lineItems.glassParts ?? []),
...(lineItems.supportingItems ?? []),
...(lineItems.vaps ?? []),
...(lineItems.promos ?? []),
];
const isPricingAvailable =
combinedLineItems.length > 0 &&
combinedLineItems.every(
(lineItem) =>
isDefined(lineItem.kitPrice) &&
isDefined(lineItem.laborAmount) &&
isDefined(lineItem.sellingPrice)
);
const isTaxAvailable =
isPricingAvailable &&
combinedLineItems.every((lineItem) => isDefined(lineItem.salesTax));
//unverified (in scenarios we dont display the price)
if (
order.payment.isInsurance &&
isDefined(order.payment.insuranceCoverage.isVerified) &&
!order.payment.insuranceCoverage.isVerified
) {
payload.priceSubTotal = "";
}
//deductible (in scenarios we dont display the price)
else if (
order.payment.isInsurance &&
isDefined(order.payment.insuranceCoverage.isVerified) &&
order.payment.insuranceCoverage.isVerified &&
isDefined(order.policy.currentDeductible) &&
order.policy.currentDeductible >= 0 &&
!order.policy.isItac &&
!order.policy.isNoComp
) {
payload.priceSubTotal = "";
} else if (isPricingAvailable) {
const subtotal = baseMixin.methods
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, false)
.toFixed(2);
payload.priceSubTotal = parseFloat(subtotal);
} else {
payload.priceSubTotal = "";
}
//unverified (in scenarios we dont display the price)
if (
order.payment.isInsurance &&
isDefined(order.payment.insuranceCoverage.isVerified) &&
!order.payment.insuranceCoverage.isVerified
) {
payload.priceTotal = "";
}
//deductible (in scenarios we dont display the price)
else if (
order.payment.isInsurance &&
isDefined(order.payment.insuranceCoverage.isVerified) &&
order.payment.insuranceCoverage.isVerified &&
isDefined(order.policy.currentDeductible) &&
order.policy.currentDeductible >= 0 &&
!order.policy.isItac &&
!order.policy.isNoComp
) {
payload.priceTotal = "";
} else if (isTaxAvailable) {
const total = baseMixin.methods
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, true)
.toFixed(2);
payload.priceTotal = parseFloat(total);
} else {
payload.priceTotal = "";
}
// Recalibration
if (hasSubmittedOrder) {
payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnSubmittedState;
} else {
payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnOrder;
}
// Appointment Type
if (isDefined(order.serviceLocation.appointmentType)) {
payload.appointmentType = order.serviceLocation.appointmentType;
} else {
payload.appointmentType = "";
}
//Insurance
if (order.payment.isInsurance) {
payload.isInsuranceVerified = order.payment.insuranceCoverage.isVerified ?? "";
payload.insuranceCompanyName = order.policy.insuranceCompanyName ?? "";
if (!order.payment.insuranceCoverage.isVerified) {
payload.isInsuranceItac = "";
payload.isInsuranceNoComp = "";
} else {
payload.isInsuranceItac = order.policy.isItac ?? "";
payload.isInsuranceNoComp = order.policy.isNoComp ?? "";
}
if (
order.policy.isItac ||
order.policy.isNoComp ||
!order.payment.insuranceCoverage.isVerified
) {
payload.insuranceDeductible = "";
} else {
payload.insuranceDeductible = order.policy.currentDeductible ?? "";
}
} else {
payload.isInsuranceVerified = "";
payload.insuranceDeductible = "";
payload.isInsuranceItac = "";
payload.isInsuranceNoComp = "";
payload.insuranceCompanyName = "";
}
pushToDataLayerIfDefined(payload);
},
pushExperimentsToDataLayer() {
const experiments = store.getters.applicationUser.experiments;
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);
});
},
pushProductArrayToDataLayer() {
// Get correct order object
const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder();
const submittedOrder = baseMixin.methods.getSubmittedOrder();
if (hasSubmittedOrder) {
var lineItems = submittedOrder.lineItems ? submittedOrder.lineItems : {};
// combine all line items
var combinedLineItems = [
...(lineItems.glassParts ?? []),
...(lineItems.supportingItems ?? []),
...(lineItems.vaps ?? []),
...(lineItems.promos ?? []),
];
//Get child parts
combinedLineItems = flattenArray(combinedLineItems);
var products = [];
var discount = 0;
var coupon = "";
var subTotal = 0;
for (var i = 0; i < combinedLineItems.length; i++) {
let part = combinedLineItems[i];
let productSku = part.partNumber;
let productType = part.partType;
let promoCode = part.promoCode;
let productPrice = baseMixin.methods.getTotalPriceOfAllLineItemsAndChildParts(
[part],
false
);
if (productSku == "DISCOUNT" || productType == "SERVICE PACKAGE DISCOUNT") {
if (coupon) {
coupon += ",";
}
if (productSku == "DISCOUNT") {
coupon += promoCode;
} else {
coupon += productSku;
}
discount += productPrice * -1;
} else {
products.push({
productType: productType,
productSku: productSku,
productPrice: productPrice.toFixed(2),
productQuantity: "1",
});
subTotal += productPrice;
}
}
pushToDataLayerIfDefined({
event: "productArray",
productArray: {
coupon: coupon,
discount: discount.toFixed(2),
subTotal: subTotal.toFixed(2),
products: products,
},
});
}
},
pushECommerceCartToDataLayer() {
const isDefined = (x) => x !== null && x !== undefined;
// Get correct order object
const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder();
const submittedOrder = baseMixin.methods.getSubmittedOrder();
if (hasSubmittedOrder) {
var lineItems = submittedOrder.lineItems ? submittedOrder.lineItems : {};
var supportingItems =
submittedOrder.lineItems && submittedOrder.lineItems.supportingItems
? submittedOrder.lineItems.supportingItems
: [];
var insurance = submittedOrder.payment && submittedOrder.payment.isInsurance;
var itac = submittedOrder.policy && submittedOrder.policy.isItac;
var nocomp = submittedOrder.policy && submittedOrder.policy.isNoComp;
var recalLineItem = supportingItems.filter(function (lineItem) {
return lineItem.partType.indexOf("RECALIBRATION") !== -1;
});
var disposalFee = supportingItems.filter(function (lineItem) {
return lineItem.partType.indexOf("DISPOSAL FEE") !== -1;
});
var mobileFee = supportingItems.filter(function (lineItem) {
return lineItem.partType.indexOf("MOBILE FEE") !== -1;
});
var repairFee = supportingItems.filter(function (lineItem) {
return lineItem.partType.indexOf("REPAIR FEE") !== -1;
});
var vaps =
submittedOrder.lineItems && submittedOrder.lineItems.vaps
? submittedOrder.lineItems.vaps
: [];
var wipers = vaps.filter(function (lineItem) {
return (
lineItem.partType.indexOf("FRONT WIPER") !== -1 ||
lineItem.partType.indexOf("REAR WIPER") !== -1
);
});
var rainDefense = vaps.filter(function (lineItem) {
return lineItem.partType.indexOf("RAIN DEFENSE") !== -1;
});
// Combine line items
var combinedLineItems = [];
if (lineItems.glassParts) {
combinedLineItems = combinedLineItems.concat(lineItems.glassParts);
}
if (recalLineItem) {
combinedLineItems = combinedLineItems.concat(recalLineItem);
}
if (disposalFee) {
combinedLineItems = combinedLineItems.concat(disposalFee);
}
if (mobileFee && !(insurance && !itac && !nocomp)) {
combinedLineItems = combinedLineItems.concat(mobileFee);
}
if (repairFee) {
combinedLineItems = combinedLineItems.concat(repairFee);
}
if (wipers) {
combinedLineItems = combinedLineItems.concat(wipers);
}
if (rainDefense) {
combinedLineItems = combinedLineItems.concat(rainDefense);
}
//Remove child Parts if any
combinedLineItems.forEach((lineItem) => {
lineItem.childParts = [];
});
var isPricingAvailable =
combinedLineItems.length > 0 &&
combinedLineItems.every(function (lineItem) {
return (
isDefined(lineItem.kitPrice) &&
isDefined(lineItem.laborAmount) &&
isDefined(lineItem.sellingPrice)
);
});
let subtotal = 0;
if (isPricingAvailable) {
subtotal = baseMixin.methods.getTotalPriceOfAllLineItemsAndChildParts(
combinedLineItems,
false
);
}
pushToDataLayerIfDefined({
event: "eCommerceCart",
eCommerceCart: {
products: combinedLineItems,
subTotal: parseInt(subtotal),
},
});
}
},
pushCommissionJunctionGtmDataToDataLayer() {
// helper check for if an object is defined (but maybe falsey)
const isDefined = (x) => x !== null && x !== undefined;
let repairReplace = "";
let coupons = "";
let refSequenceNum = "";
let accountType = "";
let amount = 0;
let cjEvent = "";
if (store.getters.applicationUser.affiliateCookies?.length > 0) {
const affiliateCookies = store.getters.applicationUser.affiliateCookies;
var cookieArray = [];
affiliateCookies.forEach((item) => {
let cookieObject = convertCookieStringToObject(item.CookieValue);
cookieArray.push(cookieObject);
});
const sortedCookies = cookieArray.sort(
(a, b) =>
new Date(b.timestamp.replace("/", "T")) -
new Date(a.timestamp.replace("/", "T"))
);
const filteredCookie = sortedCookies?.find(
(x) => x.tagEvent?.length > 0 && x.batchEvent?.length > 0
);
if (isDefined(filteredCookie)) {
cjEvent = filteredCookie.tagEvent;
const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder();
const submittedOrder = baseMixin.methods.getSubmittedOrder();
if (hasSubmittedOrder) {
repairReplace = submittedOrder.damage.isRepair ? "Repair" : "Replace";
const promos = submittedOrder.lineItems.promos ?? [];
if (promos.length === 0) {
coupons = "";
} else {
const promoCodes = promos.map((promo) => promo.promoCode);
const promoString = promoCodes.reduce(
(prev, next) => `${prev}_${next}`
);
coupons = promoString;
}
const serviceZipPackage = submittedOrder.lineItems?.supportingItems?.find(
(x) => x.partType == "SERVICE PACKAGE DISCOUNT"
);
if (isDefined(serviceZipPackage)) {
if (coupons) {
coupons += ",";
}
coupons += serviceZipPackage.partNumber;
}
refSequenceNum = submittedOrder.referralSequenceNumber;
if (isDefined(submittedOrder.payment.isInsurance)) {
accountType = submittedOrder.payment.isInsurance ? "insurance" : "cash";
} else {
accountType = "";
}
const lineItems = submittedOrder.lineItems ?? {};
const combinedLineItems = [
...(lineItems.glassParts ?? []),
...(lineItems.supportingItems ?? []),
...(lineItems.vaps ?? []),
...(lineItems.promos ?? []),
];
const isPricingAvailable =
combinedLineItems.length > 0 &&
combinedLineItems.every(
(lineItem) =>
isDefined(lineItem.kitPrice) &&
isDefined(lineItem.laborAmount) &&
isDefined(lineItem.sellingPrice)
);
if (accountType == "cash" && isPricingAvailable) {
amount = baseMixin.methods.getTotalPriceOfAllLineItemsAndChildParts(
combinedLineItems,
false
);
}
}
}
}
pushToDataLayerIfDefined({
event: "commissionJunctionGtmData",
commissionJunctionGtmData: {
cj_commission_junction_event: cjEvent,
cj_referral_sequence_number: refSequenceNum,
cj_amount: amount.toFixed(2),
cj_repair_replace: repairReplace,
cj_coupon: coupons,
},
});
},
pushPageErrorToDataLayer(error) {
pushToDataLayerIfDefined({
event: "page-error",
error: error,
});
},
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);
};
},
async initSession() {
regenerateDeviceId();
regenerateUserId();
const userId = getUserIdValue(); // cookieNames.FUNNEL_USER_ID
const deviceId = getDeviceIdValue(); // cookieNames.DXDEV
const sessionId = getSessionIdValue(); // cookieNames.SESSION_ID
const userAgent = navigator.userAgent; // navigator.userAgent
const referrer =
applicationConfig.CURRENT_ENVIRONMENT != "Localhost" ? document.referrer : null; // see above
const payload = {
userId: userId,
deviceId: deviceId,
sessionId: sessionId,
userAgent: userAgent,
referrer: referrer,
};
const response = await baseMixin.methods.dispatchStoreAction(
storeActions.INITIALIZE_SESSION,
payload,
false
);
if (response?.data) {
if (response.data.sessionKey) {
setSessionKeyIfUnset(response.data.sessionKey);
}
if (response.data.sessionId) {
setSessionIdIfUnset(response.data.sessionId);
}
}
},
noSession() {
return !areAllSessionCookiesSet();
},
// sessionExpired is true when one of the analytics cookies(sid, dxdev) has expired but we still have the vehicle year in vuex
sessionExpired() {
const fromHeritage = getQuerystringParameter(queryStrings.FROM_HERITAGE) === "true";
if (this.noSession() && !fromHeritage && store.getters.order.vehicle?.year > 0) {
return true;
} else {
return false;
}
},
async validateSession() {
// do not init session if it is expired. let the click event on nav-bar handle session expired logic
if (this.sessionExpired()) {
return;
}
if (this.noSession()) {
await this.initSession();
}
refreshSessionExpiration();
},
removeParamsFromEndpoint(endpoint) {
const endpointWithoutParams = endpoint.split("?")[0];
const numSlashesBeforeParams = 6;
let splitString = endpointWithoutParams.split("/");
if (splitString.length > numSlashesBeforeParams) {
splitString = splitString.slice(0, numSlashesBeforeParams);
return splitString.join("/");
} else {
return endpointWithoutParams;
}
},
},
computed: {
analyticsPageEvents() {
return analyticsPageEvents;
},
GaCategories() {
return GaCategories;
},
GaActions() {
return GaActions;
},
GaLabels() {
return GaLabels;
},
ValueToLogTypes() {
return ValueToLogTypes;
},
Variables() {
return Variables;
},
},
};
function pushToDataLayerIfDefined(data) {
if (window.dataLayer !== undefined) {
window.dataLayer.push(data);
}
}
function getPageNameByQueryString() {
const params = new URLSearchParams(location.search);
if (params.has(queryStrings.FMG_PAGE)) {
return params.get(queryStrings.FMG_PAGE);
} else {
return "";
}
}
function getValueToLog(value, valueToLogType) {
if (valueToLogType != null && valueToLogType === ValueToLogTypes.LAST_5) {
return value.slice(-5);
}
return value;
}
function flattenArray(arr) {
let result = [];
arr.forEach((item) => {
result.push(item);
if (item.childParts) {
result = result.concat(item.childParts);
delete item.childParts; // Remove childParts after flattening
}
});
return result;
}
function convertCookieStringToObject(cookieValue) {
const cookieObject = cookieValue.split("&").reduce((acc, pair) => {
const [key, value] = pair.split("=");
acc[key] = value;
return acc;
}, {});
return cookieObject;
}