@adobe/spacecat-shared-utils 1.28.4 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/package.json +2 -2
- package/src/formcalc.js +196 -0
- package/src/index.d.ts +30 -5
- package/src/index.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-utils-v1.30.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.29.0...@adobe/spacecat-shared-utils-v1.30.0) (2025-02-06)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* adding more opportunities utils for forms ([#580](https://github.com/adobe/spacecat-shared/issues/580)) ([4cb7d5d](https://github.com/adobe/spacecat-shared/commit/4cb7d5d94ff887932809416b3cf918fc3b56a58d))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-utils-v1.29.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.28.4...@adobe/spacecat-shared-utils-v1.29.0) (2025-02-03)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add utility api for forms metrics ([#566](https://github.com/adobe/spacecat-shared/issues/566)) ([a7365b6](https://github.com/adobe/spacecat-shared/commit/a7365b6eaabf6178a7febd2f25a4083e60d0156b))
|
|
14
|
+
|
|
1
15
|
# [@adobe/spacecat-shared-utils-v1.28.4](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.28.3...@adobe/spacecat-shared-utils-v1.28.4) (2025-02-03)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/spacecat-shared-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.0",
|
|
4
4
|
"description": "Shared modules of the Spacecat Services - utils",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"chai": "5.1.2",
|
|
40
40
|
"chai-as-promised": "8.0.1",
|
|
41
41
|
"husky": "9.1.7",
|
|
42
|
-
"nock": "
|
|
42
|
+
"nock": "14.0.0",
|
|
43
43
|
"sinon": "19.0.2",
|
|
44
44
|
"sinon-chai": "4.0.0"
|
|
45
45
|
},
|
package/src/formcalc.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const DAILY_PAGEVIEW_THRESHOLD = 200;
|
|
14
|
+
const CR_THRESHOLD_RATIO = 0.2;
|
|
15
|
+
const MOBILE = 'mobile';
|
|
16
|
+
const DESKTOP = 'desktop';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Aggregates the form vitals by device type.
|
|
20
|
+
*
|
|
21
|
+
* @param {*} formVitalsCollection - form vitals collection
|
|
22
|
+
* @returns {object} - aggregated form vitals by device type
|
|
23
|
+
*/
|
|
24
|
+
function aggregateFormVitalsByDevice(formVitalsCollection) {
|
|
25
|
+
const resultMap = new Map();
|
|
26
|
+
|
|
27
|
+
formVitalsCollection.forEach((item) => {
|
|
28
|
+
const {
|
|
29
|
+
url, formview = {}, formengagement = {}, pageview = {}, formsubmit = {},
|
|
30
|
+
} = item;
|
|
31
|
+
|
|
32
|
+
const totals = {
|
|
33
|
+
formview: { total: 0, desktop: 0, mobile: 0 },
|
|
34
|
+
formengagement: { total: 0, desktop: 0, mobile: 0 },
|
|
35
|
+
pageview: { total: 0, desktop: 0, mobile: 0 },
|
|
36
|
+
formsubmit: { total: 0, desktop: 0, mobile: 0 },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const calculateSums = (metric, initialTarget) => {
|
|
40
|
+
const updatedTarget = { ...initialTarget }; // Create a new object to store the updated totals
|
|
41
|
+
Object.entries(metric).forEach(([key, value]) => {
|
|
42
|
+
updatedTarget.total += value;
|
|
43
|
+
if (key.startsWith(DESKTOP)) {
|
|
44
|
+
updatedTarget.desktop += value;
|
|
45
|
+
} else if (key.startsWith(MOBILE)) {
|
|
46
|
+
updatedTarget.mobile += value;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return updatedTarget; // Return the updated target
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
totals.formview = calculateSums(formview, totals.formview);
|
|
53
|
+
totals.formengagement = calculateSums(formengagement, totals.formengagement);
|
|
54
|
+
totals.pageview = calculateSums(pageview, totals.pageview);
|
|
55
|
+
totals.formsubmit = calculateSums(formsubmit, totals.formsubmit);
|
|
56
|
+
|
|
57
|
+
resultMap.set(url, totals);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return resultMap;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function hasHighPageViews(interval, pageViews) {
|
|
64
|
+
return pageViews > DAILY_PAGEVIEW_THRESHOLD * interval;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function hasLowerConversionRate(formSubmit, formViews) {
|
|
68
|
+
return formSubmit / formViews < CR_THRESHOLD_RATIO;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hasLowFormViews(pageViews, formViews) {
|
|
72
|
+
return formViews > 0 && (formViews / pageViews) < 0.7;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function hasHighPageViewLowFormCtr(ctaPageViews, ctaClicks, ctaPageTotalClicks, formPageViews) {
|
|
76
|
+
return ctaPageTotalClicks > 0
|
|
77
|
+
&& (ctaClicks / ctaPageTotalClicks) < 0.4
|
|
78
|
+
&& (formPageViews / ctaPageViews) < 0.1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns the form urls with high form views and low conversion rate
|
|
83
|
+
*
|
|
84
|
+
* @param {*} formVitalsCollection - form vitals collection
|
|
85
|
+
* @returns {Array} - urls with high form views and low conversion rate
|
|
86
|
+
*/
|
|
87
|
+
export function getHighFormViewsLowConversionMetrics(formVitalsCollection, interval) {
|
|
88
|
+
const resultMap = aggregateFormVitalsByDevice(formVitalsCollection);
|
|
89
|
+
const urls = [];
|
|
90
|
+
resultMap.forEach((metrics, url) => {
|
|
91
|
+
const pageViews = metrics.pageview.total;
|
|
92
|
+
// Default to pageViews if formViews are not available
|
|
93
|
+
const formViews = metrics.formview.total || pageViews;
|
|
94
|
+
const formEngagement = metrics.formengagement.total;
|
|
95
|
+
const formSubmit = metrics.formsubmit.total || formEngagement;
|
|
96
|
+
|
|
97
|
+
if (hasHighPageViews(interval, pageViews) && hasLowerConversionRate(formSubmit, formViews)) {
|
|
98
|
+
urls.push({
|
|
99
|
+
url,
|
|
100
|
+
pageViews,
|
|
101
|
+
formViews,
|
|
102
|
+
formEngagement,
|
|
103
|
+
formSubmit,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
return urls;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Returns the form urls with high page views and low form views
|
|
112
|
+
*
|
|
113
|
+
* @param resultMap
|
|
114
|
+
* @returns {*[]}
|
|
115
|
+
*/
|
|
116
|
+
export function getHighPageViewsLowFormViewsMetrics(formVitalsCollection, interval) {
|
|
117
|
+
const urls = [];
|
|
118
|
+
const resultMap = aggregateFormVitalsByDevice(formVitalsCollection);
|
|
119
|
+
resultMap.forEach((metrics, url) => {
|
|
120
|
+
const { total: pageViews } = metrics.pageview;
|
|
121
|
+
const { total: formViews } = metrics.formview;
|
|
122
|
+
const { total: formEngagement } = metrics.formengagement;
|
|
123
|
+
|
|
124
|
+
if (hasHighPageViews(interval, pageViews) && hasLowFormViews(pageViews, formViews)) {
|
|
125
|
+
urls.push({
|
|
126
|
+
url,
|
|
127
|
+
pageViews,
|
|
128
|
+
formViews,
|
|
129
|
+
formEngagement,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
return urls;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Returns the form urls with high page views containing ctr and low form views
|
|
138
|
+
* @param formVitalsCollection
|
|
139
|
+
* @param formVitalsByDevice
|
|
140
|
+
* @returns {*[]}
|
|
141
|
+
*/
|
|
142
|
+
export function getHighPageViewsLowFormCtrMetrics(formVitalsCollection, interval) {
|
|
143
|
+
const urls = [];
|
|
144
|
+
const formVitalsByDevice = aggregateFormVitalsByDevice(formVitalsCollection);
|
|
145
|
+
formVitalsCollection.forEach((entry) => {
|
|
146
|
+
const { forminternalnavigation, pageview } = entry;
|
|
147
|
+
// Calculate `x`: sum of pageview for the URL with the highest sum
|
|
148
|
+
let x = 0;
|
|
149
|
+
let maxPageviewUrl = null;
|
|
150
|
+
if (forminternalnavigation) {
|
|
151
|
+
forminternalnavigation.forEach((nav) => {
|
|
152
|
+
if (nav.pageview) {
|
|
153
|
+
const pageviewSum = Object.values(nav.pageview).reduce((sum, val) => sum + val, 0);
|
|
154
|
+
if (pageviewSum > x) {
|
|
155
|
+
x = pageviewSum;
|
|
156
|
+
maxPageviewUrl = nav;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Skip entry if no valid maxPageviewUrl is found
|
|
163
|
+
if (!maxPageviewUrl) return;
|
|
164
|
+
|
|
165
|
+
// Calculate `y`: find the CTA with the highest clicks and include the source
|
|
166
|
+
const y = maxPageviewUrl.CTAs.reduce((maxCta, cta) => {
|
|
167
|
+
if (cta.clicks > (maxCta.clicks || 0)) {
|
|
168
|
+
return cta;
|
|
169
|
+
}
|
|
170
|
+
return maxCta;
|
|
171
|
+
}, { clicks: 0, source: '' });
|
|
172
|
+
|
|
173
|
+
// Get `z`: totalClicksOnPage for the matched URL
|
|
174
|
+
const z = maxPageviewUrl.totalClicksOnPage || 0;
|
|
175
|
+
// Calculate `f`: sum of `pageview` for `formengagement`
|
|
176
|
+
const f = Object.values(pageview).reduce((sum, val) => sum + val, 0);
|
|
177
|
+
|
|
178
|
+
// Evaluate conditions and add URL to the result if all are met
|
|
179
|
+
if (hasHighPageViews(interval, x) && hasHighPageViewLowFormCtr(x, y.clicks, z, f)) {
|
|
180
|
+
const deviceData = formVitalsByDevice.get(entry.url);
|
|
181
|
+
if (deviceData != null) {
|
|
182
|
+
urls.push({
|
|
183
|
+
url: entry.url,
|
|
184
|
+
pageViews: deviceData.pageview.total,
|
|
185
|
+
formViews: deviceData.formview.total,
|
|
186
|
+
formEngagement: deviceData.formengagement.total,
|
|
187
|
+
CTA: {
|
|
188
|
+
url: maxPageviewUrl.url,
|
|
189
|
+
source: y.source,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
return urls;
|
|
196
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -163,7 +163,32 @@ declare function replacePlaceholders(content: string, placeholders: object): str
|
|
|
163
163
|
* or null if an error occurs.
|
|
164
164
|
*/
|
|
165
165
|
declare function getPrompt(placeholders: object, filename: string, log: object):
|
|
166
|
-
Promise<string|null>;
|
|
166
|
+
Promise<string | null>;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Retrieves the high-form-view-low-form-conversion metrics from the provided array of form vitals.
|
|
170
|
+
* @param {Object[]} formVitals - An array of form vitals.
|
|
171
|
+
* @param {number} interval - The interval in days.
|
|
172
|
+
* @returns {Object[]} - An array of high-form-view-low-form-conversion metrics.
|
|
173
|
+
*/
|
|
174
|
+
declare function getHighFormViewsLowConversionMetrics(formVitals: object[], interval: number):
|
|
175
|
+
object[];
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Retrieves the high-page-view-low-form-view metrics from the provided array of form vitals.
|
|
179
|
+
* @param {Object[]} formVitals - An array of form vitals.
|
|
180
|
+
* @returns {Object[]} - An array of high-page-view-low-form-view metrics.
|
|
181
|
+
*/
|
|
182
|
+
declare function getHighPageViewsLowFormViewsMetrics(formVitals: object[], interval: number):
|
|
183
|
+
object[];
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Retrieves the high-page-view-low-form-ctr metrics from the provided array of form vitals.
|
|
187
|
+
* @param {Object[]} formVitals - An array of form vitals.
|
|
188
|
+
* @returns {Object[]} - An array of high-page-view-low-form-ctr metrics.
|
|
189
|
+
*/
|
|
190
|
+
declare function getHighPageViewsLowFormCtrMetrics(formVitals: object[], interval: number):
|
|
191
|
+
object[];
|
|
167
192
|
|
|
168
193
|
/**
|
|
169
194
|
* Retrieves stored metrics from S3.
|
|
@@ -179,7 +204,7 @@ declare function getPrompt(placeholders: object, filename: string, log: object):
|
|
|
179
204
|
* @returns {Promise<any|*[]>} - The stored metrics
|
|
180
205
|
*/
|
|
181
206
|
export function getStoredMetrics(config: object, context: object):
|
|
182
|
-
|
|
207
|
+
Promise<Array<object>>;
|
|
183
208
|
|
|
184
209
|
/**
|
|
185
210
|
* Stores metrics in S3.
|
|
@@ -198,8 +223,8 @@ export function getStoredMetrics(config: object, context: object):
|
|
|
198
223
|
export function storeMetrics(content: object, config: object, context: object): Promise<string>;
|
|
199
224
|
|
|
200
225
|
export function s3Wrapper(fn: (request: object, context: object) => Promise<Response>):
|
|
201
|
-
|
|
226
|
+
(request: object, context: object) => Promise<Response>;
|
|
202
227
|
|
|
203
|
-
export function fetch(url: string|Request, options?: RequestOptions): Promise<Response>;
|
|
228
|
+
export function fetch(url: string | Request, options?: RequestOptions): Promise<Response>;
|
|
204
229
|
|
|
205
|
-
export function tracingFetch(url: string|Request, options?: RequestOptions): Promise<Response>;
|
|
230
|
+
export function tracingFetch(url: string | Request, options?: RequestOptions): Promise<Response>;
|
package/src/index.js
CHANGED
|
@@ -61,3 +61,4 @@ export { s3Wrapper } from './s3.js';
|
|
|
61
61
|
|
|
62
62
|
export { fetch } from './adobe-fetch.js';
|
|
63
63
|
export { tracingFetch } from './tracing-fetch.js';
|
|
64
|
+
export { getHighFormViewsLowConversionMetrics, getHighPageViewsLowFormViewsMetrics, getHighPageViewsLowFormCtrMetrics } from './formcalc.js';
|