@adobe/spacecat-shared-rum-api-client 2.24.2 → 2.25.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 +14 -0
- package/package.json +1 -1
- package/src/common/constants.js +92 -0
- package/src/common/page.js +48 -0
- package/src/functions/form-vitals.js +12 -1
- package/src/functions/traffic-metrics.js +234 -0
- package/src/index.js +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-rum-api-client-v2.25.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.24.3...@adobe/spacecat-shared-rum-api-client-v2.25.0) (2025-05-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* optel traffic group metrics ([#741](https://github.com/adobe/spacecat-shared/issues/741)) ([cdfef6a](https://github.com/adobe/spacecat-shared/commit/cdfef6a42731bf5fb5056619df605e9db67e724e))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-rum-api-client-v2.24.3](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.24.2...@adobe/spacecat-shared-rum-api-client-v2.24.3) (2025-05-15)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* detect iframe forms; bug fix for duplicated iframes with # ([#736](https://github.com/adobe/spacecat-shared/issues/736)) ([5243220](https://github.com/adobe/spacecat-shared/commit/524322096233e393f59899deb9de24bdab201275))
|
|
14
|
+
|
|
1
15
|
# [@adobe/spacecat-shared-rum-api-client-v2.24.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-rum-api-client-v2.24.1...@adobe/spacecat-shared-rum-api-client-v2.24.2) (2025-05-11)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
package/src/common/constants.js
CHANGED
|
@@ -14,3 +14,95 @@ export const GRANULARITY = {
|
|
|
14
14
|
HOURLY: 'HOURLY',
|
|
15
15
|
DAILY: 'DAILY',
|
|
16
16
|
};
|
|
17
|
+
|
|
18
|
+
export const COOKIE_CONSENT_SELECTORS = [
|
|
19
|
+
// OneTrust
|
|
20
|
+
'onetrust',
|
|
21
|
+
'.onetrust-banner',
|
|
22
|
+
'#onetrust-consent-sdk',
|
|
23
|
+
'#onetrust-banner-sdk',
|
|
24
|
+
'#ot-sdk-cookie-policy',
|
|
25
|
+
'.ot-sdk-container',
|
|
26
|
+
'.ot-sdk-row',
|
|
27
|
+
'.ot-sdk-column',
|
|
28
|
+
'.ot-sdk-show-settings',
|
|
29
|
+
'#ot-',
|
|
30
|
+
'.ot-',
|
|
31
|
+
|
|
32
|
+
// Cookiebot / Usercentrics
|
|
33
|
+
'#CybotCookiebotDialog',
|
|
34
|
+
'.CybotCookiebotDialog',
|
|
35
|
+
'#CookiebotWidget',
|
|
36
|
+
'.CookiebotWidget',
|
|
37
|
+
'.cookie-consent__banner',
|
|
38
|
+
'.cookie-consent__dialog',
|
|
39
|
+
'.cookie-consent__button',
|
|
40
|
+
|
|
41
|
+
// TrustArc
|
|
42
|
+
'truste-',
|
|
43
|
+
'#truste-',
|
|
44
|
+
'#truste-consent-banner',
|
|
45
|
+
'.truste-consent-banner',
|
|
46
|
+
'#truste-consent-button',
|
|
47
|
+
'.truste-consent-button',
|
|
48
|
+
'.truste-consent-text',
|
|
49
|
+
'.truste-consent-close',
|
|
50
|
+
|
|
51
|
+
// CookieYes
|
|
52
|
+
'#cookieyes-banner',
|
|
53
|
+
'.cky-consent',
|
|
54
|
+
'.cky-banner',
|
|
55
|
+
'.cky-btn',
|
|
56
|
+
'.cky-btn-accept',
|
|
57
|
+
'.cky-btn-reject',
|
|
58
|
+
'.cky-btn-settings',
|
|
59
|
+
|
|
60
|
+
// Termly
|
|
61
|
+
'#termly-consent-banner',
|
|
62
|
+
'.termly-consent-banner',
|
|
63
|
+
'.termly-consent-button',
|
|
64
|
+
'.termly-consent-text',
|
|
65
|
+
'.termly-consent-close',
|
|
66
|
+
|
|
67
|
+
// CookieScript
|
|
68
|
+
'#cookiescript_injected',
|
|
69
|
+
'.cookiescript-consent',
|
|
70
|
+
'.cookiescript-banner',
|
|
71
|
+
'.cookiescript-btn',
|
|
72
|
+
'.cookiescript-btn-accept',
|
|
73
|
+
'.cookiescript-btn-reject',
|
|
74
|
+
'.cookiescript-btn-settings',
|
|
75
|
+
|
|
76
|
+
// CookieFirst
|
|
77
|
+
'#cookiefirst-root',
|
|
78
|
+
'.cookiefirst-banner',
|
|
79
|
+
'.cookiefirst-button',
|
|
80
|
+
'.cookiefirst-text',
|
|
81
|
+
'.cookiefirst-close',
|
|
82
|
+
|
|
83
|
+
// Quantcast Choice
|
|
84
|
+
'#qc-cmp2-ui',
|
|
85
|
+
'.qc-cmp2-ui',
|
|
86
|
+
'.qc-cmp2-summary',
|
|
87
|
+
'.qc-cmp2-footer',
|
|
88
|
+
'.qc-cmp2-header',
|
|
89
|
+
'.qc-cmp2-buttons',
|
|
90
|
+
'.qc-cmp2-button',
|
|
91
|
+
'.qc-cmp2-button-accept',
|
|
92
|
+
'.qc-cmp2-button-reject',
|
|
93
|
+
|
|
94
|
+
// Crownpeak (Evidon)
|
|
95
|
+
'#evidon-banner',
|
|
96
|
+
'.evidon-banner',
|
|
97
|
+
'.evidon-button',
|
|
98
|
+
'.evidon-text',
|
|
99
|
+
'.evidon-close',
|
|
100
|
+
|
|
101
|
+
// Popupsmart
|
|
102
|
+
'#popupsmart-consent',
|
|
103
|
+
'.popupsmart-consent',
|
|
104
|
+
'.popupsmart-banner',
|
|
105
|
+
'.popupsmart-button',
|
|
106
|
+
'.popupsmart-text',
|
|
107
|
+
'.popupsmart-close',
|
|
108
|
+
];
|
|
@@ -0,0 +1,48 @@
|
|
|
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 { hasText, isNonEmptyObject } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
import { COOKIE_CONSENT_SELECTORS } from './constants.js';
|
|
15
|
+
|
|
16
|
+
const uncategorized = 'uncategorized';
|
|
17
|
+
|
|
18
|
+
export function getPageType(bundle, pageTypes) {
|
|
19
|
+
if (!isNonEmptyObject(pageTypes)) {
|
|
20
|
+
return uncategorized;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const pageTypeEntries = Object.entries(pageTypes);
|
|
24
|
+
|
|
25
|
+
const classify = ([, regEx]) => {
|
|
26
|
+
if (regEx instanceof RegExp) {
|
|
27
|
+
return regEx.test(bundle.url);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return new RegExp(regEx).test(bundle.url);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const entry = pageTypeEntries.find(classify);
|
|
34
|
+
|
|
35
|
+
if (!hasText(entry?.[0]) || entry?.[0] === 'other | Other Pages') {
|
|
36
|
+
return uncategorized;
|
|
37
|
+
}
|
|
38
|
+
return entry?.[0] ?? uncategorized;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isConsentClick(source) {
|
|
42
|
+
if (typeof source !== 'string' || !source) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const sourceLower = source.toLowerCase();
|
|
47
|
+
return COOKIE_CONSENT_SELECTORS.some((keyword) => sourceLower.includes(keyword.toLowerCase()));
|
|
48
|
+
}
|
|
@@ -214,6 +214,17 @@ function handler(bundles) {
|
|
|
214
214
|
});
|
|
215
215
|
});
|
|
216
216
|
|
|
217
|
+
// remove duplicate urls with '#'
|
|
218
|
+
const iframeParentMapWithoutDuplicates = Object.fromEntries(
|
|
219
|
+
Object.entries(iframeParentMap).filter(([key]) => {
|
|
220
|
+
if (key.endsWith('#')) {
|
|
221
|
+
const baseUrl = key.slice(0, -1);
|
|
222
|
+
return !Object.prototype.hasOwnProperty.call(iframeParentMap, baseUrl);
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
}),
|
|
226
|
+
);
|
|
227
|
+
|
|
217
228
|
// traffic acquisition data per url - uncomment this when required
|
|
218
229
|
// const trafficByUrl = trafficAcquisition.handler(bundles);
|
|
219
230
|
// const trafficByUrlMap = Object.fromEntries(
|
|
@@ -256,7 +267,7 @@ function handler(bundles) {
|
|
|
256
267
|
const iframeParentVitalsMap = getParentPageVitalsGroupedByIFrame(
|
|
257
268
|
bundles,
|
|
258
269
|
dataChunks,
|
|
259
|
-
|
|
270
|
+
iframeParentMapWithoutDuplicates,
|
|
260
271
|
);
|
|
261
272
|
|
|
262
273
|
// populate internal navigation data
|
|
@@ -0,0 +1,234 @@
|
|
|
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 { DataChunks } from '@adobe/rum-distiller';
|
|
14
|
+
import { DELIMITER, generateKey, loadBundles } from '../utils.js';
|
|
15
|
+
import { classifyTraffic } from '../common/traffic.js';
|
|
16
|
+
import { getPageType, isConsentClick } from '../common/page.js';
|
|
17
|
+
|
|
18
|
+
function getTrafficSource(bundle) {
|
|
19
|
+
return classifyTraffic(bundle).type;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getDeviceType(bundle) {
|
|
23
|
+
return bundle.userAgent.split(':')?.[0];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function addPageTypeFacet(dataChunks, pageTypes) {
|
|
27
|
+
dataChunks.addFacet('pageType', (bundle) => getPageType(bundle, pageTypes));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function addPageTypeDeviceTypeFacet(dataChunks, pageTypes) {
|
|
31
|
+
dataChunks.addFacet('pageTypeDeviceTypes', (bundle) => {
|
|
32
|
+
const deviceType = getDeviceType(bundle);
|
|
33
|
+
const pageType = getPageType(bundle, pageTypes);
|
|
34
|
+
return generateKey(pageType, deviceType);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function addPageTypeTrafficSourceDeviceTypes(dataChunks, pageTypes) {
|
|
39
|
+
dataChunks.addFacet('pageTrafficDeviceTypes', (bundle) => {
|
|
40
|
+
const deviceType = getDeviceType(bundle);
|
|
41
|
+
const pageType = getPageType(bundle, pageTypes);
|
|
42
|
+
return generateKey(pageType, getTrafficSource(bundle), deviceType);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function addPageTypeTrafficSourceFacet(dataChunks, pageTypes) {
|
|
47
|
+
dataChunks.addFacet('pageTypeTrafficSources', (bundle) => {
|
|
48
|
+
const pageType = getPageType(bundle, pageTypes);
|
|
49
|
+
return generateKey(pageType, getTrafficSource(bundle));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function handler(bundles, options = { pageTypes: null }) {
|
|
54
|
+
const dataChunks = new DataChunks();
|
|
55
|
+
const { pageTypes: pageTypeOpt } = options;
|
|
56
|
+
|
|
57
|
+
loadBundles(bundles, dataChunks);
|
|
58
|
+
|
|
59
|
+
const metricFilter = (metrics) => {
|
|
60
|
+
const { ctr, enters, sumOfAllClicks } = metrics;
|
|
61
|
+
return {
|
|
62
|
+
ctr: ctr.sum / ctr.weight,
|
|
63
|
+
clickedSessions: ctr.sum,
|
|
64
|
+
totalSessions: ctr.weight,
|
|
65
|
+
sessionsWithEnter: enters.sum,
|
|
66
|
+
clicksOverViews: ctr.weight ? ctr.sum / ctr.weight : 0,
|
|
67
|
+
bounceRate: ctr.weight ? (1 - (ctr.sum / ctr.weight)) : 0,
|
|
68
|
+
totalNumClicks: sumOfAllClicks.sum,
|
|
69
|
+
avgClicksPerSession: ctr.sum ? sumOfAllClicks.sum / ctr.sum : 0,
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
dataChunks.addFacet('urls', (bundle) => bundle.url);
|
|
74
|
+
|
|
75
|
+
dataChunks.addFacet('trafficSources', (bundle) => getTrafficSource(bundle));
|
|
76
|
+
|
|
77
|
+
dataChunks.addFacet('urlTrafficSources', (bundle) => generateKey(bundle.url, getTrafficSource(bundle)));
|
|
78
|
+
|
|
79
|
+
dataChunks.addFacet('urlDeviceTypes', (bundle) => generateKey(bundle.url, getDeviceType(bundle)));
|
|
80
|
+
|
|
81
|
+
dataChunks.addFacet('deviceTypes', (bundle) => getDeviceType(bundle));
|
|
82
|
+
|
|
83
|
+
dataChunks.addFacet('urlTrafficSourceDeviceTypes', (bundle) => generateKey(bundle.url, getTrafficSource(bundle), getDeviceType(bundle)));
|
|
84
|
+
|
|
85
|
+
dataChunks.addFacet('deviceTypeTrafficSources', (bundle) => generateKey(getDeviceType(bundle), getTrafficSource(bundle)));
|
|
86
|
+
|
|
87
|
+
addPageTypeFacet(dataChunks, pageTypeOpt);
|
|
88
|
+
|
|
89
|
+
addPageTypeTrafficSourceFacet(dataChunks, pageTypeOpt);
|
|
90
|
+
|
|
91
|
+
addPageTypeDeviceTypeFacet(dataChunks, pageTypeOpt);
|
|
92
|
+
|
|
93
|
+
addPageTypeTrafficSourceDeviceTypes(dataChunks, pageTypeOpt);
|
|
94
|
+
|
|
95
|
+
dataChunks.addSeries('ctr', (bundle) => {
|
|
96
|
+
const isClicked = bundle.events.some((e) => e.checkpoint === 'click');
|
|
97
|
+
return isClicked ? bundle.weight : 0;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
dataChunks.addSeries('sumOfAllClicks', (bundle) => {
|
|
101
|
+
const nonConsentClicks = bundle.events
|
|
102
|
+
.filter((e) => e.checkpoint === 'click' && !isConsentClick(e.source))
|
|
103
|
+
.map(() => bundle.weight)
|
|
104
|
+
.reduce((sum, weight) => sum + weight, 0);
|
|
105
|
+
|
|
106
|
+
return nonConsentClicks;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
dataChunks.addSeries('enters', (bundle) => {
|
|
110
|
+
const containsEnter = bundle.events.some((e) => e.checkpoint === 'enter');
|
|
111
|
+
return containsEnter ? bundle.weight : 0;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const urls = dataChunks.facets.urls.map((facet) => {
|
|
115
|
+
const url = facet.value;
|
|
116
|
+
return {
|
|
117
|
+
...metricFilter(facet.metrics),
|
|
118
|
+
url,
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const pageType = dataChunks.facets.pageType.map((facet) => {
|
|
123
|
+
const type = facet.value;
|
|
124
|
+
return {
|
|
125
|
+
...metricFilter(facet.metrics),
|
|
126
|
+
type,
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const deviceTypes = dataChunks.facets.deviceTypes.map((facet) => {
|
|
131
|
+
const deviceType = facet.value;
|
|
132
|
+
return {
|
|
133
|
+
...metricFilter(facet.metrics),
|
|
134
|
+
deviceType,
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const urlDeviceTypes = dataChunks.facets.urlDeviceTypes.map((facet) => {
|
|
139
|
+
const [url, deviceType] = facet.value.split(DELIMITER);
|
|
140
|
+
return {
|
|
141
|
+
...metricFilter(facet.metrics),
|
|
142
|
+
url,
|
|
143
|
+
deviceType,
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const trafficSources = dataChunks.facets.trafficSources.map((facet) => {
|
|
148
|
+
const source = facet.value;
|
|
149
|
+
return {
|
|
150
|
+
...metricFilter(facet.metrics),
|
|
151
|
+
source,
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const urlTrafficSources = dataChunks.facets.urlTrafficSources.map((facet) => {
|
|
156
|
+
const [url, source] = facet.value.split(DELIMITER);
|
|
157
|
+
return {
|
|
158
|
+
...metricFilter(facet.metrics),
|
|
159
|
+
url,
|
|
160
|
+
source,
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const urlTrafficSourceDeviceTypes = dataChunks.facets.urlTrafficSourceDeviceTypes.map((facet) => {
|
|
165
|
+
const [url, source, deviceType] = facet.value.split(DELIMITER);
|
|
166
|
+
return {
|
|
167
|
+
...metricFilter(facet.metrics),
|
|
168
|
+
url,
|
|
169
|
+
source,
|
|
170
|
+
deviceType,
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const pageTypeTrafficSources = dataChunks.facets.pageTypeTrafficSources.map((facet) => {
|
|
175
|
+
const [type, source] = facet.value.split(DELIMITER);
|
|
176
|
+
return {
|
|
177
|
+
...metricFilter(facet.metrics),
|
|
178
|
+
type,
|
|
179
|
+
source,
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const pageTypeDeviceTypes = dataChunks.facets.pageTypeDeviceTypes.map((facet) => {
|
|
184
|
+
const [type, deviceType] = facet.value.split(DELIMITER);
|
|
185
|
+
return {
|
|
186
|
+
...metricFilter(facet.metrics),
|
|
187
|
+
type,
|
|
188
|
+
deviceType,
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const deviceTypeTrafficSources = dataChunks.facets.deviceTypeTrafficSources.map((facet) => {
|
|
193
|
+
const [deviceType, source] = facet.value.split(DELIMITER);
|
|
194
|
+
return {
|
|
195
|
+
...metricFilter(facet.metrics),
|
|
196
|
+
deviceType,
|
|
197
|
+
source,
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const pageTrafficDeviceTypes = dataChunks.facets.pageTrafficDeviceTypes.map((facet) => {
|
|
202
|
+
const [type, source, deviceType] = facet.value.split(DELIMITER);
|
|
203
|
+
return {
|
|
204
|
+
...metricFilter(facet.metrics),
|
|
205
|
+
type,
|
|
206
|
+
source,
|
|
207
|
+
deviceType,
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const metrics = {
|
|
212
|
+
url: urls,
|
|
213
|
+
urlTrafficSource: urlTrafficSources,
|
|
214
|
+
urlDeviceType: urlDeviceTypes,
|
|
215
|
+
urlTrafficSourceDeviceType: urlTrafficSourceDeviceTypes,
|
|
216
|
+
pageType,
|
|
217
|
+
pageTypeTrafficSource: pageTypeTrafficSources,
|
|
218
|
+
pageTypeDeviceType: pageTypeDeviceTypes,
|
|
219
|
+
pageTypeTrafficSourceDeviceType: pageTrafficDeviceTypes,
|
|
220
|
+
deviceType: deviceTypes,
|
|
221
|
+
deviceTypeTrafficSource: deviceTypeTrafficSources,
|
|
222
|
+
trafficSource: trafficSources,
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
return Object.entries(metrics)
|
|
226
|
+
.map(([key, value]) => ({
|
|
227
|
+
key,
|
|
228
|
+
value,
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export default {
|
|
233
|
+
handler,
|
|
234
|
+
};
|
package/src/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import trafficAcquisition from './functions/traffic-acquisition.js';
|
|
|
20
20
|
import totalMetrics from './functions/total-metrics.js';
|
|
21
21
|
import variant from './functions/variant.js';
|
|
22
22
|
import pageviews from './functions/pageviews.js';
|
|
23
|
+
import trafficMetrics from './functions/traffic-metrics.js';
|
|
23
24
|
import rageclick from './functions/opportunities/rageclick.js';
|
|
24
25
|
import highInorganicHighBounceRate from './functions/opportunities/high-inorganic-high-bounce-rate.js';
|
|
25
26
|
import highOrganicLowCtr from './functions/opportunities/high-organic-low-ctr.js';
|
|
@@ -40,6 +41,7 @@ const HANDLERS = {
|
|
|
40
41
|
'high-inorganic-high-bounce-rate': highInorganicHighBounceRate,
|
|
41
42
|
'high-organic-low-ctr': highOrganicLowCtr,
|
|
42
43
|
pageviews,
|
|
44
|
+
trafficMetrics,
|
|
43
45
|
};
|
|
44
46
|
|
|
45
47
|
function sanitize(opts) {
|