Refactoring to include loaders and improve speed

This commit is contained in:
Michaela Brydon 2023-11-22 08:47:19 -05:00
parent 32e12ec672
commit 79e6cdd553
5 changed files with 274 additions and 155 deletions

View file

@ -67,10 +67,6 @@ const endpoints = Object.freeze({
url: '/location/api/v1/location/providers', url: '/location/api/v1/location/providers',
method: 'GET' method: 'GET'
}, },
// GetTpaProviders: {
// url: '/location/api/v1/location/providers',
// method: 'GET'
// },
GetCapabilityQuestions: { GetCapabilityQuestions: {
url: '/parts/api/v1/parts/capability-questions', url: '/parts/api/v1/parts/capability-questions',
method: 'GET' method: 'GET'

View file

@ -37,66 +37,86 @@
</div> </div>
</div> </div>
</Form> </Form>
<googleMap <loader
id="map" v-if="initialLoading"
class="mb-4" ref="providersLoader"
:addresses="providerAddresses" loaderPosition="center"
:zipCode="mapZipCode"></googleMap> loaderColor="blue"
<Form :width="4"
id="providerSelectionForm" :height="4"
v-slot="{ meta }" class="loader" />
@submit="onSubmit" <div v-else>
@invalidSubmit="onInvalidSubmit"> <googleMap
<div class="container-fluid pb-2"> id="map"
<div class="mx-5"> class="mb-4"
<dropdownQuestion :addresses="providerAddresses"
id="searchRadiusFilter" :zipCode="mapZipCode"></googleMap>
v-model="filter" <Form
:cmsWidgetName="widget.filterByQuestion" id="providerSelectionForm"
inputId="filterByQuestionField" v-slot="{ meta }"
:options="filterOptions" @submit="onSubmit"
disableAutoFill @invalidSubmit="onInvalidSubmit">
:validationRules="rules.filter" /> <div class="container-fluid pb-2">
<div class="mx-5">
<dropdownQuestion
id="searchRadiusFilter"
v-model="filter"
:cmsWidgetName="widget.filterByQuestion"
inputId="filterByQuestionField"
:options="filterOptions"
disableAutoFill
:validationRules="rules.filter" />
<div class="my-4"> <loader
<buttonQuestion v-if="reloadProviders"
id="selectProviderQuestion" ref="providersLoader"
v-model="selectedProviderNumber" loaderPosition="center"
buttonTypeString="shopListButton" loaderColor="blue"
:buttonTypeObject="shopListButton" :width="4"
class="radioQuestion" :height="4"
:answers="providerButtonData" class="my-4" />
groupName="chooseShop" <div v-else>
textPosition="text-start" <div class="my-4">
isRequired <buttonQuestion
:validationRules="rules.provider" id="selectProviderQuestion"
:additionalButtonData="additionalButtonData" /> v-model="selectedProviderNumber"
<alert buttonTypeString="shopListButton"
v-if="providers?.length === 0 ?? true" :buttonTypeObject="shopListButton"
id="alertNoNetworkProviders" class="radioQuestion"
:cmsWidgetName="widget.noNetworkShopsAlert" :answers="providerButtonData"
alertClass="alert-warning" groupName="chooseShop"
:isDismissible="false" textPosition="text-start"
:manualHeadline="noNetworkShopsAlertHeaderText" /> isRequired
</div> :validationRules="rules.provider"
<div :additionalButtonData="additionalButtonData" />
class="text-center"> <alert
<textLink v-if="providers?.length === 0 ?? true"
id="preferredShopNotListedLink" id="alertNoNetworkProviders"
linkType="navigation" :cmsWidgetName="widget.noNetworkShopsAlert"
href="#!" alertClass="alert-warning"
:text="shopNotListedModalLink" :isDismissible="false"
@clickEvent="doNotSeeMyShopLinkClick" /> :manualHeadline="noNetworkShopsAlertHeaderText" />
</div>
<div
class="text-center">
<textLink
id="preferredShopNotListedLink"
linkType="navigation"
href="#!"
:text="shopNotListedModalLink"
@clickEvent="doNotSeeMyShopLinkClick" />
</div>
</div>
</div> </div>
<siteFooter
ref="siteFooter"
:cmsWidgetName="widget.siteFooter"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="backButtonAction" />
</div> </div>
<siteFooter </Form>
ref="siteFooter" </div>
:cmsWidgetName="widget.siteFooter"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="backButtonAction" />
</div>
</Form>
</div> </div>
</div> </div>
</template> </template>
@ -112,6 +132,7 @@ import alert from '@/ux-components/alert/alert.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue'; import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import googleMap from '@/iss-components/google-map/google-map.vue'; import googleMap from '@/iss-components/google-map/google-map.vue';
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue'; import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';
import loader from '@/ux-components/loader/loader.vue';
// Supporting files // Supporting files
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper'; import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
@ -130,6 +151,7 @@ export default {
buttonQuestion, buttonQuestion,
textLink, textLink,
alert, alert,
loader,
googleMap, googleMap,
siteFooter, siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names // eslint-disable-next-line vue/no-reserved-component-names
@ -159,6 +181,8 @@ export default {
filter: '', filter: '',
providers: [], providers: [],
selectedProviderNumber: '', selectedProviderNumber: '',
initialLoading: true,
reloadProviders: false,
additionalButtonData: { additionalButtonData: {
displayAvailabilityIndicators: false displayAvailabilityIndicators: false
}, },
@ -237,8 +261,12 @@ export default {
watch: { watch: {
async filter() { async filter() {
// TODO right now this results in getProviders being called once more than it needs to be // TODO right now this results in getProviders being called once more than it needs to be
this.providers = await this.getProviderButtonData(); if (!this.initialLoading) {
this.mapZipCode = this.zipCode; this.reloadProviders = true;
this.providers = await this.getProviderButtonData();
this.mapZipCode = this.zipCode;
this.reloadProviders = false;
}
}, },
providers(newProviders) { providers(newProviders) {
this.selectedProviderNumber = newProviders?.length === 1 ?? false this.selectedProviderNumber = newProviders?.length === 1 ?? false
@ -279,22 +307,29 @@ export default {
const radiusOptions = [25, 50, 100]; const radiusOptions = [25, 50, 100];
const { zipCode } = useMainStore().order.customer.address; const { zipCode } = useMainStore().order.customer.address;
const tpaProvidersRadius25 = await useMainStore().getTpaProviders(zipCode, radiusOptions[0]); console.log('getTpaProviders calls');
const tpaProvidersRadius50 = await useMainStore().getTpaProviders(zipCode, radiusOptions[1]); const tpaProvidersRadius25 = useMainStore().getTpaProviders(zipCode, radiusOptions[0]);
const tpaProvidersRadius100 = await useMainStore().getTpaProviders(zipCode, radiusOptions[2]); const tpaProvidersRadius50 = useMainStore().getTpaProviders(zipCode, radiusOptions[1]);
let radius = '25 miles'; const tpaProvidersRadius100 = useMainStore().getTpaProviders(zipCode, radiusOptions[2]);
console.log('after not awaited getTpaProviders calls');
let providers = tpaProvidersRadius25; let radius = '25 miles';
if ((tpaProvidersRadius25?.data?.shopProviders ?? []).length === 0) { let providers = await tpaProvidersRadius25;
console.log('after 25 awaited');
if ((providers?.data?.shopProviders ?? []).length === 0) {
radius = '50 miles'; radius = '50 miles';
providers = tpaProvidersRadius50; providers = await tpaProvidersRadius50;
if ((tpaProvidersRadius50?.data?.shopProviders ?? []).length === 0) { console.log('after 50 awaited');
if ((providers?.data?.shopProviders ?? []).length === 0) {
radius = '100 miles'; radius = '100 miles';
providers = tpaProvidersRadius100; providers = await tpaProvidersRadius100;
console.log('after 100 awaited');
} }
} }
console.log('after if');
this.filter = radius; this.filter = radius;
this.providers = providers?.data?.shopProviders ?? []; this.providers = providers?.data?.shopProviders ?? [];
this.initialLoading = false;
}, },
async getProviderButtonData() { async getProviderButtonData() {
const getTpaProvidersResult = await useMainStore().getTpaProviders(this.zipCode, this.radiusInMiles); const getTpaProvidersResult = await useMainStore().getTpaProviders(this.zipCode, this.radiusInMiles);
@ -307,8 +342,10 @@ export default {
); );
}, },
async searchClick() { async searchClick() {
this.reloadProviders = true;
this.providers = await this.getProviderButtonData(); this.providers = await this.getProviderButtonData();
this.mapZipCode = this.zipCode; this.mapZipCode = this.zipCode;
this.reloadProviders = false;
}, },
backButtonAction() { backButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route); this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
@ -334,7 +371,7 @@ export default {
: null; : null;
return { return {
buttonLabel: provider?.name ?? '', buttonLabel: provider?.companyName ?? '',
buttonLabelSubCopy: distance === null buttonLabelSubCopy: distance === null
? '' ? ''
: `${distance} mi`, : `${distance} mi`,

View file

@ -859,85 +859,14 @@ export const useMainStore = defineStore({
getTpaProviders(zipCode, radius) { getTpaProviders(zipCode, radius) {
const damageType = this.damage.isRepair ? 'Repair' : 'Replace'; const damageType = this.damage.isRepair ? 'Repair' : 'Replace';
const { accountNumber } = this.issConfig; const { parentAccountNumber } = this.issConfig;
const safeliteOnly = false; const safeliteOnly = false;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
globalMethods.callHttpClient({ globalMethods.callHttpClient({
method: endpoints.GetProviders.method, method: endpoints.GetProviders.method,
endpoint: `${endpoints.GetProviders.url}/${zipCode}/${damageType}/${radius}/${accountNumber}/${safeliteOnly}` endpoint: `${endpoints.GetProviders.url}/${zipCode}/${damageType}/${radius}/${parentAccountNumber}/${safeliteOnly}`
}).then((response) => resolve(response), (error) => reject(error)); }).then((response) => resolve(response), (error) => reject(error));
}); });
// return new Promise((resolve, reject) => {
// if (radius === 25) {
// resolve({
// data: {
// mobileProviderNumber: null,
// shopProviders: null
// }
// });
// } else if (radius === 50) {
// resolve({
// data: {
// mobileProviderNumber: null,
// shopProviders: [
// {
// name: 'USA Auto Glass',
// address: {
// city: 'WESTERVILLE',
// country: 'US',
// state: 'OH',
// streetAddress: '4403 EXECUTIVE PKWY',
// streetAddress2: '',
// zipCode: '43081',
// zipCodeCtu: '01820'
// },
// distanceInMiles: 8.393453111956896,
// providerNumber: '003335',
// phoneNumber: '614-123-5555'
// }
// ]
// }
// });
// } else {
// resolve({
// data: {
// mobileProviderNumber: null,
// shopProviders: [
// {
// name: 'USA Auto Glass',
// address: {
// city: 'WESTERVILLE',
// country: 'US',
// state: 'OH',
// streetAddress: '4403 EXECUTIVE PKWY',
// streetAddress2: '',
// zipCode: '43081',
// zipCodeCtu: '01820'
// },
// distanceInMiles: 8.393453111956896,
// providerNumber: '003335',
// phoneNumber: '614-123-5555'
// },
// {
// name: 'Safelite AutoGlass',
// address: {
// city: 'WORTHINGTON',
// country: 'US',
// state: 'OH',
// streetAddress: '760 DEARBORN PARK LN',
// streetAddress2: '',
// zipCode: '43085',
// zipCodeCtu: '01820'
// },
// distanceInMiles: 8.704336770196678,
// providerNumber: '001820',
// phoneNumber: '740-555-1234'
// }
// ]
// }
// });
// }
// });
}, },
async getSupportingItems() { async getSupportingItems() {

View file

@ -1961,4 +1961,145 @@ describe('Store', () => {
expect(store.order.currentDeductible).toEqual(finalDeductible); expect(store.order.currentDeductible).toEqual(finalDeductible);
}); });
}); });
describe('getTpaProviders method', () => {
describe('successful method call', () => {
it('calls getProviders api endpoint', async () => {
// Arrange
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.getCoveragePolicyInfo();
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
method: endpoints.CoveragePolicyInfo.method,
endpoint: endpoints.CoveragePolicyInfo.url
}));
});
it('Returns expected response object', async () => {
// Arrange
const response = { ReferralNumber: getRandomString(6, 6) };
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(response));
// Act
const result = store.getCoveragePolicyInfo();
// Asserts
await expect(result).resolves.toBe(response);
});
it('calls api with expected data', async () => {
// Arrange
const accountNumber = getRandomString(6, 6);
const policyNumber = getRandomString(6, 6);
const dateOfLoss = getRandomString(6, 6);
const zipCode = getRandomString(6, 6);
const referralCorrelationId = getRandomString(6, 6);
store.order.accountNumber = accountNumber;
store.order.policy.policyNumber = policyNumber;
store.order.policy.dateOfLoss = dateOfLoss;
store.order.policy.policyZipCode = zipCode;
store.order.referralCorrelationId = referralCorrelationId;
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.getCoveragePolicyInfo();
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
payload: expect.objectContaining({
accountNumber,
policyNumber,
dateOfLoss,
zipCode,
referralCorrelationId
})
}));
});
it('null response by api => no exceptions thrown', async () => {
// Arrange
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(null));
// Act
await store.getCoveragePolicyInfo();
// Asserts
expect(store.order.policy.policyLookupSuccessful).toBe(false);
});
it('no providers returned by api => promise resolves to empty list', async () => {
// Arrange
const responseNoPolicies = {
data: {
policies: []
}
};
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(responseNoPolicies));
// Act
await store.getCoveragePolicyInfo();
// Asserts
expect(store.order.policy.policyLookupSuccessful).toBe(false);
});
it('multiple providers returned by api => promise resolves to expected', async () => {
// Arrange
const insured = {
address: getRandomString(6, 6),
firstName: getRandomString(6, 6),
lastName: getRandomString(6, 6),
city: getRandomString(6, 6),
state: getRandomString(6, 6),
zipCode: getRandomString(6, 6)
};
const vehicles = [
{ name: getRandomString(6, 6) },
{ name: getRandomString(6, 6) }
];
const policy1 = {
insureds: [insured],
vehicles
};
const responseNoPolicies = {
data: {
policies: [policy1, {}]
}
};
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(responseNoPolicies));
// Act
await store.getCoveragePolicyInfo();
// Asserts
expect(store.order.policy.policyLookupSuccessful).toBe(true);
expect(store.order.customer.address.streetAddress).toBe(insured.address);
expect(store.order.customer.address.city).toBe(insured.city);
expect(store.order.customer.address.state).toBe(insured.state);
expect(store.order.customer.address.zipCode).toBe(insured.zipCode);
expect(store.order.customer.firstName).toBe(insured.firstName);
expect(store.order.customer.lastName).toBe(insured.lastName);
expect(store.order.serviceLocation.zipCode).toBe(insured.zipCode);
expect(store.order.policy.vehicles).toEqual(vehicles);
});
});
it('api call throws exception => policyLookupSuccessful false', async () => {
expect.assertions(3);
const error = 'get coverage policy info error';
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.reject(error));
// Act
await store.getCoveragePolicyInfo().catch((e) => {
expect(e).toEqual(error);
});
// Asserts
expect(globalMethods.callHttpClient)
.toHaveBeenCalledWith(expect.objectContaining({
method: endpoints.CoveragePolicyInfo.method,
endpoint: endpoints.CoveragePolicyInfo.url
}));
expect(store.order.policy.policyLookupSuccessful).toBe(false);
});
});
}); });

View file

@ -1,9 +1,10 @@
<template> <template>
<div <div
class="loader" :style="cssProps"
role="alert" class="loader"
aria-label="Loading new page" role="alert"
:class="[loaderColor, loaderPosition]"></div> aria-label="Loading new page"
:class="[loaderColor, loaderPosition]"></div>
</template> </template>
<script> <script>
@ -18,6 +19,22 @@ export default {
/* Position options: center, right, left (OPTIONAL, do NOT use on btn-* classes) */ /* Position options: center, right, left (OPTIONAL, do NOT use on btn-* classes) */
loaderPosition: { loaderPosition: {
type: String type: String
},
width: {
type: Number,
default: 1
},
height: {
type: Number,
default: 1
}
},
computed: {
cssProps() {
return {
'--loader-width': `${this.width}rem`,
'--loader-height': `${this.height}rem`
};
} }
} }
}; };
@ -45,8 +62,8 @@ export default {
mask: url(../../assets/img/icons/spinner.svg); mask: url(../../assets/img/icons/spinner.svg);
mask-size: cover; mask-size: cover;
position: relative; position: relative;
width: 1rem; width: var(--loader-width);
height: 1rem; height: var(--loader-height);
border-radius:50%; border-radius:50%;
animation: rotation 1s infinite linear; animation: rotation 1s infinite linear;
@keyframes rotation { @keyframes rotation {
@ -57,8 +74,7 @@ export default {
} }
//Spinner position //Spinner position
&.center { &.center {
right: 50%; justify-content: center;
transform: translateX(50%);
} }
&.right { &.right {
right: 1rem; right: 1rem;