From 9fb8837b0195906e88b6d78b069900c89327844f Mon Sep 17 00:00:00 2001 From: Nitin Gupta Date: Thu, 30 Jan 2025 14:44:46 +0100 Subject: [PATCH] feat: add utility api for forms metrics --- .../spacecat-shared-utils/src/formcalc.js | 98 +++++++++++++++++++ packages/spacecat-shared-utils/src/index.d.ts | 19 +++- packages/spacecat-shared-utils/src/index.js | 1 + .../test/formcalc.test.js | 65 ++++++++++++ 4 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 packages/spacecat-shared-utils/src/formcalc.js create mode 100644 packages/spacecat-shared-utils/test/formcalc.test.js diff --git a/packages/spacecat-shared-utils/src/formcalc.js b/packages/spacecat-shared-utils/src/formcalc.js new file mode 100644 index 00000000..141c7f33 --- /dev/null +++ b/packages/spacecat-shared-utils/src/formcalc.js @@ -0,0 +1,98 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +const DAILY_PAGEVIEW_THRESHOLD = 200; +const CR_THRESHOLD_RATIO = 0.2; +const MOBILE = 'mobile'; +const DESKTOP = 'desktop'; + +/** + * Aggregates the form vitals by device type. + * + * @param {*} formVitalsCollection - form vitals collection + * @returns {object} - aggregated form vitals by device type + */ +function aggregateFormVitalsByDevice(formVitalsCollection) { + const resultMap = new Map(); + + formVitalsCollection.forEach((item) => { + const { + url, formview = {}, formengagement = {}, pageview = {}, formsubmit = {}, + } = item; + + const totals = { + formview: { total: 0, desktop: 0, mobile: 0 }, + formengagement: { total: 0, desktop: 0, mobile: 0 }, + pageview: { total: 0, desktop: 0, mobile: 0 }, + formsubmit: { total: 0, desktop: 0, mobile: 0 }, + }; + + const calculateSums = (metric, initialTarget) => { + const updatedTarget = { ...initialTarget }; // Create a new object to store the updated totals + Object.entries(metric).forEach(([key, value]) => { + updatedTarget.total += value; + if (key.startsWith(DESKTOP)) { + updatedTarget.desktop += value; + } else if (key.startsWith(MOBILE)) { + updatedTarget.mobile += value; + } + }); + return updatedTarget; // Return the updated target + }; + + totals.formview = calculateSums(formview, totals.formview); + totals.formengagement = calculateSums(formengagement, totals.formengagement); + totals.pageview = calculateSums(pageview, totals.pageview); + totals.formsubmit = calculateSums(formsubmit, totals.formsubmit); + + resultMap.set(url, totals); + }); + + return resultMap; +} + +function hasHighPageViews(interval, pageViews) { + return pageViews > DAILY_PAGEVIEW_THRESHOLD * interval; +} + +function hasLowerConversionRate(formSubmit, formViews) { + return formSubmit / formViews < CR_THRESHOLD_RATIO; +} + +/** + * returns the form urls with high form views and low conversion rate + * + * @param {*} formVitalsCollection - form vitals collection + * @returns {Array} - urls with high form views and low conversion rate + */ +export function getHighFormViewsLowConversionMetrics(formVitalsCollection, interval) { + const resultMap = aggregateFormVitalsByDevice(formVitalsCollection); + const urls = []; + resultMap.forEach((metrics, url) => { + const pageViews = metrics.pageview.total; + // Default to pageViews if formViews are not available + const formViews = metrics.formview.total || pageViews; + const formEngagement = metrics.formengagement.total; + const formSubmit = metrics.formsubmit.total || formEngagement; + + if (hasHighPageViews(interval, pageViews) && hasLowerConversionRate(formSubmit, formViews)) { + urls.push({ + url, + pageViews, + formViews, + formEngagement, + formSubmit, + }); + } + }); + return urls; +} diff --git a/packages/spacecat-shared-utils/src/index.d.ts b/packages/spacecat-shared-utils/src/index.d.ts index a854e9ca..d2276e07 100644 --- a/packages/spacecat-shared-utils/src/index.d.ts +++ b/packages/spacecat-shared-utils/src/index.d.ts @@ -163,7 +163,16 @@ declare function replacePlaceholders(content: string, placeholders: object): str * or null if an error occurs. */ declare function getPrompt(placeholders: object, filename: string, log: object): - Promise; + Promise; + +/** + * Retrieves the high-form-view-low-form-conversion metrics from the provided array of form vitals. + * @param {Object[]} formVitals - An array of form vitals. + * @param {number} interval - The interval in days. + * @returns {Object[]} - An array of high-form-view-low-form-conversion metrics. + */ +declare function getHighFormViewsLowConversionMetrics(formVitals: object[], interval: number): + object[]; /** * Retrieves stored metrics from S3. @@ -179,7 +188,7 @@ declare function getPrompt(placeholders: object, filename: string, log: object): * @returns {Promise} - The stored metrics */ export function getStoredMetrics(config: object, context: object): - Promise>; + Promise>; /** * Stores metrics in S3. @@ -198,8 +207,8 @@ export function getStoredMetrics(config: object, context: object): export function storeMetrics(content: object, config: object, context: object): Promise; export function s3Wrapper(fn: (request: object, context: object) => Promise): - (request: object, context: object) => Promise; + (request: object, context: object) => Promise; -export function fetch(url: string|Request, options?: RequestOptions): Promise; +export function fetch(url: string | Request, options?: RequestOptions): Promise; -export function tracingFetch(url: string|Request, options?: RequestOptions): Promise; +export function tracingFetch(url: string | Request, options?: RequestOptions): Promise; diff --git a/packages/spacecat-shared-utils/src/index.js b/packages/spacecat-shared-utils/src/index.js index 7355d121..3147061b 100644 --- a/packages/spacecat-shared-utils/src/index.js +++ b/packages/spacecat-shared-utils/src/index.js @@ -61,3 +61,4 @@ export { s3Wrapper } from './s3.js'; export { fetch } from './adobe-fetch.js'; export { tracingFetch } from './tracing-fetch.js'; +export { getHighFormViewsLowConversionMetrics } from './formcalc.js'; diff --git a/packages/spacecat-shared-utils/test/formcalc.test.js b/packages/spacecat-shared-utils/test/formcalc.test.js new file mode 100644 index 00000000..2c56e327 --- /dev/null +++ b/packages/spacecat-shared-utils/test/formcalc.test.js @@ -0,0 +1,65 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ + +import { expect } from 'chai'; +import { + getHighFormViewsLowConversionMetrics, +} from '../src/index.js'; + +describe('Form Calc functions', () => { + it('getHighFormViewsLowConversion', () => { + const formVitalsCollection = [ + { + url: 'https://www.surest.com/contact-us', + formsubmit: { + 'desktop:windows': 100, + }, + formview: {}, + formengagement: { + 'desktop:windows': 700, + 'mobile:ios': 300, + }, + pageview: { + 'desktop:windows': 5690, + 'mobile:ios': 1000, + }, + }, + { + url: 'https://www.surest.com/info/win', + formsubmit: { + }, + formview: {}, + formengagement: { + 'desktop:windows': 4000, + 'mobile:ios': 300, + }, + pageview: { + 'desktop:windows': 4670, + 'mobile:ios': 1000, + }, + }, + ]; + + const result = getHighFormViewsLowConversionMetrics(formVitalsCollection, 7); + expect(result).to.eql([ + { + url: 'https://www.surest.com/contact-us', + pageViews: 6690, + formViews: 6690, + formEngagement: 1000, + formSubmit: 100, + }, + ]); + }); +});