Merge branch 'develop' into feature/fakeEndToEnd
This commit is contained in:
commit
4f90408201
10 changed files with 189 additions and 31 deletions
23
src/App.vue
23
src/App.vue
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }" ref="router-view">
|
||||||
<transition :duration="{ enter: 200, leave: 200 }" name="route-fade" mode="out-in">
|
<transition :duration="{ enter: 200, leave: 200 }" name="route-fade" mode="out-in">
|
||||||
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
|
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
|
||||||
<component :is="Component" @focusin="handleAnyComponentFocus" />
|
<component :is="Component" ref="component" @focusin="handleAnyComponentFocus" />
|
||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
<loadingModal :showLoader="shouldShowLoader" :showTextCarousel="shouldShowTextCarousel" />
|
<loadingModal :showLoader="shouldShowLoader" :showTextCarousel="shouldShowTextCarousel" />
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
import { handleAnyComponentFocus } from "@/helpers/button-question-focus-helper";
|
import { handleAnyComponentFocus } from "@/helpers/button-question-focus-helper";
|
||||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||||
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
|
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "app",
|
name: "app",
|
||||||
setup() {
|
setup() {
|
||||||
|
|
@ -33,6 +34,24 @@ export default {
|
||||||
loadingModal,
|
loadingModal,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add a global Javascript exception handler for logging exceptions, this handler will log when there is an uncaught exception
|
||||||
|
window.onerror = (msg, url, line, col, error) => {
|
||||||
|
// Log the windows error
|
||||||
|
// Note that col & error are new to the HTML 5 spec and may not be
|
||||||
|
// supported in every browser. It worked for me in Chrome.
|
||||||
|
global.$logger.logError(msg, {
|
||||||
|
url: url,
|
||||||
|
line: line,
|
||||||
|
column: col ?? "",
|
||||||
|
error: error ?? "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// If you return true, then error alerts (like in older versions of
|
||||||
|
// Internet Explorer) will be suppressed.
|
||||||
|
var suppressErrorAlert = true;
|
||||||
|
return suppressErrorAlert;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ const applicationConfig = {
|
||||||
YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60",
|
YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60",
|
||||||
OUTLOOK_CALENDAR:
|
OUTLOOK_CALENDAR:
|
||||||
"https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent",
|
"https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent",
|
||||||
|
FRONTEND_LOGGER_URL: process.env.VUE_APP_CONSUMER_CF_DISTRO + "/analytics/api/v1/logging",
|
||||||
};
|
};
|
||||||
|
|
||||||
export { applicationConfig };
|
export { applicationConfig };
|
||||||
|
|
|
||||||
11
src/constants/axios-response-interceptor-messages.js
Normal file
11
src/constants/axios-response-interceptor-messages.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
const axiosResponseInterceptorMessages = {
|
||||||
|
NETWORK_ERROR:
|
||||||
|
"A network error occurred. " +
|
||||||
|
"This could be a bad URL, a CORS issue, or a dropped internet connection. " +
|
||||||
|
"It is impossible for us to know.",
|
||||||
|
STATUS_CODE_ERROR: "Status Code Error",
|
||||||
|
NO_RESPONSE_ERROR: "The request was made but no response was received",
|
||||||
|
GENERIC_ERROR: "Error",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default axiosResponseInterceptorMessages;
|
||||||
8
src/constants/logging-endpoint-methods.js
Normal file
8
src/constants/logging-endpoint-methods.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
const loggingEndpointMethods = {
|
||||||
|
LOG_INFORMATION: "log-information",
|
||||||
|
LOG_WARNING: "log-warning",
|
||||||
|
LOG_ERROR: "log-error",
|
||||||
|
LOG_CRITICAL: "log-critical",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default loggingEndpointMethods;
|
||||||
|
|
@ -1,11 +1,50 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import analyticsMixIn from "@/mixins/analytics-mixin.js";
|
import analyticsMixIn from "@/mixins/analytics-mixin.js";
|
||||||
import store from "@/store";
|
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
import store from "@/store";
|
||||||
|
|
||||||
import { applicationConfig } from "@/constants/application-config.js";
|
import { applicationConfig } from "@/constants/application-config.js";
|
||||||
import { GaCategories, GaActions, GaLabels } from "@/constants/analytics";
|
import { GaCategories, GaActions, GaLabels } from "@/constants/analytics";
|
||||||
import { headerKeys } from "@/constants/header-keys";
|
import { headerKeys } from "@/constants/header-keys";
|
||||||
|
import axiosResponseInterceptorMessages from "@/constants/axios-response-interceptor-messages.js";
|
||||||
|
|
||||||
|
// Add a response interceptor for global axios error handing.
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
let rejectionError = "";
|
||||||
|
if (typeof error.response === "undefined") {
|
||||||
|
// The request was not made, could be a bad url, bad connection, or a CORS error.
|
||||||
|
rejectionError = {
|
||||||
|
response: error,
|
||||||
|
message: axiosResponseInterceptorMessages.NETWORK_ERROR,
|
||||||
|
};
|
||||||
|
} else if (error.response) {
|
||||||
|
// The request was made and the server responded with a status code
|
||||||
|
// that falls out of the range of 2xx
|
||||||
|
rejectionError = {
|
||||||
|
response: error.response,
|
||||||
|
message: axiosResponseInterceptorMessages.STATUS_CODE_ERROR,
|
||||||
|
};
|
||||||
|
} else if (error.request) {
|
||||||
|
// The request was made but no response was received
|
||||||
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
|
// http.ClientRequest in node.js
|
||||||
|
rejectionError = {
|
||||||
|
response: error.request,
|
||||||
|
message: axiosResponseInterceptorMessages.NO_RESPONSE_ERROR,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Something happened in setting up the request that triggered an Error
|
||||||
|
rejectionError = {
|
||||||
|
response: error.message,
|
||||||
|
message: axiosResponseInterceptorMessages.GENERIC_ERROR,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(rejectionError);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
callHttpClient({
|
callHttpClient({
|
||||||
|
|
@ -50,8 +89,6 @@ export default {
|
||||||
return resolve(response);
|
return resolve(response);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
if (logApiCall) {
|
if (logApiCall) {
|
||||||
analyticsMixIn.methods.pushEventToGA(
|
analyticsMixIn.methods.pushEventToGA(
|
||||||
GaCategories.API_RESPONSE,
|
GaCategories.API_RESPONSE,
|
||||||
|
|
@ -61,11 +98,16 @@ export default {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not route to error logic when no wipers found or no promo found (404s)
|
// Do not route to error logic when no wipers found or no promo found (404s)
|
||||||
if (error.response.status != "404") {
|
if (error.response.status != "404") {
|
||||||
router.navigateError();
|
router.navigateError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
global.$logger.logError(
|
||||||
|
`${method}: ${endpoint}: ${error.message}`,
|
||||||
|
error.response
|
||||||
|
);
|
||||||
|
|
||||||
return reject(error.response);
|
return reject(error.response);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,13 @@ import router from "@/router";
|
||||||
jest.mock("axios");
|
jest.mock("axios");
|
||||||
jest.mock("@/mixins/analytics-mixin");
|
jest.mock("@/mixins/analytics-mixin");
|
||||||
|
|
||||||
|
global.$logger = {
|
||||||
|
logInformation: jest.fn(),
|
||||||
|
logWarning: jest.fn(),
|
||||||
|
logError: jest.fn(),
|
||||||
|
logCritical: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
it("Global Methods - Call Http Client - Should Resolve Promise", () => {
|
it("Global Methods - Call Http Client - Should Resolve Promise", () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const endpoint = "https://mock.safelite.com";
|
const endpoint = "https://mock.safelite.com";
|
||||||
|
|
|
||||||
74
src/helpers/logger.js
Normal file
74
src/helpers/logger.js
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { applicationConfig } from "@/constants/application-config.js";
|
||||||
|
import axios from "axios";
|
||||||
|
import store from "@/store";
|
||||||
|
import loggingEndpointMethods from "@/constants/logging-endpoint-methods";
|
||||||
|
|
||||||
|
import { headerKeys } from "@/constants/header-keys";
|
||||||
|
|
||||||
|
export class Logger {
|
||||||
|
logInformation(message, details) {
|
||||||
|
this.writeLogEntry(
|
||||||
|
loggingEndpointMethods.LOG_INFORMATION,
|
||||||
|
this.formatLogEntry(message, details)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logWarning(message, details) {
|
||||||
|
this.writeLogEntry(
|
||||||
|
loggingEndpointMethods.LOG_WARNING,
|
||||||
|
this.formatLogEntry(message, details)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logError(message, details) {
|
||||||
|
this.writeLogEntry(loggingEndpointMethods.LOG_ERROR, this.formatLogEntry(message, details));
|
||||||
|
}
|
||||||
|
|
||||||
|
logCritical(message, details) {
|
||||||
|
this.writeLogEntry(
|
||||||
|
loggingEndpointMethods.LOG_CRITICAL,
|
||||||
|
this.formatLogEntry(message, details)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatLogEntry(message, details) {
|
||||||
|
return `Application: ${applicationConfig.APPLICATION_NAME}\n${message}\n${
|
||||||
|
details ? JSON.stringify(details, undefined, 2) : ""
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeLogEntry(endpoint, logEntry) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// If running locally or in the Dev environment show the log entries in the console.
|
||||||
|
if (
|
||||||
|
applicationConfig.CURRENT_ENVIRONMENT === "Localhost" ||
|
||||||
|
applicationConfig.CURRENT_ENVIRONMENT === "Dev"
|
||||||
|
) {
|
||||||
|
console.log(logEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = applicationConfig.FRONTEND_LOGGER_URL;
|
||||||
|
const headers = {
|
||||||
|
[headerKeys.EXPERIMENT]: JSON.stringify(store.getters.experimentSettings),
|
||||||
|
};
|
||||||
|
|
||||||
|
axios({
|
||||||
|
method: "POST",
|
||||||
|
url: `${url}/${endpoint}`,
|
||||||
|
data: { entry: logEntry },
|
||||||
|
crossDomain: true,
|
||||||
|
responseType: {},
|
||||||
|
headers: headers,
|
||||||
|
}).then(
|
||||||
|
(response) => {
|
||||||
|
return resolve(response);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Logger;
|
||||||
11
src/main.js
11
src/main.js
|
|
@ -8,6 +8,10 @@ import baseMixin from "@/mixins/base-mixin.js";
|
||||||
import analyticsMixin from "@/mixins/analytics-mixin.js";
|
import analyticsMixin from "@/mixins/analytics-mixin.js";
|
||||||
import experimentMixin from "@/mixins/experiment-mixin.js";
|
import experimentMixin from "@/mixins/experiment-mixin.js";
|
||||||
import "../node_modules/bootstrap/dist/js/bootstrap.js";
|
import "../node_modules/bootstrap/dist/js/bootstrap.js";
|
||||||
|
import Logger from "@/helpers/logger";
|
||||||
|
|
||||||
|
// Instantiate global logging object
|
||||||
|
global.$logger = new Logger();
|
||||||
|
|
||||||
// Vue App Setup
|
// Vue App Setup
|
||||||
const vueApp = createApp(App);
|
const vueApp = createApp(App);
|
||||||
|
|
@ -20,4 +24,11 @@ vueApp.mixin(baseMixin);
|
||||||
vueApp.mixin(analyticsMixin);
|
vueApp.mixin(analyticsMixin);
|
||||||
vueApp.mixin(experimentMixin);
|
vueApp.mixin(experimentMixin);
|
||||||
|
|
||||||
|
// Vue Error Handling
|
||||||
|
vueApp.config.errorHandler = (err, vm, info) => {
|
||||||
|
global.$logger.logError(
|
||||||
|
`Page Name - ${vm.getPageName()} - ${info}: ${err.message}\n${err.stack}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
vueApp.mount("#app");
|
vueApp.mount("#app");
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ const routes = [
|
||||||
params: to.params,
|
params: to.params,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
global.$logger.logError(`Routing ${error.stack}`);
|
||||||
|
|
||||||
if (to.query?.fmgPage === funnelStartPageName) {
|
if (to.query?.fmgPage === funnelStartPageName) {
|
||||||
deleteFunnelCookie();
|
deleteFunnelCookie();
|
||||||
|
|
|
||||||
|
|
@ -1126,14 +1126,9 @@ export const actions = {
|
||||||
payload: payload,
|
payload: payload,
|
||||||
logApiCall: false,
|
logApiCall: false,
|
||||||
})
|
})
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
return response;
|
||||||
return response;
|
});
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.log("Analytics Service Error: " + error.data);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
logCustomEvent(
|
logCustomEvent(
|
||||||
|
|
@ -1180,14 +1175,9 @@ export const actions = {
|
||||||
payload: payload,
|
payload: payload,
|
||||||
logApiCall: false,
|
logApiCall: false,
|
||||||
})
|
})
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
return response;
|
||||||
return response;
|
});
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.log("Analytics Service Error: " + error.data);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
initializeSession(context, { userId, deviceId, sessionId, userAgent, referrer }) {
|
initializeSession(context, { userId, deviceId, sessionId, userAgent, referrer }) {
|
||||||
var payload = {
|
var payload = {
|
||||||
|
|
@ -1208,14 +1198,9 @@ export const actions = {
|
||||||
payload: payload,
|
payload: payload,
|
||||||
logApiCall: false,
|
logApiCall: false,
|
||||||
})
|
})
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
return response;
|
||||||
return response;
|
});
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.log("Analytics Service Error: " + error.data);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Misc Actions
|
// Misc Actions
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue