Merge branch 'develop' into feature/Digital/SSR-87
# Conflicts: # src/store/store.spec.js
This commit is contained in:
commit
1d7be6c8cd
30 changed files with 16719 additions and 126 deletions
15261
package-lock.json
generated
15261
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="">
|
<html lang="en-US">
|
||||||
<head>
|
<head>
|
||||||
<script>
|
<script>
|
||||||
window.dataLayer = [{}];
|
window.dataLayer = [{}];
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,4 @@
|
||||||
@import "@/styles/common-typography-styles.scss";
|
@import "@/styles/common-typography-styles.scss";
|
||||||
@import "@/styles/common-error-styles.scss";
|
@import "@/styles/common-error-styles.scss";
|
||||||
@import "@/styles/common-animations.scss";
|
@import "@/styles/common-animations.scss";
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
36
src/constants/analytics.js
Normal file
36
src/constants/analytics.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
const analyticsPageEvents = {
|
||||||
|
ENTRY: "ENTRY",
|
||||||
|
EVENT: "EVENT",
|
||||||
|
};
|
||||||
|
|
||||||
|
// GA Constants
|
||||||
|
const GaEvents = {
|
||||||
|
GENERIC_EVENT: "event",
|
||||||
|
PAGE_VIEW_EVENT: "logPageview",
|
||||||
|
};
|
||||||
|
|
||||||
|
const GaCategories = {
|
||||||
|
API_RESPONSE: "Api_Response",
|
||||||
|
EVOX: "Evox",
|
||||||
|
};
|
||||||
|
|
||||||
|
const GaActions = {
|
||||||
|
RESULT: "Result",
|
||||||
|
CLICKED: "Clicked",
|
||||||
|
VIF: "vif",
|
||||||
|
SUBMITTED: "Submitted",
|
||||||
|
};
|
||||||
|
|
||||||
|
const GaLabels = {
|
||||||
|
SUCCESS: "Success",
|
||||||
|
ERROR: "Error",
|
||||||
|
LICENSE_PLATE_LOOKUP: "License_Plate_Look_Up",
|
||||||
|
VIN_LOOKUP: "Vin_Look_Up",
|
||||||
|
ADDRESS_LOOKUP: "Address_Look_up",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValueToLogTypes = {
|
||||||
|
LAST_5: "last_5",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents, ValueToLogTypes };
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
const applicationConfig = {
|
const applicationConfig = {
|
||||||
CURRENT_ENVIRONMENT: process.env.VUE_APP_CURRENT_ENVIRONMENT, // "Localhost", "Dev", "QA", and "Prod"
|
CURRENT_ENVIRONMENT: process.env.VUE_APP_CURRENT_ENVIRONMENT, // "Localhost", "Dev", "QA", and "Prod"
|
||||||
CONSUMER_CF_DISTRO: process.env.VUE_APP_CONSUMER_CF_DISTRO,
|
CONSUMER_CF_DISTRO: process.env.VUE_APP_CONSUMER_CF_DISTRO,
|
||||||
|
ANALYTICS_SESSION_TIMEOUT_MINUTES: 30,
|
||||||
|
SAVED_SESSION_TIMEOUT_DAYS: 45,
|
||||||
|
COOKIE_PATH: "/",
|
||||||
APPLICATION_NAME: "SelfService",
|
APPLICATION_NAME: "SelfService",
|
||||||
SITE_ENTRY_TRIGGER_VALUE: "SelfService",
|
SITE_ENTRY_TRIGGER_VALUE: "SelfService",
|
||||||
APPLICATION_ABBREVIATION: "iss",
|
APPLICATION_ABBREVIATION: "iss",
|
||||||
|
|
|
||||||
12
src/constants/cookie-names.js
Normal file
12
src/constants/cookie-names.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { applicationConfig } from "@/constants/application-config.js";
|
||||||
|
|
||||||
|
const cookieNames = {
|
||||||
|
ISS_SESSION_INFO: `ISSSessionInfo-${applicationConfig.CURRENT_ENVIRONMENT}`,
|
||||||
|
|
||||||
|
// Existing Safelite.com cookies
|
||||||
|
DXDEV: "dxdev",
|
||||||
|
SESSION_ID: "sid",
|
||||||
|
SESSION_KEY: "skey",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { cookieNames };
|
||||||
|
|
@ -26,7 +26,35 @@ const endpoints = {
|
||||||
GetVehicleStyles: {
|
GetVehicleStyles: {
|
||||||
url: "/vehicle/api/v1/vehicle/styles",
|
url: "/vehicle/api/v1/vehicle/styles",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
}
|
},
|
||||||
|
GetVehicle: {
|
||||||
|
url: "/vehicle/api/v1/vehicle/lookup",
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
|
LogExperimentExposureIfAssigned: {
|
||||||
|
url: "/experiments/api/v1/experiments/log-exposure",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
|
LogPageView: {
|
||||||
|
url: "/analytics/api/v1/analytics/log-page-view",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
|
LogCustomEvent: {
|
||||||
|
url: "/analytics/api/v1/analytics/log-custom-event",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
|
InitializeSession: {
|
||||||
|
url: "/analytics/api/v1/analytics/initialize",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
|
GetExperimentsByUser: {
|
||||||
|
url: "/analytics/api/v1/analytics/get-experiments",
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
|
RunExperimentsForTrigger: {
|
||||||
|
url: "/experiments/api/v1/experiments/run",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export { endpoints };
|
export { endpoints };
|
||||||
|
|
|
||||||
14
src/constants/experiments.js
Normal file
14
src/constants/experiments.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
const experimentUniverses = {
|
||||||
|
ISS_FUNNEL: "ISSFunnel",
|
||||||
|
};
|
||||||
|
|
||||||
|
const experimentSettings = {
|
||||||
|
GOOGLE_CUSTOM_DIMENSION_INDEX: "Google Custom Dimension Index",
|
||||||
|
};
|
||||||
|
|
||||||
|
const experimentTriggers = {
|
||||||
|
SITE_ENTRY: "SiteEntry",
|
||||||
|
PAGE_ENTRY: "PageEntry",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { experimentUniverses, experimentSettings, experimentTriggers };
|
||||||
3
src/constants/header-keys.js
Normal file
3
src/constants/header-keys.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const headerKeys = {
|
||||||
|
EXPERIMENT: "X-Experiment-Data",
|
||||||
|
};
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
const storeActions = {
|
|
||||||
// Content Actions
|
|
||||||
GET_ROUTE_INFO_ACTION: "getRouteInfo",
|
|
||||||
GET_HOMEPAGE_NAME: "getHomepageName",
|
|
||||||
GET_PAGE_DATA: "getPageData",
|
|
||||||
};
|
|
||||||
|
|
||||||
export { storeActions };
|
|
||||||
|
|
||||||
|
|
@ -1,14 +1,39 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import analyticsMixIn from "@/mixins/analytics-mixin.js";
|
||||||
|
import { useMainStore } from "@/store";
|
||||||
|
|
||||||
import { applicationConfig } from "@/constants/application-config.js";
|
import { applicationConfig } from "@/constants/application-config.js";
|
||||||
|
import { GaCategories, GaActions, GaLabels } from "@/constants/analytics";
|
||||||
|
import { headerKeys } from "@/constants/header-keys";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
callHttpClient({ method, endpoint, payload}) {
|
callHttpClient({ method, endpoint, payload, logApiCall = true}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
const store = useMainStore();
|
||||||
const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO;
|
const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO;
|
||||||
const payloadAndAnalyticsData = Object.assign({}, payload, { AppName: "SelfService" });
|
const payloadAndAnalyticsData = Object.assign({}, payload, { AppName: "SelfService" });
|
||||||
|
const headers = {
|
||||||
|
[headerKeys.EXPERIMENT]: JSON.stringify(store.experimentSettings),
|
||||||
|
};
|
||||||
|
|
||||||
axios({ method: method, url: cfDistroUrl + endpoint, data: payloadAndAnalyticsData, crossDomain: true, responseType: {}})
|
axios({
|
||||||
|
method: method,
|
||||||
|
url: cfDistroUrl + endpoint,
|
||||||
|
data: payloadAndAnalyticsData,
|
||||||
|
crossDomain: true,
|
||||||
|
responseType: {},
|
||||||
|
headers: headers,
|
||||||
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
if (logApiCall) {
|
||||||
|
analyticsMixIn.methods.pushEventToGA(
|
||||||
|
GaCategories.API_RESPONSE,
|
||||||
|
GaActions.RESULT,
|
||||||
|
`${GaLabels.SUCCESS}_${endpoint}`,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return resolve(response);
|
return resolve(response);
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
|
|
@ -22,7 +47,12 @@ export default {
|
||||||
//used for mocked services
|
//used for mocked services
|
||||||
async mockCallHttpClient(method, endpoint) {
|
async mockCallHttpClient(method, endpoint) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios({ method: method, url: endpoint, crossDomain: true, responseType: {}})
|
axios({
|
||||||
|
method: method,
|
||||||
|
url: endpoint,
|
||||||
|
crossDomain: true,
|
||||||
|
responseType: {}
|
||||||
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return resolve(response);
|
return resolve(response);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import globalMethods from "@/global-methods";
|
import globalMethods from "@/global-methods";
|
||||||
|
import analyticsMixIn from "@/mixins/analytics-mixin";
|
||||||
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
|
|
||||||
//Mock external dependencies
|
//Mock external dependencies
|
||||||
jest.mock("axios");
|
jest.mock("axios");
|
||||||
|
jest.mock("@/mixins/analytics-mixin");
|
||||||
|
|
||||||
it("Global Methods - Call Http Client - Should Resolve Promise", () => {
|
it("Global Methods - Call Http Client - Should Resolve Promise", () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
|
|
@ -25,6 +28,7 @@ it("Global Methods - Call Http Client - Should Resolve Promise", () => {
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
isError: true,
|
isError: true,
|
||||||
});
|
});
|
||||||
|
analyticsMixIn.methods.pushEventToGA = jest.fn();
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
globalMethods.callHttpClient(httpArgs).catch((err) => {
|
globalMethods.callHttpClient(httpArgs).catch((err) => {
|
||||||
|
|
@ -42,6 +46,8 @@ it("Global Methods - Call Http Client - Should Resolve Promise", () => {
|
||||||
}) {
|
}) {
|
||||||
//Clear node module
|
//Clear node module
|
||||||
axios.mockClear();
|
axios.mockClear();
|
||||||
|
|
||||||
|
const mountOptions = getMountOptions();
|
||||||
|
|
||||||
// Success Response
|
// Success Response
|
||||||
const response = {
|
const response = {
|
||||||
|
|
|
||||||
205
src/helpers/cookie-helper.js
Normal file
205
src/helpers/cookie-helper.js
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
import { cookieNames } from "@/constants/cookie-names";
|
||||||
|
import { applicationConfig } from "@/constants/application-config";
|
||||||
|
import { useMainStore } from "@/store";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Will update the cookie if present, or create a new one if not.
|
||||||
|
*/
|
||||||
|
export function updateOrCreateISSCookie() {
|
||||||
|
const store = useMainStore();
|
||||||
|
|
||||||
|
// Set up cookie with all the props.
|
||||||
|
setISSCookieProperties({
|
||||||
|
LastTouched: new Date().toUTCString(),
|
||||||
|
SavedSessionTimeoutDate: store.applicationUser.savedSessionTimeout,
|
||||||
|
ShouldResetState: false,
|
||||||
|
ReferralNumber: store.order.referralNumber,
|
||||||
|
ReferralDate: store.order.referralDate,
|
||||||
|
ReferralCorrelationId: store.order.referralCorrelationId,
|
||||||
|
ReferralParentAccountNumber: store.order.accountNumber,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gets the current instance of the ISS cookie.
|
||||||
|
Returns null if cookie isn't valid JSON.
|
||||||
|
*/
|
||||||
|
export function getISSCookie() {
|
||||||
|
const cookieJson = document.cookie
|
||||||
|
?.split("; ")
|
||||||
|
?.find((row) => row.startsWith(`${cookieNames.ISS_SESSION_INFO}=`))
|
||||||
|
?.split("=")[1];
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(cookieJson);
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Removes cookie from browser.
|
||||||
|
*/
|
||||||
|
export function deleteISSCookie() {
|
||||||
|
createOrUpdateCookie(cookieNames.ISS_SESSION_INFO, undefined, { maxAge: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gets cookie domain value. Localhost will be empty "".
|
||||||
|
*/
|
||||||
|
export function getCookieDomainValue() {
|
||||||
|
return isLocalhost() ? "" : `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() {
|
||||||
|
// Sometimes these cookie contains more than the device ID.
|
||||||
|
const cookieValue = getCookieValueByName(cookieNames.DXDEV);
|
||||||
|
const cookieValuesSplit = cookieValue.split("=");
|
||||||
|
|
||||||
|
// If this is the only value, just use that.
|
||||||
|
if (cookieValuesSplit.length === 2 && cookieValuesSplit[0] === "did") {
|
||||||
|
return cookieValuesSplit[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookieValueMatch = cookieValue.match("^did=[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}");
|
||||||
|
if (cookieValueMatch) {
|
||||||
|
return cookieValueMatch[0].split("=")[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return "00000000-0000-0000-0000-000000000000";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gets value of skey cookie, returns 0 if not found.
|
||||||
|
*/
|
||||||
|
export function getSessionKeyValue() {
|
||||||
|
const cookieValue = getCookieValueByName(cookieNames.SESSION_KEY);
|
||||||
|
|
||||||
|
if (cookieValue) {
|
||||||
|
return cookieValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gets value of skey cookie, returns 0 if not found.
|
||||||
|
*/
|
||||||
|
export function getSessionIdValue() {
|
||||||
|
const cookieValue = getCookieValueByName(cookieNames.SESSION_ID);
|
||||||
|
|
||||||
|
if (cookieValue) {
|
||||||
|
return cookieValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "00000000-0000-0000-0000-000000000000";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Updates session ID cookie with new expiration date
|
||||||
|
*/
|
||||||
|
export function updateSessionIdCookie() {
|
||||||
|
createOrUpdateCookie(cookieNames.SESSION_ID, getSessionIdValue(), { maxAge: 60 * 30 });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCookieProperties(
|
||||||
|
properties,
|
||||||
|
{ useDefaultISSCookieAttributes = true, maxAge, isSecure }
|
||||||
|
) {
|
||||||
|
if (typeof properties == "object") {
|
||||||
|
Object.keys(properties).forEach((key) => {
|
||||||
|
createOrUpdateCookie(key, properties[key], {
|
||||||
|
useDefaultISSCookieAttributes,
|
||||||
|
maxAge,
|
||||||
|
isSecure,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
===========================
|
||||||
|
= PRIVATE FUNCTIONS =
|
||||||
|
===========================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Used to set properties on the ISS cookie.
|
||||||
|
Takes an object with properties to set. Will overwrite existing properties.
|
||||||
|
*/
|
||||||
|
function setISSCookieProperties(properties) {
|
||||||
|
if (typeof properties == "object") {
|
||||||
|
let cookie = getISSCookie();
|
||||||
|
|
||||||
|
if (cookie !== null) {
|
||||||
|
Object.keys(properties).forEach((key) => {
|
||||||
|
cookie[key] = properties[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookieValueJson = JSON.stringify(cookie ?? {});
|
||||||
|
createOrUpdateCookie(cookieNames.ISS_SESSION_INFO, cookieValueJson, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Used to create a cookie.
|
||||||
|
`useDefaultISSCookieAttributes` will set the path and domain to our defaults
|
||||||
|
*/
|
||||||
|
function createOrUpdateCookie(
|
||||||
|
key,
|
||||||
|
value = "",
|
||||||
|
{ useDefaultISSCookieAttributes = true, maxAge, isSecure = true }
|
||||||
|
) {
|
||||||
|
let cookieToAdd = `${key}=${value}; `;
|
||||||
|
|
||||||
|
if (useDefaultISSCookieAttributes) {
|
||||||
|
cookieToAdd += `path=${applicationConfig.COOKIE_PATH}; ${getCookieDomainValue()} `;
|
||||||
|
}
|
||||||
|
if (isSecure && !isLocalhost()) {
|
||||||
|
cookieToAdd += `secure; `;
|
||||||
|
}
|
||||||
|
if (!isNaN(maxAge)) {
|
||||||
|
cookieToAdd += `max-age=${maxAge};`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.cookie = cookieToAdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gets current domain without the subdomain for cookie.
|
||||||
|
*/
|
||||||
|
function getDomainWithoutSubdomain() {
|
||||||
|
let url = location.hostname;
|
||||||
|
if (isLocalhost()) {
|
||||||
|
return "localhost";
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlParts = url.split(".");
|
||||||
|
|
||||||
|
return `.${urlParts
|
||||||
|
.slice(0)
|
||||||
|
.slice(-(urlParts.length === 4 ? 3 : 2))
|
||||||
|
.join(".")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gets cookie value by name, returns empty string if not found.
|
||||||
|
*/
|
||||||
|
function getCookieValueByName(name) {
|
||||||
|
const value = "; " + document.cookie;
|
||||||
|
const parts = value.split("; " + name + "=");
|
||||||
|
|
||||||
|
if (parts.length === 2) {
|
||||||
|
return parts.pop().split(";").shift();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLocalhost() {
|
||||||
|
return location.hostname.includes("localhost");
|
||||||
|
}
|
||||||
48
src/helpers/session-helper.js
Normal file
48
src/helpers/session-helper.js
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { applicationConfig } from "@/constants/application-config";
|
||||||
|
import { getISSCookie } from "@/helpers/cookie-helper.js";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Method to determine if our analytics session has timed out or not.
|
||||||
|
Amount used for timeout is configurable in application-config.js
|
||||||
|
*/
|
||||||
|
export function isAnalyticsSessionStillActive() {
|
||||||
|
if (getISSCookie() !== null) {
|
||||||
|
const lastTouchedValue = getISSCookie().LastTouched;
|
||||||
|
const timeoutAmount = applicationConfig.ANALYTICS_SESSION_TIMEOUT_MINUTES;
|
||||||
|
|
||||||
|
const isMoreThanHalfHourAgo =
|
||||||
|
(new Date() - new Date(lastTouchedValue)) / 60000 > timeoutAmount;
|
||||||
|
|
||||||
|
if (isMoreThanHalfHourAgo) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Method to determine if the users 'saved' session is still active.
|
||||||
|
When user state is created, there is a date that is saved into state
|
||||||
|
this method checks against that date.
|
||||||
|
|
||||||
|
Note: That time for saved session timeout is configurable in application-config.js
|
||||||
|
*/
|
||||||
|
export function isSavedSessionStillActive() {
|
||||||
|
if (getISSCookie() !== null) {
|
||||||
|
const savedSessionTimeStamp = new Date(getISSCookie().SavedSessionTimeoutDate);
|
||||||
|
const isSavedSessionTimedOut = new Date(new Date().toUTCString()) > savedSessionTimeStamp;
|
||||||
|
|
||||||
|
return !isSavedSessionTimedOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Function to calculate the date for the saved session timeout.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getDateForSavedSessionTimeout() {
|
||||||
|
const currentDate = new Date(new Date().toUTCString());
|
||||||
|
currentDate.setDate(currentDate.getDate() + applicationConfig.SAVED_SESSION_TIMEOUT_DAYS);
|
||||||
|
return currentDate.toUTCString();
|
||||||
|
}
|
||||||
89
src/helpers/session-helper.spec.js
Normal file
89
src/helpers/session-helper.spec.js
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
import * as cookieHelper from "@/helpers/cookie-helper";
|
||||||
|
import {
|
||||||
|
isAnalyticsSessionStillActive,
|
||||||
|
isSavedSessionStillActive,
|
||||||
|
getDateForSavedSessionTimeout,
|
||||||
|
} from "@/helpers/session-helper";
|
||||||
|
import { applicationConfig } from "@/constants/application-config";
|
||||||
|
|
||||||
|
describe("isAnalyticsSessionStillActive", () => {
|
||||||
|
test("isAnalyticsSessionStillActive, should return true", () => {
|
||||||
|
// Arrange
|
||||||
|
const mockDate = new Date(new Date().toUTCString());
|
||||||
|
mockDate.setDate(mockDate.getDate() + 1);
|
||||||
|
|
||||||
|
cookieHelper.getISSCookie = jest
|
||||||
|
.spyOn(cookieHelper, "getISSCookie")
|
||||||
|
.mockReturnValue({ LastTouched: mockDate });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = isAnalyticsSessionStillActive();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("isAnalyticsSessionStillActive, should return false", () => {
|
||||||
|
// Arrange
|
||||||
|
const mockDate = new Date(new Date().toUTCString());
|
||||||
|
mockDate.setDate(mockDate.getDate() - 1);
|
||||||
|
|
||||||
|
cookieHelper.getISSCookie = jest
|
||||||
|
.spyOn(cookieHelper, "getISSCookie")
|
||||||
|
.mockReturnValue({ LastTouched: mockDate });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = isAnalyticsSessionStillActive();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isSavedSessionStillActive", () => {
|
||||||
|
test("isSavedSessionStillActive, should return true", () => {
|
||||||
|
// Arrange
|
||||||
|
const mockDate = new Date(new Date().toUTCString());
|
||||||
|
mockDate.setDate(mockDate.getDate() + 1);
|
||||||
|
|
||||||
|
cookieHelper.getISSCookie = jest
|
||||||
|
.spyOn(cookieHelper, "getISSCookie")
|
||||||
|
.mockReturnValue({ SavedSessionTimeoutDate: mockDate });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = isSavedSessionStillActive();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("isSavedSessionStillActive, should return false", () => {
|
||||||
|
// Arrange
|
||||||
|
const mockDate = new Date(new Date().toUTCString());
|
||||||
|
mockDate.setDate(mockDate.getDate() - 1);
|
||||||
|
|
||||||
|
cookieHelper.getISSCookie = jest
|
||||||
|
.spyOn(cookieHelper, "getISSCookie")
|
||||||
|
.mockReturnValue({ SavedSessionTimeoutDate: mockDate });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = isSavedSessionStillActive();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getDateForSavedSessionTimeout", () => {
|
||||||
|
test("getDateForSavedSessionTimeout, should equal application config setting", () => {
|
||||||
|
// Arrange
|
||||||
|
const currentDate = new Date(new Date().toUTCString());
|
||||||
|
currentDate.setDate(currentDate.getDate() + applicationConfig.SAVED_SESSION_TIMEOUT_DAYS);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = getDateForSavedSessionTimeout();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toEqual(currentDate.toUTCString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,8 +1,22 @@
|
||||||
import { storeActions } from "@/constants/store-actions";
|
|
||||||
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
|
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
|
||||||
import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
||||||
import { issPageValues } from "@/router/router-constants/issPage-values";
|
import { issPageValues } from "@/router/router-constants/issPage-values";
|
||||||
|
import { cookieNames } from "@/constants/cookie-names";
|
||||||
import { Form } from "vee-validate";
|
import { Form } from "vee-validate";
|
||||||
|
import baseMixin from "@/mixins/base-mixin";
|
||||||
|
import {
|
||||||
|
getCookieDomainValue,
|
||||||
|
setCookieProperties,
|
||||||
|
} from "@/helpers/cookie-helper";
|
||||||
|
import {
|
||||||
|
analyticsPageEvents,
|
||||||
|
GaCategories,
|
||||||
|
GaActions,
|
||||||
|
GaLabels,
|
||||||
|
GaEvents,
|
||||||
|
ValueToLogTypes,
|
||||||
|
} from "@/constants/analytics";
|
||||||
|
import { routerParams } from "@/router/router-constants/router-params";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
import { useMainStore } from "@/store";
|
import { useMainStore } from "@/store";
|
||||||
import { mapStores } from "pinia";
|
import { mapStores } from "pinia";
|
||||||
|
|
@ -20,27 +34,142 @@ const mockMixin = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Common methods
|
// Common methods
|
||||||
export function getMountOptions(mockData) {
|
export function getMountOptions(mockData) {
|
||||||
// Define our mocks to attached to the 'global' object for Vue/Jest.
|
// Define our mocks to attached to the 'global' object for Vue/Jest.
|
||||||
const mocks = {};
|
const mocks = {};
|
||||||
|
|
||||||
|
|
||||||
// Mock const files
|
// Mock const files
|
||||||
mocks.storeActions = storeActions;
|
mocks.navigationScenarios = navigationScenarios;
|
||||||
mocks.navigationScenarios = navigationScenarios;
|
mocks.vehicleCategories = vehicleCategories;
|
||||||
mocks.vehicleCategories = vehicleCategories;
|
mocks.issPageValues = issPageValues;
|
||||||
mocks.issPageValues = issPageValues;
|
mocks.queryStrings = queryStrings;
|
||||||
mocks.queryStrings = queryStrings;
|
mocks.$router = mockData?.router;
|
||||||
mocks.$router = mockData?.router;
|
|
||||||
|
const global = {
|
||||||
const global = {
|
mocks: mocks,
|
||||||
mocks: mocks,
|
mixins: [mockMixin],
|
||||||
mixins: [mockMixin],
|
plugins: [pinia],
|
||||||
plugins: [pinia],
|
stubs: { Form }
|
||||||
stubs: { Form }
|
};
|
||||||
};
|
|
||||||
|
return { global };
|
||||||
return { global };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Heritage integration common methods
|
||||||
|
export const cookies = {
|
||||||
|
[cookieNames.ISS_SESSION_INFO]: `{"ReferralNumber":"1566818","ReferralDate":"2022-03-15T10:56:24.597","ReferralCorrelationId":"404d2b04-f86e-45c3-b373-127b6217b060","ShouldResetState":false}`,
|
||||||
|
UNIQUE_SESSION_ID: "33756020-b58e-4ec7-b8b8-3f1576719c40",
|
||||||
|
anotherCookie: "{}",
|
||||||
|
someOtherCookie: "{}",
|
||||||
|
dxdev: "did=21b9b94a-ec23-42c1-aaac-e2ae4e4dbffe",
|
||||||
|
sid: "cba0c3d1-3c1b-4305-bb56-31aa50f58e27",
|
||||||
|
skey: "12345",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setupCookies({ ISSCookieValue = "", includeHeritageCookie = true }) {
|
||||||
|
Object.keys(cookies).forEach((key) => {
|
||||||
|
const cookieValue =
|
||||||
|
key == cookieNames.ISS_SESSION_INFO ? ISSCookieValue : cookies[key];
|
||||||
|
if (includeHeritageCookie || key != cookieNames.ISS_SESSION_INFO)
|
||||||
|
setCookieProperties({ [key]: cookieValue }, { isSecure: false });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes test cookies for testing cookie-helper and order-helper
|
||||||
|
export function removeAllTestCookies() {
|
||||||
|
Object.keys(cookies).forEach((key) => {
|
||||||
|
document.cookie = `${key}=;Max-Age=0;`;
|
||||||
|
document.cookie = `${key}=;Max-Age=0;path=/;${getCookieDomainValue()}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupMocksForJsFiles() {
|
||||||
|
return { baseMixin };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private methods
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Common methods
|
||||||
|
export function getMountOptions(mockData) {
|
||||||
|
// Define our mocks to attached to the 'global' object for Vue/Jest.
|
||||||
|
const mocks = {};
|
||||||
|
|
||||||
|
//this is mocking if you use the mixin directly(baseMixin.methods.dispatchStoreAction) vs this.dispatchStoreAction
|
||||||
|
setupBaseMixinDispatchStoreAction(mockData);
|
||||||
|
|
||||||
|
mocks.pushEventToGA = jest.fn();
|
||||||
|
mocks.pushPageViewToGA = jest.fn();
|
||||||
|
mocks.logEvent = jest.fn();
|
||||||
|
mocks.pushExperimentsToDataLayer = jest.fn();
|
||||||
|
mocks.prependActionToMethod = jest.fn();
|
||||||
|
mocks.dispatchStoreAction = jest.fn();
|
||||||
|
mocks.dispatchStoreAction.mockImplementation((actionName) => {
|
||||||
|
let actionFilterResult = mockData.actionList?.filter((x) => x.actionName == actionName);
|
||||||
|
|
||||||
|
if (actionFilterResult?.length === 1) {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: actionFilterResult[0].data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock const files
|
||||||
|
mocks.storeActions = storeActions;
|
||||||
|
mocks.storeMutations = storeMutations;
|
||||||
|
mocks.navigationScenarios = navigationScenarios;
|
||||||
|
mocks.vehicleCategories = vehicleCategories;
|
||||||
|
mocks.issPageValues = issPageValues;
|
||||||
|
mocks.analyticsPageEvents = analyticsPageEvents;
|
||||||
|
mocks.GaCategories = GaCategories;
|
||||||
|
mocks.GaActions = GaActions;
|
||||||
|
mocks.GaLabels = GaLabels;
|
||||||
|
mocks.GaEvents = GaEvents;
|
||||||
|
mocks.ValueToLogTypes = ValueToLogTypes;
|
||||||
|
mocks.queryStrings = queryStrings;
|
||||||
|
mocks.routerParams = routerParams;
|
||||||
|
|
||||||
|
// Mock $store and $router when accessing this.$store/$router
|
||||||
|
mocks.$store = mockData.store;
|
||||||
|
mocks.$router = mockData.router;
|
||||||
|
mocks.$route = mockData.route;
|
||||||
|
mocks.$loadScript = mockData.loadScript;
|
||||||
|
|
||||||
|
const global = {
|
||||||
|
mocks: mocks,
|
||||||
|
mixins: mockData.mixins,
|
||||||
|
plugins: [pinia],
|
||||||
|
stubs: { Form }
|
||||||
|
};
|
||||||
|
|
||||||
|
return { global };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function getMockOrderInfo(
|
||||||
|
mockReferralNumber,
|
||||||
|
mockCorrelationId,
|
||||||
|
mockReferralDate,
|
||||||
|
accountNumber = "0",
|
||||||
|
savedSessionId,
|
||||||
|
crmCustomerId
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
referralNumber: mockReferralNumber,
|
||||||
|
referralCorrelationId: mockCorrelationId,
|
||||||
|
referralDate: mockReferralDate,
|
||||||
|
accountNumber: accountNumber,
|
||||||
|
savedSessionId: savedSessionId,
|
||||||
|
crmCustomerId: crmCustomerId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
@ -91,7 +91,7 @@
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
selectedMake(make) {
|
selectedMake(make) {
|
||||||
this.mainStore.updateVehicleMake(make);
|
this.mainStore.updateVehicleMake( make );
|
||||||
this.$router.navigate(
|
this.$router.navigate(
|
||||||
this.navigationScenarios.SELECTED_MAKE,
|
this.navigationScenarios.SELECTED_MAKE,
|
||||||
this.$route
|
this.$route
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
selectedModel(model) {
|
selectedModel(model) {
|
||||||
this.mainStore.updateVehicleModel(model);
|
this.mainStore.updateVehicleModel( model );
|
||||||
this.$router.navigate(
|
this.$router.navigate(
|
||||||
this.navigationScenarios.SELECTED_MODEL,
|
this.navigationScenarios.SELECTED_MODEL,
|
||||||
this.$route
|
this.$route
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "vehicle-style",
|
name: "vehicle-style",
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -77,11 +76,13 @@ export default {
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
selectedStyle(style) {
|
selectedStyle(style) {
|
||||||
this.mainStore.updateVehicleStyle(style);
|
this.mainStore.updateVehicleStyle( style );
|
||||||
this.$router.navigate(
|
this.mainStore.setVehicle().then(() => {
|
||||||
this.navigationScenarios.SELECTED_STYLE,
|
this.$router.navigate(
|
||||||
this.$route
|
this.navigationScenarios.SELECTED_STYLE,
|
||||||
);
|
this.$route
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,8 @@
|
||||||
watch: {
|
watch: {
|
||||||
selectedYear(year) {
|
selectedYear(year) {
|
||||||
const parsedYear = parseInt(year);
|
const parsedYear = parseInt(year);
|
||||||
this.mainStore.updateVehicleYear(parsedYear);
|
this.mainStore.updateVehicleYear( parsedYear );
|
||||||
|
|
||||||
this.$router.navigate(
|
this.$router.navigate(
|
||||||
this.navigationScenarios.SELECTED_YEAR,
|
this.navigationScenarios.SELECTED_YEAR,
|
||||||
this.$route
|
this.$route
|
||||||
|
|
|
||||||
18
src/main.js
18
src/main.js
|
|
@ -8,6 +8,10 @@ import baseMixin from "@/mixins/base-mixin.js";
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import Maska from "maska";
|
import Maska from "maska";
|
||||||
|
|
||||||
|
import analyticsMixin from "@/mixins/analytics-mixin.js";
|
||||||
|
import experimentMixin from "@/mixins/experiment-mixin.js";
|
||||||
|
|
||||||
|
// Vue App Setup
|
||||||
const vueApp = createApp(App);
|
const vueApp = createApp(App);
|
||||||
|
|
||||||
// Pinia
|
// Pinia
|
||||||
|
|
@ -16,14 +20,12 @@ vueApp.use(pinia);
|
||||||
pinia.use(piniaPluginPersistedstate);
|
pinia.use(piniaPluginPersistedstate);
|
||||||
useMainStore().populateInitialState();
|
useMainStore().populateInitialState();
|
||||||
|
|
||||||
|
|
||||||
// Additional Vue items to setup
|
// Additional Vue items to setup
|
||||||
vueApp.mixin(baseMixin);
|
|
||||||
vueApp.use(Maska);
|
|
||||||
vueApp.use(router);
|
vueApp.use(router);
|
||||||
|
vueApp.use(Maska);
|
||||||
|
vueApp.mixin(baseMixin);
|
||||||
|
vueApp.mixin(analyticsMixin);
|
||||||
|
vueApp.mixin(experimentMixin);
|
||||||
|
|
||||||
vueApp.mount("#app");
|
vueApp.mount("#app");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
205
src/mixins/analytics-mixin.js
Normal file
205
src/mixins/analytics-mixin.js
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
import {
|
||||||
|
setCookieProperties,
|
||||||
|
getDeviceIdValue,
|
||||||
|
getSessionIdValue,
|
||||||
|
getSessionKeyValue,
|
||||||
|
} from "@/helpers/cookie-helper";
|
||||||
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
|
import { experimentSettings } from "@/constants/experiments";
|
||||||
|
import {
|
||||||
|
analyticsPageEvents,
|
||||||
|
GaCategories,
|
||||||
|
GaActions,
|
||||||
|
GaLabels,
|
||||||
|
GaEvents,
|
||||||
|
ValueToLogTypes,
|
||||||
|
} from "@/constants/analytics";
|
||||||
|
import { cookieNames } from "@/constants/cookie-names";
|
||||||
|
import { useMainStore } from "@/store";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
logPageView(pageEvent) {
|
||||||
|
const currentPageName = getPageNameByQueryString();
|
||||||
|
var payload = {
|
||||||
|
userId: getDeviceIdValue(),
|
||||||
|
sessionKey: getSessionKeyValue(),
|
||||||
|
pageName: currentPageName,
|
||||||
|
sessionId: getSessionIdValue(),
|
||||||
|
action: "",
|
||||||
|
event: pageEvent,
|
||||||
|
shouldUseSessionId: false,
|
||||||
|
experimentsForUser: useMainStore().applicationUser.experiments,
|
||||||
|
};
|
||||||
|
|
||||||
|
useMainStore().logPageView(payload);
|
||||||
|
},
|
||||||
|
|
||||||
|
logCustomEvent(category, action, label, value) {
|
||||||
|
const currentPageName = getPageNameByQueryString();
|
||||||
|
|
||||||
|
var payload = {
|
||||||
|
userId: getDeviceIdValue(),
|
||||||
|
sessionKey: getSessionKeyValue(),
|
||||||
|
pageName: currentPageName,
|
||||||
|
sessionId: getSessionIdValue(),
|
||||||
|
category: category,
|
||||||
|
action: action,
|
||||||
|
label: label,
|
||||||
|
value: value,
|
||||||
|
shouldUseSessionId: false,
|
||||||
|
experimentsForUser: useMainStore().applicationUser.experiments,
|
||||||
|
};
|
||||||
|
|
||||||
|
useMainStore().logCustomEvent(payload);
|
||||||
|
},
|
||||||
|
|
||||||
|
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: `/iss/?${queryStrings.ISS_PAGE}=${currentPageName}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
pushToDataLayerIfDefined(eventToBePushed);
|
||||||
|
|
||||||
|
if (pushToLogApp) {
|
||||||
|
this.logCustomEvent(category, action, labelToLog, undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
pushPageViewToGA() {
|
||||||
|
const currentPageName = getPageNameByQueryString();
|
||||||
|
const pageViewEvent = {
|
||||||
|
event: GaEvents.PAGE_VIEW_EVENT,
|
||||||
|
pagePath: `/iss/?${queryStrings.ISS_PAGE}=${currentPageName}`,
|
||||||
|
pageTitle: currentPageName,
|
||||||
|
};
|
||||||
|
|
||||||
|
pushToDataLayerIfDefined(pageViewEvent);
|
||||||
|
|
||||||
|
this.logPageView(analyticsPageEvents.ENTRY);
|
||||||
|
},
|
||||||
|
|
||||||
|
pushExperimentsToDataLayer() {
|
||||||
|
const experiments = useMainStore().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);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
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() {
|
||||||
|
const sid = getSessionIdValue();
|
||||||
|
const skey = getSessionKeyValue();
|
||||||
|
var payload = {
|
||||||
|
userId: getDeviceIdValue(),
|
||||||
|
sessionId: sid,
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
referrer: document.referrer,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await useMainStore().initializeSession(payload);
|
||||||
|
|
||||||
|
if (response.data) {
|
||||||
|
if (response.data.sessionKey && skey === 0) {
|
||||||
|
setCookieProperties(
|
||||||
|
{ [cookieNames.SESSION_KEY]: response.data.sessionKey },
|
||||||
|
{
|
||||||
|
useDefaultFunnelCookieAttributes: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (response.data.sessionId && sid === "00000000-0000-0000-0000-000000000000") {
|
||||||
|
setCookieProperties(
|
||||||
|
{ [cookieNames.SESSION_ID]: response.data.sessionId },
|
||||||
|
{
|
||||||
|
maxAge: 60 * 30, // 30 minutes
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
noSession() {
|
||||||
|
return (
|
||||||
|
getSessionKeyValue() === 0 ||
|
||||||
|
getSessionIdValue() === "00000000-0000-0000-0000-000000000000"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
analyticsPageEvents() {
|
||||||
|
return analyticsPageEvents;
|
||||||
|
},
|
||||||
|
GaCategories() {
|
||||||
|
return GaCategories;
|
||||||
|
},
|
||||||
|
GaActions() {
|
||||||
|
return GaActions;
|
||||||
|
},
|
||||||
|
GaLabels() {
|
||||||
|
return GaLabels;
|
||||||
|
},
|
||||||
|
ValueToLogTypes() {
|
||||||
|
return ValueToLogTypes;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function pushToDataLayerIfDefined(data) {
|
||||||
|
if (window.dataLayer !== undefined) {
|
||||||
|
window.dataLayer.push(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPageNameByQueryString() {
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
|
||||||
|
if (params.has(queryStrings.ISS_PAGE)) {
|
||||||
|
return params.get(queryStrings.ISS_PAGE);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValueToLog(value, valueToLogType) {
|
||||||
|
if (valueToLogType != null && valueToLogType === ValueToLogTypes.LAST_5) {
|
||||||
|
return value.slice(-5);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
280
src/mixins/analytics-mixin.spec.js
Normal file
280
src/mixins/analytics-mixin.spec.js
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
import analyticsMixin from "@/mixins/analytics-mixin";
|
||||||
|
import { setupMocksForJsFiles, setupCookies } from "@/helpers/unit-test-helper.js";
|
||||||
|
import {
|
||||||
|
analyticsPageEvents,
|
||||||
|
GaCategories,
|
||||||
|
GaActions,
|
||||||
|
GaLabels,
|
||||||
|
GaEvents,
|
||||||
|
ValueToLogTypes,
|
||||||
|
} from "@/constants/analytics";
|
||||||
|
import { useMainStore } from "@/store";
|
||||||
|
|
||||||
|
describe("analyticsMixin.js", () => {
|
||||||
|
test("logPageView: calls dispatch with type and payload", () => {
|
||||||
|
const type = "";
|
||||||
|
const payload = {};
|
||||||
|
|
||||||
|
const mockData = {
|
||||||
|
};
|
||||||
|
const mocks = setupMocksForJsFiles(mockData);
|
||||||
|
|
||||||
|
const testCookieValue = {
|
||||||
|
sid: "10000000-0000-0000-0000-000000000001",
|
||||||
|
};
|
||||||
|
|
||||||
|
setupCookies({ funnelCookieValue: JSON.stringify(testCookieValue) });
|
||||||
|
|
||||||
|
useMainStore().logPageView(payload);
|
||||||
|
|
||||||
|
expect(useMainStore().logPageView).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("logCustomEvent: calls dispatch with type and payload", () => {
|
||||||
|
const mockData = {};
|
||||||
|
const mocks = setupMocksForJsFiles(mockData);
|
||||||
|
|
||||||
|
useMainStore().logCustomEvent("someCat", "someAction", "someLabel", "someVal");
|
||||||
|
|
||||||
|
expect(useMainStore().logCustomEvent).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("pushEventToGA, should call dataLayer push and logCustomEvent too", () => {
|
||||||
|
// Arrange
|
||||||
|
window.dataLayer = [];
|
||||||
|
const mockData = {};
|
||||||
|
const mocks = setupMocksForJsFiles(mockData);
|
||||||
|
var mockDataLayer = [];
|
||||||
|
mockDataLayer.push({
|
||||||
|
event: "event",
|
||||||
|
category: "category",
|
||||||
|
action: "action",
|
||||||
|
label: "label",
|
||||||
|
value: undefined,
|
||||||
|
path: "/iss/?issPage=",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
analyticsMixin.methods.pushEventToGA("category", "action", "label", true);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mockDataLayer).toEqual(expect.arrayContaining(window.dataLayer));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("pushEventToGA, should call dataLayer push and ValueToLogTypes.LAST_5 only logs last 5 of label", () => {
|
||||||
|
// Arrange
|
||||||
|
window.dataLayer = [];
|
||||||
|
var expectedDataLayer = [];
|
||||||
|
expectedDataLayer.push({
|
||||||
|
event: "event",
|
||||||
|
category: "category",
|
||||||
|
action: "action",
|
||||||
|
label: "33333",
|
||||||
|
value: undefined,
|
||||||
|
path: "/iss/?issPage=",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
analyticsMixin.methods.pushEventToGA(
|
||||||
|
"category",
|
||||||
|
"action",
|
||||||
|
"1111122222333333",
|
||||||
|
false,
|
||||||
|
ValueToLogTypes.LAST_5
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(expectedDataLayer).toEqual(expect.arrayContaining(window.dataLayer));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("pushEventToGA, should call dataLayer push and ValueToLogTypes.LAST_5 logs only the last 3 characters for a 3 character string", () => {
|
||||||
|
// Arrange
|
||||||
|
window.dataLayer = [];
|
||||||
|
var expectedDataLayer = [];
|
||||||
|
expectedDataLayer.push({
|
||||||
|
event: "event",
|
||||||
|
category: "category",
|
||||||
|
action: "action",
|
||||||
|
label: "111",
|
||||||
|
value: undefined,
|
||||||
|
path: "/iss/?issPage=",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
analyticsMixin.methods.pushEventToGA(
|
||||||
|
"category",
|
||||||
|
"action",
|
||||||
|
"111",
|
||||||
|
false,
|
||||||
|
ValueToLogTypes.LAST_5
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(expectedDataLayer).toEqual(expect.arrayContaining(window.dataLayer));
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
test("Experiments, should push to dataLayer with default Google Custom Dimension Index", () => {
|
||||||
|
// Arrange
|
||||||
|
window.dataLayer = [];
|
||||||
|
|
||||||
|
const mockExperimentData = [
|
||||||
|
{
|
||||||
|
settings: {},
|
||||||
|
variationName: "test",
|
||||||
|
universeName: "testUniverse",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Mock store
|
||||||
|
jest.mock(
|
||||||
|
"@/store",
|
||||||
|
() => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
{ virtual: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
useMainStore.getters = {
|
||||||
|
applicationUserObj: {
|
||||||
|
experiments: [
|
||||||
|
{
|
||||||
|
settings: {},
|
||||||
|
variationName: "test",
|
||||||
|
universeName: "testUniverse",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
analyticsMixin.methods.pushExperimentsToDataLayer(mockExperimentData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(window.dataLayer).toEqual([
|
||||||
|
{
|
||||||
|
experimentId_99: undefined,
|
||||||
|
variationId_99: undefined,
|
||||||
|
experimentName_99: "testUniverse",
|
||||||
|
variationName_99: "test",
|
||||||
|
customDimension_99: "undefined_undefined_testUniverse_test",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Experiments, should push to dataLayer with custom Google Custom Dimension Index", () => {
|
||||||
|
// Arrange
|
||||||
|
window.dataLayer = [];
|
||||||
|
|
||||||
|
const mockExperimentData = [
|
||||||
|
{
|
||||||
|
settings: { "Google Custom Dimension Index": "5" },
|
||||||
|
variationName: "test",
|
||||||
|
universeName: "testUniverse",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Mock store
|
||||||
|
jest.mock(
|
||||||
|
"@/store",
|
||||||
|
() => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
{ virtual: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
useMainStore.getters = {
|
||||||
|
applicationUserObj: {
|
||||||
|
experiments: [
|
||||||
|
{
|
||||||
|
settings: { "Google Custom Dimension Index": "5" },
|
||||||
|
variationName: "test",
|
||||||
|
universeName: "testUniverse",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
analyticsMixin.methods.pushExperimentsToDataLayer(mockExperimentData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(window.dataLayer).toEqual([
|
||||||
|
{
|
||||||
|
experimentId_5: undefined,
|
||||||
|
variationId_5: undefined,
|
||||||
|
experimentName_5: "testUniverse",
|
||||||
|
variationName_5: "test",
|
||||||
|
customDimension_5: "undefined_undefined_testUniverse_test",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
test("Obj is not null after action prepended", () => {
|
||||||
|
//Arrange
|
||||||
|
const obj = { baseMethodName: "testMethodName", data: "testData" };
|
||||||
|
const method = { name: "testMethodName", data: "testData" };
|
||||||
|
const action = "testAction";
|
||||||
|
|
||||||
|
//Act
|
||||||
|
analyticsMixin.methods.prependActionToMethod(obj, method, action);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(obj != null);
|
||||||
|
});
|
||||||
|
test("Obj method name does not include bound", () => {
|
||||||
|
//Arrange
|
||||||
|
const obj = { baseMethodName: "testMethodName", data: "testData" };
|
||||||
|
const method = { name: "testMethodName", data: "testData" };
|
||||||
|
const action = "testAction";
|
||||||
|
|
||||||
|
//Act
|
||||||
|
analyticsMixin.methods.prependActionToMethod(obj, method, action);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(method.name.startsWith("bound ")).toBe(false);
|
||||||
|
});
|
||||||
|
test("Prepended action does not include bound", () => {
|
||||||
|
//Arrange
|
||||||
|
const obj = { baseMethodName: "testMethodName", data: "testData" };
|
||||||
|
const method = { name: "testMethodName", data: "testData" };
|
||||||
|
const action = "testAction";
|
||||||
|
|
||||||
|
//Act
|
||||||
|
analyticsMixin.methods.prependActionToMethod(obj, method, action);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(action.startsWith("bound ")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("analyticsPageEvents returns constants analyticsPageEvents", () => {
|
||||||
|
//Act
|
||||||
|
const analyticsPE = analyticsMixin.computed.analyticsPageEvents();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(analyticsPE).toEqual(analyticsPageEvents);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("GaActions returns constants GaActions", () => {
|
||||||
|
//Act
|
||||||
|
const gaActions = analyticsMixin.computed.GaActions();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(gaActions).toEqual(GaActions);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("GaCategories returns constants GaCategories", () => {
|
||||||
|
//Act
|
||||||
|
const gaCategories = analyticsMixin.computed.GaCategories();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(gaCategories).toEqual(GaCategories);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("GaLabels returns constants GaLabels", () => {
|
||||||
|
//Act
|
||||||
|
const gaLabels = analyticsMixin.computed.GaLabels();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(gaLabels).toEqual(GaLabels);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { storeActions } from "@/constants/store-actions.js";
|
|
||||||
|
import { mapStores } from "pinia";
|
||||||
|
import { useMainStore } from "@/store";
|
||||||
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
||||||
import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
import { dynamicStrings } from "@/constants/dynamic-strings";
|
import { dynamicStrings } from "@/constants/dynamic-strings";
|
||||||
import { useMainStore } from "@/store";
|
|
||||||
import { mapStores } from "pinia";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -25,11 +25,9 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
// store will be accessible globally as its id + 'Store'
|
// store will be accessible globally as its id + 'Store'
|
||||||
...mapStores(useMainStore),
|
...mapStores(useMainStore),
|
||||||
storeActions() {
|
|
||||||
return storeActions;
|
|
||||||
},
|
|
||||||
navigationScenarios() {
|
navigationScenarios() {
|
||||||
return navigationScenarios;
|
return navigationScenarios;
|
||||||
},
|
},
|
||||||
|
|
@ -46,13 +44,4 @@ export default {
|
||||||
return "widget-name-" + this.cmsWidgetName;
|
return "widget-name-" + this.cmsWidgetName;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function encodeUriData(payload) {
|
|
||||||
if (payload && Object.keys(payload).length > 0) {
|
|
||||||
// Loop through the payload and encode the values
|
|
||||||
Object.keys(payload).forEach((key) => {
|
|
||||||
payload[key] = encodeURIComponent(payload[key]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
17
src/mixins/experiment-mixin.js
Normal file
17
src/mixins/experiment-mixin.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { useMainStore } from "@/store";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
hasSettingEqualTo(settingName, settingValue) {
|
||||||
|
return useMainStore.experimentSettings[settingName] == settingValue;
|
||||||
|
},
|
||||||
|
hasSetting(settingName) {
|
||||||
|
return Object.hasOwn( useMainStore.experimentSettings, settingName);
|
||||||
|
},
|
||||||
|
getSettingValue(settingName) {
|
||||||
|
return this.hasSetting(settingName)
|
||||||
|
? useMainStore.experimentSettings[settingName]
|
||||||
|
: null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -6,13 +6,14 @@ import { useMainStore } from '@/store';
|
||||||
import eventBus from "@/helpers/event-bus/event-bus";
|
import eventBus from "@/helpers/event-bus/event-bus";
|
||||||
import { globalEvents, globalEventTypes } from "@/constants/events";
|
import { globalEvents, globalEventTypes } from "@/constants/events";
|
||||||
|
|
||||||
|
import analyticsMixin from "@/mixins/analytics-mixin";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'root',
|
name: 'root',
|
||||||
async beforeEnter(to, from, next) {
|
async beforeEnter(to, from, next) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
to.query.issPage = !to.query.issPage ? issPageValues.VEHICLE_YEAR : to.query.issPage;
|
to.query.issPage = !to.query.issPage ? issPageValues.VEHICLE_YEAR : to.query.issPage;
|
||||||
|
|
||||||
if (router.hasRoute(to.query.issPage)) {
|
if (router.hasRoute(to.query.issPage)) {
|
||||||
|
|
@ -52,10 +53,23 @@ const router = createRouter({
|
||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.afterEach((to, from) => {
|
||||||
|
|
||||||
|
const store = useMainStore();
|
||||||
|
// Update lastPageVisited in the store
|
||||||
|
store.updateLastPageVisited(to.name);
|
||||||
|
|
||||||
|
// Push page view to GA
|
||||||
|
analyticsMixin.methods.pushPageViewToGA();
|
||||||
|
|
||||||
|
// Push experiments to Data Layer
|
||||||
|
analyticsMixin.methods.pushExperimentsToDataLayer();
|
||||||
|
});
|
||||||
|
|
||||||
// Get route information by page name.
|
// Get route information by page name.
|
||||||
// This will reach out to the Cms and there is a 1:1 relationship between page names and route names.
|
// This will reach out to the Cms and there is a 1:1 relationship between page names and route names.
|
||||||
async function GetRouteInfoFromPageName(pageName) {
|
async function GetRouteInfoFromPageName(pageName) {
|
||||||
|
|
||||||
const response = await useMainStore().getRouteInfo(pageName);
|
const response = await useMainStore().getRouteInfo(pageName);
|
||||||
|
|
||||||
const jsonFromResponse = JSON.parse(response.data.Result);
|
const jsonFromResponse = JSON.parse(response.data.Result);
|
||||||
|
|
@ -69,7 +83,7 @@ async function GetRouteInfoFromPageName(pageName) {
|
||||||
component: lazyLoadComponent(jsonFromResponse[key].LayoutName),
|
component: lazyLoadComponent(jsonFromResponse[key].LayoutName),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return routeData;
|
return routeData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -124,6 +138,7 @@ function navigateToUrl(url, optionalQuery = {}) {
|
||||||
function getNavigationMap (scenario, currentRoute) {
|
function getNavigationMap (scenario, currentRoute) {
|
||||||
const issPageValue = currentRoute.query.issPage;
|
const issPageValue = currentRoute.query.issPage;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const matchedQueryValue = routingTable(useMainStore())
|
const matchedQueryValue = routingTable(useMainStore())
|
||||||
.filter(
|
.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,18 @@ export const issPageValues = {
|
||||||
VEHICLE_YEAR: "vehicle-year",
|
VEHICLE_YEAR: "vehicle-year",
|
||||||
VEHICLE_MODEL: "vehicle-model",
|
VEHICLE_MODEL: "vehicle-model",
|
||||||
VEHICLE_STYLE: "vehicle-style",
|
VEHICLE_STYLE: "vehicle-style",
|
||||||
|
VEHICLE_DAMAGE: "vehicle-damage",
|
||||||
|
ADDRESS_LOOKUP: "address-lookup",
|
||||||
|
VIN_LOOKUP: "vin-lookup",
|
||||||
|
VEHICLE_PARTS: "vehicle-parts",
|
||||||
|
PART_QUESTIONS: "part-questions",
|
||||||
|
MOLDING_QUESTIONS: "molding-questions",
|
||||||
|
CAPABILITY_QUESTIONS: "capability-questions",
|
||||||
|
LICENSE_PLATE_LOOKUP: "license-plate-lookup",
|
||||||
|
REVEAL: "reveal",
|
||||||
|
ESTIMATE: "estimate",
|
||||||
|
ADDRESS_VEHICLES: "address-vehicles",
|
||||||
|
QUOTE: "quote",
|
||||||
|
HERITAGE: "heritage",
|
||||||
};
|
};
|
||||||
|
|
||||||
5
src/router/router-constants/router-params.js
Normal file
5
src/router/router-constants/router-params.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
const routerParams = {
|
||||||
|
DISPLAY_VEHICLE_CHANGE_ALERT: "displayVehicleChangeAlert",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { routerParams };
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { endpoints } from "@/constants/endpoints.js";
|
import { endpoints } from "@/constants/endpoints.js";
|
||||||
|
import { getDateForSavedSessionTimeout } from "@/helpers/session-helper";
|
||||||
import globalMethods from "@/global-methods";
|
import globalMethods from "@/global-methods";
|
||||||
import { applicationConfig } from "@/constants/application-config";
|
import { applicationConfig } from "@/constants/application-config";
|
||||||
|
|
||||||
|
|
@ -34,11 +35,17 @@ const getDefaultState = () => {
|
||||||
accountNumber: 0,
|
accountNumber: 0,
|
||||||
},
|
},
|
||||||
applicationUser: {
|
applicationUser: {
|
||||||
crmCustomerId: null,
|
|
||||||
lastPageVisited: null,
|
lastPageVisited: null,
|
||||||
experiments: [],
|
experiments: [],
|
||||||
triggeredSiteEntry: false,
|
triggeredSiteEntry: false,
|
||||||
eventBus: []
|
eventBus: [],
|
||||||
|
pageData: {},
|
||||||
|
savedSessionTimeout: getDateForSavedSessionTimeout(),
|
||||||
|
saveSessionPromise: null,
|
||||||
|
savedSessionId: null,
|
||||||
|
crmCustomerId: null,
|
||||||
|
lastPageVisited: null,
|
||||||
|
triggeredSiteEntry: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -58,6 +65,70 @@ export const useMainStore = defineStore({
|
||||||
return matchedEvent?.eventValue;
|
return matchedEvent?.eventValue;
|
||||||
},
|
},
|
||||||
eventBus: (state) => state.applicationUser.eventBus,
|
eventBus: (state) => state.applicationUser.eventBus,
|
||||||
|
applicationUserObj: (state) => state.applicationUser,
|
||||||
|
pageData: (state) => (page) => {
|
||||||
|
return state.applicationUser.pageData[page];
|
||||||
|
},
|
||||||
|
experimentOrder: (state) => {
|
||||||
|
return {
|
||||||
|
funnelVehicleYear: state.order.vehicle.year,
|
||||||
|
funnelVehicleMake: state.order.vehicle.make,
|
||||||
|
funnelVehicleModel: state.order.vehicle.model,
|
||||||
|
funnelVehicleStyle: state.order.vehicle.style,
|
||||||
|
funnelIsRepair: state.order.damage.isRepair,
|
||||||
|
funnelNumberOfChips: state.order.damage.numberOfChips,
|
||||||
|
funnelCarId: state.order.vehicle.carId,
|
||||||
|
funnelServiceCity: state.order.serviceLocation.city,
|
||||||
|
funnelServiceState: state.order.serviceLocation.state,
|
||||||
|
funnelServiceZipCode: state.order.serviceLocation.zipCode,
|
||||||
|
funnelParentAccountNumber: state.order.accountNumber,
|
||||||
|
funnelIsCoverageVerified: state.order.payment.insuranceCoverage.isVerified,
|
||||||
|
funnelHasRecalibrationPart: getHasRecalibrationPart(state),
|
||||||
|
funnelSelectedMultiGlass: state.order.damage.glassToReplace?.length > 1,
|
||||||
|
funnelSelectedWindshieldGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||||
|
state.order.damage.glassToReplace,
|
||||||
|
"glassLocation"
|
||||||
|
).includes(damageLocationsSelected.WINDSHIELD),
|
||||||
|
funnelSelectedBackGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||||
|
state.order.damage.glassToReplace,
|
||||||
|
"glassLocation"
|
||||||
|
).includes(damageLocationsSelected.REAR),
|
||||||
|
funnelSelectedDriverSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||||
|
state.order.damage.glassToReplace,
|
||||||
|
"glassLocation"
|
||||||
|
).includes(damageLocationsSelected.DRIVER),
|
||||||
|
funnelSelectedPassengerSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||||
|
state.order.damage.glassToReplace,
|
||||||
|
"glassLocation"
|
||||||
|
).includes(damageLocationsSelected.PASSENGER),
|
||||||
|
|
||||||
|
funnelOrderPartNumbers: [
|
||||||
|
...getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||||
|
state.order.lineItems.glassParts,
|
||||||
|
"partNumber"
|
||||||
|
),
|
||||||
|
...getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||||
|
state.order.lineItems.otherParts,
|
||||||
|
"partNumber"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
funnelOrderPartTypes: [
|
||||||
|
...getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||||
|
state.order.lineItems.glassParts,
|
||||||
|
"recalibrationType"
|
||||||
|
),
|
||||||
|
...getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||||
|
state.order.lineItems.otherParts,
|
||||||
|
"recalibrationType"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
experimentSettings: (state) =>
|
||||||
|
state.applicationUser.experiments
|
||||||
|
.map((x) => x.settings)
|
||||||
|
.reduce((r, c) => Object.assign(r, c), {}) ?? {}
|
||||||
},
|
},
|
||||||
actions:
|
actions:
|
||||||
{
|
{
|
||||||
|
|
@ -77,12 +148,13 @@ export const useMainStore = defineStore({
|
||||||
endpoint: endpoints.GetHomepageInfo.url(applicationConfig.APPLICATION_ABBREVIATION),
|
endpoint: endpoints.GetHomepageInfo.url(applicationConfig.APPLICATION_ABBREVIATION),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getPageData(pageName) {
|
getPageData(pageName) {
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetPageData.method,
|
method: endpoints.GetPageData.method,
|
||||||
endpoint: endpoints.GetPageData.url(applicationConfig.APPLICATION_ABBREVIATION, pageName),
|
endpoint: endpoints.GetPageData.url(applicationConfig.APPLICATION_ABBREVIATION, pageName),
|
||||||
payload: {},
|
payload: {},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Vehicle API Actions
|
// Vehicle API Actions
|
||||||
|
|
@ -118,34 +190,87 @@ export const useMainStore = defineStore({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Vehicle API Actions
|
setVehicle() {
|
||||||
updateVehicleYear(year)
|
return globalMethods
|
||||||
|
.callHttpClient({
|
||||||
|
methods: endpoints.GetVehicle.method,
|
||||||
|
endpoint: `${endpoints.GetVehicle.url}/${this.order.vehicle.year}/${this.order.vehicle.make}/${this.order.vehicle.model}/${this.order.vehicle.style}`,
|
||||||
|
payload: {},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
updateVehicle(response.vehicle);
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateVehicle(vehicle) {
|
||||||
|
this.order.vehicle = { ...this.order.vehicle, ...vehicle };
|
||||||
|
},
|
||||||
|
|
||||||
|
resetVehicleState()
|
||||||
{
|
{
|
||||||
if (this.order.vehicle.year !== year) {
|
this.order.vehicle.year = null;
|
||||||
|
this.order.vehicle.make = null;
|
||||||
|
this.order.vehicle.model = null;
|
||||||
|
this.order.vehicle.style = null;
|
||||||
|
this.order.vehicle.carId = null;
|
||||||
|
this.order.vehicle.category = null;
|
||||||
|
this.order.vehicle.vin = null;
|
||||||
|
this.order.vehicle.imageUrl = null;
|
||||||
|
this.order.vehicle.imageVifNumber = null;
|
||||||
|
this.order.vehicle.imageColor = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateVehicleYear(year) {
|
||||||
|
if(this.order.vehicle.year != year)
|
||||||
|
{
|
||||||
|
this.resetVehicleState();
|
||||||
this.order.vehicle.year = year;
|
this.order.vehicle.year = year;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateVehicleMake(make)
|
updateVehicleMake(make) {
|
||||||
{
|
if(this.order.vehicle.make != make)
|
||||||
if (this.order.vehicle.make !== make) {
|
{
|
||||||
|
const year = this.order.vehicle.year;
|
||||||
|
|
||||||
|
this.resetVehicleState();
|
||||||
|
|
||||||
|
this.order.vehicle.year = year;
|
||||||
this.order.vehicle.make = make;
|
this.order.vehicle.make = make;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateVehicleModel(model)
|
updateVehicleModel(model) {
|
||||||
{
|
if(this.order.vehicle.model != model)
|
||||||
if (this.order.vehicle.model !== model) {
|
{
|
||||||
|
const year = this.order.vehicle.year;
|
||||||
|
const make = this.order.vehicle.make;
|
||||||
|
|
||||||
|
this.resetVehicleState();
|
||||||
|
|
||||||
|
this.order.vehicle.year = year;
|
||||||
|
this.order.vehicle.make = make;
|
||||||
this.order.vehicle.model = model;
|
this.order.vehicle.model = model;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateVehicleStyle(style)
|
updateVehicleStyle(style) {
|
||||||
{
|
if(this.order.vehicle.style != style)
|
||||||
if (this.order.vehicle.style !== style) {
|
{
|
||||||
|
const year = this.order.vehicle.year;
|
||||||
|
const make = this.order.vehicle.make;
|
||||||
|
const model = this.order.vehicle.model;
|
||||||
|
|
||||||
|
this.resetVehicleState();
|
||||||
|
|
||||||
|
this.order.vehicle.year = year;
|
||||||
|
this.order.vehicle.make = make;
|
||||||
|
this.order.vehicle.model = model;
|
||||||
this.order.vehicle.style = style;
|
this.order.vehicle.style = style;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addEventToBus (event) {
|
addEventToBus (event) {
|
||||||
this.applicationUser.eventBus.push(event);
|
this.applicationUser.eventBus.push(event);
|
||||||
},
|
},
|
||||||
|
|
@ -168,7 +293,108 @@ export const useMainStore = defineStore({
|
||||||
if(!localStorage.getItem(storeId)) {
|
if(!localStorage.getItem(storeId)) {
|
||||||
this.$state = state;
|
this.$state = state;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Analytics Actions
|
||||||
|
logExperimentExposure({ userId, sessionKey, pageName, experiment }) {
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
|
method: endpoints.LogExperimentExposureIfAssigned.method,
|
||||||
|
endpoint: endpoints.LogExperimentExposureIfAssigned.url,
|
||||||
|
payload: {
|
||||||
|
experimentForLogging: {
|
||||||
|
userId: userId,
|
||||||
|
experimentUniverseId: experiment.universeId,
|
||||||
|
experimentUniverseName: experiment.universeName,
|
||||||
|
experimentTestId: experiment.testId,
|
||||||
|
experimentTestName: experiment.testName,
|
||||||
|
experimentVariationId: experiment.variationId,
|
||||||
|
experimentVariationName: experiment.variationName,
|
||||||
|
enabled: experiment.isActive,
|
||||||
|
isExposed: experiment.isExposed,
|
||||||
|
userPartitionNumber: experiment.userPartitionNumber,
|
||||||
|
assignmentId: experiment.assignmentId,
|
||||||
|
sessionKey: sessionKey,
|
||||||
|
pageName: pageName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
logPageView({ userId, sessionKey, pageName, sessionId, action, event, shouldUseSessionId, experimentsForUser })
|
||||||
|
{
|
||||||
|
var payload = {
|
||||||
|
userId: userId,
|
||||||
|
sessionKey: sessionKey,
|
||||||
|
sessionId: sessionId,
|
||||||
|
pageName: pageName,
|
||||||
|
applicationName: applicationConfig.APPLICATION_NAME,
|
||||||
|
action: action,
|
||||||
|
event: event,
|
||||||
|
shouldUseSessionId: shouldUseSessionId,
|
||||||
|
experimentsForUser: experimentsForUser,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
|
method: endpoints.LogPageView.method,
|
||||||
|
endpoint: endpoints.LogPageView.url,
|
||||||
|
payload: payload,
|
||||||
|
logApiCall: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
logCustomEvent({ userId, sessionKey, pageName, sessionId, category, action, label, value, shouldUseSessionId, experimentsForUser})
|
||||||
|
{
|
||||||
|
var payload = {
|
||||||
|
userId: userId,
|
||||||
|
sessionKey: sessionKey,
|
||||||
|
sessionId: sessionId,
|
||||||
|
pageName: pageName,
|
||||||
|
applicationName: applicationConfig.APPLICATION_NAME,
|
||||||
|
category: category,
|
||||||
|
action: action,
|
||||||
|
label: label,
|
||||||
|
value: value,
|
||||||
|
shouldUseSessionId: shouldUseSessionId,
|
||||||
|
experimentsForUser: experimentsForUser,
|
||||||
|
};
|
||||||
|
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
|
method: endpoints.LogCustomEvent.method,
|
||||||
|
endpoint: endpoints.LogCustomEvent.url,
|
||||||
|
payload: payload,
|
||||||
|
logApiCall: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initializeSession({ userId, sessionId, userAgent, referrer }) {
|
||||||
|
var payload = {
|
||||||
|
applicationName: applicationConfig.APPLICATION_NAME,
|
||||||
|
userId: userId,
|
||||||
|
deviceId: userId,
|
||||||
|
sessionId: sessionId,
|
||||||
|
userAgent: userAgent,
|
||||||
|
operatorId: "WEB",
|
||||||
|
userName: "SafeliteISS",
|
||||||
|
referrer: referrer,
|
||||||
|
};
|
||||||
|
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
|
method: endpoints.InitializeSession.method,
|
||||||
|
endpoint: endpoints.InitializeSession.url,
|
||||||
|
payload: payload,
|
||||||
|
logApiCall: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateLastPageVisited(lastPageVisited) {
|
||||||
|
this.applicationUser.lastPageVisited = lastPageVisited;
|
||||||
|
},
|
||||||
|
|
||||||
|
GetExperimentsByUser(userId) {
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
|
method: endpoints.GetExperimentsByUser.method,
|
||||||
|
endpoint: `${endpoints.GetExperimentsByUser.url}/${userId}`,
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
persist: true
|
persist: true
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useMainStore } from '@/store';
|
import { useMainStore } from '@/store';
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
|
import globalMethods from "@/global-methods";
|
||||||
import App from '@/App.vue';
|
import App from '@/App.vue';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -82,4 +83,61 @@ describe("Store", () => {
|
||||||
expect(actual).toEqual(event.eventValue);
|
expect(actual).toEqual(event.eventValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("UpdateVehicle should merge vehicle with response object", () => {
|
||||||
|
store.order.vehicle = {
|
||||||
|
year: 2020,
|
||||||
|
make: "Honda",
|
||||||
|
model: "Civic",
|
||||||
|
style: "4 door sedan",
|
||||||
|
carId: null,
|
||||||
|
category: null,
|
||||||
|
vin: null,
|
||||||
|
imageUrl: null,
|
||||||
|
imageVifNumber: null,
|
||||||
|
imageColor: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = {data: {
|
||||||
|
"carId": "CR00069299",
|
||||||
|
"category": "CAR",
|
||||||
|
"year": 2020,
|
||||||
|
"make": "Honda",
|
||||||
|
"model": "Civic",
|
||||||
|
"style": "4 door sedan",
|
||||||
|
"imageUrl": "https://dbhdyzvm8lm25.cloudfront.net/color_0320_032/MY2020/13996/13996_cc0320_032_WX.jpg",
|
||||||
|
"imageVifNumber": "13996",
|
||||||
|
"imageVifColor": "white"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
store.updateVehicle(response.data);
|
||||||
|
|
||||||
|
expect(store.order.vehicle.imageUrl).toEqual(response.data.imageUrl);
|
||||||
|
expect(store.order.vehicle.imageVifNumber).toEqual(response.data.imageVifNumber);
|
||||||
|
expect(store.order.vehicle.imageVifColor).toEqual(response.data.imageVifColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("setVehicle should call globalMethods.callHttpClient", () => {
|
||||||
|
store.order.vehicle = jest.fn();
|
||||||
|
|
||||||
|
const response = {data: {
|
||||||
|
"carId": "CR00069299",
|
||||||
|
"category": "CAR",
|
||||||
|
"year": 2020,
|
||||||
|
"make": "Honda",
|
||||||
|
"model": "Civic",
|
||||||
|
"style": "4 door sedan",
|
||||||
|
"imageUrl": "https://dbhdyzvm8lm25.cloudfront.net/color_0320_032/MY2020/13996/13996_cc0320_032_WX.jpg",
|
||||||
|
"imageVifNumber": "13996",
|
||||||
|
"imageVifColor": "white"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(response));
|
||||||
|
|
||||||
|
store.setVehicle();
|
||||||
|
|
||||||
|
expect(globalMethods.callHttpClient).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
Loading…
Reference in a new issue