From 02b3e3c3f93cdc87cfd90705e1969689b3683816 Mon Sep 17 00:00:00 2001 From: Jeremy-Z Date: Mon, 16 Mar 2026 12:20:21 -0400 Subject: [PATCH] Initial digital session logging commits. --- src/constants/endpoints.js | 8 ++ src/mixins/analytics-mixin.js | 172 ++++++++++++++++++++++++++++++++++ src/router/index.js | 6 ++ src/store/index.js | 160 +++++++++++++++++++++++++++++++ 4 files changed, 346 insertions(+) diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 057ef9e4..897f9873 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -181,6 +181,14 @@ const endpoints = Object.freeze({ url: `${ANALYTICS_BASE_URL}/initialize`, method: 'POST' }, + LogDigitalConsumer: { + url: "/analytics/api/v1/analytics/digitalconsumer-log", + method: "POST", + }, + LogIssSessionData: { + url: "/analytics/api/v1/analytics/digitalconsumer-session-logging", + method: "POST", + }, GetExperimentsByUser: { url: `${ANALYTICS_BASE_URL}/get-experiments`, method: 'GET' diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 297bb8e2..71477ca2 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -161,6 +161,178 @@ export default { return baseMethod.apply(object, arguments); }; }, + + logDigitalConsumer() { + const store = useMainStore(); + const currentPageName = this.getPageNameByQueryString(); + const universes = store.applicationUser.experiments; + const deviceId = getDeviceIdValue(); // cookieNames.DXDEV + const userId = getUserIdValue(); // cookieNames.ISS_USER_ID + const sid = getSessionIdValue(); // cookieNames.SESSION_ID + + 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; + + // TODO: Find equivalent ISS methods for these: + const submittedOrder = null;//baseMixin.methods.getSubmittedOrder(); + const hasSubmittedOrder = false;//baseMixin.methods.hasSubmittedOrder(); + const hasSubmittedOrderAtConfirmationPage = + hasSubmittedOrder && currentPageName?.toLowerCase() == routeData.CONFIRMATION.name; + + var payload = { + deviceId: deviceId, + sessionId: sid, + actionName: `Browser page:${currentPageName}`, + referralSequenceNumber: hasSubmittedOrderAtConfirmationPage + ? submittedOrder.referralSequenceNumber + : store.order.referralSequenceNumber, + referralNumber: hasSubmittedOrderAtConfirmationPage + ? submittedOrder.referralNumber + : store.order.referralNumber, + workOrderId: hasSubmittedOrderAtConfirmationPage + ? submittedOrder.workOrderId + : store.order.workOrderId, + workOrderNumber: hasSubmittedOrderAtConfirmationPage + ? submittedOrder.workOrderNumber + : store.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 hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder(); + const submittedOrder = baseMixin.methods.getSubmittedOrder(); + const order = hasSubmittedOrder ? submittedOrder : store.order; + + const hasSubmittedApplicationUser = baseMixin.methods.hasSubmittedApplicationUser(); + const submittedApplicationUser = baseMixin.methods.getSubmittedApplicationUser(); + const applicationUser = hasSubmittedApplicationUser + ? submittedApplicationUser + : store.applicationUser; + + const isEarlyBird = order?.lineItems?.supportingItems?.find( + (lineItem) => lineItem.partType == partTypeStrings.EARLY_BIRD + ); + + 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.fmgSessionId = applicationUser?.savedSessionId; + sessionData.skey = getskeyValue().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 = order?.payment?.isInsurance + ? "Insurance" + : "Cash"; + 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 = order?.payment?.isPia ?? false; + sessionData.piaType = order?.payment?.piaType; + sessionData.parentAccountNumber = order?.payment?.parentAccountNumber; + sessionData.billToAccountNumber = order?.payment?.billToAccountNumber; + sessionData.settledTenderAmount = order?.settledTenderAmount; + sessionData.recalRequired = containsRecalParts(order?.lineItems); + 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?.serviceLocation?.techNotes ? true : false; + sessionData.paymentMethod = order?.payment?.piaType; + sessionData.isTextingOptedIn = order?.customer?.isSmsOptIn ? true : false; + sessionData.isEarlyBird = isEarlyBird ? true : false; + sessionData.insuranceCo = order?.policy?.insuranceCompanyName; + sessionData.deductible = order?.policy?.currentDeductible; + sessionData.isVerified = order?.payment?.insuranceCoverage?.isVerified ?? false; + sessionData.coverageStatus = order?.payment?.insuranceCoverage?.coverageStatus; + sessionData.coverageSubStatus = order?.payment?.insuranceCoverage?.coverageSubStatus; + sessionData.isNoComp = order?.policy?.isNoComp; + sessionData.isItac = order?.policy?.isItac; + sessionData.subTotalPrice = getSubTotal(order?.lineItems); + sessionData.totalPrice = getAmountDue(order?.lineItems, true); + sessionData.userAgent = navigator.userAgent; + sessionData.cashPriceSubTotal = order?.cashPriceSubTotal; + + store.logIssSessionData(sessionData); + }, async initSession() { regenerateDeviceId(); diff --git a/src/router/index.js b/src/router/index.js index c6a18493..3b7b205f 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -178,6 +178,12 @@ router.afterEach(async (to, from) => { if (to.query.issPage !== issPageValues.ENTRY_PAGE) { document.title = routerTitles[to.query.issPage] || 'Safelite Solutions®'; + // digital consumer logging + analyticsMixin.methods.logDigitalConsumer(); + + // digital consumer ISS session logging to snowflake + //analyticsMixin.methods.pushIssSessionData(); + // Push page view to GA analyticsMixin.methods.pushPageViewToGA(); diff --git a/src/store/index.js b/src/store/index.js index 59df6e99..963929b1 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2622,6 +2622,166 @@ export const useMainStore = defineStore({ updateTriggeredSiteEntry(wasSiteEntryTriggered) { this.applicationUser.triggeredSiteEntry = wasSiteEntryTriggered; }, + + logDigitalConsumer( + { + deviceId, + sessionId, + actionName, + referralSequenceNumber, + referralNumber, + workOrderId, + workOrderNumber, + conceptVariation, + isConceptExposed, + } + ) { + var payload = { + sessionId: sessionId, + deviceId: deviceId, + actionName: actionName ?? "", + referralSequenceNumber: referralSequenceNumber ?? "", + referralNumber: referralNumber ?? "", + applicationName: "ISS", //baseMixin.methods.isMobileDevice() ? "2.0 Mobile" : "2.0", + workOrderId: workOrderId ?? "", + workOrderNumber: workOrderNumber ?? "", + conceptVariation: conceptVariation, + isConceptExposed: isConceptExposed, + }; + + console.error("Logging Digital Consumer Action: ", payload); + + return globalMethods.callHttpClient({ + method: endpoints.LogDigitalConsumer.method, + endpoint: endpoints.LogDigitalConsumer.url, + payload, + logApiCall: false, + bailoutOnError: false + }).then( + (response) => response, + (error) => { + console.log(`Analytics Service Error: ${error.data}`); + } + ); + }, + + logIssSessionData( + { + currentPage, + sid, + deviceId, + issSessionId, + skey, + userId, + carId, + vehicleYear, + vehicleMake, + vehicleModel, + vehicleStyle, + hasVin, + cashOrInsuranceAccountType, + damageType, + productType, + eon, + referralNumber, + referralSequenceNumber, + referralDate, + workOrderNumber, + workOrderId, + isPia, + piaType, + parentAccountNumber, + billToAccountNumber, + settledTenderAmount, + recalRequired, + recalType, + serviceZipCode, + providerCtu, + appointmentDate, + serviceType, + promoCodes, + hasTechnicianNotes, + paymentMethod, + isTextingOptedIn, + isEarlyBird, + insuranceCo, + deductible, + isVerified, + coverageStatus, + coverageSubStatus, + isNoComp, + isItac, + subTotalPrice, + totalPrice, + userAgent, + cashPriceSubTotal, + } + ) { + var payload = { + currentPage: currentPage, + sid: sid, + deviceId: deviceId, + fmgSessionId: issSessionId, + skey: skey, + userId: userId, + carId: carId, + vehicleYear: vehicleYear, + vehicleMake: vehicleMake, + vehicleModel: vehicleModel, + vehicleStyle: vehicleStyle, + hasVin: hasVin, + cashOrInsuranceAccountType: cashOrInsuranceAccountType, + isVerified: isVerified, + coverageStatus: coverageStatus, + coverageSubStatus: coverageSubStatus, + damageType: damageType, + productType: productType, + eon: eon, + referralNumber: referralNumber, + referralSequenceNumber: referralSequenceNumber, + referralDate: referralDate, + workOrderNumber: workOrderNumber, + workOrderId: workOrderId, + isPia: isPia, + piaType: piaType, + parentAccountNumber: parentAccountNumber, + settledTenderAmount: settledTenderAmount, + recalRequired: recalRequired, + recalType: recalType, + serviceZipCode: serviceZipCode, + providerCtu: providerCtu, + appointmentDate: appointmentDate, + serviceType: serviceType, + promoCodes: promoCodes, + hasTechnicianNotes: hasTechnicianNotes, + paymentMethod: paymentMethod, + isTextingOptedIn: isTextingOptedIn, + isEarlyBird: isEarlyBird, + insuranceCo: insuranceCo, + deductible: deductible, + isNoComp: isNoComp, + isItac: isItac, + subTotalPrice: subTotalPrice, + totalPrice: totalPrice, + userAgent: userAgent, + cashPriceSubTotal: cashPriceSubTotal, + billToAccountNumber: billToAccountNumber, + }; + + return globalMethods.callHttpClient({ + method: endpoints.LogIssSessionData.method, + endpoint: endpoints.LogIssSessionData.url, + payload, + logApiCall: false, + bailoutOnError: false + }).then( + (response) => response, + (error) => { + console.log(`Analytics Service Error: ${error.data}`); + } + ); + }, + updateContactInfo(contactInfo) { this.order.customer.firstName = contactInfo?.firstName ?? this.order.customer.firstName;