Merge pull request #1087 from Safelite/feature/digital/global-bailouts

Initial setup of global bailout system
This commit is contained in:
Josh Dassinger 2026-02-17 11:49:52 -06:00 committed by GitHub
commit bc9cca6aa3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 66 additions and 32 deletions

View file

@ -23,7 +23,10 @@ const applicationConfig = Object.freeze({
YAHOO_CALENDAR: 'https://calendar.yahoo.com/?v=60',
OUTLOOK_CALENDAR:
'https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent',
FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging"
FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging",
BAILOUT_ON_APPLICATION_ERROR: true,
BAILOUT_ON_API_ERROR: true,
BAILOUT_ON_ROUTER_ERROR: true
});
export default applicationConfig;

View file

@ -12,7 +12,10 @@ const bailoutCode = Object.freeze({
NoPartsAvailable: 10,
PartsServiceError: 11,
SafeliteNotTheProvider: 12,
VehicleYMMSLookupError: 13
VehicleYMMSLookupError: 13,
ApplicationError: 14,
ApiError: 15,
RouterError: 16
});
export default bailoutCode;

View file

@ -17,6 +17,18 @@ const bailoutMessage = Object.freeze({
code: bailoutCode.Unknown,
message: `An unknown bailout occurred: ${getItemData(error)}`
}),
applicationError: (error) => ({
code: bailoutCode.ApplicationError,
message: `An application error occurred: ${getItemData(error)}`
}),
apiError: (error) => ({
code: bailoutCode.ApiError,
message: `An API error occurred: ${getItemData(error)}`
}),
routerError: (error) => ({
code: bailoutCode.RouterError,
message: `A router error occurred: ${getItemData(error)}`
}),
saveSessionError: (error) => ({
code: bailoutCode.SaveSessionError,
message: `An error occurred during save session: ${getItemData(error)}`

View file

@ -15,10 +15,6 @@ axios.interceptors.response.use(
if (typeof error.response === 'undefined') {
// The request was not made, could be a bad url, bad connection or a CORS error.
rejectionError = {
message:
'A network error occurred. '
+ 'This could be a CORS issue or a dropped internet connection. '
+ 'It is impossible for us to know.',
cause: error,
response: error,
message: axiosResponseInterceptorMessages.NETWORK_ERROR
@ -51,7 +47,7 @@ axios.interceptors.response.use(
);
export default {
callHttpClient({ method, endpoint, payload, logApiCall = true }) {
callHttpClient({ method, endpoint, payload, logApiCall = true, bailoutOnError = true }) {
return new Promise((resolve, reject) => {
const store = useMainStore();
const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO;
@ -66,9 +62,10 @@ export default {
[headerKeys.SESSION_SEQUENCE_NUMBER]: sessionKey
};
const url = cfDistroUrl + endpoint;
axios({
method,
url: cfDistroUrl + endpoint,
url,
data: payloadAndAnalyticsData,
crossDomain: true,
responseType: 'json',
@ -97,10 +94,11 @@ export default {
}
if (error.response.status !== 404) {
global.$logger.logError(
`${method}: ${endpoint}: ${error.message}`,
error.response
);
global.$logger.logError(`${method}: ${endpoint}: ${error.message}`, error.response);
if (bailoutOnError && global.bailoutOnAxiosError !== undefined)
{
global.bailoutOnAxiosError({ url, error });
}
}
return reject(error.response);
}

View file

@ -14,6 +14,8 @@ import router from './router';
import App from './App.vue';
import Logger from "@/helpers/logger";
import bailoutMessage from '@/constants/bailoutMessage';
import applicationConfig from '@/constants/application-config';
// Instantiate global logging object
global.$logger = new Logger();
@ -46,15 +48,26 @@ function getPageName(vm) {
// Vue Error Handling
vueApp.config.errorHandler = (err, vm, info) => {
const pageName = getPageName(vm);
global.$logger.logError(
`Page Name - ${pageName} - ${info}: ${err.message}\n${err.stack}`
);
global.$logger.logError(`Page Name - ${pageName} - ${info}: ${err.message}\n${err.stack}`);
if (applicationConfig.BAILOUT_ON_APPLICATION_ERROR) {
router.navigateBailout(bailoutMessage.applicationError(`[${pageName}] ${info}: ${err.message}\n${err.stack}`));
}
};
// Vue Router Error Handling
router.onError((err) => {
global.$logger.logError(err.message, err.cause);
if (applicationConfig.BAILOUT_ON_ROUTER_ERROR) {
router.navigateBailout(bailoutMessage.routerError(`${err.message}\n${err.stack}`));
}
});
global.bailoutOnAxiosError = (error) => {
if (applicationConfig.BAILOUT_ON_API_ERROR) {
router.navigateBailout(bailoutMessage.apiError(error));
}
}
vueApp.mount('#app');
// define global rules

View file

@ -281,7 +281,10 @@ function navigate(
}
// Match our maps up and navigate if we have a destination.
const matchingScenarioMap = getNavigationMap(scenario, currentRoute);
let matchingScenarioMap = getNavigationMap(scenario, currentRoute);
if (!matchingScenarioMap && scenario === navigationScenarios.BAILOUT) {
matchingScenarioMap = { destinationIssPageValue: issPageValues.BAILOUT_PAGE }
}
if (!matchingScenarioMap) {
window.console.error('No matching scenario found. Please review the routing table.');
@ -332,6 +335,18 @@ function navigateToUrl(url, optionalQuery = {}) {
window.location.assign(externalUrl);
}
router.navigateBailout = (bailoutData = null) => {
if (bailoutData != null && !useMainStore().isBailout) {
useMainStore().setBailout(bailoutData)
}
router.navigate(
navigationScenarios.BAILOUT,
router.currentRoute.value,
{},
{ [routerParams.SKIP_SAVE_SESSION]: true }
);
}
// Get navigation map depending on the scenario and the current 'page' you're on.
function getNavigationMap(scenario, currentRoute) {
const issPageValue = currentRoute.query.issPage;

View file

@ -114,6 +114,7 @@ const navigationScenarios = Object.freeze({
// Bailout
CLICKED_FORWARD_WITH_BAILOUT: 'CLICKED_FORWARD_WITH_BAILOUT',
CLICKED_NEED_HELP_WITH_BAILOUT: 'CLICKED_NEED_HELP_WITH_BAILOUT',
BAILOUT: 'BAILOUT'
});
export default navigationScenarios;

View file

@ -879,8 +879,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({
method: endpoints.GetMobilePremiumFee.method,
endpoint: `${endpoints.GetMobilePremiumFee.url}/${paymentType}/${damageType}`,
logApiCall: true
endpoint: `${endpoints.GetMobilePremiumFee.url}/${paymentType}/${damageType}`
});
},
getMobileTimeSlots(startDate, endDate, zipCodeOverride = null) {
@ -935,14 +934,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({
method: endpoints.GetMobileTimeSlots.method,
endpoint: endpoints.GetMobileTimeSlots.url,
payload,
logApiCall: true,
additionalSuccessEventDataHandler: (response) =>
getTimeSlotsAdditionalEventData(
response.data.provisionalTriggers,
zipCodeOverride ?? order.serviceLocation.zipCode,
response.data.days?.[0]?.date
)
payload
});
},
getShopTimeSlots(startDate, endDate, shopAppointmentType, providerNumber) {
@ -998,8 +990,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({
method: endpoints.GetShopTimeSlots.method,
endpoint: endpoints.GetShopTimeSlots.url,
payload,
additionalSuccessEventDataHandler: (response) => provisionalTriggersToString(response.data.provisionalTriggers)
payload
});
},
async getWipers() {
@ -1553,8 +1544,7 @@ export const useMainStore = defineStore({
method: endpoints.SaveSession.method,
endpoint: endpoints.SaveSession.url,
payload,
additionalSuccessEventDataHandler: () =>
`Email provided: ${customer.emailAddress ? 'true' : 'false'}`
bailoutOnError: false
}).then((response) => {
if (loadedFromDupeCheck) {
this.order.loadedSessionClearedPreviousData = true;
@ -2466,8 +2456,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({
method: endpoints.GetAlertReasons.method,
endpoint: `${endpoints.GetAlertReasons.url}/${ctu}`,
payload: {},
logApiCall: true
payload: {}
});
},