Merge branch 'develop' into feature/SSR-129

This commit is contained in:
Kulbhushan Kaushik 2022-11-30 13:13:35 -05:00
commit 5d56f6ed7a
5 changed files with 307 additions and 69 deletions

View file

@ -112,10 +112,12 @@ describe("analyticsMixin.js", () => {
// Assert
expect(expectedDataLayer).toEqual(expect.arrayContaining(window.dataLayer));
});
/*
test("Experiments, should push to dataLayer with default Google Custom Dimension Index", () => {
// Arrange
window.dataLayer = [];
const store = useMainStore();
window.dataLayer = [];
const mockExperimentData = [
{
@ -125,26 +127,7 @@ describe("analyticsMixin.js", () => {
},
];
// Mock store
jest.mock(
"@/store",
() => {
return {};
},
{ virtual: true }
);
useMainStore.getters = {
applicationUserObj: {
experiments: [
{
settings: {},
variationName: "test",
universeName: "testUniverse",
},
],
},
};
store.applicationUserObj.experiments = mockExperimentData;
// Act
analyticsMixin.methods.pushExperimentsToDataLayer(mockExperimentData);
@ -163,7 +146,9 @@ describe("analyticsMixin.js", () => {
test("Experiments, should push to dataLayer with custom Google Custom Dimension Index", () => {
// Arrange
window.dataLayer = [];
const store = useMainStore();
window.dataLayer = [];
const mockExperimentData = [
{
@ -173,26 +158,7 @@ describe("analyticsMixin.js", () => {
},
];
// Mock store
jest.mock(
"@/store",
() => {
return {};
},
{ virtual: true }
);
useMainStore.getters = {
applicationUserObj: {
experiments: [
{
settings: { "Google Custom Dimension Index": "5" },
variationName: "test",
universeName: "testUniverse",
},
],
},
};
store.applicationUserObj.experiments = mockExperimentData;
// Act
analyticsMixin.methods.pushExperimentsToDataLayer(mockExperimentData);
@ -208,7 +174,7 @@ describe("analyticsMixin.js", () => {
},
]);
});
*/
test("Obj is not null after action prepended", () => {
//Arrange
const obj = { baseMethodName: "testMethodName", data: "testData" };

View file

@ -3,14 +3,14 @@ import { useMainStore } from "@/store";
export default {
methods: {
hasSettingEqualTo(settingName, settingValue) {
return useMainStore.experimentSettings[settingName] == settingValue;
return useMainStore().experimentSettings[settingName] == settingValue;
},
hasSetting(settingName) {
return Object.hasOwn( useMainStore.experimentSettings, settingName);
return Object.hasOwn( useMainStore().experimentSettings, settingName);
},
getSettingValue(settingName) {
return this.hasSetting(settingName)
? useMainStore.experimentSettings[settingName]
? useMainStore().experimentSettings[settingName]
: null;
},
},

View file

@ -0,0 +1,155 @@
import experimentMixin from "@/mixins/experiment-mixin";
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { useMainStore } from "@/store";
describe("experiment-mixin", () => {
describe("hasSettingEqualTo", () => {
test("setting exists and value matches => return true", () => {
// Arrange
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.hasSettingEqualTo("Setting2", "Value2");
// Assert
expect(result).toEqual(true);
});
test("setting exists and value does not match => return false", () => {
// Arrange
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.hasSettingEqualTo("Setting2", "Value3");
// Assert
expect(result).toEqual(false);
});
test("setting does not exist => return false", () => {
// Arrange
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.hasSettingEqualTo("SettingBoogly", "Woogly");
// Assert
expect(result).toEqual(false);
});
test("there are no settings => return false", () => {
// Arrange
const { wrapper } = setupMocks({ experimentSettings: {} });
// Act
const result = wrapper.vm.hasSettingEqualTo("Setting2", "Value3");
// Assert
expect(result).toEqual(false);
});
});
describe("hasSetting", () => {
test("has setting => return true", () => {
// Arrange
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.hasSetting("Setting4");
// Assert
expect(result).toEqual(true);
});
test("does not have setting => return false", () => {
// Arrange
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.hasSetting("BooglyWoogly");
// Assert
expect(result).toEqual(false);
});
test("experimentSettings is empty => return false", () => {
// Arrange
const { wrapper } = setupMocks({ experimentSettings: {} });
// Act
const result = wrapper.vm.hasSetting("Setting4");
// Assert
expect(result).toEqual(false);
});
});
describe("getSettingValue", () => {
test("has setting => return correct value", () => {
// Arrange
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.getSettingValue("Setting3");
// Assert
expect(result).toEqual("Value3");
});
test("does not have setting => return null", () => {
// Arrange
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.getSettingValue("Hello");
// Assert
expect(result).toBeNull();
});
test("experimentSettings is empty => return null", () => {
// Arrange
const { wrapper } = setupMocks({ experimentSettings: {} });
// Act
const result = wrapper.vm.getSettingValue("Hello");
// Assert
expect(result).toBeNull();
});
});
});
function setupMocks({ experimentSettings }) {
const store = useMainStore();
const mocks = getMountOptions({});
const testExperimentSettings = {
Setting1: "Value1",
Setting2: "Value2",
Setting3: "Value3",
Setting4: "Value1",
Setting5: "Value2",
Setting6: "Value3",
};
const mockExperimentData = [
{
settings: experimentSettings ?? testExperimentSettings,
variationName: "test",
universeName: "testUniverse",
},
];
store.applicationUser.experiments = mockExperimentData;
const mockComponent = {
template: "<div></div>",
mixins: [experimentMixin],
};
const wrapper = shallowMount(mockComponent, mocks);
return { wrapper };
}

View file

@ -6,6 +6,13 @@ import { useMainStore } from '@/store';
import eventBus from "@/helpers/event-bus/event-bus";
import { globalEvents, globalEventTypes } from "@/constants/events";
import baseMixin from "@/mixins/base-mixin";
import {
getDeviceIdValue,
updateOrCreateISSCookie,
updateSessionIdCookie,
} from "@/helpers/cookie-helper";
import { experimentTriggers } from "@/constants/experiments";
import { applicationConfig } from "@/constants/application-config";
import analyticsMixin from "@/mixins/analytics-mixin";
@ -14,8 +21,19 @@ const routes = [
path: '/',
name: 'root',
async beforeEnter(to, from, next) {
try {
try {
to.query.issPage = !to.query.issPage ? issPageValues.VEHICLE_YEAR : to.query.issPage;
if (analyticsMixin.methods.noSession()) {
await analyticsMixin.methods.initSession();
} else {
updateSessionIdCookie();
}
await runExperiments(to.query.issPage);
// Process ISS cookie.
updateOrCreateISSCookie();
if (router.hasRoute(to.query.issPage)) {
return next({ name: to.query.issPage, query: to.query, params: to.params });
@ -195,4 +213,23 @@ function GoToStartOn404(next) {
};
// Run SiteEntry and PageEntry triggers for experiments
async function runExperiments(nextPage) {
const store = useMainStore();
if (!store.applicationUser.triggeredSiteEntry) {
await store.runExperimentsForTrigger({
userId: getDeviceIdValue(),
triggerEvent: experimentTriggers.SITE_ENTRY,
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE,
});
}
await store.runExperimentsForTrigger({
userId: getDeviceIdValue(),
triggerEvent: experimentTriggers.PAGE_ENTRY,
triggerValue: nextPage,
});
}
export default router;

View file

@ -2,8 +2,10 @@ import { defineStore } from "pinia";
import { endpoints } from "@/constants/endpoints.js";
import { getDateForSavedSessionTimeout } from "@/helpers/session-helper";
import globalMethods from "@/global-methods";
import { experimentTriggers } from "@/constants/experiments";
import { applicationConfig } from "@/constants/application-config";
import { issPageValues } from "@/router/router-constants/issPage-values";
import { damageLocationsSelected } from "@/constants/damage-locations-selected";
const storeId = 'main';
@ -49,6 +51,18 @@ const getDefaultState = () => {
glassParts: null,
otherParts: null
},
payment: {
isInsurance: true,
insuranceCoverage: {
isVerified: false,
},
},
serviceLocation: {
address: null,
city: null,
state: null,
zipCode: null,
},
referralNumber: null,
referralDate: null,
accountNumber: 0,
@ -92,38 +106,37 @@ export const useMainStore = defineStore({
},
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(
issVehicleYear: state.order.vehicle.year,
issVehicleMake: state.order.vehicle.make,
issVehicleModel: state.order.vehicle.model,
issVehicleStyle: state.order.vehicle.style,
issIsRepair: state.order.damage.isRepair,
issNumberOfChips: state.order.damage.numberOfChips,
issCarId: state.order.vehicle.carId,
issServiceCity: state.order.serviceLocation.city,
issServiceState: state.order.serviceLocation.state,
issServiceZipCode: state.order.serviceLocation.zipCode,
issParentAccountNumber: state.order.accountNumber,
issIsCoverageVerified: state.order.payment.insuranceCoverage.isVerified,
issHasRecalibrationPart: getHasRecalibrationPart(state),
issSelectedMultiGlass: state.order.damage.glassToReplace?.length > 1,
issSelectedWindshieldGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.damage.glassToReplace,
"glassLocation"
).includes(damageLocationsSelected.WINDSHIELD),
funnelSelectedBackGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
issSelectedBackGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.damage.glassToReplace,
"glassLocation"
).includes(damageLocationsSelected.REAR),
funnelSelectedDriverSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
issSelectedDriverSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.damage.glassToReplace,
"glassLocation"
).includes(damageLocationsSelected.DRIVER),
funnelSelectedPassengerSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
issSelectedPassengerSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.damage.glassToReplace,
"glassLocation"
).includes(damageLocationsSelected.PASSENGER),
funnelOrderPartNumbers: [
issOrderPartNumbers: [
...getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.glassParts,
"partNumber"
@ -134,7 +147,7 @@ export const useMainStore = defineStore({
),
],
funnelOrderPartTypes: [
issOrderPartTypes: [
...getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.glassParts,
"recalibrationType"
@ -582,6 +595,13 @@ export const useMainStore = defineStore({
this.applicationUser.lastPageVisited = lastPageVisited;
},
updateExperiments(experiments) {
this.applicationUser.experiments = experiments;
},
updateTriggeredSiteEntry(wasSiteEntryTriggered) {
this.applicationUser.triggeredSiteEntry = wasSiteEntryTriggered;
},
GetExperimentsByUser(userId) {
return globalMethods.callHttpClient({
method: endpoints.GetExperimentsByUser.method,
@ -589,12 +609,72 @@ export const useMainStore = defineStore({
payload: {},
});
},
async runExperimentsForTrigger({ userId, triggerEvent, triggerValue }) {
if (triggerEvent == experimentTriggers.SITE_ENTRY) {
this.updateTriggeredSiteEntry(true);
}
var payload = {
applicationName: applicationConfig.APPLICATION_NAME,
userId: userId,
triggerEvent: triggerEvent,
triggerValue: triggerValue,
experimentOrder: this.experimentOrder,
};
const response = await globalMethods.callHttpClient({
method: endpoints.RunExperimentsForTrigger.method,
endpoint: endpoints.RunExperimentsForTrigger.url,
payload: payload,
});
this.updateExperiments(response.data.experiments);
},
},
persist: true
});
// Private Functions
function getHasRecalibrationPart(state) {
var hasRequiresRecalibration =
getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.glassParts,
"requiresRecalibration"
)?.length > 0;
var hasRecalibrationType =
getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.glassParts,
"recalibrationType"
)?.length > 0;
if (hasRequiresRecalibration) {
if (hasRecalibrationType) {
// Has both 'requiresRecalibration' and 'recalibrationType' and 'recalibrationType'
return (
getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.glassParts,
"recalibrationType"
)[0].toLowerCase() != "unknown"
);
} else {
// Has 'requiresRecalibration' but no 'recalibrationType' at all
return true;
}
} else {
// Does not have 'requiresRecalibration'
return false;
}
}
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
return (array ?? []).map((x) => x[propertyName]).filter((x) => x);
}
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
if (!arrayOfObjects) return null;