@adobe/spacecat-shared-rum-api-client 2.23.3 → 2.24.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 CHANGED
@@ -1,3 +1,17 @@
1
+ # [@adobe/spacecat-shared-rum-api-client-v2.24.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.23.4...@adobe/spacecat-shared-rum-api-client-v2.24.0) (2025-05-09)
2
+
3
+
4
+ ### Features
5
+
6
+ * identify iframe forms ([#715](https://github.com/adobe/spacecat-shared/issues/715)) ([d2e0a47](https://github.com/adobe/spacecat-shared/commit/d2e0a47f1020471a6f22f9d713e6bf9c03df2279))
7
+
8
+ # [@adobe/spacecat-shared-rum-api-client-v2.23.4](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.23.3...@adobe/spacecat-shared-rum-api-client-v2.23.4) (2025-04-30)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * form source proper handling in form vitals ([#711](https://github.com/adobe/spacecat-shared/issues/711)) ([e37bf0a](https://github.com/adobe/spacecat-shared/commit/e37bf0a6df28952a0e51f8689c4016a9bbff82e0))
14
+
1
15
  # [@adobe/spacecat-shared-rum-api-client-v2.23.3](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.23.2...@adobe/spacecat-shared-rum-api-client-v2.23.3) (2025-04-26)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-rum-api-client",
3
- "version": "2.23.3",
3
+ "version": "2.24.0",
4
4
  "description": "Shared modules of the Spacecat Services - Rum API client",
5
5
  "type": "module",
6
6
  "engines": {
@@ -14,7 +14,7 @@ import { DataChunks } from '@adobe/rum-distiller';
14
14
  import { generateKey, DELIMITER, loadBundles } from '../utils.js';
15
15
 
16
16
  const METRICS = ['formview', 'formengagement', 'formsubmit'];
17
- const CHECKPOINTS = ['viewblock', 'click', 'fill', 'formsubmit', 'navigate'];
17
+ const CHECKPOINTS = ['viewblock', 'click', 'fill', 'formsubmit', 'navigate', 'viewmedia'];
18
18
  const KEYWORDS_TO_FILTER = ['search'];
19
19
 
20
20
  function initializeResult(url) {
@@ -41,7 +41,7 @@ function filterEvents(bundles) {
41
41
  }
42
42
 
43
43
  const isFormRelatedEvent = ['fill', 'formsubmit'].includes(event.checkpoint)
44
- || /\bform\b/.test(event.source);
44
+ || /\bform\b|aemform\w*/i.test(event.source);
45
45
  return isFormRelatedEvent && !KEYWORDS_TO_FILTER.some((keyword) => event.source
46
46
  && event.source.toLowerCase().includes(keyword));
47
47
  }),
@@ -82,6 +82,7 @@ function populateFormsInternalNavigation(bundles, formVitals) {
82
82
  dataChunks.filter = { checkpoint: ['navigate'] };
83
83
  dataChunks.filtered.forEach((bundle) => {
84
84
  const formInternalNav = bundle.events.find((e) => e.checkpoint === 'navigate');
85
+
85
86
  const formVital = findByUrl(formVitals, bundle.url);
86
87
  if (formInternalNav && formVital
87
88
  && !formVital.forminternalnavigation
@@ -89,7 +90,7 @@ function populateFormsInternalNavigation(bundles, formVitals) {
89
90
  const fv = findByUrl(formVitals, formInternalNav.source);
90
91
  formVital.forminternalnavigation.push({
91
92
  url: formInternalNav.source,
92
- pageview: fv?.pageview,
93
+ ...(fv && { pageview: fv.pageview }),
93
94
  });
94
95
  }
95
96
  });
@@ -133,8 +134,58 @@ function containsFormVitals(row) {
133
134
  return METRICS.some((metric) => Object.keys(row[metric]).length > 0);
134
135
  }
135
136
 
137
+ function getParentPageVitalsGroupedByIFrame(bundles, dataChunks, iframeParentMap) {
138
+ const iframeVitals = {};
139
+ if (dataChunks.facets.urlUserAgents) {
140
+ dataChunks.facets.urlUserAgents.reduce((acc, { value, weight }) => {
141
+ const [url, userAgent] = value.split(DELIMITER);
142
+
143
+ let iframeSrc = null;
144
+ for (const iframeUrl of Object.keys(iframeParentMap)) {
145
+ for (const parentUrl of iframeParentMap[iframeUrl]) {
146
+ if (parentUrl === url) {
147
+ iframeSrc = iframeUrl;
148
+ break;
149
+ }
150
+ }
151
+ }
152
+ if (iframeSrc) {
153
+ acc[url] = acc[url] || { url, pageview: {}, forminternalnavigation: [] };
154
+ acc[url].pageview[userAgent] = acc[url].pageview[userAgent] || weight;
155
+ acc[url].iframeSrc = iframeSrc;
156
+ }
157
+ return acc;
158
+ }, iframeVitals);
159
+ }
160
+ const groupedByIframeSrc = {};
161
+ const parentWebVitals = {};
162
+
163
+ // select the parent page with the most views
164
+ for (const [url, obj] of Object.entries(iframeVitals)) {
165
+ const { iframeSrc } = obj;
166
+ const totalViews = (obj.pageview.mobile || 0) + (obj.pageview.desktop || 0);
167
+ if (!groupedByIframeSrc[iframeSrc] || totalViews > groupedByIframeSrc[iframeSrc].totalViews) {
168
+ groupedByIframeSrc[iframeSrc] = { url, totalViews };
169
+ }
170
+ }
171
+
172
+ for (const { url } of Object.values(groupedByIframeSrc)) {
173
+ parentWebVitals[url] = iframeVitals[url];
174
+ }
175
+
176
+ populateFormsInternalNavigation(bundles, parentWebVitals);
177
+ findFormCTAForInternalNavigation(bundles, Object.values(parentWebVitals));
178
+
179
+ const iframeParentVitalsMap = {};
180
+ for (const vitals of Object.values(parentWebVitals)) {
181
+ iframeParentVitalsMap[vitals.iframeSrc] = vitals;
182
+ }
183
+ return iframeParentVitalsMap;
184
+ }
185
+
136
186
  function handler(bundles) {
137
187
  // Filter out search related events
188
+
138
189
  const bundlesWithFilteredEvents = filterEvents(bundles);
139
190
 
140
191
  const dataChunks = new DataChunks();
@@ -143,17 +194,26 @@ function handler(bundles) {
143
194
  const formViewdataChunks = new DataChunks();
144
195
  loadBundles(bundlesWithFilteredEvents, formViewdataChunks);
145
196
  const formSourceMap = {};
197
+ const iframeParentMap = {};
146
198
  const globalFormSourceSet = new Set();
147
- formViewdataChunks.filter = { checkpoint: ['viewblock'] };
199
+ formViewdataChunks.filter = { checkpoint: ['viewblock', 'viewmedia'] };
148
200
  formViewdataChunks.filtered.forEach(({ url, events }) => {
149
201
  formSourceMap[url] = formSourceMap[url] || new Set();
150
- events.forEach(({ checkpoint, source }) => {
202
+ events.forEach(({ checkpoint, source, target }) => {
151
203
  if (checkpoint === 'viewblock' && source) {
152
204
  formSourceMap[url].add(source);
153
205
  globalFormSourceSet.add(source);
154
206
  }
207
+ if (checkpoint === 'viewmedia' && target) {
208
+ const regex = /aemform[\w.]*\.iframe[\w.]*/;
209
+ if (regex.test(target)) {
210
+ iframeParentMap[target] = iframeParentMap[target] || new Set();
211
+ iframeParentMap[target].add(url);
212
+ }
213
+ }
155
214
  });
156
215
  });
216
+
157
217
  // traffic acquisition data per url - uncomment this when required
158
218
  // const trafficByUrl = trafficAcquisition.handler(bundles);
159
219
  // const trafficByUrlMap = Object.fromEntries(
@@ -176,25 +236,57 @@ function handler(bundles) {
176
236
  // aggregates metrics per group (url and user agent)
177
237
  dataChunks.facets.urlUserAgents.reduce((acc, { value, metrics, weight }) => {
178
238
  const [url, userAgent] = value.split(DELIMITER);
179
- const key = formSourceMap[url].has(source) ? generateKey(url, source) : url;
180
- acc[key] = acc[key] || initializeResult(url);
181
- acc[key].pageview[userAgent] = acc[key].pageview[userAgent] || weight;
182
- // Enable traffic acquisition for persistence by uncommenting this line
183
- // acc[key].trafficacquisition = trafficByUrlMap[url];
184
- acc[key].formsource = source;
185
- METRICS.filter((metric) => metrics[metric].sum) // filter out user-agents with no form vitals
186
- .forEach((metric) => {
187
- acc[key][metric][userAgent] = metrics[metric].sum;
188
- });
239
+ if (formSourceMap[url].has(source)) {
240
+ const key = generateKey(url, source);
241
+ acc[key] = acc[key] || initializeResult(url);
242
+ acc[key].pageview[userAgent] = acc[key].pageview[userAgent] || weight;
243
+ // Enable traffic acquisition for persistence by uncommenting this line
244
+ // acc[key].trafficacquisition = trafficByUrlMap[url];
245
+ acc[key].formsource = source;
246
+ // filter out user-agents with no form vitals
247
+ METRICS.filter((metric) => metrics[metric].sum)
248
+ .forEach((metric) => {
249
+ acc[key][metric][userAgent] = metrics[metric].sum;
250
+ });
251
+ }
189
252
  return acc;
190
253
  }, formVitals);
191
254
  });
255
+
256
+ const iframeParentVitalsMap = getParentPageVitalsGroupedByIFrame(
257
+ bundles,
258
+ dataChunks,
259
+ iframeParentMap,
260
+ );
261
+
192
262
  // populate internal navigation data
193
263
  populateFormsInternalNavigation(bundles, formVitals);
194
264
  // filter out pages with no form vitals
195
265
  const filteredFormVitals = Object.values(formVitals).filter(containsFormVitals);
196
266
  findFormCTAForInternalNavigation(bundles, filteredFormVitals);
197
- return filteredFormVitals;
267
+
268
+ const updatedFormVitals = filteredFormVitals.map((formVital) => {
269
+ const formVitalCopy = { ...formVital };
270
+ const parentFormVital = iframeParentVitalsMap[formVital.url];
271
+ if (parentFormVital) {
272
+ const {
273
+ url,
274
+ pageview,
275
+ forminternalnavigation,
276
+ iframeSrc,
277
+ } = parentFormVital;
278
+ Object.assign(formVitalCopy, {
279
+ url,
280
+ pageview: { ...pageview },
281
+ forminternalnavigation,
282
+
283
+ iframeSrc,
284
+ });
285
+ }
286
+ return formVitalCopy;
287
+ });
288
+
289
+ return [...updatedFormVitals];
198
290
  }
199
291
 
200
292
  export default {