diff --git a/src/constants/cookie-names.js b/src/constants/cookie-names.js index 2756a8eb7..c52debc2a 100644 --- a/src/constants/cookie-names.js +++ b/src/constants/cookie-names.js @@ -1,5 +1,10 @@ const cookieNames = { FUNNEL_SESSION_INFO: "FunnelSessionInfo", + + // Existing Safelite.com cookies + DXDEV: "dxdev", + SESSION_ID: "sid", + SESSION_KEY: "skey" }; export { cookieNames }; diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 2aa1fe226..2325c29cd 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -63,6 +63,10 @@ const endpoints = { url: "/location/api/v1/location/zip", method: "GET", }, + LogExperimentExposureIfAssigned:{ + url: "/analytics/api/v1/analytics/log-experiment-exposure", + method: "POST", + } }; export { endpoints }; diff --git a/src/constants/experiments.js b/src/constants/experiments.js new file mode 100644 index 000000000..13a51aa7f --- /dev/null +++ b/src/constants/experiments.js @@ -0,0 +1,6 @@ +const experimentUniverses = { + CONCEPT_FUNNEL: 'ConceptFunnel' +}; + +export { experimentUniverses }; + \ No newline at end of file diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 640364213..9e87416d4 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -17,6 +17,7 @@ const storeActions = { LOAD_ORDER: "loadOrder", SET_REFERRAL_INFORMATION: "setReferralInformation", VALIDATE_ZIP: "validateZip", + LOG_EXPERIMENT_EXPOSURE: "logExperimentExposure", // DEPENDENCY MUTATIONS RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleAndDependencies", diff --git a/src/global-methods.js b/src/global-methods.js index f0675c02d..c0823f29a 100644 --- a/src/global-methods.js +++ b/src/global-methods.js @@ -30,6 +30,7 @@ export default { } }, (error) => { + console.error(error); return reject(error.response); } ); diff --git a/src/helpers/heritage-integration/cookie-helper.js b/src/helpers/heritage-integration/cookie-helper.js index 36516a570..5a9ed4691 100644 --- a/src/helpers/heritage-integration/cookie-helper.js +++ b/src/helpers/heritage-integration/cookie-helper.js @@ -24,13 +24,13 @@ export function updateOrCreateFunnelCookie() { /* Gets the current instance of the funnel cookie. Returns null if cookie isn't valid JSON. -*/ +*/ export function getFunnelCookie() { const cookieJson = document.cookie ?.split("; ") ?.find(row => row.startsWith(`${cookieNames.FUNNEL_SESSION_INFO}=`)) ?.split("=")[1]; - + try { return JSON.parse(cookieJson); } catch (error) { @@ -45,6 +45,61 @@ export function deleteFunnelCookie() { document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}=; Max-Age=0; path=${applicationConfig.COOKIE_PATH}; ${getCookieDomainValue()}`; } +/* + Gets cookie domain value. Localhost will be empty "". +*/ +export function getCookieDomainValue() { + return location.hostname.includes("localhost") ? "" : `domain=${getDomainWithoutSubdomain()};`; +} + +/* + Gets value of dxdev cookie, and then extracts "did" value from it. + Returns empty string if cookie not found or "did" string not present. +*/ +export function getDeviceIdValue(){ + const cookieValue = getCookieByName(cookieNames.DXDEV); + const cookieValueMatch = cookieValue.match("(?<=did=).*?(?=&)"); + + if(cookieValueMatch){ + return cookieValueMatch[0]; + } + + return ''; +} + +/* + Gets value of sid cookie, returns empty string if not found. +*/ +export function getSessionIdValue(){ + const cookieValue = getCookieByName(cookieNames.SESSION_ID); + + if(cookieValue){ + return cookieValue; + } + + return ''; +} + +/* + Gets value of skey cookie, returns 0 if not found. +*/ +export function getSessionKeyValue(){ + const cookieValue = getCookieByName(cookieNames.SESSION_KEY); + + if(cookieValue){ + return cookieValue; + } + + return 0; +} + +/* +=========================== += PRIVATE FUNCTIONS = +=========================== +*/ + + /* Used to set properties on the funnel cookie. Takes an object with properties to set. Will overwrite existing properties. @@ -65,10 +120,9 @@ function setFunnelCookieProperties(properties) { } } -export function getCookieDomainValue() { - return location.hostname.includes("localhost") ? "" : `domain=${getDomainWithoutSubdomain()};`; -} - +/* + Gets current domain without the subdomain for cookie. +*/ function getDomainWithoutSubdomain() { let url = location.hostname; if (url.includes("localhost")) { @@ -82,3 +136,16 @@ function getDomainWithoutSubdomain() { .slice(-(urlParts.length === 4 ? 3 : 2)) .join('.')}`; } + +/* + Gets cookie value by name, returns empty string if not found. +*/ +function getCookieByName(name) { + const value = "; " + document.cookie; + const parts = value.split("; " + name + "="); + + if (parts.length === 2) { + return parts.pop().split(";").shift(); + } + return ""; +} \ No newline at end of file diff --git a/src/helpers/layout-helper.js b/src/helpers/layout-helper.js index 10f331faf..0a8177e25 100644 --- a/src/helpers/layout-helper.js +++ b/src/helpers/layout-helper.js @@ -15,10 +15,10 @@ export function settleAllPromises(promiseResultMap) { // when returned, so other promises do. Map the results to the object // so that the object is the return data. - if (results[i].value.data === undefined) { - resultMap[promiseName] = results[i].value; + if (results[i]?.value?.data === undefined) { + resultMap[promiseName] = results[i]?.value; } else { - resultMap[promiseName] = results[i].value.data; + resultMap[promiseName] = results[i]?.value?.data; } } diff --git a/src/layouts/vehicle-year/vehicle-year.vue b/src/layouts/vehicle-year/vehicle-year.vue index 305cef7fc..85faa0d61 100644 --- a/src/layouts/vehicle-year/vehicle-year.vue +++ b/src/layouts/vehicle-year/vehicle-year.vue @@ -17,11 +17,16 @@ import yearQuestion from "@/layouts/vehicle-year/year-question/year-question"; import funnelHeader from "@/common-components/funnel-header/funnel-header"; import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner"; import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header"; + // Supporting files import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { settleAllPromises } from "@/helpers/layout-helper"; import { storeMutations } from "@/constants/store-mutations"; import { storeActions } from "@/constants/store-actions"; +import { experimentUniverses } from "@/constants/experiments"; +import { getDeviceIdValue, getSessionIdValue, getSessionKeyValue } from "@/helpers/heritage-integration/cookie-helper"; + +import baseMixin from "@/mixins/base-mixin"; import store from "@/store"; export default { @@ -36,8 +41,19 @@ export default { async beforeRouteEnter(to, from, next) { // Call APIs const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); - const yearQuestionInitialDataPromise = - yearQuestion.methods.loadInitialData(); + const yearQuestionInitialDataPromise = yearQuestion.methods.loadInitialData(); + + // Log experiment exposure + const logExperimentExposurePromise = baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOG_EXPERIMENT_EXPOSURE, + { + userId: getDeviceIdValue(), + sessionKey: getSessionKeyValue(), + sessionId: getSessionIdValue(), + pageName: to.query.fmgPage, + serverName: 'ConceptFunnel', + pageEvent: { action: '', event: 'ENTRY' }, + universeName: experimentUniverses.CONCEPT_FUNNEL + }, false); // Settle promises and get results const promiseResultMap = [ @@ -49,6 +65,10 @@ export default { resultKey: "yearQuestionInitialData", promise: yearQuestionInitialDataPromise, }, + { + resultKey: "logExperimentExposure", + promise: logExperimentExposurePromise, + }, ]; let resultMap = await settleAllPromises(promiseResultMap); diff --git a/src/store/index.js b/src/store/index.js index 07336ccf0..0fc7dec76 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -93,7 +93,7 @@ export const mutations = { updateVehicleVin(state, vin) { state.order.vehicle.vin = vin; }, - updateIsRepair(state, isRepair){ + updateIsRepair(state, isRepair) { state.order.damage.isRepair = isRepair; }, updateNumberOfChips(state, numberOfChips) { @@ -248,7 +248,7 @@ export const actions = { method: endpoints.LookupVinByPlate.method, endpoint: endpoints.LookupVinByPlate.url, payload: { - licensePlate: licensePlate, + licensePlate: licensePlate, licenseState: licenseState }, }); @@ -292,12 +292,12 @@ export const actions = { }, getDamageOptions(context, { carId }) { return globalMethods.callHttpClient({ - methods: endpoints.GetDamageOptions.method, + methods: endpoints.GetDamageOptions.method, endpoint: `${endpoints.GetDamageOptions.url}/${carId}`, payload: {}, }); }, - validateZip(context, {zip}) { + validateZip(context, { zip }) { return globalMethods.callHttpClient({ methods: endpoints.ValidateZip.method, endpoint: `${endpoints.ValidateZip.url}/${zip}` @@ -363,6 +363,22 @@ export const actions = { context.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, referralCorrelationId); }, + logExperimentExposure(context, { userId, sessionKey, sessionId, pageName, serverName, pageEvent, universeName }) { + return globalMethods.callHttpClient({ + method: endpoints.LogExperimentExposureIfAssigned.method, + endpoint: endpoints.LogExperimentExposureIfAssigned.url, + payload: { + userId: userId, + sessionKey: sessionKey, + sessionId: sessionId, + pageName: pageName, + serverName: serverName, + pageEvent: pageEvent, + universeName: universeName + } + }); + }, + // Parts API Actions getPartsOrQuestions(context, { carId, glassArray, zipCode, vin = '' }) { return globalMethods.callHttpClient({