@adobe/spacecat-shared-rum-api-client 1.8.4 → 2.0.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 +20 -0
- package/README.md +102 -51
- package/package.json +7 -5
- package/src/common/aggregateFns.js +28 -0
- package/src/common/constants.js +16 -0
- package/src/common/flat-bundle.js +195 -0
- package/src/common/rum-bundler-client.js +92 -0
- package/src/functions/404.js +46 -0
- package/src/functions/cwv.js +75 -0
- package/src/index.d.ts +22 -124
- package/src/index.js +21 -175
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-rum-api-client-v2.0.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.0.0...@adobe/spacecat-shared-rum-api-client-v2.0.1) (2024-06-06)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **deps:** update dependency @adobe/fetch to v4.1.4 ([#253](https://github.com/adobe/spacecat-shared/issues/253)) ([687d739](https://github.com/adobe/spacecat-shared/commit/687d73947f15344ed6f4d6e74f223aa838ec3d6a))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-rum-api-client-v2.0.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v1.8.4...@adobe/spacecat-shared-rum-api-client-v2.0.0) (2024-06-06)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* client for rum bundler ([#252](https://github.com/adobe/spacecat-shared/issues/252)) ([854fc58](https://github.com/adobe/spacecat-shared/commit/854fc583d7184048d22c357cf11f349d3c1cfc9d))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### BREAKING CHANGES
|
|
17
|
+
|
|
18
|
+
* With this change RUMAPIClient will not be using
|
|
19
|
+
helix-run-query as datasource anymore
|
|
20
|
+
|
|
1
21
|
# [@adobe/spacecat-shared-rum-api-client-v1.8.4](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v1.8.3...@adobe/spacecat-shared-rum-api-client-v1.8.4) (2024-06-01)
|
|
2
22
|
|
|
3
23
|
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm install @adobe/spacecat-shared-rum-api-client
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
#### Creating and instance from Helix UniversalContext
|
|
16
16
|
|
|
17
17
|
```js
|
|
18
18
|
const context = {}; // Your AWS Lambda context object
|
|
@@ -20,66 +20,117 @@ const rumApiClient = RUMAPIClient.createFrom(context);
|
|
|
20
20
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
`RUMAPIClient` class needs RUM API domain key to be instantiated:
|
|
23
|
+
#### From constructor
|
|
26
24
|
|
|
27
25
|
```js
|
|
28
|
-
const
|
|
29
|
-
const rumApiClient = new RUMAPIClient(domainKey);
|
|
26
|
+
const rumApiClient = new RUMAPIClient();
|
|
30
27
|
```
|
|
31
28
|
|
|
32
|
-
###
|
|
29
|
+
### Running a query
|
|
33
30
|
|
|
34
31
|
```js
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
const opts = {
|
|
33
|
+
domain: 'www.aem.live',
|
|
34
|
+
domainkey: '<domain-key>',
|
|
35
|
+
granularity: 'hourly',
|
|
36
|
+
interval: 10
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const result = await rumApiClient.query('cwv', opts);
|
|
40
|
+
console.log(`Query result: ${result}`)
|
|
40
41
|
```
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
**Note**: all queries must be lowercase
|
|
44
|
+
|
|
45
|
+
### Query Options: the 'opts' object
|
|
46
|
+
|
|
47
|
+
| option | required | default | remarks |
|
|
48
|
+
|-------------|----------|---------|---------------------|
|
|
49
|
+
| domain | yes | | |
|
|
50
|
+
| domainkey | yes | | |
|
|
51
|
+
| interval | no | 7 | days in integer |
|
|
52
|
+
| granularity | no | daily | 'daily' or 'hourly' |
|
|
53
|
+
|
|
54
|
+
## Available queries
|
|
55
|
+
|
|
56
|
+
### cwv
|
|
57
|
+
|
|
58
|
+
Calculates the CWV data for a given domain within the requested interval. It gets the
|
|
59
|
+
P75 values for LCP, CLS, INP, TTFB metrics, along with the number of data points available for
|
|
60
|
+
each metric. Additionally, it provides grouping by URL and includes the count of page view data.
|
|
61
|
+
|
|
62
|
+
An example response:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
[
|
|
66
|
+
{
|
|
67
|
+
"url": "https://www.aem.live/home",
|
|
68
|
+
"pageviews": 2620,
|
|
69
|
+
"lcp": 2099.699999988079,
|
|
70
|
+
"lcpCount": 9,
|
|
71
|
+
"cls": 0.020660136604802475,
|
|
72
|
+
"clsCount": 7,
|
|
73
|
+
"inp": 12,
|
|
74
|
+
"inpCount": 3,
|
|
75
|
+
"ttfb": 520.4500000476837,
|
|
76
|
+
"ttfbCount": 18
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"url": "https://www.aem.live/developer/block-collection",
|
|
80
|
+
"pageviews": 2000,
|
|
81
|
+
"lcp": 512.1249999403954,
|
|
82
|
+
"lcpCount": 4,
|
|
83
|
+
"cls": 0.0005409526209424976,
|
|
84
|
+
"clsCount": 4,
|
|
85
|
+
"inp": 20,
|
|
86
|
+
"inpCount": 2,
|
|
87
|
+
"ttfb": 122.90000003576279,
|
|
88
|
+
"ttfbCount": 4
|
|
89
|
+
}
|
|
90
|
+
]
|
|
50
91
|
```
|
|
92
|
+
### 404
|
|
93
|
+
|
|
94
|
+
Calculates the number of 404 errors for a specified domain within the requested interval. The results
|
|
95
|
+
are grouped by URL and the source of the 404 error. The output includes all the various sources that
|
|
96
|
+
direct traffic to the 404 page, as well as the total number of views originating from these sources.
|
|
97
|
+
|
|
98
|
+
An example response:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
[
|
|
102
|
+
{
|
|
103
|
+
"url": "https://www.aem.live/developer/tutorial",
|
|
104
|
+
"views": 400,
|
|
105
|
+
"all_sources": [
|
|
106
|
+
"https://www.google.com",
|
|
107
|
+
"",
|
|
108
|
+
"https://www.instagram.com"
|
|
109
|
+
],
|
|
110
|
+
"source_count": 3,
|
|
111
|
+
"top_source": "https://www.google.com"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"url": "https://www.aem.live/some-other-page",
|
|
115
|
+
"views": 300,
|
|
116
|
+
"all_sources": [
|
|
117
|
+
"https://www.bing.com",
|
|
118
|
+
""
|
|
119
|
+
],
|
|
120
|
+
"source_count": 2,
|
|
121
|
+
"top_source": ""
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"url": "https://www.aem.live/developer/",
|
|
125
|
+
"views": 100,
|
|
126
|
+
"all_sources": [
|
|
127
|
+
""
|
|
128
|
+
],
|
|
129
|
+
"source_count": 1,
|
|
130
|
+
"top_source": ""
|
|
131
|
+
}
|
|
132
|
+
]
|
|
51
133
|
|
|
52
|
-
### Getting RUM Dashboard Data
|
|
53
|
-
|
|
54
|
-
```js
|
|
55
|
-
const url = "example.com";
|
|
56
|
-
|
|
57
|
-
const rumData = await rumApiClient.getRUMDashboard({ url });
|
|
58
|
-
console.log(`RUM data: ${rumData}`)
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Getting 404 checkpoints
|
|
62
|
-
|
|
63
|
-
```js
|
|
64
|
-
const url = "example.com";
|
|
65
|
-
|
|
66
|
-
const backlink = await rumApiClient.get404Sources({ url });
|
|
67
|
-
console.log(`404 Checkpoints: ${backlink}`)
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Getting Edge Delivery Services Domains
|
|
71
|
-
|
|
72
|
-
```js
|
|
73
|
-
const url = "all";
|
|
74
|
-
|
|
75
|
-
const domains = await rumApiClient.getDomainList({}, url);
|
|
76
|
-
console.log(`Backlink created: ${backlink}`)
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Testing
|
|
80
|
-
Run the included tests with the following command:
|
|
81
|
-
```
|
|
82
|
-
npm test
|
|
83
134
|
```
|
|
84
135
|
|
|
85
136
|
## Linting
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/spacecat-shared-rum-api-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Shared modules of the Spacecat Services - Rum API client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"types": "src/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"test": "c8 mocha",
|
|
9
|
+
"test": "c8 mocha 'test/**/*.test.js'",
|
|
10
10
|
"lint": "eslint .",
|
|
11
|
-
"clean": "rm -rf package-lock.json node_modules"
|
|
11
|
+
"clean": "rm -rf package-lock.json node_modules",
|
|
12
|
+
"run": "node src/test.js"
|
|
12
13
|
},
|
|
13
14
|
"mocha": {
|
|
14
15
|
"require": "test/setup-env.js",
|
|
@@ -30,11 +31,12 @@
|
|
|
30
31
|
"access": "public"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@adobe/fetch": "4.1.
|
|
34
|
+
"@adobe/fetch": "4.1.4",
|
|
34
35
|
"@adobe/helix-shared-wrap": "2.0.2",
|
|
35
36
|
"@adobe/helix-universal": "4.5.2",
|
|
36
37
|
"@adobe/spacecat-shared-utils": "1.4.0",
|
|
37
|
-
"aws4": "1.13.0"
|
|
38
|
+
"aws4": "1.13.0",
|
|
39
|
+
"d3-array": "3.2.4"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
40
42
|
"chai": "4.4.1",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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
|
+
/**
|
|
14
|
+
* Calculates the total page views by URL from an array of bundles.
|
|
15
|
+
* @param {Array<Object>} bundles - An array of RUM bundles (NOT Flat bundles).
|
|
16
|
+
* @returns {Object} An object where keys are URLs and values are the total page views for each URL.
|
|
17
|
+
*/
|
|
18
|
+
function pageviewsByUrl(bundles) {
|
|
19
|
+
return bundles.reduce((acc, cur) => {
|
|
20
|
+
if (!acc[cur.url]) acc[cur.url] = 0;
|
|
21
|
+
acc[cur.url] += cur.weight;
|
|
22
|
+
return acc;
|
|
23
|
+
}, {});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
pageviewsByUrl,
|
|
28
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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
|
+
export const GRANULARITY = {
|
|
14
|
+
HOURLY: 'HOURLY',
|
|
15
|
+
DAILY: 'DAILY',
|
|
16
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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
|
+
/**
|
|
14
|
+
* This utility class is designed to flatten checkpoints of RUM bundles served from the
|
|
15
|
+
* RUM bundler API. Its primary purpose is to simplify data juggling.
|
|
16
|
+
*
|
|
17
|
+
* RUM bundles are returned grouped by id. Often, there's a need to further group the bundles by
|
|
18
|
+
* another checkpoint such as URL, source, or target. By using this class, RUM bundles can be
|
|
19
|
+
* flattened for easier implementation of all required grouping operations.
|
|
20
|
+
*
|
|
21
|
+
* This class extends the standard Array class, to make all standard functions such as filter, map,
|
|
22
|
+
* and reduce available for use with this class as well.
|
|
23
|
+
*
|
|
24
|
+
* For instance, when the RUM bundler returns "bundles" in the following format:
|
|
25
|
+
*
|
|
26
|
+
* ```json
|
|
27
|
+
* [
|
|
28
|
+
* {
|
|
29
|
+
* "id": "BSX",
|
|
30
|
+
* "time": "2024-05-26T05:00:02.706Z",
|
|
31
|
+
* "url": "https://www.aem.live/developer/tutorial",
|
|
32
|
+
* "weight": 100,
|
|
33
|
+
* "events": [
|
|
34
|
+
* {
|
|
35
|
+
* "checkpoint": "navigate",
|
|
36
|
+
* "target": "visible",
|
|
37
|
+
* "source": "https://www.aem.live/docs/",
|
|
38
|
+
* "timeDelta": 2706.199951171875
|
|
39
|
+
* },
|
|
40
|
+
* {
|
|
41
|
+
* "checkpoint": "loadresource",
|
|
42
|
+
* "target": 4,
|
|
43
|
+
* "source": "https://www.aem.live/new-nav.plain.html",
|
|
44
|
+
* "timeDelta": 2707.699951171875
|
|
45
|
+
* },
|
|
46
|
+
* {
|
|
47
|
+
* "checkpoint": "play",
|
|
48
|
+
* "source": "https://www.hlx.live/developer/videos/tutorial-step1.mp4",
|
|
49
|
+
* "timeDelta": 12671.89990234375
|
|
50
|
+
* },
|
|
51
|
+
* {
|
|
52
|
+
* "checkpoint": "viewmedia",
|
|
53
|
+
* "target": "https://www.aem.live/developer/media_1c03ad909a87a4e318a33e780b93e4a1f8e7581a3.png",
|
|
54
|
+
* "timeDelta": 43258.39990234375
|
|
55
|
+
* }
|
|
56
|
+
* ]
|
|
57
|
+
* }
|
|
58
|
+
* ]
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* After flattening, the FlatBundle appears as follows:
|
|
62
|
+
*
|
|
63
|
+
* ```json
|
|
64
|
+
* [
|
|
65
|
+
* {
|
|
66
|
+
* "id": "BSX",
|
|
67
|
+
* "time": "2024-05-26T05:00:02.706Z",
|
|
68
|
+
* "url": "https://www.aem.live/developer/tutorial",
|
|
69
|
+
* "weight": 100,
|
|
70
|
+
* "checkpoint": "navigate",
|
|
71
|
+
* "target": "visible",
|
|
72
|
+
* "source": "https://www.aem.live/docs/",
|
|
73
|
+
* "timeDelta": 2706.199951171875
|
|
74
|
+
* },
|
|
75
|
+
* {
|
|
76
|
+
* "id": "BSX",
|
|
77
|
+
* "time": "2024-05-26T05:00:02.706Z",
|
|
78
|
+
* "url": "https://www.aem.live/developer/tutorial",
|
|
79
|
+
* "weight": 100,
|
|
80
|
+
* "checkpoint": "loadresource",
|
|
81
|
+
* "target": 4,
|
|
82
|
+
* "source": "https://www.aem.live/new-nav.plain.html",
|
|
83
|
+
* "timeDelta": 2707.699951171875
|
|
84
|
+
* },
|
|
85
|
+
* {
|
|
86
|
+
* "id": "BSX",
|
|
87
|
+
* "time": "2024-05-26T05:00:02.706Z",
|
|
88
|
+
* "url": "https://www.aem.live/developer/tutorial",
|
|
89
|
+
* "weight": 100,
|
|
90
|
+
* "checkpoint": "play",
|
|
91
|
+
* "source": "https://www.hlx.live/developer/videos/tutorial-step1.mp4",
|
|
92
|
+
* "timeDelta": 12671.89990234375
|
|
93
|
+
* },
|
|
94
|
+
* {
|
|
95
|
+
* "id": "BSX",
|
|
96
|
+
* "time": "2024-05-26T05:00:02.706Z",
|
|
97
|
+
* "url": "https://www.aem.live/developer/tutorial",
|
|
98
|
+
* "weight": 100,
|
|
99
|
+
* "checkpoint": "viewmedia",
|
|
100
|
+
* "target": "https://www.aem.live/developer/media_1c03ad909a87a4e318a33e780b93e4a1f8e7581a3.png",
|
|
101
|
+
* "timeDelta": 43258.39990234375
|
|
102
|
+
* }
|
|
103
|
+
* ]
|
|
104
|
+
*
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @extends Array
|
|
108
|
+
*/
|
|
109
|
+
export class FlatBundle extends Array {
|
|
110
|
+
static fromArray(array) {
|
|
111
|
+
const flattened = array.flatMap((bundle) => {
|
|
112
|
+
const temp = { ...bundle };
|
|
113
|
+
delete temp.events;
|
|
114
|
+
return bundle.events.map((event) => ({
|
|
115
|
+
...temp,
|
|
116
|
+
...event,
|
|
117
|
+
}));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
Object.setPrototypeOf(flattened, FlatBundle.prototype);
|
|
121
|
+
return flattened;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Groups FlatBundles by one or more keys. For example, this method can be used to
|
|
126
|
+
* group Flat Bundles by url and source as shown below:
|
|
127
|
+
*
|
|
128
|
+
* ```js
|
|
129
|
+
* FlatBundle.fromArray(bundles)
|
|
130
|
+
* .groupBy('url', 'source');
|
|
131
|
+
* ```
|
|
132
|
+
* The output of this code returns a nested object with keys ("url", "source") and
|
|
133
|
+
* "items", nested to the depth of the number of keys used for grouping.
|
|
134
|
+
*
|
|
135
|
+
* An example response for grouping by url and source could be like:
|
|
136
|
+
*
|
|
137
|
+
* ```json
|
|
138
|
+
* [
|
|
139
|
+
* {
|
|
140
|
+
* "url": "some-url",
|
|
141
|
+
* "items": [
|
|
142
|
+
* { "source": "source1", "items": [] },
|
|
143
|
+
* { "source": "source2", "items": [] }
|
|
144
|
+
* ]
|
|
145
|
+
* },
|
|
146
|
+
* {
|
|
147
|
+
* "url": "some-other-url",
|
|
148
|
+
* "items": [
|
|
149
|
+
* { "source": "source3", "items": [] }
|
|
150
|
+
* ]
|
|
151
|
+
* }
|
|
152
|
+
* ]
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
155
|
+
*
|
|
156
|
+
* @param {...string} keys - The keys to group by.
|
|
157
|
+
* @returns {Array} The grouped flat bundles.
|
|
158
|
+
*/
|
|
159
|
+
groupBy(...keys) {
|
|
160
|
+
// Initialize the result as an empty array
|
|
161
|
+
const result = [];
|
|
162
|
+
|
|
163
|
+
// Create a map to hold references to the current levels
|
|
164
|
+
const map = new Map();
|
|
165
|
+
|
|
166
|
+
// Iterate over each item in the array
|
|
167
|
+
for (const item of this) {
|
|
168
|
+
let currentLevel = result;
|
|
169
|
+
let mapLevel = map;
|
|
170
|
+
|
|
171
|
+
// Iterate over each key to build the nested structure
|
|
172
|
+
for (const key of keys) {
|
|
173
|
+
const groupValue = item[key];
|
|
174
|
+
|
|
175
|
+
// Check if the group already exists
|
|
176
|
+
if (!mapLevel.has(groupValue)) {
|
|
177
|
+
// Create a new group if it doesn't exist
|
|
178
|
+
const newGroup = { [key]: groupValue, items: [] };
|
|
179
|
+
currentLevel.push(newGroup);
|
|
180
|
+
mapLevel.set(groupValue, { group: newGroup, nextMap: new Map() });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Move to the next level
|
|
184
|
+
const { group, nextMap } = mapLevel.get(groupValue);
|
|
185
|
+
currentLevel = group.items;
|
|
186
|
+
mapLevel = nextMap;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Add the item to the final level
|
|
190
|
+
currentLevel.push(item);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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
|
+
/* eslint-disable no-await-in-loop */
|
|
13
|
+
|
|
14
|
+
import { hasText } from '@adobe/spacecat-shared-utils';
|
|
15
|
+
import { fetch } from '../utils.js';
|
|
16
|
+
import { GRANULARITY } from './constants.js';
|
|
17
|
+
|
|
18
|
+
const BASE_URL = 'https://rum.fastly-aem.page/bundles';
|
|
19
|
+
const HOURS_IN_DAY = 24;
|
|
20
|
+
const ONE_HOUR = 1000 * 60 * 60;
|
|
21
|
+
const ONE_DAY = ONE_HOUR * HOURS_IN_DAY;
|
|
22
|
+
|
|
23
|
+
const CHUNK_SIZE = 31;
|
|
24
|
+
|
|
25
|
+
function filterBundles(checkpoints = []) {
|
|
26
|
+
return (bundle) => {
|
|
27
|
+
if (checkpoints.length > 0) {
|
|
28
|
+
const events = bundle.events.filter((event) => checkpoints.includes(event.checkpoint));
|
|
29
|
+
return {
|
|
30
|
+
...bundle,
|
|
31
|
+
events,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return bundle;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function constructUrl(domain, date, granularity, domainkey) {
|
|
39
|
+
const year = date.getUTCFullYear();
|
|
40
|
+
const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
41
|
+
const day = date.getUTCDate().toString().padStart(2, '0');
|
|
42
|
+
const hour = granularity.toUpperCase() === GRANULARITY.HOURLY ? `/${date.getUTCHours().toString().padStart(2, '0')}` : '';
|
|
43
|
+
|
|
44
|
+
return `${BASE_URL}/${domain}/${year}/${month}/${day}${hour}?domainkey=${domainkey}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getUrlChunks(urls, chunkSize) {
|
|
48
|
+
return Array(Math.ceil(urls.length / chunkSize))
|
|
49
|
+
.fill()
|
|
50
|
+
.map((_, index) => urls.slice(index * chunkSize, (index + 1) * chunkSize));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function fetchBundles(opts = {}) {
|
|
54
|
+
const {
|
|
55
|
+
domain,
|
|
56
|
+
domainkey,
|
|
57
|
+
interval = 7,
|
|
58
|
+
granularity = GRANULARITY.DAILY,
|
|
59
|
+
checkpoints = [],
|
|
60
|
+
} = opts;
|
|
61
|
+
|
|
62
|
+
if (!hasText(domain) || !hasText(domainkey)) {
|
|
63
|
+
throw new Error('Missing required parameters');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const multiplier = granularity.toUpperCase() === GRANULARITY.HOURLY ? ONE_HOUR : ONE_DAY;
|
|
67
|
+
const range = granularity.toUpperCase() === GRANULARITY.HOURLY
|
|
68
|
+
? interval * HOURS_IN_DAY
|
|
69
|
+
: interval + 1;
|
|
70
|
+
|
|
71
|
+
const urls = [];
|
|
72
|
+
const currentDate = new Date();
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < range; i += 1) {
|
|
75
|
+
const date = new Date(currentDate.getTime() - i * multiplier);
|
|
76
|
+
urls.push(constructUrl(domain, date, granularity, domainkey));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const chunks = getUrlChunks(urls, CHUNK_SIZE);
|
|
80
|
+
|
|
81
|
+
const result = [];
|
|
82
|
+
for (const chunk of chunks) {
|
|
83
|
+
const responses = await Promise.all(chunk.map((url) => fetch(url)));
|
|
84
|
+
const bundles = await Promise.all(responses.map((response) => response.json()));
|
|
85
|
+
result.push(...bundles.flatMap((b) => b.rumBundles.map(filterBundles(checkpoints))));
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
fetchBundles,
|
|
92
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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 { FlatBundle } from '../common/flat-bundle.js';
|
|
14
|
+
|
|
15
|
+
function collect404s(groupedByUrlAndSource) {
|
|
16
|
+
const { url, items: itemsByUrl } = groupedByUrlAndSource;
|
|
17
|
+
|
|
18
|
+
// find top source which has the most amount of occurrences
|
|
19
|
+
const { source: topSource } = itemsByUrl.reduce(
|
|
20
|
+
(max, obj) => (obj.items.length > max.items.length ? obj : max),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// calculate the total number of views per 404 event
|
|
24
|
+
const views = itemsByUrl.flatMap((item) => item.items).reduce((acc, cur) => acc + cur.weight, 0);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
url,
|
|
28
|
+
views,
|
|
29
|
+
all_sources: itemsByUrl.map((item) => item.source),
|
|
30
|
+
source_count: itemsByUrl.length,
|
|
31
|
+
top_source: topSource,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function handler(bundles) {
|
|
36
|
+
return FlatBundle.fromArray(bundles)
|
|
37
|
+
.filter((row) => row.checkpoint === '404')
|
|
38
|
+
.groupBy('url', 'source')
|
|
39
|
+
.map(collect404s)
|
|
40
|
+
.sort((a, b) => b.views - a.views); // sort desc by views
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default {
|
|
44
|
+
handler,
|
|
45
|
+
checkpoints: ['404'],
|
|
46
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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 { quantile } from 'd3-array';
|
|
14
|
+
import { pageviewsByUrl } from '../common/aggregateFns.js';
|
|
15
|
+
import { FlatBundle } from '../common/flat-bundle.js';
|
|
16
|
+
|
|
17
|
+
const CWV_METRICS = ['lcp', 'cls', 'inp', 'ttfb'].map((metric) => `cwv-${metric}`);
|
|
18
|
+
|
|
19
|
+
function collectCWVs(groupedByUrlIdTime) {
|
|
20
|
+
const { url, items: itemsByUrl } = groupedByUrlIdTime;
|
|
21
|
+
|
|
22
|
+
// first level: grouped by url
|
|
23
|
+
const CWVs = itemsByUrl.reduce((acc, { items: itemsById }) => {
|
|
24
|
+
// second level: grouped by id
|
|
25
|
+
const itemsByTime = itemsById.flatMap((itemById) => itemById.items);
|
|
26
|
+
// third level: grouped by time
|
|
27
|
+
const maximums = itemsByTime.reduce((values, item) => {
|
|
28
|
+
// each session (id-time) can contain multiple measurement for the same metric
|
|
29
|
+
// we need to find the maximum per metric type
|
|
30
|
+
// eslint-disable-next-line no-param-reassign
|
|
31
|
+
values[item.checkpoint] = Math.max(values[item.checkpoint] || 0, item.value);
|
|
32
|
+
return values;
|
|
33
|
+
}, {});
|
|
34
|
+
|
|
35
|
+
// max values per id for each metric type are collected into an array
|
|
36
|
+
CWV_METRICS.forEach((metric) => {
|
|
37
|
+
if (!acc[metric]) acc[metric] = [];
|
|
38
|
+
if (maximums[metric]) {
|
|
39
|
+
acc[metric].push(maximums[metric]);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return acc;
|
|
43
|
+
}, {});
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
url,
|
|
47
|
+
lcp: quantile(CWVs['cwv-lcp'], 0.75) || null,
|
|
48
|
+
lcpCount: CWVs['cwv-lcp'].length,
|
|
49
|
+
cls: quantile(CWVs['cwv-cls'], 0.75) || null,
|
|
50
|
+
clsCount: CWVs['cwv-cls'].length,
|
|
51
|
+
inp: quantile(CWVs['cwv-inp'], 0.75) || null,
|
|
52
|
+
inpCount: CWVs['cwv-inp'].length,
|
|
53
|
+
ttfb: quantile(CWVs['cwv-ttfb'], 0.75) || null,
|
|
54
|
+
ttfbCount: CWVs['cwv-ttfb'].length,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function handler(bundles) {
|
|
59
|
+
const pageviews = pageviewsByUrl(bundles);
|
|
60
|
+
|
|
61
|
+
return FlatBundle.fromArray(bundles)
|
|
62
|
+
.groupBy('url', 'id', 'time')
|
|
63
|
+
.map(collectCWVs)
|
|
64
|
+
.filter((row) => row.lcp || row.cls || row.inp || row.ttfb) // filter out pages with no cwv data
|
|
65
|
+
.map((acc) => {
|
|
66
|
+
acc.pageviews = pageviews[acc.url];
|
|
67
|
+
return acc;
|
|
68
|
+
})
|
|
69
|
+
.sort((a, b) => b.pageviews - a.pageviews); // sort desc by pageviews
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default {
|
|
73
|
+
handler,
|
|
74
|
+
checkpoints: ['cwv-lcp', 'cwv-cls', 'cwv-inp', 'cwv-ttfb'],
|
|
75
|
+
};
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -13,136 +13,34 @@
|
|
|
13
13
|
import { UniversalContext } from '@adobe/helix-universal';
|
|
14
14
|
|
|
15
15
|
export interface RUMAPIOptions {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
limit?: number;
|
|
21
|
-
url?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface RUMDashboardOptions {
|
|
25
|
-
interval?: number;
|
|
26
|
-
startdate?: string,
|
|
27
|
-
enddate?: string,
|
|
28
|
-
offset?: number;
|
|
29
|
-
limit?: number;
|
|
30
|
-
domainkey?: string,
|
|
31
|
-
url?: string;
|
|
16
|
+
domain: string;
|
|
17
|
+
domainkey: string;
|
|
18
|
+
interval?: number;
|
|
19
|
+
granularity?: 'hourly' | 'daily';
|
|
32
20
|
}
|
|
33
21
|
|
|
34
22
|
export default class RUMAPIClient {
|
|
35
23
|
/**
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
24
|
+
* Static factory method to create an instance of RUMAPIClient.
|
|
25
|
+
* @param {UniversalContext} context - An object containing the AWS Lambda context information
|
|
26
|
+
* @returns An instance of RUMAPIClient.
|
|
27
|
+
* @remarks This method is designed to create a new instance from an AWS Lambda context.
|
|
28
|
+
* The created instance is stored in the Lambda context, and subsequent calls to
|
|
29
|
+
* this method will return the singleton instance if previously created.
|
|
30
|
+
*/
|
|
43
31
|
static createFrom(context: UniversalContext): RUMAPIClient;
|
|
44
32
|
|
|
45
33
|
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* @remarks The domain key is specific to the RUM API.
|
|
50
|
-
*/
|
|
51
|
-
constructor(domainkey: string);
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Asynchronous method to create a RUM backlink.
|
|
55
|
-
* @param {string} url - A string representing the URL for the backlink.
|
|
56
|
-
* @param {number} expiry - An integer representing the expiry value for the backlink.
|
|
57
|
-
* @param {RUMDashboardOptions} params - An object representing the parameters to be included
|
|
58
|
-
* @returns A Promise resolving to a string representing url of the created backlink.
|
|
59
|
-
* @remarks This method creates a backlink to the RUM dashboard, allowing users
|
|
60
|
-
* to view their reports and monitor real user activities.
|
|
61
|
-
*/
|
|
62
|
-
createRUMBacklink(url: string, expiry: number, params?: RUMDashboardOptions): Promise<string>;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Asynchronous method to create a 404 backlink.
|
|
66
|
-
* @param {string} url - A string representing the URL for the backlink.
|
|
67
|
-
* @param {number} expiry - An integer representing the expiry value for the backlink.
|
|
68
|
-
* @param {RUMDashboardOptions} params - An object representing the parameters to be included
|
|
69
|
-
* @returns A Promise resolving to a string representing url of the created backlink.
|
|
70
|
-
* @remarks This method creates a backlink to the 404 report, allowing users
|
|
71
|
-
* to view their 404 pages.
|
|
72
|
-
*/
|
|
73
|
-
create404Backlink(url: string, expiry: number, params?: RUMDashboardOptions): Promise<string>;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Asynchronous method to return the RUM dashboard API call response data.
|
|
77
|
-
* @param {RUMAPIOptions} params - An object representing the parameters to be included
|
|
78
|
-
* for the RUM Dashboard API call.
|
|
79
|
-
* @returns A Promise resolving to the RUM dashboard response data.
|
|
80
|
-
*/
|
|
81
|
-
getRUMDashboard(params?: RUMAPIOptions): Promise<Array<object>>;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Asynchronous method to return the Experimentation API call response data.
|
|
85
|
-
* @param {RUMAPIOptions} params - An object representing the parameters to be included
|
|
86
|
-
* for the Experimentation data API call.
|
|
87
|
-
* @returns A Promise resolving to the Experimentation response data.
|
|
88
|
-
*/
|
|
89
|
-
getExperimentationData(params?: RUMAPIOptions): Promise<Array<object>>;
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Method to return the url composed of params that the Experimentation API is called with.
|
|
93
|
-
* @param {RUMAPIOptions} params - An object representing the parameters to be included
|
|
94
|
-
* for the Experimentation API call.
|
|
95
|
-
* @returns A string returning the Experimentation url including query parameters.
|
|
96
|
-
*/
|
|
97
|
-
createExperimentationURL(params?: RUMAPIOptions): string;
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Method to return the url composed of params that the rum-sources API is called with.
|
|
101
|
-
* @param {RUMAPIOptions} params - An object representing the parameters to be included
|
|
102
|
-
* for the rum-sources API call.
|
|
103
|
-
* @returns A string returning the rum-sources url including query parameters.
|
|
104
|
-
*/
|
|
105
|
-
createConversionURL(params?: RUMAPIOptions): string;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Asynchronous method to return the 404 sources API call response data.
|
|
109
|
-
* @param {RUMAPIOptions} params - An object representing the parameters to be included
|
|
110
|
-
* for the 404 sources API call.
|
|
111
|
-
* @returns A Promise resolving to the 404 sources response data.
|
|
112
|
-
*/
|
|
113
|
-
get404Sources(params?: RUMAPIOptions): Promise<Array<object>>;
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Method to return the url composed of params that the RUM Dashboard API is called with.
|
|
117
|
-
* @param {RUMAPIOptions} params - An object representing the parameters to be included
|
|
118
|
-
* for the RUM Dashboard API call.
|
|
119
|
-
* @returns A string returning to the RUM Dashboard url including query parameters.
|
|
120
|
-
*/
|
|
121
|
-
createRUMURL(params?: RUMAPIOptions): string;
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Method to return the url composed of params that the 404 sources API is called with.
|
|
125
|
-
* @param {RUMAPIOptions} params - An object representing the parameters to be included
|
|
126
|
-
* for the 404 sources API call.
|
|
127
|
-
* @returns A string returning to the 404 sources url including query parameters.
|
|
128
|
-
*/
|
|
129
|
-
create404URL(params?: RUMAPIOptions): string;
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Asynchronous method to return an array with the domain for a specific url
|
|
133
|
-
* or an array of all domain urls
|
|
134
|
-
* @param {RUMAPIOptions} params - An object representing the parameters to be included
|
|
135
|
-
* for the domain list call.
|
|
136
|
-
* @returns A Promise resolving to an array of the domain for a specific url
|
|
137
|
-
* or an array of all domain urls .
|
|
138
|
-
*/
|
|
139
|
-
getDomainList(params?: RUMAPIOptions): Promise<Array<string>>;
|
|
34
|
+
* Constructor for creating an instance of RUMAPIClient.
|
|
35
|
+
*/
|
|
36
|
+
constructor();
|
|
140
37
|
|
|
141
38
|
/**
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
39
|
+
* Asynchronous method to run queries against RUM Bundler API.
|
|
40
|
+
* @param {string} query - Name of the query to run.
|
|
41
|
+
* @param {RUMAPIOptions} opts - A object containing options for query to run.
|
|
42
|
+
* @returns A Promise resolving to an object with the query results.
|
|
43
|
+
* @remarks See the README.md for the available queries.
|
|
44
|
+
*/
|
|
45
|
+
query(query: string, opts?: RUMAPIOptions): Promise<object>;
|
|
148
46
|
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -9,192 +9,38 @@
|
|
|
9
9
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
} from '@adobe/spacecat-shared-utils';
|
|
16
|
-
import { fetch } from './utils.js';
|
|
12
|
+
import { fetchBundles } from './common/rum-bundler-client.js';
|
|
13
|
+
import notfound from './functions/404.js';
|
|
14
|
+
import cwv from './functions/cwv.js';
|
|
17
15
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
NOT_FOUND_DASHBOARD_UI: 'https://data.aem.live/404-reports',
|
|
22
|
-
RUM_DASHBOARD: 'https://helix-pages.anywhere.run/helix-services/run-query@v3/rum-dashboard',
|
|
23
|
-
DOMAIN_LIST: 'https://helix-pages.anywhere.run/helix-services/run-query@v3/dash/domain-list',
|
|
24
|
-
RUM_404: 'https://helix-pages.anywhere.run/helix-services/run-query@v3/rum-404',
|
|
25
|
-
RUM_SOURCES: 'https://helix-pages.anywhere.run/helix-services/run-query@v3/rum-sources',
|
|
26
|
-
RUM_EXPERIMENTS: 'https://helix-pages.anywhere.run/helix-services/run-query@v3/rum-experiments',
|
|
16
|
+
const HANDLERS = {
|
|
17
|
+
404: notfound,
|
|
18
|
+
cwv,
|
|
27
19
|
};
|
|
28
20
|
|
|
29
|
-
const DOMAIN_LIST_DEFAULT_PARAMS = {
|
|
30
|
-
interval: 30,
|
|
31
|
-
offset: 0,
|
|
32
|
-
limit: 100000,
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export const RUM_DEFAULT_PARAMS = {
|
|
36
|
-
interval: 7,
|
|
37
|
-
offset: 0,
|
|
38
|
-
limit: 101,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export const CONVERSION_DEFAULT_PARAMS = {
|
|
42
|
-
...RUM_DEFAULT_PARAMS,
|
|
43
|
-
checkpoint: 'convert',
|
|
44
|
-
aggregate: false,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export const create404URL = (params = {}) => createUrl(
|
|
48
|
-
APIS.RUM_404,
|
|
49
|
-
{
|
|
50
|
-
...RUM_DEFAULT_PARAMS, ...params,
|
|
51
|
-
},
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
export const createRUMURL = (params = {}) => createUrl(
|
|
55
|
-
APIS.RUM_DASHBOARD,
|
|
56
|
-
{
|
|
57
|
-
...RUM_DEFAULT_PARAMS, ...params,
|
|
58
|
-
},
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
export const createExperimentationURL = (params = {}) => createUrl(
|
|
62
|
-
APIS.RUM_EXPERIMENTS,
|
|
63
|
-
{
|
|
64
|
-
...RUM_DEFAULT_PARAMS, ...params,
|
|
65
|
-
},
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
export const createConversionURL = (params = {}) => createUrl(
|
|
69
|
-
APIS.RUM_SOURCES,
|
|
70
|
-
{
|
|
71
|
-
...CONVERSION_DEFAULT_PARAMS, ...params,
|
|
72
|
-
},
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
export async function sendRequest(url, opts) {
|
|
76
|
-
let respJson;
|
|
77
|
-
try {
|
|
78
|
-
const resp = await (isObject(opts) ? fetch(url, opts) : fetch(url));
|
|
79
|
-
respJson = await resp.json();
|
|
80
|
-
} catch (e) {
|
|
81
|
-
throw new Error(`Error during rum api call: ${e.message}`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const data = respJson?.results?.data;
|
|
85
|
-
if (!isArray(data)) {
|
|
86
|
-
throw new Error('Unexpected response from rum api. $.results.data is not array');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return data;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function generateDomainKey(domainkey, url, expiry) {
|
|
93
|
-
if (!hasText(url) || !isInteger(expiry)) {
|
|
94
|
-
throw new Error('Invalid input: url and expiry date parameters are required');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const params = {
|
|
98
|
-
domainkey,
|
|
99
|
-
url,
|
|
100
|
-
expiry: dateAfterDays(expiry).toISOString(),
|
|
101
|
-
note: 'generated by spacecat alerting',
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const data = await sendRequest(createUrl(APIS.ROTATE_DOMAINKEYS, params), { method: 'POST' });
|
|
105
|
-
|
|
106
|
-
if (data.length === 0) {
|
|
107
|
-
throw new Error('Unexpected response: Rum api returned empty result');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (data[0].status !== 'success') {
|
|
111
|
-
throw new Error('Unexpected response: Response was not successful');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!hasText(data[0].key)) {
|
|
115
|
-
throw new Error('Unexpected response: Rum api returned null domain key');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return data[0].key;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async function createBacklink(dashboardUrl, domainKey, domainUrl, expiry, params = {}) {
|
|
122
|
-
const scopedDomainKey = await generateDomainKey(domainKey, domainUrl, expiry);
|
|
123
|
-
const dataDeskParams = { ...params };
|
|
124
|
-
if (dataDeskParams.startdate) {
|
|
125
|
-
delete dataDeskParams.interval;
|
|
126
|
-
}
|
|
127
|
-
return createUrl(dashboardUrl, {
|
|
128
|
-
offset: 0,
|
|
129
|
-
limit: 100,
|
|
130
|
-
url: domainUrl,
|
|
131
|
-
domainkey: scopedDomainKey,
|
|
132
|
-
...(dataDeskParams?.startdate ? {} : { interval: expiry }),
|
|
133
|
-
...dataDeskParams,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
21
|
export default class RUMAPIClient {
|
|
138
22
|
static createFrom(context) {
|
|
139
23
|
if (context.rumApiClient) return context.rumApiClient;
|
|
140
24
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
const client = new RUMAPIClient(domainkey);
|
|
25
|
+
const client = new RUMAPIClient();
|
|
144
26
|
context.rumApiClient = client;
|
|
145
27
|
return client;
|
|
146
28
|
}
|
|
147
29
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
this.domainkey = domainkey;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async getRUMDashboard(params = {}) {
|
|
157
|
-
return sendRequest(createRUMURL({ ...params, domainkey: this.domainkey }));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async getExperimentationData(params = {}) {
|
|
161
|
-
return sendRequest(createExperimentationURL({ ...params, domainkey: this.domainkey }));
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async getConversionData(params = {}) {
|
|
165
|
-
return sendRequest(createConversionURL({ ...params, domainkey: this.domainkey }));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async get404Sources(params = {}) {
|
|
169
|
-
return sendRequest(create404URL({ ...params, domainkey: this.domainkey }));
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async getDomainList(params = {}) {
|
|
173
|
-
const data = await sendRequest(createUrl(
|
|
174
|
-
APIS.DOMAIN_LIST,
|
|
175
|
-
{ domainkey: this.domainkey, ...DOMAIN_LIST_DEFAULT_PARAMS, ...params },
|
|
176
|
-
));
|
|
177
|
-
|
|
178
|
-
return data.map((row) => row.hostname);
|
|
179
|
-
}
|
|
30
|
+
// eslint-disable-next-line class-methods-use-this
|
|
31
|
+
async query(query, opts) {
|
|
32
|
+
const { handler, checkpoints } = HANDLERS[query] || {};
|
|
33
|
+
if (!handler) throw new Error(`Unknown query ${query}`);
|
|
180
34
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
expiry,
|
|
187
|
-
params,
|
|
188
|
-
);
|
|
189
|
-
}
|
|
35
|
+
try {
|
|
36
|
+
const bundles = await fetchBundles({
|
|
37
|
+
...opts,
|
|
38
|
+
checkpoints,
|
|
39
|
+
});
|
|
190
40
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
url,
|
|
196
|
-
expiry,
|
|
197
|
-
params,
|
|
198
|
-
);
|
|
41
|
+
return handler(bundles);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
throw new Error(`Query '${query}' failed. Opts: ${JSON.stringify(opts)}. Reason: ${e.message}`);
|
|
44
|
+
}
|
|
199
45
|
}
|
|
200
46
|
}
|