@adobe/spacecat-shared-rum-api-client 2.30.0 → 2.31.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 +14 -0
- package/package.json +1 -1
- package/src/common/rum-bundler-client.js +37 -1
- package/src/functions/traffic-metrics.js +76 -37
- package/src/index.js +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-rum-api-client-v2.31.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.31.0...@adobe/spacecat-shared-rum-api-client-v2.31.1) (2025-06-30)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* check status before parsing rum bundles ([#807](https://github.com/adobe/spacecat-shared/issues/807)) ([36566e4](https://github.com/adobe/spacecat-shared/commit/36566e4ab1cb8e72e3e5e0673df3171c7793ab82))
|
|
7
|
+
|
|
8
|
+
# [@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)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* expose urls with most paid traffic ([#825](https://github.com/adobe/spacecat-shared/issues/825)) ([88dde01](https://github.com/adobe/spacecat-shared/commit/88dde01843ae32643a54d93868b7ef7eec10a62a))
|
|
14
|
+
|
|
1
15
|
# [@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
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -57,6 +57,20 @@ function filterEvents(checkpoints = []) {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function sanitizeURL(url) {
|
|
61
|
+
try {
|
|
62
|
+
const parsedUrl = new URL(url);
|
|
63
|
+
if (parsedUrl.searchParams.has('domainkey')) {
|
|
64
|
+
parsedUrl.searchParams.set('domainkey', 'redacted');
|
|
65
|
+
}
|
|
66
|
+
return parsedUrl.toString();
|
|
67
|
+
/* c8 ignore next 4 */
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
69
|
+
} catch (e) {
|
|
70
|
+
return url;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
60
74
|
function constructUrl(domain, date, granularity, domainkey) {
|
|
61
75
|
const year = date.getUTCFullYear();
|
|
62
76
|
const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
@@ -246,6 +260,7 @@ async function fetchBundles(opts, log) {
|
|
|
246
260
|
const chunks = getUrlChunks(urls, CHUNK_SIZE);
|
|
247
261
|
|
|
248
262
|
let totalTransferSize = 0;
|
|
263
|
+
const failedUrls = [];
|
|
249
264
|
|
|
250
265
|
const result = [];
|
|
251
266
|
for (const chunk of chunks) {
|
|
@@ -255,7 +270,21 @@ async function fetchBundles(opts, log) {
|
|
|
255
270
|
totalTransferSize += parseInt(response.headers.get('content-length'), 10);
|
|
256
271
|
return response;
|
|
257
272
|
}));
|
|
258
|
-
|
|
273
|
+
|
|
274
|
+
const bundlesRaw = await Promise.all(
|
|
275
|
+
responses.map(async (response, index) => {
|
|
276
|
+
if (response.ok) {
|
|
277
|
+
return response.json();
|
|
278
|
+
} else {
|
|
279
|
+
const failedUrl = response.url || chunk[index];
|
|
280
|
+
log.warn(`Skipping response at index ${index}: status ${response.status} - url: ${sanitizeURL(failedUrl)}`);
|
|
281
|
+
failedUrls.push(failedUrl);
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const bundles = bundlesRaw.filter(Boolean);
|
|
259
288
|
bundles.forEach((b) => {
|
|
260
289
|
b.rumBundles
|
|
261
290
|
.filter((bundle) => !filterBotTraffic || !isBotTraffic(bundle))
|
|
@@ -264,6 +293,13 @@ async function fetchBundles(opts, log) {
|
|
|
264
293
|
});
|
|
265
294
|
}
|
|
266
295
|
log.info(`Retrieved RUM bundles. Total transfer size (in KB): ${(totalTransferSize / 1024).toFixed(2)}`);
|
|
296
|
+
|
|
297
|
+
// Add failedUrls to opts object for access by callers
|
|
298
|
+
if (failedUrls.length > 0) {
|
|
299
|
+
// eslint-disable-next-line no-param-reassign
|
|
300
|
+
opts.failedUrls = failedUrls;
|
|
301
|
+
}
|
|
302
|
+
|
|
267
303
|
return mergeBundlesWithSameId(result);
|
|
268
304
|
}
|
|
269
305
|
|
|
@@ -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
|
-
|
|
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,
|
|
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,
|
|
62
|
+
return generateKey(pageType, getTrafficSourceKey(bundle, memo));
|
|
50
63
|
});
|
|
51
64
|
}
|
|
52
65
|
|
|
53
|
-
|
|
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
|
|
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(
|
|
86
|
+
loadBundles(filteredBundles, dataChunks);
|
|
58
87
|
|
|
59
88
|
const metricFilter = (metrics) => {
|
|
60
|
-
const {
|
|
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
|
-
|
|
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)) :
|
|
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) =>
|
|
106
|
+
dataChunks.addFacet('trafficSources', (bundle) => getTS(bundle));
|
|
76
107
|
|
|
77
|
-
dataChunks.addFacet('urlTrafficSources', (bundle) => generateKey(bundle.url,
|
|
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,
|
|
114
|
+
dataChunks.addFacet('urlTrafficSourceDeviceTypes', (bundle) => generateKey(bundle.url, getTS(bundle), getDeviceType(bundle)));
|
|
84
115
|
|
|
85
|
-
dataChunks.addFacet('deviceTypeTrafficSources', (bundle) => generateKey(getDeviceType(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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
package/src/index.js
CHANGED
|
@@ -122,7 +122,6 @@ export default class RUMAPIClient {
|
|
|
122
122
|
|
|
123
123
|
try {
|
|
124
124
|
const domainkey = await this._getDomainkey(opts);
|
|
125
|
-
|
|
126
125
|
const bundles = await fetchBundles({
|
|
127
126
|
...opts,
|
|
128
127
|
domainkey,
|
|
@@ -130,7 +129,6 @@ export default class RUMAPIClient {
|
|
|
130
129
|
}, this.log);
|
|
131
130
|
|
|
132
131
|
this.log.info(`Query "${query}" fetched ${bundles.length} bundles`);
|
|
133
|
-
|
|
134
132
|
return handler(bundles, opts);
|
|
135
133
|
} catch (e) {
|
|
136
134
|
throw new Error(`Query '${query}' failed. Opts: ${JSON.stringify(sanitize(opts))}. Reason: ${e.message}`);
|
|
@@ -164,7 +162,6 @@ export default class RUMAPIClient {
|
|
|
164
162
|
}, this.log);
|
|
165
163
|
|
|
166
164
|
const results = {};
|
|
167
|
-
|
|
168
165
|
this.log.info(`Multi query ${JSON.stringify(queries.join(', '))} fetched ${bundles.length} bundles`);
|
|
169
166
|
|
|
170
167
|
// Execute each query handler sequentially
|