Handle other edge cases

This commit is contained in:
FrankRua 2022-03-24 14:36:43 -04:00
parent 8e21ccec6c
commit 0e6932d266
10 changed files with 181 additions and 86 deletions

View file

@ -1,6 +1,7 @@
const applicationConfig = {
CONSUMER_APIGATEWAY_URL: process.env.VUE_APP_CONSUMER_API_GATEWAY,
SESSION_TIMEOUT_CONFIG: 30
SESSION_TIMEOUT_CONFIG: 30,
SAVED_SESSION_TIMEOUT: 45
};
export { applicationConfig };

View file

@ -0,0 +1,7 @@
const queryStrings = {
FMG_PAGE: 'fmgPage',
START_TYPE: 'start_type'
};
export { queryStrings };

View file

@ -1,8 +0,0 @@
const widgetNames = {
FUNNEL_SUB_HEADER_WIDGET: "FunnelSubHeaderWidget",
RADIO_QUESTION_WIDGET: "RadioQuestionWidget",
VEHICLE_BANNER_WIDGET: "VehicleBannerWidget",
FUNNEL_HEADER_WIDGET: "FunnelHeaderWidget",
};
export { widgetNames };

View file

@ -1,5 +1,4 @@
import { storeActions } from "@/constants/store-actions.js";
import { widgetNames } from "@/constants/widget-names.js";
import store from "@/store";
export function fetchCmsContentForPage(fmgPage) {

View file

@ -1,13 +1,18 @@
import { storeActions } from "@/constants/store-actions.js";
import { cookieNames } from "@/constants/cookie-names";
import { queryStrings } from "@/constants/query-strings";
import { externalUrls } from "@/router/router-constants/externalUrl-values";
import { applicationConfig } from "@/constants/application-config";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import store from "@/store";
import router from "@/router";
import baseMixin from "../mixins/base-mixin";
/*
Will call API to save existing order, or create new one depending where it's called from.
This will also set Referral information in the store after saving, and then
update the cookie.
*/
export async function saveOrder() {
console.log("saving order...");
const savedOrderInfo = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.SAVE_ORDER);
@ -23,6 +28,12 @@ export async function saveOrder() {
updateOrCreateConceptCookie();
}
/*
Will call API and hydrate state with data from API if present. If there is no order present
method will return null. If the cookie dictates the state should be reset
it will reset the state and go back to the start of the funnel.
*/
export async function loadOrderIfPresent() {
console.log("attempting to load referral....");
const conceptCookie = getConceptCookie();
@ -47,7 +58,27 @@ export async function loadOrderIfPresent() {
return (await loadOrder(conceptCookie.ReferralNumber, conceptCookie.ReferralDate, conceptCookie.ReferralCorrelationId)).data;
}
export async function getPageToRouteExistingOrderTo() {
/*
If the user has visited the concept funnel before this method will determine the bets place to
drop them so they don't start at the beginning again. This method will return 'heritage' if
the user has an existing order and they come back in from the Safelite.com CTA.
*/
export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHeritageOrder = false) {
// If the user is coming in via the Safelite.Com CTA
if (toRoute.query[queryStrings.START_TYPE] === 'fmg') {
console.log("Start type is FMG... trying to figure out where to send them...");
// If they have an existing order, return 'heritage' for the page name.
if (existingHeritageOrder) {
console.log("Existing heritage order found, returning 'heritage' for page redirect...");
return 'heritage';
}
}
// If this is a non-CTA navigation, determine where to send the user based on page prerequisites.
// This also works if a user has a 'fmg' start_type query string but no current order.
// That shouldn't happen, but it's possible.
const vehicleMakeComponent = (await lazyLoadComponent('vehicle-make')()).default;
const vehicleModelComponent = (await lazyLoadComponent('vehicle-model')()).default;
const vehicleStyleComponent = (await lazyLoadComponent('vehicle-style')()).default;
@ -61,19 +92,30 @@ export async function getPageToRouteExistingOrderTo() {
return "vehicle-model";
} else if (!vehicleDamageComponent.methods.arePagePrerequisitesValid()) {
return "vehicle-style";
}else {
return "vehicle-damage";
} else if (!store.getters.damage.isRepair || !store.getters.vehicle.carId) {
return 'vehicle-damage'
} else {
if (store.getters.vehicle.vin) {
return "vin-lookup";
} else {
return "estimate"
}
}
}
/*
Will update the cookie if present, or create a new one if not.
*/
export function updateOrCreateConceptCookie() {
console.log("Updating cookie...", {
LastTouched: new Date().toUTCString(),
SavedQuoteTimeoutDate: store.getters.applicationUser.savedSessionTimeout,
DidHeritageFunnelUpdateLast: false,
ShouldResetState: false,
ReferralNumber: store.state.order.referralNumber,
ReferralDate: store.state.order.referralDate,
ReferralCorrelationId: store.state.order.referralCorrelationId,
ReferralNumber: store.getters.order.referralNumber,
ReferralDate: store.getters.order.referralDate,
ReferralCorrelationId: store.getters.order.referralCorrelationId,
});
// Create the cookie
@ -82,21 +124,25 @@ export function updateOrCreateConceptCookie() {
// Set up cookie with all the props.
setConceptCookieProperties({
LastTouched: new Date().toUTCString(),
SavedQuoteTimeoutDate: store.getters.applicationUser.savedSessionTimeout,
DidHeritageFunnelUpdateLast: false,
ShouldResetState: false,
ReferralNumber: store.state.order.referralNumber,
ReferralDate: store.state.order.referralDate,
ReferralCorrelationId: store.state.order.referralCorrelationId,
ReferralNumber: store.getters.order.referralNumber,
ReferralDate: store.getters.order.referralDate,
ReferralCorrelationId: store.getters.order.referralCorrelationId,
});
}
export function isConceptSessionStillActive() {
/*
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 (getConceptCookie() !== null) {
const lastTouchedValue = getConceptCookie().LastTouched;
const timeoutAmount = applicationConfig.SESSION_TIMEOUT_CONFIG;
const isMoreThanHalfHourAgo = ((new Date() - new Date(lastTouchedValue)) / 60000) > timeoutAmount;
console.log("has session expired -->", isMoreThanHalfHourAgo);
if (isMoreThanHalfHourAgo) {
return false;
@ -106,6 +152,31 @@ export function isConceptSessionStillActive() {
}
}
/*
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 (getConceptCookie() !== null) {
const savedSessionTimeStamp = new Date(getConceptCookie().SavedQuoteTimeoutDate);
const isSavedSessionTimedOut = (new Date(new Date().toUTCString()) > savedSessionTimeStamp);
console.log("Saved Session Timed Out? --->", isSavedSessionTimedOut);
if (isSavedSessionTimedOut) {
return false;
}
return true;
}
}
/*
Used to navigate to the heritage funnel with the correct query string and url.
*/
export async function navigateToHeritageFunnel() {
// Create the order (or save existing order) when navigating to Heritage Funnel.
@ -114,7 +185,7 @@ export async function navigateToHeritageFunnel() {
router.navigateToExternalUrl(
externalUrls.HERITAGE_FUNNEL,
{
corid: store.state.order.referralCorrelationId,
corid: store.getters.order.referralCorrelationId,
src: "concept-funnel",
cns: "all",
experiments: "RemoveServiceAreaPage=ServAreaRemoval_V7=ServAreaNoRemove_V7_TEST=true,ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_TEST=true"
@ -122,6 +193,10 @@ export async function navigateToHeritageFunnel() {
);
}
/*
Gets the current instance of the concept funnel cookie.
Returns null if cookie isn't valid JSON.
*/
export function getConceptCookie() {
const cookieJson = document.cookie
?.split("; ")
@ -135,28 +210,40 @@ export function getConceptCookie() {
}
}
/*
Function to get the date for the saved session timeout.
*/
// --------- PRIVATE FUNCTIONS ---------
async function loadOrder(referralNumber, referralDate, correlationId) {
console.log("loading order...", referralNumber, referralDate, correlationId);
try {
const response = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOAD_ORDER,
{
referralNumber: referralNumber.toString(),
referralDate: referralDate,
correlationId: correlationId
}, false);
return response;
} catch (e) {
console.log("error loading order:", e);
}
export function getDateForSavedSessionTimeout() {
const currentDate = new Date(new Date().toUTCString())
currentDate.setDate(currentDate.getDate() + applicationConfig.SAVED_SESSION_TIMEOUT)
return currentDate.toUTCString();
}
//-------------------------------------\\\
// --------- PRIVATE FUNCTIONS --------- \\\
//----------------------------------------\\\
/*
Calls API to load order given the referral number, referralDate, and referralCorrelationId
and returns the response.
*/
async function loadOrder(referralNumber, referralDate, correlationId) {
console.log("loading order...", referralNumber, referralDate, correlationId);
const response = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOAD_ORDER,
{
referralNumber: referralNumber.toString(),
referralDate: referralDate,
correlationId: correlationId
}, false);
return response;
}
/*
Used to set properties on the concept funnel cookie.
Takes an object with properties to set. Will overwrite existing properties.
*/
function setConceptCookieProperties(properties) {
if (typeof properties == "object") {
let cookie = getConceptCookie();
@ -173,6 +260,9 @@ function setConceptCookieProperties(properties) {
}
}
/*
Removes concept cookie from browser.
*/
function deleteConceptCookie() {
// If this cookie is ever created from the concept funnel, will need to add another
// line with the path=/fmg/

View file

@ -1,9 +1,7 @@
import store from "@/store";
import router from "@/router";
import { storeActions } from "@/constants/store-actions.js";
import { storeMutations } from "@/constants/store-mutations.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { widgetNames } from "@/constants/widget-names.js";
import { vehicleCategories } from "@/constants/vehicle-categories.js";
export default {
@ -33,9 +31,6 @@ export default {
navigationScenarios() {
return navigationScenarios;
},
widgetNames() {
return widgetNames;
},
vehicleCategories() {
return vehicleCategories;
},

View file

@ -1,6 +1,5 @@
import baseMixin from "@/mixins/base-mixin";
import { storeActions } from "@/constants/store-actions.js";
import { widgetNames } from "@/constants/widget-names.js";
import { storeMutations } from "@/constants/store-mutations.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import store from "@/store";
@ -66,17 +65,6 @@ describe("baseMixin.js", () => {
// Assert
expect(navigationScenariosForTest).toEqual(navigationScenarios);
});
test("computed: widgetNames should be equal to import object", () => {
// Arrange
const mixIn = getMixInInstance({});
// Act
let widgetNamesForTest = mixIn.computed.widgetNames();
// Assert
expect(widgetNamesForTest).toEqual(widgetNames);
});
});
function getMixInInstance({ isDispatchSuccess = true }) {
@ -100,7 +88,6 @@ function getMixInInstance({ isDispatchSuccess = true }) {
const baseMixIn = baseMixin;
baseMixIn.methods.$route = route;
baseMixIn.methods.storeActions = storeActions;
baseMixIn.methods.widgetNames = widgetNames;
store.dispatch = storeDispatch;
store.commit = jest.fn();

View file

@ -4,7 +4,7 @@ import { storeActions } from "@/constants/store-actions";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import { routingTable } from "@/router/router-constants/routing-table.js";
import { globalEvents, globalEventTypes } from "@/constants/events";
import * as integrationHelper from "@/helpers/heritage-integration-helper";
import * as heritageIntegrationHelper from "@/helpers/heritage-integration-helper";
import baseMixin from "@/mixins/base-mixin";
import eventBus from "@/helpers/event-bus/event-bus";
@ -35,20 +35,25 @@ const routes = [
} else {
try {
// Check our 'Session' is still good. If not, reset state and go back to the start.
if (!integrationHelper.isConceptSessionStillActive()) {
baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.RESET_STATE);
// If the saved session has timed out, clear the session, execute 404 logic.
if (!heritageIntegrationHelper.isSavedSessionStillActive()) {
await GoToFunnelStartOn404(next);
}
// Create funnel cookie, or update it if it already exists.
integrationHelper.updateOrCreateConceptCookie();
// Process concept funnel cookie.
heritageIntegrationHelper.updateOrCreateConceptCookie();
// On entering the concept funnel "fresh", read cookie information, decide what to do next.
if (from.redirectedFrom === undefined) {
await integrationHelper.loadOrderIfPresent();
const loadOrderResponse = await heritageIntegrationHelper.loadOrderIfPresent();
const pageToRedirectTo = await heritageIntegrationHelper.getPageToRouteExistingOrderTo(to, loadOrderResponse);
const pageToRedirectTo = await integrationHelper.getPageToRouteExistingOrderTo();
// If getPageToRouteExistingOrderTo determines that the return user needs to
// go back to heritage funnel, send them there and stop our current navigation.
if (pageToRedirectTo === 'heritage') {
await heritageIntegrationHelper.navigateToHeritageFunnel();
return next(false);
}
// Assign our fmgPage so it will load normally like the other pages.
to.query.fmgPage = pageToRedirectTo;
@ -154,8 +159,8 @@ async function navigate(scenario, currentRoute, invalidateOnSave, optionalQuery
baseMixin.methods.savePageDataToStore(destinationFmgPageValue, optionalPageData);
// if cookie and referralNumber/Date exists
if (integrationHelper.getConceptCookie()?.ReferralNumber && integrationHelper.getConceptCookie()?.ReferralDate) {
await integrationHelper.saveOrder();
if (heritageIntegrationHelper.getConceptCookie()?.ReferralNumber && heritageIntegrationHelper.getConceptCookie()?.ReferralDate) {
await heritageIntegrationHelper.saveOrder();
}
router.push({
@ -187,7 +192,7 @@ function getNavigationMap(scenario, currentRoute) {
//---------------------------------------------------------- Private Functions ----------------------------------------------------------
// Navigate to an external url.
function navigateToUrl(url, optionalQuery) {
function navigateToUrl(url, optionalQuery = {}) {
// possibly show some loading screen in the future here.
var externalUrl = new URL(url);

View file

@ -1,3 +0,0 @@
const routerParameterKeys = { };
export { routerParameterKeys };

View file

@ -1,6 +1,7 @@
import { createStore } from "vuex";
import { endpoints } from "@/constants/endpoints.js";
import { storeMutations } from "@/constants/store-mutations";
import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration-helper";
import createPersistedState from "vuex-persistedstate";
import globalMethods from "@/global-methods";
@ -17,16 +18,32 @@ const getDefaultState = () => {
style: null,
carId: null,
category: null,
vin: null,
imageUrl: null,
imageVifNumber: null,
imageColor: null,
registration: {
licensePlate: null,
address: null,
city: null,
state: null,
zipCode: null,
firstName: null,
lastName: null,
},
},
serviceLocation: {
zip: null,
},
customer: {
emailAddress: null,
},
damage: {
isRepair: null,
numberOfChips: null,
glassToReplace: null,
},
lineItems:{
lineItems: {
glassParts: null,
otherParts: null
},
@ -37,7 +54,8 @@ const getDefaultState = () => {
},
applicationUser: {
eventBus: [],
pageData: {}
pageData: {},
savedSessionTimeout: getDateForSavedSessionTimeout()
},
}
};
@ -74,19 +92,19 @@ export const mutations = {
updateVehicleImageColor(state, imageColor) {
state.order.vehicle.imageColor = imageColor;
},
updateIsRepair(state, isRepair){
updateIsRepair(state, isRepair) {
state.order.damage.isRepair = isRepair;
},
updateNumberOfChips(state, numberOfChips){
updateNumberOfChips(state, numberOfChips) {
state.order.damage.numberOfChips = numberOfChips;
},
updateGlassToReplace(state, glassToReplace){
updateGlassToReplace(state, glassToReplace) {
state.order.damage.glassToReplace = glassToReplace;
},
updateParts(state, partsData){
updateParts(state, partsData) {
state.order.lineItems.glassParts = partsData;
},
updatePageData(state, pageData){
updatePageData(state, pageData) {
state.applicationUser.pageData[pageData.page] = pageData.data;
},
updateReferralCorrelationId(state, referralCorrelationId) {
@ -149,7 +167,7 @@ export const mutations = {
// Misc Mutations
setLoadOrderInformation(state, orderInformation) {
state.order.referralNumber = orderInformation.referralNumber;
state.order.referralDate = orderInformation.referralDate;
state.order.referralDate = orderInformation.referralDate;
state.order.referralCorrelationId = orderInformation.correlationId;
state.order.vehicle = {
year: orderInformation.vehicle?.year,
@ -186,7 +204,9 @@ export const getters = {
lineItems: (state) => state.order.lineItems,
pageData: (state) => (page) => {
return state.applicationUser.pageData[page];
}
},
applicationUser: (state) => state.applicationUser,
order: (state) => state.order,
}
// Export Actions
@ -357,7 +377,7 @@ export const actions = {
});
},
loadOrder(context, {referralNumber, referralDate, correlationId}) {
loadOrder(context, { referralNumber, referralDate, correlationId }) {
return globalMethods.callHttpClient({
method: endpoints.LoadOrder.method,
endpoint: endpoints.LoadOrder.url,
@ -366,7 +386,7 @@ export const actions = {
referralDate: referralDate,
correlationId: correlationId
},
}).then( (response) => {
}).then((response) => {
context.commit(storeMutations.SET_LOAD_CONCEPT_SESSION_INFO, response.data);
return response;
});
@ -384,4 +404,6 @@ export default createStore({
mutations,
getters,
actions,
});
});
// Private Functions