@adobe/spacecat-shared-utils 1.34.0 → 1.35.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 CHANGED
@@ -1,3 +1,17 @@
1
+ # [@adobe/spacecat-shared-utils-v1.35.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.35.0...@adobe/spacecat-shared-utils-v1.35.1) (2025-04-01)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * adding forms constants at common place ([#672](https://github.com/adobe/spacecat-shared/issues/672)) ([4b8254d](https://github.com/adobe/spacecat-shared/commit/4b8254d32112e4921ae63396733101fa1f3bbed7))
7
+
8
+ # [@adobe/spacecat-shared-utils-v1.35.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.34.0...@adobe/spacecat-shared-utils-v1.35.0) (2025-03-06)
9
+
10
+
11
+ ### Features
12
+
13
+ * tracing fetch timeout ([#657](https://github.com/adobe/spacecat-shared/issues/657)) ([67335e3](https://github.com/adobe/spacecat-shared/commit/67335e32b7cceeb500ca58dc05518876589a108a))
14
+
1
15
  # [@adobe/spacecat-shared-utils-v1.34.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.33.3...@adobe/spacecat-shared-utils-v1.34.0) (2025-03-05)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.34.0",
3
+ "version": "1.35.1",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "engines": {
package/src/formcalc.js CHANGED
@@ -11,7 +11,9 @@
11
11
  */
12
12
 
13
13
  const DAILY_PAGEVIEW_THRESHOLD = 200;
14
- const CR_THRESHOLD_RATIO = 0.2;
14
+ const CR_THRESHOLD_RATIO = 0.3;
15
+ // audit interval
16
+ const INTERVAL = 15;
15
17
  const MOBILE = 'mobile';
16
18
  const DESKTOP = 'desktop';
17
19
 
@@ -60,8 +62,8 @@ function aggregateFormVitalsByDevice(formVitalsCollection) {
60
62
  return resultMap;
61
63
  }
62
64
 
63
- function hasHighPageViews(interval, pageViews) {
64
- return pageViews > DAILY_PAGEVIEW_THRESHOLD * interval;
65
+ function hasHighPageViews(pageViews) {
66
+ return pageViews > DAILY_PAGEVIEW_THRESHOLD * INTERVAL;
65
67
  }
66
68
 
67
69
  function hasLowerConversionRate(formSubmit, formViews) {
@@ -84,7 +86,7 @@ function hasHighPageViewLowFormCtr(ctaPageViews, ctaClicks, ctaPageTotalClicks,
84
86
  * @param {*} formVitalsCollection - form vitals collection
85
87
  * @returns {Array} - urls with high form views and low conversion rate
86
88
  */
87
- export function getHighFormViewsLowConversionMetrics(formVitalsCollection, interval) {
89
+ export function getHighFormViewsLowConversionMetrics(formVitalsCollection) {
88
90
  const resultMap = aggregateFormVitalsByDevice(formVitalsCollection);
89
91
  const urls = [];
90
92
  resultMap.forEach((metrics, url) => {
@@ -94,7 +96,7 @@ export function getHighFormViewsLowConversionMetrics(formVitalsCollection, inter
94
96
  const formEngagement = metrics.formengagement.total;
95
97
  const formSubmit = metrics.formsubmit.total || formEngagement;
96
98
 
97
- if (hasHighPageViews(interval, pageViews) && hasLowerConversionRate(formSubmit, formViews)) {
99
+ if (hasHighPageViews(pageViews) && hasLowerConversionRate(formSubmit, formViews)) {
98
100
  urls.push({
99
101
  url,
100
102
  pageViews,
@@ -113,7 +115,7 @@ export function getHighFormViewsLowConversionMetrics(formVitalsCollection, inter
113
115
  * @param resultMap
114
116
  * @returns {*[]}
115
117
  */
116
- export function getHighPageViewsLowFormViewsMetrics(formVitalsCollection, interval) {
118
+ export function getHighPageViewsLowFormViewsMetrics(formVitalsCollection) {
117
119
  const urls = [];
118
120
  const resultMap = aggregateFormVitalsByDevice(formVitalsCollection);
119
121
  resultMap.forEach((metrics, url) => {
@@ -121,7 +123,7 @@ export function getHighPageViewsLowFormViewsMetrics(formVitalsCollection, interv
121
123
  const { total: formViews } = metrics.formview;
122
124
  const { total: formEngagement } = metrics.formengagement;
123
125
 
124
- if (hasHighPageViews(interval, pageViews) && hasLowFormViews(pageViews, formViews)) {
126
+ if (hasHighPageViews(pageViews) && hasLowFormViews(pageViews, formViews)) {
125
127
  urls.push({
126
128
  url,
127
129
  pageViews,
@@ -139,7 +141,7 @@ export function getHighPageViewsLowFormViewsMetrics(formVitalsCollection, interv
139
141
  * @param formVitalsByDevice
140
142
  * @returns {*[]}
141
143
  */
142
- export function getHighPageViewsLowFormCtrMetrics(formVitalsCollection, interval) {
144
+ export function getHighPageViewsLowFormCtrMetrics(formVitalsCollection) {
143
145
  const urls = [];
144
146
  const formVitalsByDevice = aggregateFormVitalsByDevice(formVitalsCollection);
145
147
  formVitalsCollection.forEach((entry) => {
@@ -176,7 +178,7 @@ export function getHighPageViewsLowFormCtrMetrics(formVitalsCollection, interval
176
178
  const f = Object.values(pageview).reduce((sum, val) => sum + val, 0);
177
179
 
178
180
  // Evaluate conditions and add URL to the result if all are met
179
- if (hasHighPageViews(interval, x) && hasHighPageViewLowFormCtr(x, y.clicks, z, f)) {
181
+ if (hasHighPageViews(x) && hasHighPageViewLowFormCtr(x, y.clicks, z, f)) {
180
182
  const deviceData = formVitalsByDevice.get(entry.url);
181
183
  if (deviceData != null) {
182
184
  urls.push({
@@ -9,7 +9,7 @@
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 { Request } from '@adobe/fetch';
12
+ import { Request, timeoutSignal, AbortError } from '@adobe/fetch';
13
13
  import AWSXRay from 'aws-xray-sdk';
14
14
 
15
15
  import { fetch as adobeFetch } from './adobe-fetch.js';
@@ -107,17 +107,60 @@ const handleSubSegmentError = (subSegment, request, error) => {
107
107
  subSegment.close(error);
108
108
  };
109
109
 
110
+ /**
111
+ * Creates a timeout error with a consistent format
112
+ * @param {number} timeout - The timeout value in milliseconds
113
+ * @returns {Error} A formatted timeout error
114
+ */
115
+ const createTimeoutError = (timeout) => {
116
+ const timeoutError = new Error(`Request timeout after ${timeout}ms`);
117
+ timeoutError.code = 'ETIMEOUT';
118
+ return timeoutError;
119
+ };
120
+
121
+ /**
122
+ * Performs a fetch request with timeout handling
123
+ * @param {string|Request} resource - The resource to fetch
124
+ * @param {Object} options - Options for the fetch call
125
+ * @param {Object} signal - The timeout signal
126
+ * @returns {Promise<Response>} The fetch response
127
+ */
128
+ const fetchWithTimeout = async (resource, options, signal) => {
129
+ try {
130
+ const fetchOptions = { ...options, signal };
131
+ return await adobeFetch(resource, fetchOptions);
132
+ } catch (error) {
133
+ if (error instanceof AbortError) {
134
+ // Extract timeout from signal (implementation detail, but necessary)
135
+ // eslint-disable-next-line no-underscore-dangle
136
+ const timeout = signal._ms || 10000;
137
+ throw createTimeoutError(timeout);
138
+ }
139
+ throw error;
140
+ } finally {
141
+ signal.clear(); // Clean up the signal
142
+ }
143
+ };
144
+
110
145
  /**
111
146
  * Performs a fetch request and adds AWS X-Ray tracing, including request/response tracking.
112
147
  * @param {string} url - The URL for the request.
113
- * @param {Object} options - Options to be passed to the fetch call.
148
+ * @param {Object} [options] - Optional options to be passed to the fetch call.
149
+ * @param {number} [options.timeout=10000] - Timeout in milliseconds (defaults to 10 seconds).
114
150
  * @returns {Promise<Response>} The response from the fetch request.
115
151
  */
116
152
  export async function tracingFetch(url, options) {
117
153
  const parentSegment = AWSXRay.getSegment();
118
154
 
119
- options = isObject(options) ? options : {};
120
- options.headers = isObject(options.headers) ? options.headers : new Headers();
155
+ options = isObject(options) ? { ...options } : {};
156
+ options.headers = isObject(options.headers) ? options.headers : { };
157
+
158
+ // Set default timeout of 10 seconds if not specified
159
+ const timeout = options.timeout || 10000;
160
+ delete options.timeout; // Remove from options as we'll handle it separately
161
+
162
+ // Create a timeout signal
163
+ const signal = timeoutSignal(timeout);
121
164
 
122
165
  // find user-agent header in headers case insensitively
123
166
  let hasUserAgent = false;
@@ -131,10 +174,12 @@ export async function tracingFetch(url, options) {
131
174
  options.headers['User-Agent'] = SPACECAT_USER_AGENT;
132
175
  }
133
176
 
177
+ // If no parent segment, perform fetch without tracing
134
178
  if (!parentSegment) {
135
- return adobeFetch(url, options);
179
+ return fetchWithTimeout(url, options, signal);
136
180
  }
137
181
 
182
+ // With parent segment, create subsegment and add tracing
138
183
  const request = new Request(url, options);
139
184
  const { hostname } = new URL(request.url);
140
185
  const subSegment = createSubsegment(parentSegment, hostname);
@@ -145,21 +190,22 @@ export async function tracingFetch(url, options) {
145
190
  setTraceHeaders(request, parentSegment, subSegment);
146
191
  }
147
192
 
148
- const capturedAdobeFetch = async () => {
149
- let response = null;
150
- try {
151
- response = await adobeFetch(request);
152
- } catch (e) {
153
- handleSubSegmentError(subSegment, request, e);
154
- throw e;
155
- }
193
+ subSegment.addAnnotation('timeout_ms', timeout);
156
194
 
157
- setSubSegmentFlagsByStatusCode(subSegment, response.status);
195
+ try {
196
+ // Create a new request with the signal
197
+ const requestWithSignal = new Request(request, { signal });
198
+
199
+ // Use the same fetchWithTimeout function but catch errors to handle subsegment
200
+ const response = await fetchWithTimeout(requestWithSignal, { }, signal);
158
201
 
202
+ setSubSegmentFlagsByStatusCode(subSegment, response.status);
159
203
  addFetchRequestDataToSegment(subSegment, request, response);
160
204
  subSegment.close();
161
- return response;
162
- };
163
205
 
164
- return capturedAdobeFetch();
206
+ return response;
207
+ } catch (error) {
208
+ handleSubSegmentError(subSegment, request, error);
209
+ throw error;
210
+ }
165
211
  }