@escapenavigator/utils 2.0.35 → 2.0.36
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/dist/classify-api-error.d.ts +3 -1
- package/dist/classify-api-error.js +17 -14
- package/dist/classify-api-error.spec.js +22 -14
- package/dist/get-order-cancelation-date/index.d.ts +2 -1
- package/dist/get-order-cancelation-date/index.js +6 -1
- package/dist/tz-date.d.ts +1 -1
- package/dist/tz-date.js +7 -2
- package/package.json +3 -3
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
*
|
|
24
24
|
* skip → не репортим
|
|
25
25
|
* 304 (etag-success), отменённые axios-запросы (ERR_CANCELED),
|
|
26
|
-
* 401/403 на app/auth (ожидаемый session/role flow)
|
|
26
|
+
* 401/403 на app/auth (ожидаемый session/role flow), а также
|
|
27
|
+
* обрывы связи на стороне клиента: `Network Error` и
|
|
28
|
+
* «timeout of 0ms exceeded» (запрос умер до ответа сервера).
|
|
27
29
|
*
|
|
28
30
|
* Группировка (`fingerprint`) делается так, чтобы все события с одинаковым
|
|
29
31
|
* `apiKey + errorCode` сливались в один issue. Иначе Sentry плодит отдельный
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
*
|
|
25
25
|
* skip → не репортим
|
|
26
26
|
* 304 (etag-success), отменённые axios-запросы (ERR_CANCELED),
|
|
27
|
-
* 401/403 на app/auth (ожидаемый session/role flow)
|
|
27
|
+
* 401/403 на app/auth (ожидаемый session/role flow), а также
|
|
28
|
+
* обрывы связи на стороне клиента: `Network Error` и
|
|
29
|
+
* «timeout of 0ms exceeded» (запрос умер до ответа сервера).
|
|
28
30
|
*
|
|
29
31
|
* Группировка (`fingerprint`) делается так, чтобы все события с одинаковым
|
|
30
32
|
* `apiKey + errorCode` сливались в один issue. Иначе Sentry плодит отдельный
|
|
@@ -84,14 +86,14 @@ function classifyApiError(ctx) {
|
|
|
84
86
|
return { severity: 'skip', level: 'info', fingerprint: null, shouldReport: false };
|
|
85
87
|
}
|
|
86
88
|
// «timeout of 0ms exceeded» — Safari/iOS обрывает XHR при смене сети или
|
|
87
|
-
// уходе вкладки в
|
|
89
|
+
// уходе вкладки в фон (timeout у запроса не выставлен, обрыв не наш).
|
|
90
|
+
//
|
|
91
|
+
// Раньше репортили как business/info «для картины мобильной связности»:
|
|
92
|
+
// за месяц это дало десяток issue на 50+ человек
|
|
93
|
+
// (ESCAPE-NAVIGATOR-APP-3J/3K/3M/3H/2P/3G/33), в которых ни одного бага.
|
|
94
|
+
// Картину связности всё равно лучше видно по метрикам, а не по issue.
|
|
88
95
|
if (code === 'ECONNABORTED' && typeof message === 'string' && /timeout of 0ms/i.test(message)) {
|
|
89
|
-
return {
|
|
90
|
-
severity: 'business',
|
|
91
|
-
level: 'info',
|
|
92
|
-
fingerprint: ['api', 'mobile-abort', apiKey],
|
|
93
|
-
shouldReport: true,
|
|
94
|
-
};
|
|
96
|
+
return { severity: 'skip', level: 'info', fingerprint: null, shouldReport: false };
|
|
95
97
|
}
|
|
96
98
|
if (typeof status === 'number' && SKIP_HTTP_STATUSES.has(status)) {
|
|
97
99
|
return { severity: 'skip', level: 'info', fingerprint: null, shouldReport: false };
|
|
@@ -155,14 +157,15 @@ function classifyApiError(ctx) {
|
|
|
155
157
|
// code === 'ERR_NETWORK' (axios>=0.27). На проде это почти всегда
|
|
156
158
|
// клиентская связность (мобильный обрыв, смена соты, троттлинг/RKN).
|
|
157
159
|
// На виджете GET'ы уже ретраятся — сюда долетает только финальный фейл.
|
|
160
|
+
//
|
|
161
|
+
// Не репортим: за месяц это ~1000 событий в app/widget/auth, разбитых на
|
|
162
|
+
// десятки issue по apiKey, и ни одно не было багом. Настоящая
|
|
163
|
+
// недоступность бэка видна иначе и этим фильтром не задета: 5xx и
|
|
164
|
+
// сработавший таймаут (25s у виджета) выше классифицируются как critical,
|
|
165
|
+
// а падение домена/сертификата ловится мониторингом со стороны бэкенда.
|
|
158
166
|
const isNetworkError = !status && (code === 'ERR_NETWORK' || (!code && isNetworkErrorMessage(message)));
|
|
159
167
|
if (isNetworkError) {
|
|
160
|
-
return {
|
|
161
|
-
severity: 'business',
|
|
162
|
-
level: 'warning',
|
|
163
|
-
fingerprint: ['api', 'network', apiKey],
|
|
164
|
-
shouldReport: true,
|
|
165
|
-
};
|
|
168
|
+
return { severity: 'skip', level: 'info', fingerprint: null, shouldReport: false };
|
|
166
169
|
}
|
|
167
170
|
// Сюда падают: какие-то странные кейсы без status/кода/сетевого message.
|
|
168
171
|
// Считаем critical, но без агрессивного группирования — пусть Sentry
|
|
@@ -43,6 +43,28 @@ describe('classifyApiError', () => {
|
|
|
43
43
|
const r = (0, classify_api_error_1.classifyApiError)({ ...base, status: 404, app: 'auth' });
|
|
44
44
|
expect(r.shouldReport).toBe(true);
|
|
45
45
|
});
|
|
46
|
+
it('чистый network error → skip (обрыв связи у клиента)', () => {
|
|
47
|
+
expect((0, classify_api_error_1.classifyApiError)({ ...base, code: 'ERR_NETWORK' }).shouldReport).toBe(false);
|
|
48
|
+
expect((0, classify_api_error_1.classifyApiError)({ ...base, message: 'Network Error' }).shouldReport).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
it('мобильный обрыв (timeout of 0ms) → skip', () => {
|
|
51
|
+
const r = (0, classify_api_error_1.classifyApiError)({
|
|
52
|
+
...base,
|
|
53
|
+
code: 'ECONNABORTED',
|
|
54
|
+
message: 'timeout of 0ms exceeded',
|
|
55
|
+
});
|
|
56
|
+
expect(r.severity).toBe('skip');
|
|
57
|
+
expect(r.shouldReport).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
it('сработавший таймаут виджета (25s) → НЕ skip, это медленный бэк', () => {
|
|
60
|
+
const r = (0, classify_api_error_1.classifyApiError)({
|
|
61
|
+
...base,
|
|
62
|
+
code: 'ECONNABORTED',
|
|
63
|
+
message: 'timeout of 25000ms exceeded',
|
|
64
|
+
});
|
|
65
|
+
expect(r.severity).toBe('critical');
|
|
66
|
+
expect(r.shouldReport).toBe(true);
|
|
67
|
+
});
|
|
46
68
|
it('5xx на login → НЕ skip (это уже настоящий сбой входа)', () => {
|
|
47
69
|
const r = (0, classify_api_error_1.classifyApiError)({
|
|
48
70
|
apiKey: 'login',
|
|
@@ -99,20 +121,6 @@ describe('classifyApiError', () => {
|
|
|
99
121
|
expect(r.severity).toBe('business');
|
|
100
122
|
expect(r.fingerprint).toContain('bookingWindowClosed');
|
|
101
123
|
});
|
|
102
|
-
it('мобильный обрыв (timeout of 0ms) → business/info', () => {
|
|
103
|
-
const r = (0, classify_api_error_1.classifyApiError)({
|
|
104
|
-
...base,
|
|
105
|
-
code: 'ECONNABORTED',
|
|
106
|
-
message: 'timeout of 0ms exceeded',
|
|
107
|
-
});
|
|
108
|
-
expect(r.severity).toBe('business');
|
|
109
|
-
expect(r.level).toBe('info');
|
|
110
|
-
expect(r.fingerprint).toContain('mobile-abort');
|
|
111
|
-
});
|
|
112
|
-
it('чистый network error → business', () => {
|
|
113
|
-
expect((0, classify_api_error_1.classifyApiError)({ ...base, code: 'ERR_NETWORK' }).severity).toBe('business');
|
|
114
|
-
expect((0, classify_api_error_1.classifyApiError)({ ...base, message: 'Network Error' }).severity).toBe('business');
|
|
115
|
-
});
|
|
116
124
|
});
|
|
117
125
|
describe('debug (generic 4xx — шум, но репортим info-level)', () => {
|
|
118
126
|
it.each([
|
|
@@ -3,7 +3,8 @@ type Props = {
|
|
|
3
3
|
utcDate: string;
|
|
4
4
|
minHoursForFreeCanceling: number;
|
|
5
5
|
cancelationRule: QuestroomCancelationTypeEnum;
|
|
6
|
-
|
|
6
|
+
/** Часовой пояс локации; при отсутствии считаем в UTC (см. ниже). */
|
|
7
|
+
timeZone?: string;
|
|
7
8
|
};
|
|
8
9
|
export declare function getOrderCancelationDate({ utcDate, minHoursForFreeCanceling, cancelationRule, timeZone, }: Props): {
|
|
9
10
|
orderCancelationDate: Date;
|
|
@@ -5,7 +5,12 @@ const questroom_cancelation_type_enum_1 = require("@escapenavigator/types/dist/q
|
|
|
5
5
|
const date_1 = require("../date");
|
|
6
6
|
function getOrderCancelationDate({ utcDate, minHoursForFreeCanceling, cancelationRule, timeZone, }) {
|
|
7
7
|
const minHours = cancelationRule === questroom_cancelation_type_enum_1.QuestroomCancelationTypeEnum.NO ? 0 : minHoursForFreeCanceling;
|
|
8
|
-
|
|
8
|
+
// Локация заказа может быть удалена (`questroom.location === null`), и
|
|
9
|
+
// тогда часового пояса просто нет. `utcToZonedTime` на undefined даёт
|
|
10
|
+
// Invalid Date, а `formatDate` на нём кидает — падала вся клиентская
|
|
11
|
+
// страница заказа (ESCAPE-NAVIGATOR-ORDERS-2D). Дата в UTC хуже
|
|
12
|
+
// локальной, но лучше пустого экрана.
|
|
13
|
+
const date = (0, date_1.addHours)((0, date_1.utcToZonedTime)(utcDate, timeZone || 'UTC'), minHours * -1);
|
|
9
14
|
return {
|
|
10
15
|
orderCancelationDate: date,
|
|
11
16
|
orderCancelationHumantDate: (0, date_1.formatDate)(date, 'dd.MM.yyyy, HH:mm'),
|
package/dist/tz-date.d.ts
CHANGED
package/dist/tz-date.js
CHANGED
|
@@ -14,9 +14,14 @@ exports.convertDateToZonedDate = convertDateToZonedDate;
|
|
|
14
14
|
const convertUTCDateToZonedDate = ({ date, timeZone, offset = 0, format = 'dd.MM.yyyy, HH:mm', showTZ = false, }) => {
|
|
15
15
|
if (!date)
|
|
16
16
|
return '';
|
|
17
|
-
|
|
17
|
+
// Часового пояса может не быть (удалённая локация заказа — см.
|
|
18
|
+
// getOrderCancelationDate): на undefined `utcToZonedTime` даёт Invalid
|
|
19
|
+
// Date, а `formatDate` на нём кидает и роняет весь экран. Показываем
|
|
20
|
+
// время в UTC и честно подписываем его в `showTZ`.
|
|
21
|
+
const tz = timeZone || 'UTC';
|
|
22
|
+
const d = (0, date_1.utcToZonedTime)(date, tz);
|
|
18
23
|
const dt = (0, date_1.addHours)(d, offset);
|
|
19
24
|
const res = (0, date_1.formatDate)(dt, format);
|
|
20
|
-
return showTZ ? `${res} (${
|
|
25
|
+
return showTZ ? `${res} (${tz})` : res;
|
|
21
26
|
};
|
|
22
27
|
exports.convertUTCDateToZonedDate = convertUTCDateToZonedDate;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escapenavigator/utils",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.36",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"test": "jest"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@escapenavigator/types": "^2.0.
|
|
17
|
+
"@escapenavigator/types": "^2.0.34",
|
|
18
18
|
"axios": "^0.21.4",
|
|
19
19
|
"class-transformer": "^0.5.1",
|
|
20
20
|
"class-validator": "^0.13.2",
|
|
@@ -29,5 +29,5 @@
|
|
|
29
29
|
"ts-jest": "^29.1.1",
|
|
30
30
|
"typescript": "^5.6"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "3112d9d954979e406e77a787d459e16c49da3d5a"
|
|
33
33
|
}
|