@adobe/spacecat-shared-rum-api-client 2.24.3 → 2.25.1

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.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.25.0...@adobe/spacecat-shared-rum-api-client-v2.25.1) (2025-05-19)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * use rum bundler for consent classification ([#744](https://github.com/adobe/spacecat-shared/issues/744)) ([251ca76](https://github.com/adobe/spacecat-shared/commit/251ca76ec65c3775fdc1656143cfd1767bfb9b1c))
7
+
8
+ # [@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)
9
+
10
+
11
+ ### Features
12
+
13
+ * optel traffic group metrics ([#741](https://github.com/adobe/spacecat-shared/issues/741)) ([cdfef6a](https://github.com/adobe/spacecat-shared/commit/cdfef6a42731bf5fb5056619df605e9db67e724e))
14
+
1
15
  # [@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)
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.3",
3
+ "version": "2.25.1",
4
4
  "description": "Shared modules of the Spacecat Services - Rum API client",
5
5
  "type": "module",
6
6
  "engines": {
@@ -0,0 +1,44 @@
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, isString } from '@adobe/spacecat-shared-utils';
14
+ import classifyConsent from '@adobe/rum-distiller/consent.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
+ const consent = isString(source) ? classifyConsent(source?.toLocaleLowerCase()) : undefined;
43
+ return !(consent === undefined);
44
+ }
@@ -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) {