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; } } };