@adobe/spacecat-shared-rum-api-client 2.24.2 → 2.25.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.25.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.24.3...@adobe/spacecat-shared-rum-api-client-v2.25.0) (2025-05-16)
2
+
3
+
4
+ ### Features
5
+
6
+ * optel traffic group metrics ([#741](https://github.com/adobe/spacecat-shared/issues/741)) ([cdfef6a](https://github.com/adobe/spacecat-shared/commit/cdfef6a42731bf5fb5056619df605e9db67e724e))
7
+
8
+ # [@adobe/spacecat-shared-rum-api-client-v2.24.3](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.24.2...@adobe/spacecat-shared-rum-api-client-v2.24.3) (2025-05-15)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * detect iframe forms; bug fix for duplicated iframes with # ([#736](https://github.com/adobe/spacecat-shared/issues/736)) ([5243220](https://github.com/adobe/spacecat-shared/commit/524322096233e393f59899deb9de24bdab201275))
14
+
1
15
  # [@adobe/spacecat-shared-rum-api-client-v2.24.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.24.1...@adobe/spacecat-shared-rum-api-client-v2.24.2) (2025-05-11)
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.24.2",
3
+ "version": "2.25.0",
4
4
  "description": "Shared modules of the Spacecat Services - Rum API client",
5
5
  "type": "module",
6
6
  "engines": {
@@ -14,3 +14,95 @@ export const GRANULARITY = {
14
14
  HOURLY: 'HOURLY',
15
15
  DAILY: 'DAILY',
16
16
  };
17
+
18
+ export const COOKIE_CONSENT_SELECTORS = [
19
+ // OneTrust
20
+ 'onetrust',
21
+ '.onetrust-banner',
22
+ '#onetrust-consent-sdk',
23
+ '#onetrust-banner-sdk',
24
+ '#ot-sdk-cookie-policy',
25
+ '.ot-sdk-container',
26
+ '.ot-sdk-row',
27
+ '.ot-sdk-column',
28
+ '.ot-sdk-show-settings',
29
+ '#ot-',
30
+ '.ot-',
31
+
32
+ // Cookiebot / Usercentrics
33
+ '#CybotCookiebotDialog',
34
+ '.CybotCookiebotDialog',
35
+ '#CookiebotWidget',
36
+ '.CookiebotWidget',
37
+ '.cookie-consent__banner',
38
+ '.cookie-consent__dialog',
39
+ '.cookie-consent__button',
40
+
41
+ // TrustArc
42
+ 'truste-',
43
+ '#truste-',
44
+ '#truste-consent-banner',
45
+ '.truste-consent-banner',
46
+ '#truste-consent-button',
47
+ '.truste-consent-button',
48
+ '.truste-consent-text',
49
+ '.truste-consent-close',
50
+
51
+ // CookieYes
52
+ '#cookieyes-banner',
53
+ '.cky-consent',
54
+ '.cky-banner',
55
+ '.cky-btn',
56
+ '.cky-btn-accept',
57
+ '.cky-btn-reject',
58
+ '.cky-btn-settings',
59
+
60
+ // Termly
61
+ '#termly-consent-banner',
62
+ '.termly-consent-banner',
63
+ '.termly-consent-button',
64
+ '.termly-consent-text',
65
+ '.termly-consent-close',
66
+
67
+ // CookieScript
68
+ '#cookiescript_injected',
69
+ '.cookiescript-consent',
70
+ '.cookiescript-banner',
71
+ '.cookiescript-btn',
72
+ '.cookiescript-btn-accept',
73
+ '.cookiescript-btn-reject',
74
+ '.cookiescript-btn-settings',
75
+
76
+ // CookieFirst
77
+ '#cookiefirst-root',
78
+ '.cookiefirst-banner',
79
+ '.cookiefirst-button',
80
+ '.cookiefirst-text',
81
+ '.cookiefirst-close',
82
+
83
+ // Quantcast Choice
84
+ '#qc-cmp2-ui',
85
+ '.qc-cmp2-ui',
86
+ '.qc-cmp2-summary',
87
+ '.qc-cmp2-footer',
88
+ '.qc-cmp2-header',
89
+ '.qc-cmp2-buttons',
90
+ '.qc-cmp2-button',
91
+ '.qc-cmp2-button-accept',
92
+ '.qc-cmp2-button-reject',
93
+
94
+ // Crownpeak (Evidon)
95
+ '#evidon-banner',
96
+ '.evidon-banner',
97
+ '.evidon-button',
98
+ '.evidon-text',
99
+ '.evidon-close',
100
+
101
+ // Popupsmart
102
+ '#popupsmart-consent',
103
+ '.popupsmart-consent',
104
+ '.popupsmart-banner',
105
+ '.popupsmart-button',
106
+ '.popupsmart-text',
107
+ '.popupsmart-close',
108
+ ];
@@ -0,0 +1,48 @@
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
+ import { hasText, isNonEmptyObject } from '@adobe/spacecat-shared-utils';
14
+ import { COOKIE_CONSENT_SELECTORS } from './constants.js';
15
+
16
+ const uncategorized = 'uncategorized';
17
+
18
+ export function getPageType(bundle, pageTypes) {
19
+ if (!isNonEmptyObject(pageTypes)) {
20
+ return uncategorized;
21
+ }
22
+
23
+ const pageTypeEntries = Object.entries(pageTypes);
24
+
25
+ const classify = ([, regEx]) => {
26
+ if (regEx instanceof RegExp) {
27
+ return regEx.test(bundle.url);
28
+ }
29
+
30
+ return new RegExp(regEx).test(bundle.url);
31
+ };
32
+
33
+ const entry = pageTypeEntries.find(classify);
34
+
35
+ if (!hasText(entry?.[0]) || entry?.[0] === 'other | Other Pages') {
36
+ return uncategorized;
37
+ }
38
+ return entry?.[0] ?? uncategorized;
39
+ }
40
+
41
+ export function isConsentClick(source) {
42
+ if (typeof source !== 'string' || !source) {
43
+ return false;
44
+ }
45
+
46
+ const sourceLower = source.toLowerCase();
47
+ return COOKIE_CONSENT_SELECTORS.some((keyword) => sourceLower.includes(keyword.toLowerCase()));
48
+ }
@@ -214,6 +214,17 @@ function handler(bundles) {
214
214
  });
215
215
  });
216
216
 
217
+ // remove duplicate urls with '#'
218
+ const iframeParentMapWithoutDuplicates = Object.fromEntries(
219
+ Object.entries(iframeParentMap).filter(([key]) => {
220
+ if (key.endsWith('#')) {
221
+ const baseUrl = key.slice(0, -1);
222
+ return !Object.prototype.hasOwnProperty.call(iframeParentMap, baseUrl);
223
+ }
224
+ return true;
225
+ }),
226
+ );
227
+
217
228
  // traffic acquisition data per url - uncomment this when required
218
229
  // const trafficByUrl = trafficAcquisition.handler(bundles);
219
230
  // const trafficByUrlMap = Object.fromEntries(
@@ -256,7 +267,7 @@ function handler(bundles) {
256
267
  const iframeParentVitalsMap = getParentPageVitalsGroupedByIFrame(
257
268
  bundles,
258
269
  dataChunks,
259
- iframeParentMap,
270
+ iframeParentMapWithoutDuplicates,
260
271
  );
261
272
 
262
273
  // populate internal navigation data
@@ -0,0 +1,234 @@
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
+ import { DataChunks } from '@adobe/rum-distiller';
14
+ import { DELIMITER, generateKey, loadBundles } from '../utils.js';
15
+ import { classifyTraffic } from '../common/traffic.js';
16
+ import { getPageType, isConsentClick } from '../common/page.js';
17
+
18
+ function getTrafficSource(bundle) {
19
+ return classifyTraffic(bundle).type;
20
+ }
21
+
22
+ function getDeviceType(bundle) {
23
+ return bundle.userAgent.split(':')?.[0];
24
+ }
25
+
26
+ function addPageTypeFacet(dataChunks, pageTypes) {
27
+ dataChunks.addFacet('pageType', (bundle) => getPageType(bundle, pageTypes));
28
+ }
29
+
30
+ function addPageTypeDeviceTypeFacet(dataChunks, pageTypes) {
31
+ dataChunks.addFacet('pageTypeDeviceTypes', (bundle) => {
32
+ const deviceType = getDeviceType(bundle);
33
+ const pageType = getPageType(bundle, pageTypes);
34
+ return generateKey(pageType, deviceType);
35
+ });
36
+ }
37
+
38
+ function addPageTypeTrafficSourceDeviceTypes(dataChunks, pageTypes) {
39
+ dataChunks.addFacet('pageTrafficDeviceTypes', (bundle) => {
40
+ const deviceType = getDeviceType(bundle);
41
+ const pageType = getPageType(bundle, pageTypes);
42
+ return generateKey(pageType, getTrafficSource(bundle), deviceType);
43
+ });
44
+ }
45
+
46
+ function addPageTypeTrafficSourceFacet(dataChunks, pageTypes) {
47
+ dataChunks.addFacet('pageTypeTrafficSources', (bundle) => {
48
+ const pageType = getPageType(bundle, pageTypes);
49
+ return generateKey(pageType, getTrafficSource(bundle));
50
+ });
51
+ }
52
+
53
+ function handler(bundles, options = { pageTypes: null }) {
54
+ const dataChunks = new DataChunks();
55
+ const { pageTypes: pageTypeOpt } = options;
56
+
57
+ loadBundles(bundles, dataChunks);
58
+
59
+ const metricFilter = (metrics) => {
60
+ const { ctr, enters, sumOfAllClicks } = metrics;
61
+ return {
62
+ ctr: ctr.sum / ctr.weight,
63
+ clickedSessions: ctr.sum,
64
+ totalSessions: ctr.weight,
65
+ sessionsWithEnter: enters.sum,
66
+ clicksOverViews: ctr.weight ? ctr.sum / ctr.weight : 0,
67
+ bounceRate: ctr.weight ? (1 - (ctr.sum / ctr.weight)) : 0,
68
+ totalNumClicks: sumOfAllClicks.sum,
69
+ avgClicksPerSession: ctr.sum ? sumOfAllClicks.sum / ctr.sum : 0,
70
+ };
71
+ };
72
+
73
+ dataChunks.addFacet('urls', (bundle) => bundle.url);
74
+
75
+ dataChunks.addFacet('trafficSources', (bundle) => getTrafficSource(bundle));
76
+
77
+ dataChunks.addFacet('urlTrafficSources', (bundle) => generateKey(bundle.url, getTrafficSource(bundle)));
78
+
79
+ dataChunks.addFacet('urlDeviceTypes', (bundle) => generateKey(bundle.url, getDeviceType(bundle)));
80
+
81
+ dataChunks.addFacet('deviceTypes', (bundle) => getDeviceType(bundle));
82
+
83
+ dataChunks.addFacet('urlTrafficSourceDeviceTypes', (bundle) => generateKey(bundle.url, getTrafficSource(bundle), getDeviceType(bundle)));
84
+
85
+ dataChunks.addFacet('deviceTypeTrafficSources', (bundle) => generateKey(getDeviceType(bundle), getTrafficSource(bundle)));
86
+
87
+ addPageTypeFacet(dataChunks, pageTypeOpt);
88
+
89
+ addPageTypeTrafficSourceFacet(dataChunks, pageTypeOpt);
90
+
91
+ addPageTypeDeviceTypeFacet(dataChunks, pageTypeOpt);
92
+
93
+ addPageTypeTrafficSourceDeviceTypes(dataChunks, pageTypeOpt);
94
+
95
+ dataChunks.addSeries('ctr', (bundle) => {
96
+ const isClicked = bundle.events.some((e) => e.checkpoint === 'click');
97
+ return isClicked ? bundle.weight : 0;
98
+ });
99
+
100
+ dataChunks.addSeries('sumOfAllClicks', (bundle) => {
101
+ const nonConsentClicks = bundle.events
102
+ .filter((e) => e.checkpoint === 'click' && !isConsentClick(e.source))
103
+ .map(() => bundle.weight)
104
+ .reduce((sum, weight) => sum + weight, 0);
105
+
106
+ return nonConsentClicks;
107
+ });
108
+
109
+ dataChunks.addSeries('enters', (bundle) => {
110
+ const containsEnter = bundle.events.some((e) => e.checkpoint === 'enter');
111
+ return containsEnter ? bundle.weight : 0;
112
+ });
113
+
114
+ const urls = dataChunks.facets.urls.map((facet) => {
115
+ const url = facet.value;
116
+ return {
117
+ ...metricFilter(facet.metrics),
118
+ url,
119
+ };
120
+ });
121
+
122
+ const pageType = dataChunks.facets.pageType.map((facet) => {
123
+ const type = facet.value;
124
+ return {
125
+ ...metricFilter(facet.metrics),
126
+ type,
127
+ };
128
+ });
129
+
130
+ const deviceTypes = dataChunks.facets.deviceTypes.map((facet) => {
131
+ const deviceType = facet.value;
132
+ return {
133
+ ...metricFilter(facet.metrics),
134
+ deviceType,
135
+ };
136
+ });
137
+
138
+ const urlDeviceTypes = dataChunks.facets.urlDeviceTypes.map((facet) => {
139
+ const [url, deviceType] = facet.value.split(DELIMITER);
140
+ return {
141
+ ...metricFilter(facet.metrics),
142
+ url,
143
+ deviceType,
144
+ };
145
+ });
146
+
147
+ const trafficSources = dataChunks.facets.trafficSources.map((facet) => {
148
+ const source = facet.value;
149
+ return {
150
+ ...metricFilter(facet.metrics),
151
+ source,
152
+ };
153
+ });
154
+
155
+ const urlTrafficSources = dataChunks.facets.urlTrafficSources.map((facet) => {
156
+ const [url, source] = facet.value.split(DELIMITER);
157
+ return {
158
+ ...metricFilter(facet.metrics),
159
+ url,
160
+ source,
161
+ };
162
+ });
163
+
164
+ const urlTrafficSourceDeviceTypes = dataChunks.facets.urlTrafficSourceDeviceTypes.map((facet) => {
165
+ const [url, source, deviceType] = facet.value.split(DELIMITER);
166
+ return {
167
+ ...metricFilter(facet.metrics),
168
+ url,
169
+ source,
170
+ deviceType,
171
+ };
172
+ });
173
+
174
+ const pageTypeTrafficSources = dataChunks.facets.pageTypeTrafficSources.map((facet) => {
175
+ const [type, source] = facet.value.split(DELIMITER);
176
+ return {
177
+ ...metricFilter(facet.metrics),
178
+ type,
179
+ source,
180
+ };
181
+ });
182
+
183
+ const pageTypeDeviceTypes = dataChunks.facets.pageTypeDeviceTypes.map((facet) => {
184
+ const [type, deviceType] = facet.value.split(DELIMITER);
185
+ return {
186
+ ...metricFilter(facet.metrics),
187
+ type,
188
+ deviceType,
189
+ };
190
+ });
191
+
192
+ const deviceTypeTrafficSources = dataChunks.facets.deviceTypeTrafficSources.map((facet) => {
193
+ const [deviceType, source] = facet.value.split(DELIMITER);
194
+ return {
195
+ ...metricFilter(facet.metrics),
196
+ deviceType,
197
+ source,
198
+ };
199
+ });
200
+
201
+ const pageTrafficDeviceTypes = dataChunks.facets.pageTrafficDeviceTypes.map((facet) => {
202
+ const [type, source, deviceType] = facet.value.split(DELIMITER);
203
+ return {
204
+ ...metricFilter(facet.metrics),
205
+ type,
206
+ source,
207
+ deviceType,
208
+ };
209
+ });
210
+
211
+ const metrics = {
212
+ url: urls,
213
+ urlTrafficSource: urlTrafficSources,
214
+ urlDeviceType: urlDeviceTypes,
215
+ urlTrafficSourceDeviceType: urlTrafficSourceDeviceTypes,
216
+ pageType,
217
+ pageTypeTrafficSource: pageTypeTrafficSources,
218
+ pageTypeDeviceType: pageTypeDeviceTypes,
219
+ pageTypeTrafficSourceDeviceType: pageTrafficDeviceTypes,
220
+ deviceType: deviceTypes,
221
+ deviceTypeTrafficSource: deviceTypeTrafficSources,
222
+ trafficSource: trafficSources,
223
+ };
224
+
225
+ return Object.entries(metrics)
226
+ .map(([key, value]) => ({
227
+ key,
228
+ value,
229
+ }));
230
+ }
231
+
232
+ export default {
233
+ handler,
234
+ };
package/src/index.js CHANGED
@@ -20,6 +20,7 @@ import trafficAcquisition from './functions/traffic-acquisition.js';
20
20
  import totalMetrics from './functions/total-metrics.js';
21
21
  import variant from './functions/variant.js';
22
22
  import pageviews from './functions/pageviews.js';
23
+ import trafficMetrics from './functions/traffic-metrics.js';
23
24
  import rageclick from './functions/opportunities/rageclick.js';
24
25
  import highInorganicHighBounceRate from './functions/opportunities/high-inorganic-high-bounce-rate.js';
25
26
  import highOrganicLowCtr from './functions/opportunities/high-organic-low-ctr.js';
@@ -40,6 +41,7 @@ const HANDLERS = {
40
41
  'high-inorganic-high-bounce-rate': highInorganicHighBounceRate,
41
42
  'high-organic-low-ctr': highOrganicLowCtr,
42
43
  pageviews,
44
+ trafficMetrics,
43
45
  };
44
46
 
45
47
  function sanitize(opts) {