@adobe/spacecat-shared-rum-api-client 2.30.0 → 2.31.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,10 @@
1
+ # [@adobe/spacecat-shared-rum-api-client-v2.31.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.30.0...@adobe/spacecat-shared-rum-api-client-v2.31.0) (2025-06-25)
2
+
3
+
4
+ ### Features
5
+
6
+ * expose urls with most paid traffic ([#825](https://github.com/adobe/spacecat-shared/issues/825)) ([88dde01](https://github.com/adobe/spacecat-shared/commit/88dde01843ae32643a54d93868b7ef7eec10a62a))
7
+
1
8
  # [@adobe/spacecat-shared-rum-api-client-v2.30.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.29.0...@adobe/spacecat-shared-rum-api-client-v2.30.0) (2025-06-25)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-rum-api-client",
3
- "version": "2.30.0",
3
+ "version": "2.31.0",
4
4
  "description": "Shared modules of the Spacecat Services - Rum API client",
5
5
  "type": "module",
6
6
  "engines": {
@@ -15,8 +15,21 @@ import { DELIMITER, generateKey, loadBundles } from '../utils.js';
15
15
  import { classifyTraffic } from '../common/traffic.js';
16
16
  import { getPageType, isConsentClick } from '../common/page.js';
17
17
 
18
- function getTrafficSource(bundle) {
19
- return classifyTraffic(bundle).type;
18
+ function getTrafficSource(bundle, memo) {
19
+ const id = `${bundle.id}-${bundle.url}-${bundle.time}`;
20
+ if (id in memo) {
21
+ return memo[id];
22
+ }
23
+ const result = classifyTraffic(bundle);
24
+ // eslint-disable-next-line no-param-reassign
25
+ memo[id] = result;
26
+ return result;
27
+ }
28
+
29
+ function getTrafficSourceKey(bundle, memo) {
30
+ const classifyResult = getTrafficSource(bundle, memo);
31
+ const { type, category, vendor } = classifyResult;
32
+ return `${type}:${category}:${vendor}`;
20
33
  }
21
34
 
22
35
  function getDeviceType(bundle) {
@@ -35,36 +48,54 @@ function addPageTypeDeviceTypeFacet(dataChunks, pageTypes) {
35
48
  });
36
49
  }
37
50
 
38
- function addPageTypeTrafficSourceDeviceTypes(dataChunks, pageTypes) {
51
+ function addPageTypeTrafficSourceDeviceTypes(dataChunks, pageTypes, memo) {
39
52
  dataChunks.addFacet('pageTrafficDeviceTypes', (bundle) => {
40
53
  const deviceType = getDeviceType(bundle);
41
54
  const pageType = getPageType(bundle, pageTypes);
42
- return generateKey(pageType, getTrafficSource(bundle), deviceType);
55
+ return generateKey(pageType, getTrafficSourceKey(bundle, memo), deviceType);
43
56
  });
44
57
  }
45
58
 
46
- function addPageTypeTrafficSourceFacet(dataChunks, pageTypes) {
59
+ function addPageTypeTrafficSourceFacet(dataChunks, pageTypes, memo) {
47
60
  dataChunks.addFacet('pageTypeTrafficSources', (bundle) => {
48
61
  const pageType = getPageType(bundle, pageTypes);
49
- return generateKey(pageType, getTrafficSource(bundle));
62
+ return generateKey(pageType, getTrafficSourceKey(bundle, memo));
50
63
  });
51
64
  }
52
65
 
53
- function handler(bundles, options = { pageTypes: null }) {
66
+ /**
67
+ * Handler for traffic metrics.
68
+ * @param {Array} bundles - The RUM bundles.
69
+ * @param {Object} options - Options object.
70
+ * @param {Object} [options.pageTypes] - Page type regex or mapping.
71
+ * @param {string} [options.trafficType] - Eg, 'paid', 'earned', 'owned', 'all'. Defaults to 'all'.
72
+ */
73
+ function handler(bundles, options = { pageTypes: null, trafficType: 'all' }) {
54
74
  const dataChunks = new DataChunks();
55
- const { pageTypes: pageTypeOpt } = options;
75
+ const trafficSourceMemo = {};
76
+ const { pageTypes: pageTypeOpt, trafficType = 'all' } = options;
77
+
78
+ let filteredBundles = bundles;
79
+ if (trafficType && trafficType !== 'all') {
80
+ filteredBundles = bundles
81
+ .filter((bundle) => getTrafficSource(bundle, trafficSourceMemo).type === trafficType);
82
+ }
83
+
84
+ const getTS = (bundle) => getTrafficSourceKey(bundle, trafficSourceMemo);
56
85
 
57
- loadBundles(bundles, dataChunks);
86
+ loadBundles(filteredBundles, dataChunks);
58
87
 
59
88
  const metricFilter = (metrics) => {
60
- const { ctr, enters, sumOfAllClicks } = metrics;
89
+ const {
90
+ ctr, enters, sumOfAllClicks, facet,
91
+ } = metrics;
61
92
  return {
62
- ctr: ctr.sum / ctr.weight,
93
+ ctr: ctr.weight !== 0 ? ctr.sum / ctr.weight : 0,
63
94
  clickedSessions: ctr.sum,
64
- totalSessions: ctr.weight,
95
+ pageViews: facet.weight,
65
96
  sessionsWithEnter: enters.sum,
66
- clicksOverViews: ctr.weight ? ctr.sum / ctr.weight : 0,
67
- bounceRate: ctr.weight ? (1 - (ctr.sum / ctr.weight)) : 0,
97
+ clicksOverViews: ctr.weight !== 0 ? ctr.sum / ctr.weight : 0,
98
+ bounceRate: ctr.weight !== 0 ? (1 - (ctr.sum / ctr.weight)) : 1,
68
99
  totalNumClicks: sumOfAllClicks.sum,
69
100
  avgClicksPerSession: ctr.sum ? sumOfAllClicks.sum / ctr.sum : 0,
70
101
  };
@@ -72,25 +103,25 @@ function handler(bundles, options = { pageTypes: null }) {
72
103
 
73
104
  dataChunks.addFacet('urls', (bundle) => bundle.url);
74
105
 
75
- dataChunks.addFacet('trafficSources', (bundle) => getTrafficSource(bundle));
106
+ dataChunks.addFacet('trafficSources', (bundle) => getTS(bundle));
76
107
 
77
- dataChunks.addFacet('urlTrafficSources', (bundle) => generateKey(bundle.url, getTrafficSource(bundle)));
108
+ dataChunks.addFacet('urlTrafficSources', (bundle) => generateKey(bundle.url, getTS(bundle)));
78
109
 
79
110
  dataChunks.addFacet('urlDeviceTypes', (bundle) => generateKey(bundle.url, getDeviceType(bundle)));
80
111
 
81
112
  dataChunks.addFacet('deviceTypes', (bundle) => getDeviceType(bundle));
82
113
 
83
- dataChunks.addFacet('urlTrafficSourceDeviceTypes', (bundle) => generateKey(bundle.url, getTrafficSource(bundle), getDeviceType(bundle)));
114
+ dataChunks.addFacet('urlTrafficSourceDeviceTypes', (bundle) => generateKey(bundle.url, getTS(bundle), getDeviceType(bundle)));
84
115
 
85
- dataChunks.addFacet('deviceTypeTrafficSources', (bundle) => generateKey(getDeviceType(bundle), getTrafficSource(bundle)));
116
+ dataChunks.addFacet('deviceTypeTrafficSources', (bundle) => generateKey(getDeviceType(bundle), getTS(bundle)));
86
117
 
87
118
  addPageTypeFacet(dataChunks, pageTypeOpt);
88
119
 
89
- addPageTypeTrafficSourceFacet(dataChunks, pageTypeOpt);
120
+ addPageTypeTrafficSourceFacet(dataChunks, pageTypeOpt, trafficSourceMemo);
90
121
 
91
122
  addPageTypeDeviceTypeFacet(dataChunks, pageTypeOpt);
92
123
 
93
- addPageTypeTrafficSourceDeviceTypes(dataChunks, pageTypeOpt);
124
+ addPageTypeTrafficSourceDeviceTypes(dataChunks, pageTypeOpt, trafficSourceMemo);
94
125
 
95
126
  dataChunks.addSeries('ctr', (bundle) => {
96
127
  const isClicked = bundle.events.some((e) => e.checkpoint === 'click');
@@ -111,100 +142,108 @@ function handler(bundles, options = { pageTypes: null }) {
111
142
  return containsEnter ? bundle.weight : 0;
112
143
  });
113
144
 
114
- const urls = dataChunks.facets.urls.map((facet) => {
115
- const url = facet.value;
116
- return {
117
- ...metricFilter(facet.metrics),
118
- url,
119
- };
120
- });
145
+ const urls = dataChunks.facets.urls.map((facet) => ({
146
+ ...metricFilter({ ...facet.metrics, facet }),
147
+ url: facet.value,
148
+ urls: [facet.value],
149
+ }));
121
150
 
122
151
  const pageType = dataChunks.facets.pageType.map((facet) => {
123
152
  const type = facet.value;
124
153
  return {
125
- ...metricFilter(facet.metrics),
154
+ ...metricFilter({ ...facet.metrics, facet }),
126
155
  type,
156
+ urls: [...new Set(facet.entries.map((b) => b.url))],
127
157
  };
128
158
  });
129
159
 
130
160
  const deviceTypes = dataChunks.facets.deviceTypes.map((facet) => {
131
161
  const deviceType = facet.value;
132
162
  return {
133
- ...metricFilter(facet.metrics),
163
+ ...metricFilter({ ...facet.metrics, facet }),
134
164
  deviceType,
165
+ urls: [...new Set(facet.entries.map((b) => b.url))],
135
166
  };
136
167
  });
137
168
 
138
169
  const urlDeviceTypes = dataChunks.facets.urlDeviceTypes.map((facet) => {
139
170
  const [url, deviceType] = facet.value.split(DELIMITER);
140
171
  return {
141
- ...metricFilter(facet.metrics),
172
+ ...metricFilter({ ...facet.metrics, facet }),
142
173
  url,
143
174
  deviceType,
175
+ urls: [...new Set(facet.entries.map((b) => b.url))],
144
176
  };
145
177
  });
146
178
 
147
179
  const trafficSources = dataChunks.facets.trafficSources.map((facet) => {
148
180
  const source = facet.value;
149
181
  return {
150
- ...metricFilter(facet.metrics),
182
+ ...metricFilter({ ...facet.metrics, facet }),
151
183
  source,
184
+ urls: [...new Set(facet.entries.map((b) => b.url))],
152
185
  };
153
186
  });
154
187
 
155
188
  const urlTrafficSources = dataChunks.facets.urlTrafficSources.map((facet) => {
156
189
  const [url, source] = facet.value.split(DELIMITER);
157
190
  return {
158
- ...metricFilter(facet.metrics),
191
+ ...metricFilter({ ...facet.metrics, facet }),
159
192
  url,
160
193
  source,
194
+ urls: [...new Set(facet.entries.map((b) => b.url))],
161
195
  };
162
196
  });
163
197
 
164
198
  const urlTrafficSourceDeviceTypes = dataChunks.facets.urlTrafficSourceDeviceTypes.map((facet) => {
165
199
  const [url, source, deviceType] = facet.value.split(DELIMITER);
166
200
  return {
167
- ...metricFilter(facet.metrics),
201
+ ...metricFilter({ ...facet.metrics, facet }),
168
202
  url,
169
203
  source,
170
204
  deviceType,
205
+ urls: [...new Set(facet.entries.map((b) => b.url))],
171
206
  };
172
207
  });
173
208
 
174
209
  const pageTypeTrafficSources = dataChunks.facets.pageTypeTrafficSources.map((facet) => {
175
210
  const [type, source] = facet.value.split(DELIMITER);
176
211
  return {
177
- ...metricFilter(facet.metrics),
212
+ ...metricFilter({ ...facet.metrics, facet }),
178
213
  type,
179
214
  source,
215
+ urls: [...new Set(facet.entries.map((b) => b.url))],
180
216
  };
181
217
  });
182
218
 
183
219
  const pageTypeDeviceTypes = dataChunks.facets.pageTypeDeviceTypes.map((facet) => {
184
220
  const [type, deviceType] = facet.value.split(DELIMITER);
185
221
  return {
186
- ...metricFilter(facet.metrics),
222
+ ...metricFilter({ ...facet.metrics, facet }),
187
223
  type,
188
224
  deviceType,
225
+ urls: [...new Set(facet.entries.map((b) => b.url))],
189
226
  };
190
227
  });
191
228
 
192
229
  const deviceTypeTrafficSources = dataChunks.facets.deviceTypeTrafficSources.map((facet) => {
193
230
  const [deviceType, source] = facet.value.split(DELIMITER);
194
231
  return {
195
- ...metricFilter(facet.metrics),
232
+ ...metricFilter({ ...facet.metrics, facet }),
196
233
  deviceType,
197
234
  source,
235
+ urls: [...new Set(facet.entries.map((b) => b.url))],
198
236
  };
199
237
  });
200
238
 
201
239
  const pageTrafficDeviceTypes = dataChunks.facets.pageTrafficDeviceTypes.map((facet) => {
202
240
  const [type, source, deviceType] = facet.value.split(DELIMITER);
203
241
  return {
204
- ...metricFilter(facet.metrics),
242
+ ...metricFilter({ ...facet.metrics, facet }),
205
243
  type,
206
244
  source,
207
245
  deviceType,
246
+ urls: [...new Set(facet.entries.map((b) => b.url))],
208
247
  };
209
248
  });
210
249