Merge branch 'develop' into feature/CSR-1112

This commit is contained in:
Leah Schumann 2023-04-26 13:10:42 -04:00
commit 1bc9050113
16 changed files with 145 additions and 46 deletions

View file

@ -19,6 +19,7 @@ const GaActions = {
CLICKED: "Clicked", CLICKED: "Clicked",
VIF: "vif", VIF: "vif",
SUBMITTED: "Submitted", SUBMITTED: "Submitted",
DISPLAYED: "Displayed",
}; };
const GaLabels = { const GaLabels = {

View file

@ -87,6 +87,7 @@ import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-bu
import listCard from "@/ux-components/list-card/list-card"; import listCard from "@/ux-components/list-card/list-card";
import radio from "@/ux-components/radio/radio"; import radio from "@/ux-components/radio/radio";
import { useField, ErrorMessage } from "vee-validate"; import { useField, ErrorMessage } from "vee-validate";
import { queryStrings } from "@/constants/query-strings";
export default { export default {
name: "buttonQuestion", name: "buttonQuestion",
@ -130,6 +131,10 @@ export default {
isSmallQuestionText: Boolean, isSmallQuestionText: Boolean,
availability: String, availability: String,
customButtonQuestionId: String, customButtonQuestionId: String,
logDisplayedValuesEvent: {
type: Boolean,
default: false,
},
}, },
setup(props) { setup(props) {
const propsClone = Object.assign({}, props); const propsClone = Object.assign({}, props);
@ -254,6 +259,29 @@ export default {
modelValue() { modelValue() {
this.resetField(); this.resetField();
}, },
answers() {
//once we get the answers to display from parent, see if we need a GA event to log what we showed
if (this.logDisplayedValuesEvent && this.answers.length > 0) {
var eventLabel = "";
//build comma separated list of all items in button list that we are going to display on page
this.answers.forEach((item) => {
if (item.Name) {
eventLabel += item.Name + ",";
}
if (item.buttonLabel) {
eventLabel += item.buttonLabel + ",";
}
});
eventLabel = eventLabel.slice(0, -1); //remove the last comma
this.pushEventToGA(
this.$route.query[queryStrings.FMG_PAGE],
this.GaActions.DISPLAYED,
eventLabel,
true
);
}
},
}, },
components: { components: {
listButton, listButton,

View file

@ -1,6 +1,7 @@
import store from "@/store"; import store from "@/store";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { damageLocationsSelected as glassLocations } from "@/constants/damage-locations-selected";
export function getDamageString() { export function getDamageString() {
// If it's a repair it's always a windshield. // If it's a repair it's always a windshield.
@ -45,6 +46,14 @@ export function getIsWindshieldOnly() {
return returnString; return returnString;
} }
export function includesWindshieldReplacement() {
const windshieldMatches =
store.getters.order.damage.glassToReplace?.filter(
(glassToReplace) => glassToReplace.glassLocation === glassLocations.WINDSHIELD
) ?? [];
return windshieldMatches.length > 0;
}
export async function isGlassAvailableForCarId(carId) { export async function isGlassAvailableForCarId(carId) {
const newGlassOptions = await baseMixin.methods.dispatchStoreAction( const newGlassOptions = await baseMixin.methods.dispatchStoreAction(
storeActions.GET_DAMAGE_OPTIONS, storeActions.GET_DAMAGE_OPTIONS,

View file

@ -7,6 +7,7 @@ import { storeActions } from "@/constants/store-actions.js";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import experimentMixin from "@/mixins/experiment-mixin"; import experimentMixin from "@/mixins/experiment-mixin";
import { experimentSettings } from "@/constants/experiments"; import { experimentSettings } from "@/constants/experiments";
import { includesWindshieldReplacement } from "@/helpers/damage-helper";
import store from "@/store"; import store from "@/store";
import router from "@/router"; import router from "@/router";
@ -73,6 +74,7 @@ export async function skipVinLookup() {
return ( return (
store.getters.damage.isRepair || store.getters.damage.isRepair ||
isVinOptionalVehicle || isVinOptionalVehicle ||
!includesWindshieldReplacement() ||
experimentMixin.methods.hasSettingEqualTo(experimentSettings.SUPPRESS_VIN_CAPTURE, "true") experimentMixin.methods.hasSettingEqualTo(experimentSettings.SUPPRESS_VIN_CAPTURE, "true")
); );
} }
@ -85,6 +87,7 @@ export async function skipVinLookupNotRepair() {
return ( return (
!store.getters.damage.isRepair && !store.getters.damage.isRepair &&
(isVinOptionalVehicle || (isVinOptionalVehicle ||
!includesWindshieldReplacement() ||
experimentMixin.methods.hasSettingEqualTo( experimentMixin.methods.hasSettingEqualTo(
experimentSettings.SUPPRESS_VIN_CAPTURE, experimentSettings.SUPPRESS_VIN_CAPTURE,
"true" "true"

View file

@ -135,7 +135,7 @@ describe("getPageToRouteExistingOrderTo", () => {
expect(result).toBe(fmgPageValues.VEHICLE_DAMAGE); expect(result).toBe(fmgPageValues.VEHICLE_DAMAGE);
}); });
test("user has YMMS and no vehicle questions > should return vin-lookup", async () => { test("user has YMMS and no vehicle questions > should return estimate", async () => {
// Arrange // Arrange
const toRoute = { const toRoute = {
query: {}, query: {},
@ -172,7 +172,7 @@ describe("getPageToRouteExistingOrderTo", () => {
const result = await getPageToRouteExistingOrderTo(toRoute, false); const result = await getPageToRouteExistingOrderTo(toRoute, false);
//Assert //Assert
expect(result).toBe(fmgPageValues.VIN_LOOKUP); expect(result).toBe(fmgPageValues.ESTIMATE);
}); });
test("user has YMMS but no questions or carId > should return estimate", async () => { test("user has YMMS but no questions or carId > should return estimate", async () => {

View file

@ -45,7 +45,7 @@
<div class="row mb-4" v-show="showAddressFields" aria-live="polite"> <div class="row mb-4" v-show="showAddressFields" aria-live="polite">
<div class="col"> <div class="col">
<dropdownQuestion <dropdownQuestion
customInputId="state" customDropdownId="state"
cmsWidgetName="StateQuestionWidget" cmsWidgetName="StateQuestionWidget"
v-model="addressModel.state" v-model="addressModel.state"
ref="state" ref="state"
@ -115,6 +115,10 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
preserveCityAndStateOnReset: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return { return {
@ -390,8 +394,10 @@ export default {
this.displayNoMatchWarning = true; this.displayNoMatchWarning = true;
this.addressModel.city = ""; this.addressModel.city = "";
this.addressModel.state = ""; if (!this.preserveCityAndStateOnReset) {
this.addressModel.zipCode = ""; this.addressModel.state = "";
this.addressModel.zipCode = "";
}
this.showAddressFields = true; this.showAddressFields = true;
this.displayVerificationWarning = false; this.displayVerificationWarning = false;

View file

@ -101,6 +101,9 @@ describe("estimate.vue", () => {
//Arrange //Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
delete window.location;
window.location = { search: "?fmgPage=estimate&zipcode=43015" };
//Act //Act
estimate.beforeRouteEnter.call( estimate.beforeRouteEnter.call(
wrapper.vm, wrapper.vm,

View file

@ -18,7 +18,8 @@
buttonTypeString="listButton" buttonTypeString="listButton"
v-model="selectedVinLookupMethod" v-model="selectedVinLookupMethod"
isRequired isRequired
validationRules="option-required" /> validationRules="option-required"
:logDisplayedValuesEvent="true" />
</div> </div>
<div v-else> <div v-else>
<alert <alert
@ -147,11 +148,17 @@ export default {
const queryString = window.location.search; const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString); const urlParams = new URLSearchParams(queryString);
const hasZip = urlParams.has(queryStrings.ZIP_CODE); const lowerCaseParams = new URLSearchParams();
const zip = urlParams.get(queryStrings.ZIP_CODE); for (const [name, value] of urlParams) {
lowerCaseParams.append(name.toLowerCase(), value);
}
const zip = lowerCaseParams.get(queryStrings.ZIP_CODE)
? lowerCaseParams.get(queryStrings.ZIP_CODE)
: store.getters.order.serviceLocation.zipCode;
var vinByAddressPromise; var vinByAddressPromise;
if (hasZip) { if (zip) {
vinByAddressPromise = baseMixin.methods.dispatchStoreAction( vinByAddressPromise = baseMixin.methods.dispatchStoreAction(
storeActions.IS_VIN_BY_ADDRESS_PERMISSIBLE, storeActions.IS_VIN_BY_ADDRESS_PERMISSIBLE,
zip, zip,
@ -191,7 +198,7 @@ export default {
} }
} }
if (zip && resultMap.vinByAddress === false) { if (!zip || resultMap.vinByAddress === false) {
var indexToRemove = resultMap.cmsContent.VinLookupMethod.Answers.findIndex( var indexToRemove = resultMap.cmsContent.VinLookupMethod.Answers.findIndex(
(answer) => answer.Name === "HomeAddress" (answer) => answer.Name === "HomeAddress"
); );
@ -199,6 +206,7 @@ export default {
resultMap.cmsContent.VinLookupMethod.Answers.splice(indexToRemove, 1); resultMap.cmsContent.VinLookupMethod.Answers.splice(indexToRemove, 1);
} }
} }
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
}); });
}, },

View file

@ -7,7 +7,7 @@
:displayGenericVehicleImage="false" /> :displayGenericVehicleImage="false" />
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<div class="fade-on-route-transition sub-container make-tall"> <div class="fade-on-route-transition sub-container make-tall">
<div class="row my-2"> <div class="row mt-2 mb-4">
<div class="col"> <div class="col">
<textboxQuestion <textboxQuestion
cmsWidgetName="LicensePlateNumberQuestionWidget" cmsWidgetName="LicensePlateNumberQuestionWidget"
@ -17,7 +17,7 @@
validationRules="license-plate-required" /> validationRules="license-plate-required" />
</div> </div>
</div> </div>
<div class="row my-2"> <div class="row mt-0 mb-4">
<div class="col"> <div class="col">
<textboxQuestion <textboxQuestion
cmsWidgetName="RegistrationZipQuestionWidget" cmsWidgetName="RegistrationZipQuestionWidget"
@ -27,7 +27,7 @@
validationRules="zip-required|zip-format" /> validationRules="zip-required|zip-format" />
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-0">
<div class="col"> <div class="col">
<textboxQuestion <textboxQuestion
cmsWidgetName="EmailAddressQuestionWidget" cmsWidgetName="EmailAddressQuestionWidget"

View file

@ -7,7 +7,8 @@
:buttonTypeObject="servicePackageRadio" :buttonTypeObject="servicePackageRadio"
v-model="selectedPackageName" v-model="selectedPackageName"
:validationRules="validationRules" :validationRules="validationRules"
:isRequired="isRequired" /> :isRequired="isRequired"
:logDisplayedValuesEvent="true" />
</template> </template>
<script> <script>

View file

@ -66,6 +66,18 @@ export default {
}, },
}, },
watch: { watch: {
answersToDisplay: {
handler(newValue) {
// If there is only one option to display and that option is 'Mobile' then select it
if (
newValue.length == 1 &&
newValue.findIndex((answer) => answer.Name == "Mobile") != -1
) {
this.selectedValues = "Mobile";
}
},
immediate: true,
},
isMobileOnly: { isMobileOnly: {
handler(newValue) { handler(newValue) {
if (newValue) { if (newValue) {

View file

@ -17,15 +17,15 @@
@click-event="openModal" @click-event="openModal"
aria-label="Modal window" /> aria-label="Modal window" />
</div> </div>
<textBlock
:customText="mobileFeeText"
cmsWidgetName="MobileFeeDisclaimerWidget"
typeStyle="caption" />
<div v-show="errorMessage" class="row my-1 form-test-error"> <div v-show="errorMessage" class="row my-1 form-test-error">
<span class="d-inline-flex small mt-0 center-error-message" role="alert"> <span class="d-inline-flex small mt-0 center-error-message" role="alert">
{{ errorMessage }} {{ errorMessage }}
</span> </span>
</div> </div>
<textBlock
:customText="mobileFeeText"
cmsWidgetName="MobileFeeDisclaimerWidget"
typeStyle="caption" />
</div> </div>
<modal <modal
:ref="modalName" :ref="modalName"
@ -37,7 +37,8 @@
<addressQuestions <addressQuestions
ref="addressQuestions" ref="addressQuestions"
v-model="internalModel.addressQuestions" v-model="internalModel.addressQuestions"
captureApartmentNumberOrBusinessName="true" /> captureApartmentNumberOrBusinessName="true"
preserveCityAndStateOnReset="true" />
<vehicleProtectedQuestion <vehicleProtectedQuestion
ref="vehicleProtectedQuestion" ref="vehicleProtectedQuestion"
v-model="internalModel.isVehicleProtected" v-model="internalModel.isVehicleProtected"

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="vin-information"> <div class="vin-information">
<div class="vin-toggle mt-2" :class="[isActive ? 'active' : '']" @click="toggleClass()"> <div class="vin-toggle" :class="[isActive ? 'active' : '']" @click="toggleClass()">
<textLink linkType="text" href="#!" :text="WhereCanIFindMyVINHeadline" /> <textLink linkType="text" href="#!" :text="WhereCanIFindMyVINHeadline" />
</div> </div>
<div class="vin-info"> <div class="vin-info">

View file

@ -36,12 +36,12 @@
alertClass="alert-danger" /> alertClass="alert-danger" />
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row">
<div class="col"> <div class="col">
<vinInformation /> <vinInformation />
</div> </div>
</div> </div>
<div class="row my-2"> <div class="row mb-0 mt-4">
<div class="col"> <div class="col">
<textboxQuestion <textboxQuestion
cmsWidgetName="ServiceZipQuestionWidget" cmsWidgetName="ServiceZipQuestionWidget"
@ -52,7 +52,7 @@
validationRules="zip-required|zip-format" /> validationRules="zip-required|zip-format" />
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-4">
<div class="col"> <div class="col">
<textboxQuestion <textboxQuestion
cmsWidgetName="EmailAddressQuestionWidget" cmsWidgetName="EmailAddressQuestionWidget"

View file

@ -143,18 +143,18 @@ export default {
false false
); );
if (response.data) { if (response?.data) {
if (response.data.sessionKey && skey === 0) { if (response?.data.sessionKey && skey === 0) {
setCookieProperties( setCookieProperties(
{ [cookieNames.SESSION_KEY]: response.data.sessionKey }, { [cookieNames.SESSION_KEY]: response?.data.sessionKey },
{ {
useDefaultFunnelCookieAttributes: false, useDefaultFunnelCookieAttributes: false,
} }
); );
} }
if (response.data.sessionId && sid === "00000000-0000-0000-0000-000000000000") { if (response?.data.sessionId && sid === "00000000-0000-0000-0000-000000000000") {
setCookieProperties( setCookieProperties(
{ [cookieNames.SESSION_ID]: response.data.sessionId }, { [cookieNames.SESSION_ID]: response?.data.sessionId },
{ {
maxAge: 60 * 30, // 30 minutes maxAge: 60 * 30, // 30 minutes
} }

View file

@ -727,12 +727,21 @@ export const actions = {
experimentsForUser: experimentsForUser, experimentsForUser: experimentsForUser,
}; };
return globalMethods.callHttpClient({ return globalMethods
method: endpoints.LogPageView.method, .callHttpClient({
endpoint: endpoints.LogPageView.url, method: endpoints.LogPageView.method,
payload: payload, endpoint: endpoints.LogPageView.url,
logApiCall: false, payload: payload,
}); logApiCall: false,
})
.then(
(response) => {
return response;
},
(error) => {
console.log("Analytics Service Error: " + error.data);
}
);
}, },
logCustomEvent( logCustomEvent(
context, context,
@ -763,12 +772,21 @@ export const actions = {
experimentsForUser: experimentsForUser, experimentsForUser: experimentsForUser,
}; };
return globalMethods.callHttpClient({ return globalMethods
method: endpoints.LogCustomEvent.method, .callHttpClient({
endpoint: endpoints.LogCustomEvent.url, method: endpoints.LogCustomEvent.method,
payload: payload, endpoint: endpoints.LogCustomEvent.url,
logApiCall: false, payload: payload,
}); logApiCall: false,
})
.then(
(response) => {
return response;
},
(error) => {
console.log("Analytics Service Error: " + error.data);
}
);
}, },
initializeSession(context, { userId, sessionId, userAgent, referrer }) { initializeSession(context, { userId, sessionId, userAgent, referrer }) {
var payload = { var payload = {
@ -782,12 +800,21 @@ export const actions = {
referrer: referrer, referrer: referrer,
}; };
return globalMethods.callHttpClient({ return globalMethods
method: endpoints.InitializeSession.method, .callHttpClient({
endpoint: endpoints.InitializeSession.url, method: endpoints.InitializeSession.method,
payload: payload, endpoint: endpoints.InitializeSession.url,
logApiCall: false, payload: payload,
}); logApiCall: false,
})
.then(
(response) => {
return response;
},
(error) => {
console.log("Analytics Service Error: " + error.data);
}
);
}, },
// Misc Actions // Misc Actions