@adobe/spacecat-shared-rum-api-client 2.12.5 → 2.13.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.13.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.12.6...@adobe/spacecat-shared-rum-api-client-v2.13.0) (2024-11-28)
2
+
3
+
4
+ ### Features
5
+
6
+ * Introduce configurable grouping patterns ([#458](https://github.com/adobe/spacecat-shared/issues/458)) ([6414b13](https://github.com/adobe/spacecat-shared/commit/6414b13ccffdf567460efddb019f8f3de0200b5a))
7
+
8
+ # [@adobe/spacecat-shared-rum-api-client-v2.12.6](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.12.5...@adobe/spacecat-shared-rum-api-client-v2.12.6) (2024-11-27)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **deps:** update dependency @adobe/rum-distiller to v1.11.1 ([#462](https://github.com/adobe/spacecat-shared/issues/462)) ([4fcfcd0](https://github.com/adobe/spacecat-shared/commit/4fcfcd0b78c2dceed6e2cd517cd4f54dd9c420d5)), closes [#8203](https://github.com/adobe/spacecat-shared/issues/8203)
14
+
1
15
  # [@adobe/spacecat-shared-rum-api-client-v2.12.5](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.12.4...@adobe/spacecat-shared-rum-api-client-v2.12.5) (2024-11-27)
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.12.5",
3
+ "version": "2.13.0",
4
4
  "description": "Shared modules of the Spacecat Services - Rum API client",
5
5
  "type": "module",
6
6
  "engines": {
@@ -39,7 +39,7 @@
39
39
  "@adobe/helix-shared-wrap": "2.0.2",
40
40
  "@adobe/helix-universal": "5.0.6",
41
41
  "@adobe/spacecat-shared-utils": "1.22.4",
42
- "@adobe/rum-distiller": "1.10.0",
42
+ "@adobe/rum-distiller": "1.11.1",
43
43
  "aws4": "1.13.2",
44
44
  "urijs": "^1.19.11"
45
45
  },
@@ -13,37 +13,144 @@
13
13
  import {
14
14
  DataChunks, series, facets,
15
15
  } from '@adobe/rum-distiller';
16
- import { loadBundles } from '../utils.js';
16
+ import { generateKey, DELIMITER, loadBundles } from '../utils.js';
17
17
 
18
- function handler(bundles) {
19
- const dataChunks = new DataChunks();
18
+ const METRICS = ['lcp', 'cls', 'inp', 'ttfb'];
19
+
20
+ const USER_AGENT_DELIMITER = ':';
21
+
22
+ const FACET_TYPE = {
23
+ GROUP: 'group',
24
+ URL: 'url',
25
+ };
26
+
27
+ const findMatchedPattern = (url, urlPatterns) => {
28
+ for (const urlPattern of urlPatterns) {
29
+ const regex = new RegExp(`^${urlPattern.pattern.replace(/\*/g, '.*')}$`);
30
+ if (regex.test(url)) {
31
+ return urlPattern;
32
+ }
33
+ }
34
+ return null;
35
+ };
36
+
37
+ const mapUrlsToPatterns = (bundles, patterns) => {
38
+ const urlToPatternMap = {};
39
+ if (!patterns || patterns.length === 0) {
40
+ return urlToPatternMap;
41
+ }
20
42
 
43
+ for (const bundle of bundles) {
44
+ const matchedPattern = findMatchedPattern(bundle.url, patterns);
45
+
46
+ if (matchedPattern) {
47
+ // Check if any cwv metric exists in bundle.events
48
+ const hasMetrics = bundle.events.some((event) => METRICS.some((metric) => event.checkpoint.includes(`cwv-${metric}`)));
49
+
50
+ if (hasMetrics) {
51
+ urlToPatternMap[bundle.url] = matchedPattern;
52
+ }
53
+ }
54
+ }
55
+ return urlToPatternMap;
56
+ };
57
+
58
+ const calculateMetricsPercentile = (metrics) => ({
59
+ lcp: metrics.lcp.percentile(75) || null,
60
+ lcpCount: metrics.lcp.count || 0,
61
+ cls: metrics.cls.percentile(75) || null,
62
+ clsCount: metrics.cls.count || 0,
63
+ inp: metrics.inp.percentile(75) || null,
64
+ inpCount: metrics.inp.count || 0,
65
+ ttfb: metrics.ttfb.percentile(75) || null,
66
+ ttfbCount: metrics.ttfb.count || 0,
67
+ });
68
+
69
+ function handler(rawBundles, opts = []) {
70
+ const bundles = rawBundles.map((bundle) => ({
71
+ ...bundle,
72
+ url: facets.url(bundle), // URL without ids, hashes, and other encoded data
73
+ }));
74
+ const urlToPatternMap = mapUrlsToPatterns(bundles, opts.groupedURLs);
75
+
76
+ const dataChunks = new DataChunks();
21
77
  loadBundles(bundles, dataChunks);
22
78
 
23
- dataChunks.addFacet('urls', facets.url);
24
-
25
- dataChunks.addSeries('lcp', series.lcp);
26
- dataChunks.addSeries('cls', series.cls);
27
- dataChunks.addSeries('inp', series.inp);
28
- dataChunks.addSeries('ttfb', series.ttfb);
29
-
30
- return dataChunks.facets.urls.map((urlFacet) => ({
31
- url: urlFacet.value,
32
- pageviews: urlFacet.weight,
33
- lcp: urlFacet.metrics.lcp.percentile(75) || null,
34
- lcpCount: urlFacet.metrics.lcp.count,
35
- cls: urlFacet.metrics.cls.percentile(75) || null,
36
- clsCount: urlFacet.metrics.cls.count,
37
- inp: urlFacet.metrics.inp.percentile(75) || null,
38
- inpCount: urlFacet.metrics.inp.count,
39
- ttfb: urlFacet.metrics.ttfb.percentile(75) || null,
40
- ttfbCount: urlFacet.metrics.ttfb.count,
41
- }))
42
- .filter((row) => row.lcp || row.cls || row.inp || row.ttfb) // filter out pages with no cwv data
43
- .sort((a, b) => b.pageviews - a.pageviews); // sort desc by pageviews
79
+ // groups by url and device
80
+ dataChunks.addFacet(
81
+ 'urlsDevices',
82
+ (bundle) => generateKey(bundle.url, bundle.userAgent.split(USER_AGENT_DELIMITER)[0]),
83
+ );
84
+
85
+ // groups by pattern and device
86
+ dataChunks.addFacet('patternsDevices', (bundle) => {
87
+ const device = bundle.userAgent.split(USER_AGENT_DELIMITER)[0];
88
+ return urlToPatternMap[bundle.url]?.pattern
89
+ ? generateKey(urlToPatternMap[bundle.url].pattern, device)
90
+ : null;
91
+ });
92
+
93
+ // counts metrics per each facet
94
+ METRICS.forEach((metric) => dataChunks.addSeries(metric, series[metric]));
95
+
96
+ const patternsResult = dataChunks.facets.patternsDevices.reduce((acc, facet) => {
97
+ const [pattern, deviceType] = facet.value.split(DELIMITER);
98
+ const patternData = Object.values(urlToPatternMap).find((p) => p.pattern === pattern);
99
+
100
+ acc[pattern] = acc[pattern] || {
101
+ type: FACET_TYPE.GROUP,
102
+ name: patternData.name,
103
+ pattern,
104
+ pageviews: 0,
105
+ metrics: [],
106
+ };
107
+
108
+ // Increment the total pageviews for pattern
109
+ acc[pattern].pageviews += facet.weight;
110
+
111
+ // Add metrics for the specific device type
112
+ acc[pattern].metrics.push({
113
+ deviceType,
114
+ pageviews: facet.weight, // Pageviews for this device type
115
+ ...calculateMetricsPercentile(facet.metrics),
116
+ });
117
+
118
+ return acc;
119
+ }, {});
120
+
121
+ const urlsResult = dataChunks.facets.urlsDevices.reduce((acc, facet) => {
122
+ const [url, deviceType] = facet.value.split(DELIMITER);
123
+
124
+ acc[url] = acc[url] || {
125
+ type: FACET_TYPE.URL,
126
+ url,
127
+ pageviews: 0,
128
+ metrics: [],
129
+ };
130
+
131
+ // Increment the total pageviews for url
132
+ acc[url].pageviews += facet.weight;
133
+
134
+ // Add metrics for the specific device type
135
+ acc[url].metrics.push({
136
+ deviceType,
137
+ pageviews: facet.weight, // Pageviews for this device type
138
+ ...calculateMetricsPercentile(facet.metrics),
139
+ });
140
+
141
+ return acc;
142
+ }, {});
143
+
144
+ const result = [...Object.values(patternsResult), ...Object.values(urlsResult)]
145
+ // filter out pages with no cwv data
146
+ .filter((row) => METRICS.some((metric) => row.metrics.some((entry) => entry[metric])))
147
+ // sort desc by pageviews
148
+ .sort((a, b) => b.metrics.pageviews - a.metrics.pageviews);
149
+
150
+ return result;
44
151
  }
45
152
 
46
153
  export default {
47
154
  handler,
48
- checkpoints: ['cwv-lcp', 'cwv-cls', 'cwv-inp', 'cwv-ttfb'],
155
+ checkpoints: METRICS.map((metric) => `cwv-${metric}`),
49
156
  };
package/src/index.d.ts CHANGED
@@ -17,6 +17,10 @@ export interface RUMAPIOptions {
17
17
  domainkey: string;
18
18
  interval?: number;
19
19
  granularity?: 'hourly' | 'daily';
20
+ groupedURLs?: Array<{
21
+ name: string;
22
+ pattern: string;
23
+ }>;
20
24
  }
21
25
 
22
26
  export default class RUMAPIClient {
package/src/index.js CHANGED
@@ -52,7 +52,7 @@ export default class RUMAPIClient {
52
52
  checkpoints,
53
53
  });
54
54
 
55
- return handler(bundles);
55
+ return handler(bundles, opts);
56
56
  } catch (e) {
57
57
  throw new Error(`Query '${query}' failed. Opts: ${JSON.stringify(opts)}. Reason: ${e.message}`);
58
58
  }