@adobe/spacecat-shared-rum-api-client 2.36.5 → 2.37.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 +6 -6
- package/src/common/rum-bundler-client.js +1 -1
- package/src/functions/reports/optimization/graph.js +97 -0
- package/src/functions/reports/optimization/metrics.js +30 -0
- package/src/functions/reports/optimization/utils.js +201 -0
- package/src/index.js +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-rum-api-client-v2.37.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.37.0...@adobe/spacecat-shared-rum-api-client-v2.37.1) (2025-09-06)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **deps:** update external fixes ([#920](https://github.com/adobe/spacecat-shared/issues/920)) ([1a6b1e1](https://github.com/adobe/spacecat-shared/commit/1a6b1e1ac9531a41c86406ada4bd4ab903307fdc))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-rum-api-client-v2.37.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.36.5...@adobe/spacecat-shared-rum-api-client-v2.37.0) (2025-08-28)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* added metrics and graph data functions for optimization-report ([#919](https://github.com/adobe/spacecat-shared/issues/919)) ([29461b2](https://github.com/adobe/spacecat-shared/commit/29461b246b74423dcd39b0af4e1d140f1a3d3af1))
|
|
14
|
+
|
|
1
15
|
# [@adobe/spacecat-shared-rum-api-client-v2.36.5](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.36.4...@adobe/spacecat-shared-rum-api-client-v2.36.5) (2025-08-25)
|
|
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.
|
|
3
|
+
"version": "2.37.1",
|
|
4
4
|
"description": "Shared modules of the Spacecat Services - Rum API client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -38,17 +38,17 @@
|
|
|
38
38
|
"@adobe/fetch": "4.2.2",
|
|
39
39
|
"@adobe/helix-shared-wrap": "2.0.2",
|
|
40
40
|
"@adobe/helix-universal": "5.2.2",
|
|
41
|
-
"@adobe/spacecat-shared-utils": "1.26.4",
|
|
42
41
|
"@adobe/rum-distiller": "1.17.0",
|
|
42
|
+
"@adobe/spacecat-shared-utils": "1.48.0",
|
|
43
43
|
"aws4": "1.13.2",
|
|
44
44
|
"urijs": "1.19.11"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"chai": "5.
|
|
48
|
-
"chai-as-promised": "8.0.
|
|
49
|
-
"nock": "14.0.
|
|
47
|
+
"chai": "5.3.3",
|
|
48
|
+
"chai-as-promised": "8.0.2",
|
|
49
|
+
"nock": "14.0.10",
|
|
50
50
|
"sinon": "20.0.0",
|
|
51
|
-
"sinon-chai": "4.0.
|
|
51
|
+
"sinon-chai": "4.0.1",
|
|
52
52
|
"typescript": "5.9.2"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
import {
|
|
13
|
+
filterBundles,
|
|
14
|
+
initializeDataChunks,
|
|
15
|
+
extractMetrics,
|
|
16
|
+
createTimeSeriesData,
|
|
17
|
+
calculateTotals,
|
|
18
|
+
validateDateRange,
|
|
19
|
+
} from './utils.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Process URL-specific data
|
|
23
|
+
* @param {Object} urlFacet - URL facet from DataChunks
|
|
24
|
+
* @returns {Object} URL-specific data with totals and time series
|
|
25
|
+
*/
|
|
26
|
+
function processUrlData(urlFacet) {
|
|
27
|
+
const urlBundles = urlFacet.entries;
|
|
28
|
+
const urlDataChunks = initializeDataChunks(urlBundles, { includeDateFacet: true });
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
total: extractMetrics(urlFacet),
|
|
32
|
+
timeSeries: createTimeSeriesData(urlDataChunks.facets.date),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Process bundles into aggregated graph data
|
|
38
|
+
* @param {Object[]} bundles - Array of RUM bundles
|
|
39
|
+
* @returns {Object} Aggregated traffic data
|
|
40
|
+
*/
|
|
41
|
+
function processBundles(bundles) {
|
|
42
|
+
const dataChunks = initializeDataChunks(bundles, {
|
|
43
|
+
includeUrlFacet: true,
|
|
44
|
+
includeDateFacet: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Process URL-specific data
|
|
48
|
+
const byUrl = {};
|
|
49
|
+
if (dataChunks.facets.url) {
|
|
50
|
+
dataChunks.facets.url.forEach((urlFacet) => {
|
|
51
|
+
byUrl[urlFacet.value] = processUrlData(urlFacet);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Process overall traffic data
|
|
56
|
+
const trafficData = createTimeSeriesData(dataChunks.facets.date);
|
|
57
|
+
|
|
58
|
+
return { trafficData, byUrl };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Main handler function to generate graph data
|
|
63
|
+
* @param {Object[]} bundles - Array of RUM bundles
|
|
64
|
+
* @param {Object} opts - Options object
|
|
65
|
+
* @returns {Object}
|
|
66
|
+
*/
|
|
67
|
+
function handler(bundles, opts) {
|
|
68
|
+
if (!opts) {
|
|
69
|
+
return {
|
|
70
|
+
trafficData: [],
|
|
71
|
+
byUrl: {},
|
|
72
|
+
totals: {},
|
|
73
|
+
urlsFiltered: [],
|
|
74
|
+
granularity: 'DAILY',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const {
|
|
79
|
+
startTime, endTime, urls = [],
|
|
80
|
+
} = opts;
|
|
81
|
+
|
|
82
|
+
validateDateRange(startTime, endTime);
|
|
83
|
+
|
|
84
|
+
const filteredBundles = filterBundles(bundles, opts);
|
|
85
|
+
const result = processBundles(filteredBundles);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
...result,
|
|
89
|
+
totals: calculateTotals(result.trafficData),
|
|
90
|
+
urlsFiltered: urls,
|
|
91
|
+
granularity: opts.granularity || 'DAILY',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default {
|
|
96
|
+
handler,
|
|
97
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
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 { initializeDataChunks, calculateMetrics, filterBundles } from './utils.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Handler for optimization reports metrics
|
|
17
|
+
* @param {Object[]} bundles - Array of RUM bundles
|
|
18
|
+
* @param {Object} opts - Options object
|
|
19
|
+
* @returns {Object} Calculated metrics
|
|
20
|
+
*/
|
|
21
|
+
function handler(bundles, opts) {
|
|
22
|
+
const options = opts || {};
|
|
23
|
+
const filteredBundles = filterBundles(bundles, options);
|
|
24
|
+
const dataChunks = initializeDataChunks(filteredBundles);
|
|
25
|
+
return calculateMetrics(dataChunks);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default {
|
|
29
|
+
handler,
|
|
30
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
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
|
+
import { DataChunks, series, facets } from '@adobe/rum-distiller';
|
|
13
|
+
import { computeConversionRate } from '@adobe/rum-distiller/utils.js';
|
|
14
|
+
import { urlMatchesFilter } from '@adobe/spacecat-shared-utils';
|
|
15
|
+
import { loadBundles } from '../../../utils.js';
|
|
16
|
+
|
|
17
|
+
// Constants
|
|
18
|
+
const METRIC_NAMES = ['organic', 'visits', 'pageViews', 'bounces', 'conversions', 'engagement'];
|
|
19
|
+
const CONVERSION_SPEC = { checkpoint: ['click'] };
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create date facet function for YYYY-MM-DD format
|
|
23
|
+
* @returns {Function} Date facet function
|
|
24
|
+
*/
|
|
25
|
+
function createDateFacet(bundle) {
|
|
26
|
+
const date = new Date(bundle.time);
|
|
27
|
+
return date.toISOString().split('T')[0];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create conversion series function
|
|
32
|
+
* @param {DataChunks} dataChunks - DataChunks instance
|
|
33
|
+
* @returns {Function} Conversion series function
|
|
34
|
+
*/
|
|
35
|
+
function createConversionSeries(dataChunks) {
|
|
36
|
+
return (bundle) => (bundle
|
|
37
|
+
&& dataChunks.hasConversion(bundle, CONVERSION_SPEC) ? bundle.weight : 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initialize DataChunks with common configuration
|
|
42
|
+
* @param {Object[]} bundles - Array of RUM bundles
|
|
43
|
+
* @param {Object} options - Configuration options
|
|
44
|
+
* @param {boolean} options.includeUrlFacet - Whether to include URL facet aggregation
|
|
45
|
+
* @param {boolean} options.includeDateFacet - Whether to include date facet aggregation
|
|
46
|
+
* @returns {DataChunks} Configured DataChunks instance
|
|
47
|
+
*/
|
|
48
|
+
export function initializeDataChunks(bundles, options = {}) {
|
|
49
|
+
const { includeUrlFacet = false, includeDateFacet = false } = options;
|
|
50
|
+
|
|
51
|
+
const dataChunks = new DataChunks();
|
|
52
|
+
|
|
53
|
+
// Handle null/undefined bundles
|
|
54
|
+
const validBundles = (!bundles || !Array.isArray(bundles)) ? [] : bundles;
|
|
55
|
+
|
|
56
|
+
// Filter out bundles with missing or invalid URLs
|
|
57
|
+
const processedBundles = validBundles.filter((bundle) => bundle && bundle.url);
|
|
58
|
+
|
|
59
|
+
loadBundles(processedBundles, dataChunks);
|
|
60
|
+
|
|
61
|
+
// Add checkpoint facet for conversion detection
|
|
62
|
+
dataChunks.addFacet('checkpoint', facets.checkpoint, 'every', 'none');
|
|
63
|
+
|
|
64
|
+
// Add URL facet if requested
|
|
65
|
+
if (includeUrlFacet) {
|
|
66
|
+
dataChunks.addFacet('url', facets.url, 'some', 'none');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Add date facet if requested
|
|
70
|
+
if (includeDateFacet) {
|
|
71
|
+
dataChunks.addFacet('date', createDateFacet);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Add metrics series
|
|
75
|
+
dataChunks.addSeries('pageViews', series.pageViews);
|
|
76
|
+
dataChunks.addSeries('engagement', series.engagement);
|
|
77
|
+
dataChunks.addSeries('bounces', series.bounces);
|
|
78
|
+
dataChunks.addSeries('organic', series.organic);
|
|
79
|
+
dataChunks.addSeries('visits', series.visits);
|
|
80
|
+
dataChunks.addSeries('conversions', createConversionSeries(dataChunks));
|
|
81
|
+
|
|
82
|
+
return dataChunks;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Extract metrics from a facet
|
|
87
|
+
* @param {Object} facet - DataChunks facet
|
|
88
|
+
* @returns {Object} Metrics object
|
|
89
|
+
*/
|
|
90
|
+
export function extractMetrics(facet) {
|
|
91
|
+
return METRIC_NAMES.reduce((acc, metric) => {
|
|
92
|
+
acc[metric] = facet.metrics[metric]?.sum || 0;
|
|
93
|
+
return acc;
|
|
94
|
+
}, {});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Create time series data from date facets
|
|
99
|
+
* @param {Object[]} dateFacets - Array of date facets
|
|
100
|
+
* @returns {Object[]} Sorted time series data
|
|
101
|
+
*/
|
|
102
|
+
export function createTimeSeriesData(dateFacets) {
|
|
103
|
+
return dateFacets
|
|
104
|
+
.map((facet) => ({
|
|
105
|
+
date: facet.value,
|
|
106
|
+
...extractMetrics(facet),
|
|
107
|
+
}))
|
|
108
|
+
.sort((a, b) => new Date(a.date) - new Date(b.date));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Calculate metrics from a DataChunks instance
|
|
113
|
+
* @param {DataChunks} chunk - DataChunks instance
|
|
114
|
+
* @returns {Object} Calculated metrics
|
|
115
|
+
*/
|
|
116
|
+
export function calculateMetrics(chunk) {
|
|
117
|
+
const {
|
|
118
|
+
totals,
|
|
119
|
+
} = chunk;
|
|
120
|
+
return {
|
|
121
|
+
pageViews: { total: totals.pageViews?.sum || 0 },
|
|
122
|
+
visits: { total: totals.visits?.sum || 0 },
|
|
123
|
+
organicTraffic: { total: totals.organic?.sum || 0 },
|
|
124
|
+
bounces: {
|
|
125
|
+
total: totals.bounces?.sum || 0,
|
|
126
|
+
rate: computeConversionRate(totals.bounces?.sum || 0, totals.visits?.sum || 0) || 0,
|
|
127
|
+
},
|
|
128
|
+
engagement: {
|
|
129
|
+
total: totals.engagement?.sum || 0,
|
|
130
|
+
rate: computeConversionRate(totals.conversions?.sum || 0, totals.engagement?.sum || 0) || 0,
|
|
131
|
+
},
|
|
132
|
+
conversions: {
|
|
133
|
+
total: totals.conversions?.sum || 0,
|
|
134
|
+
rate: computeConversionRate(totals.conversions?.sum || 0, totals.pageViews?.sum || 0) || 0,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Calculate totals from time series data
|
|
141
|
+
* @param {Object[]} timeSeriesData - Array of time series data points
|
|
142
|
+
* @returns {Object} Totals object
|
|
143
|
+
*/
|
|
144
|
+
export function calculateTotals(timeSeriesData) {
|
|
145
|
+
return timeSeriesData.reduce((acc, data) => {
|
|
146
|
+
METRIC_NAMES.forEach((metric) => {
|
|
147
|
+
acc[metric] += (data[metric] || 0);
|
|
148
|
+
});
|
|
149
|
+
return acc;
|
|
150
|
+
}, METRIC_NAMES.reduce((acc, metric) => {
|
|
151
|
+
acc[metric] = 0;
|
|
152
|
+
return acc;
|
|
153
|
+
}, {}));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Filter bundles based on the outlierUrls and urls
|
|
158
|
+
* @param {Object[]} bundles - Array of RUM bundles
|
|
159
|
+
* @param {Object} opts - Options object
|
|
160
|
+
* @param {string[]} opts.outlierUrls - URLs to exclude
|
|
161
|
+
* @param {string[]} opts.urls - URLs to include
|
|
162
|
+
* @returns {Object[]} Filtered bundles
|
|
163
|
+
*/
|
|
164
|
+
export function filterBundles(bundles, opts) {
|
|
165
|
+
// Handle null/undefined opts
|
|
166
|
+
const options = opts || {};
|
|
167
|
+
|
|
168
|
+
const {
|
|
169
|
+
outlierUrls,
|
|
170
|
+
urls,
|
|
171
|
+
} = options;
|
|
172
|
+
|
|
173
|
+
if (!bundles || !Array.isArray(bundles)) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Filter bundles by outlier URLs if provided
|
|
178
|
+
let filteredBundles = bundles;
|
|
179
|
+
if (outlierUrls && outlierUrls.length > 0) {
|
|
180
|
+
filteredBundles = filteredBundles
|
|
181
|
+
.filter((item) => item && item.url && !urlMatchesFilter(item.url, outlierUrls));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// If urls filter is provided, keep only those URLs
|
|
185
|
+
if (urls && urls.length > 0) {
|
|
186
|
+
filteredBundles = filteredBundles
|
|
187
|
+
.filter((item) => item && item.url && urlMatchesFilter(item.url, urls));
|
|
188
|
+
}
|
|
189
|
+
return filteredBundles;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Validate date range
|
|
194
|
+
* @param {string} startTime
|
|
195
|
+
* @param {string} endTime
|
|
196
|
+
*/
|
|
197
|
+
export function validateDateRange(startTime, endTime) {
|
|
198
|
+
if (startTime && endTime && new Date(startTime) > new Date(endTime)) {
|
|
199
|
+
throw new Error('Start time must be before end time');
|
|
200
|
+
}
|
|
201
|
+
}
|
package/src/index.js
CHANGED
|
@@ -25,6 +25,8 @@ import rageclick from './functions/opportunities/rageclick.js';
|
|
|
25
25
|
import highInorganicHighBounceRate from './functions/opportunities/high-inorganic-high-bounce-rate.js';
|
|
26
26
|
import highOrganicLowCtr from './functions/opportunities/high-organic-low-ctr.js';
|
|
27
27
|
import trafficAnalysis from './functions/traffic-analysis.js';
|
|
28
|
+
import optimizationReportMetrics from './functions/reports/optimization/metrics.js';
|
|
29
|
+
import optimizationReportGraph from './functions/reports/optimization/graph.js';
|
|
28
30
|
|
|
29
31
|
// exported for tests
|
|
30
32
|
export const RUM_BUNDLER_API_HOST = 'https://bundles.aem.page';
|
|
@@ -44,6 +46,8 @@ const HANDLERS = {
|
|
|
44
46
|
pageviews,
|
|
45
47
|
trafficMetrics,
|
|
46
48
|
'traffic-analysis': trafficAnalysis,
|
|
49
|
+
'optimization-report-metrics': optimizationReportMetrics,
|
|
50
|
+
'optimization-report-graph': optimizationReportGraph,
|
|
47
51
|
};
|
|
48
52
|
|
|
49
53
|
function sanitize(opts) {
|