From 16566bf9680d95815ed95d8f1156709835db4c76 Mon Sep 17 00:00:00 2001 From: Matt Sykes Date: Mon, 27 Jul 2026 15:44:57 -0400 Subject: [PATCH] CASH-3095: Track VIN lookup not-found events via AnalyticsService metrics. Add a fire-and-forget metrics helper and increment fmg_event_v1 for VIN, license plate, address, and image lookup failures. --- src/constants/endpoints.js | 4 ++ src/constants/metrics.js | 18 +++++++ src/helpers/metrics.js | 48 +++++++++++++++++++ src/layouts/address-lookup/address-lookup.vue | 12 +++++ .../license-plate-lookup.vue | 12 +++++ src/layouts/vin-lookup/vin-lookup.vue | 17 +++++++ src/main.js | 2 + 7 files changed, 113 insertions(+) create mode 100644 src/constants/metrics.js create mode 100644 src/helpers/metrics.js diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 3035b4ad7..025d06b42 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -184,6 +184,10 @@ const endpoints = { url: "/analytics/api/v1/analytics/log-custom-event", method: "POST", }, + PushMetric: { + url: "/analytics/api/v1/metrics/push", + method: "POST", + }, InitializeSession: { url: "/analytics/api/v1/analytics/initialize", method: "POST", diff --git a/src/constants/metrics.js b/src/constants/metrics.js new file mode 100644 index 000000000..2e8be4fcf --- /dev/null +++ b/src/constants/metrics.js @@ -0,0 +1,18 @@ +export const MetricNames = { + FMG_EVENT_V1: "fmg_event_v1", +}; + +export const MetricCategories = { + VIN_LOOKUP: "VIN Lookup", +}; + +export const MetricActions = { + NOT_FOUND: "Not Found", +}; + +export const MetricLabels = { + BY_VIN_NUMBER: "By VIN number", + BY_LICENSE_PLATE: "By license plate", + BY_ADDRESS: "By address", + BY_IMAGE: "By image", +}; diff --git a/src/helpers/metrics.js b/src/helpers/metrics.js new file mode 100644 index 000000000..b4d5975d2 --- /dev/null +++ b/src/helpers/metrics.js @@ -0,0 +1,48 @@ +import { applicationConfig } from "@/constants/application-config.js"; +import { endpoints } from "@/constants/endpoints"; +import { headerKeys } from "@/constants/header-keys"; + +function mapEnvironment(currentEnvironment) { + switch (currentEnvironment) { + case "Prod": + return "prod"; + case "QA": + return "qa"; + case "Localhost": + case "Dev": + case "SysTest": + default: + return "dev"; + } +} + +export class Metrics { + incrementCounter(metricName, labels = {}, description) { + const body = { + metricName, + metricType: "counter", + incrementValue: 1, + description, + labels: { + event_environment: mapEnvironment(applicationConfig.CURRENT_ENVIRONMENT), + event_host: window.location.host, + event_path: window.location.pathname, + ...labels, + }, + }; + + const url = applicationConfig.CONSUMER_CF_DISTRO + endpoints.PushMetric.url; + + return fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + [headerKeys.APPLICATION_NAME]: applicationConfig.APPLICATION_NAME, + }, + body: JSON.stringify(body), + keepalive: true, + }).catch(() => {}); + } +} + +export default Metrics; diff --git a/src/layouts/address-lookup/address-lookup.vue b/src/layouts/address-lookup/address-lookup.vue index f79ee5667..dce44bc3a 100644 --- a/src/layouts/address-lookup/address-lookup.vue +++ b/src/layouts/address-lookup/address-lookup.vue @@ -122,6 +122,12 @@ import baseMixin from "@/mixins/base-mixin.js"; import store from "@/store"; import vinPagesMixin from "@/mixins/vin-pages-mixin"; import { routeData } from "@/router/constants/routes"; +import { + MetricNames, + MetricCategories, + MetricActions, + MetricLabels, +} from "@/constants/metrics"; // DEFINE VALIDATION RULES - Note: Additional rules are defined in Customer Questions and Address Questions components defineRule("service-zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED)); @@ -354,6 +360,12 @@ export default { }); } } else { + global.$metrics.incrementCounter(MetricNames.FMG_EVENT_V1, { + event_category: MetricCategories.VIN_LOOKUP, + event_action: MetricActions.NOT_FOUND, + event_label: MetricLabels.BY_ADDRESS, + }); + // No VINS found. const isVinLookupRequired = this.$store.getters.vehicle.vinRequired; if (isVinLookupRequired) { diff --git a/src/layouts/license-plate-lookup/license-plate-lookup.vue b/src/layouts/license-plate-lookup/license-plate-lookup.vue index a3873e437..e58a8e387 100644 --- a/src/layouts/license-plate-lookup/license-plate-lookup.vue +++ b/src/layouts/license-plate-lookup/license-plate-lookup.vue @@ -103,6 +103,12 @@ import baseMixin from "@/mixins/base-mixin.js"; import store from "@/store"; import vinPagesMixin from "@/mixins/vin-pages-mixin"; import { routeData } from "@/router/constants/routes"; +import { + MetricNames, + MetricCategories, + MetricActions, + MetricLabels, +} from "@/constants/metrics"; // DEFINE VALIDATION RULES defineRule("license-plate-required", required(errorMessages.LICENSE_PLATE_REQUIRED)); @@ -253,6 +259,12 @@ export default { this.licensePlate, resultMap.registrationZipValidationResponse.state ).catch(() => { + global.$metrics.incrementCounter(MetricNames.FMG_EVENT_V1, { + event_category: MetricCategories.VIN_LOOKUP, + event_action: MetricActions.NOT_FOUND, + event_label: MetricLabels.BY_LICENSE_PLATE, + }); + // No VIN found. const isVinLookupRequired = this.$store.getters.vehicle.vinRequired; if (isVinLookupRequired) { diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index a8a700de0..3d9e71c34 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -146,6 +146,12 @@ import store from "@/store"; import vinPagesMixin from "@/mixins/vin-pages-mixin"; import { routeData } from "@/router/constants/routes"; import { bailoutCodes } from "@/constants/bailout-codes"; +import { + MetricNames, + MetricCategories, + MetricActions, + MetricLabels, +} from "@/constants/metrics"; // DEFINE VALIDATION RULES defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED)); @@ -292,6 +298,12 @@ export default { if (!resultMap.vehicleLookupResponse || !resultMap.zipCodeData.isServiceable) { // If the vehicle result is undefined, the vin entered was invalid. if (!resultMap.vehicleLookupResponse) { + global.$metrics.incrementCounter(MetricNames.FMG_EVENT_V1, { + event_category: MetricCategories.VIN_LOOKUP, + event_action: MetricActions.NOT_FOUND, + event_label: MetricLabels.BY_VIN_NUMBER, + }); + const isVinLookupRequired = this.$store.getters.vehicle.vinRequired; if (isVinLookupRequired) { this.requiredVinNotFound = true; @@ -487,6 +499,11 @@ export default { if (response.data.length > 0) { resolve(response.data[0]); } else { + global.$metrics.incrementCounter(MetricNames.FMG_EVENT_V1, { + event_category: MetricCategories.VIN_LOOKUP, + event_action: MetricActions.NOT_FOUND, + event_label: MetricLabels.BY_IMAGE, + }); reject("No VINs detected."); } }) diff --git a/src/main.js b/src/main.js index 5d655db85..90980313b 100644 --- a/src/main.js +++ b/src/main.js @@ -14,8 +14,10 @@ import "../node_modules/jquery-ui/dist/jquery-ui.min.js"; window.$ = window.jQuery = require("jquery"); import Logger from "@/helpers/logger"; +import Metrics from "@/helpers/metrics"; // Instantiate global logging object global.$logger = new Logger(); +global.$metrics = new Metrics(); // Vue App Setup const vueApp = createApp(App);