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',
method: 'GET'
},
// GetTpaProviders: {
// url: '/location/api/v1/location/providers',
// method: 'GET'
// },
GetCapabilityQuestions: {
url: '/parts/api/v1/parts/capability-questions',
method: 'GET'

View file

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

View file

@ -859,85 +859,14 @@ export const useMainStore = defineStore({
getTpaProviders(zipCode, radius) {
const damageType = this.damage.isRepair ? 'Repair' : 'Replace';
const { accountNumber } = this.issConfig;
const { parentAccountNumber } = this.issConfig;
const safeliteOnly = false;
return new Promise((resolve, reject) => {
globalMethods.callHttpClient({
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));
});
// 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() {

View file

@ -1961,4 +1961,145 @@ describe('Store', () => {
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>
<div
class="loader"
role="alert"
aria-label="Loading new page"
:class="[loaderColor, loaderPosition]"></div>
<div
:style="cssProps"
class="loader"
role="alert"
aria-label="Loading new page"
:class="[loaderColor, loaderPosition]"></div>
</template>
<script>
@ -18,6 +19,22 @@ export default {
/* Position options: center, right, left (OPTIONAL, do NOT use on btn-* classes) */
loaderPosition: {
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-size: cover;
position: relative;
width: 1rem;
height: 1rem;
width: var(--loader-width);
height: var(--loader-height);
border-radius:50%;
animation: rotation 1s infinite linear;
@keyframes rotation {
@ -57,8 +74,7 @@ export default {
}
//Spinner position
&.center {
right: 50%;
transform: translateX(50%);
justify-content: center;
}
&.right {
right: 1rem;