@oneuptime/common 11.3.4 → 11.3.6

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.
Files changed (59) hide show
  1. package/Server/Infrastructure/Queue.ts +103 -16
  2. package/Server/Services/AnalyticsDatabaseService.ts +9 -5
  3. package/Server/Services/TeamMemberService.ts +38 -1
  4. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +31 -0
  5. package/Server/Utils/Monitor/IncomingRequestIncidentGrouping.ts +428 -0
  6. package/Server/Utils/Monitor/MonitorAlert.ts +158 -8
  7. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +26 -0
  8. package/Server/Utils/Monitor/MonitorIncident.ts +147 -2
  9. package/Server/Utils/Monitor/MonitorResource.ts +74 -0
  10. package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +2 -1
  11. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +32 -0
  12. package/Tests/Server/Utils/Monitor/IncomingRequestIncidentGrouping.test.ts +389 -0
  13. package/Tests/Server/Utils/Monitor/SeriesAbsenceResolutionGuard.test.ts +177 -0
  14. package/Tests/Types/Events/Recurring.test.ts +475 -0
  15. package/Tests/Types/Monitor/CriteriaFilter.test.ts +348 -0
  16. package/Tests/Types/OnCallDutyPolicy/RestrictionTimes.test.ts +331 -0
  17. package/Tests/UI/Components/Alert.test.tsx +23 -15
  18. package/Tests/UI/Components/Breadcrumbs.test.tsx +18 -6
  19. package/Tests/UI/Components/Button.test.tsx +3 -3
  20. package/Tests/UI/Components/DictionaryFilterOperator.test.ts +174 -0
  21. package/Tests/UI/Components/EmptyState/EmptyState.test.tsx +4 -3
  22. package/Tests/UI/Components/Input.test.tsx +3 -2
  23. package/Tests/UI/Components/Pill.test.tsx +4 -2
  24. package/Tests/UI/Components/SideMenuItem.test.tsx +18 -8
  25. package/Tests/UI/Components/TextArea.test.tsx +6 -4
  26. package/Types/Monitor/IncomingMonitor/IncidentGroupingConfig.ts +74 -0
  27. package/Types/Monitor/MonitorCriteriaInstance.ts +22 -0
  28. package/UI/Components/Dictionary/Dictionary.tsx +123 -50
  29. package/UI/Components/Dictionary/DictionaryFilterOperator.ts +96 -1
  30. package/UI/Components/MasterPage/MasterPage.tsx +6 -1
  31. package/build/dist/Server/Infrastructure/Queue.js +83 -15
  32. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  33. package/build/dist/Server/Services/AnalyticsDatabaseService.js +9 -5
  34. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  35. package/build/dist/Server/Services/TeamMemberService.js +37 -1
  36. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  37. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +26 -0
  38. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  39. package/build/dist/Server/Utils/Monitor/IncomingRequestIncidentGrouping.js +288 -0
  40. package/build/dist/Server/Utils/Monitor/IncomingRequestIncidentGrouping.js.map +1 -0
  41. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +119 -8
  42. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  43. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +24 -0
  44. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  45. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +103 -2
  46. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  47. package/build/dist/Server/Utils/Monitor/MonitorResource.js +62 -0
  48. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  49. package/build/dist/Types/Monitor/IncomingMonitor/IncidentGroupingConfig.js +8 -0
  50. package/build/dist/Types/Monitor/IncomingMonitor/IncidentGroupingConfig.js.map +1 -0
  51. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +10 -0
  52. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  53. package/build/dist/UI/Components/Dictionary/Dictionary.js +60 -9
  54. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  55. package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js +69 -0
  56. package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js.map +1 -1
  57. package/build/dist/UI/Components/MasterPage/MasterPage.js +7 -1
  58. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  59. package/package.json +1 -1
@@ -0,0 +1,475 @@
1
+ import OneUptimeDate from "../../../Types/Date";
2
+ import EventInterval from "../../../Types/Events/EventInterval";
3
+ import Recurring from "../../../Types/Events/Recurring";
4
+ import BadDataException from "../../../Types/Exception/BadDataException";
5
+ import { JSONObject, ObjectType } from "../../../Types/JSON";
6
+ import PositiveNumber from "../../../Types/PositiveNumber";
7
+
8
+ describe("Recurring", () => {
9
+ const baseDate: Date = OneUptimeDate.fromString("2020-01-01T00:00:00.000Z");
10
+
11
+ const makeRecurring: (
12
+ intervalType: EventInterval,
13
+ intervalCount: number,
14
+ ) => Recurring = (
15
+ intervalType: EventInterval,
16
+ intervalCount: number,
17
+ ): Recurring => {
18
+ const recurring: Recurring = new Recurring();
19
+ recurring.intervalType = intervalType;
20
+ recurring.intervalCount = new PositiveNumber(intervalCount);
21
+ return recurring;
22
+ };
23
+
24
+ describe("getDefaultRotationData / getDefault", () => {
25
+ test("defaults to every 1 day", () => {
26
+ const data: ReturnType<typeof Recurring.getDefaultRotationData> =
27
+ Recurring.getDefaultRotationData();
28
+
29
+ expect(data.intervalType).toBe(EventInterval.Day);
30
+ expect(data.intervalCount.toNumber()).toBe(1);
31
+ });
32
+
33
+ test("getDefault returns a Recurring instance with the defaults", () => {
34
+ const recurring: Recurring = Recurring.getDefault();
35
+
36
+ expect(recurring).toBeInstanceOf(Recurring);
37
+ expect(recurring.intervalType).toBe(EventInterval.Day);
38
+ expect(recurring.intervalCount.toNumber()).toBe(1);
39
+ });
40
+ });
41
+
42
+ describe("getNextDateInterval", () => {
43
+ test("adds hours for an hourly interval", () => {
44
+ const next: Date = Recurring.getNextDateInterval(
45
+ baseDate,
46
+ makeRecurring(EventInterval.Hour, 2),
47
+ );
48
+
49
+ expect(next.getTime()).toBe(
50
+ OneUptimeDate.addRemoveHours(baseDate, 2).getTime(),
51
+ );
52
+ });
53
+
54
+ test("adds days for a daily interval", () => {
55
+ const next: Date = Recurring.getNextDateInterval(
56
+ baseDate,
57
+ makeRecurring(EventInterval.Day, 3),
58
+ );
59
+
60
+ expect(next.getTime()).toBe(
61
+ OneUptimeDate.addRemoveDays(baseDate, 3).getTime(),
62
+ );
63
+ });
64
+
65
+ test("adds seven days per week for a weekly interval", () => {
66
+ const next: Date = Recurring.getNextDateInterval(
67
+ baseDate,
68
+ makeRecurring(EventInterval.Week, 2),
69
+ );
70
+
71
+ expect(next.getTime()).toBe(
72
+ OneUptimeDate.addRemoveDays(baseDate, 14).getTime(),
73
+ );
74
+ });
75
+
76
+ test("adds months for a monthly interval", () => {
77
+ const next: Date = Recurring.getNextDateInterval(
78
+ baseDate,
79
+ makeRecurring(EventInterval.Month, 1),
80
+ );
81
+
82
+ expect(next.getTime()).toBe(
83
+ OneUptimeDate.addRemoveMonths(baseDate, 1).getTime(),
84
+ );
85
+ });
86
+
87
+ test("adds years for a yearly interval", () => {
88
+ const next: Date = Recurring.getNextDateInterval(
89
+ baseDate,
90
+ makeRecurring(EventInterval.Year, 1),
91
+ );
92
+
93
+ expect(next.getTime()).toBe(
94
+ OneUptimeDate.addRemoveYears(baseDate, 1).getTime(),
95
+ );
96
+ });
97
+
98
+ test("moves backwards when getDateInThePast is true", () => {
99
+ const next: Date = Recurring.getNextDateInterval(
100
+ baseDate,
101
+ makeRecurring(EventInterval.Day, 1),
102
+ true,
103
+ );
104
+
105
+ expect(next.getTime()).toBe(
106
+ OneUptimeDate.addRemoveDays(baseDate, -1).getTime(),
107
+ );
108
+ });
109
+
110
+ test("adds multiple hours when intervalCount is greater than one", () => {
111
+ const next: Date = Recurring.getNextDateInterval(
112
+ baseDate,
113
+ makeRecurring(EventInterval.Hour, 2),
114
+ );
115
+
116
+ expect(next.getTime()).toBe(
117
+ OneUptimeDate.addRemoveHours(baseDate, 2).getTime(),
118
+ );
119
+ });
120
+
121
+ test("adds multiple days when intervalCount is greater than one", () => {
122
+ const next: Date = Recurring.getNextDateInterval(
123
+ baseDate,
124
+ makeRecurring(EventInterval.Day, 3),
125
+ );
126
+
127
+ expect(next.getTime()).toBe(
128
+ OneUptimeDate.addRemoveDays(baseDate, 3).getTime(),
129
+ );
130
+ });
131
+
132
+ test("adds fourteen days for a two-week interval", () => {
133
+ const next: Date = Recurring.getNextDateInterval(
134
+ baseDate,
135
+ makeRecurring(EventInterval.Week, 2),
136
+ );
137
+
138
+ expect(next.getTime()).toBe(
139
+ OneUptimeDate.addRemoveDays(baseDate, 14).getTime(),
140
+ );
141
+ });
142
+
143
+ test("adds multiple months when intervalCount is greater than one", () => {
144
+ const next: Date = Recurring.getNextDateInterval(
145
+ baseDate,
146
+ makeRecurring(EventInterval.Month, 2),
147
+ );
148
+
149
+ expect(next.getTime()).toBe(
150
+ OneUptimeDate.addRemoveMonths(baseDate, 2).getTime(),
151
+ );
152
+ });
153
+
154
+ test("adds multiple years when intervalCount is greater than one", () => {
155
+ const next: Date = Recurring.getNextDateInterval(
156
+ baseDate,
157
+ makeRecurring(EventInterval.Year, 2),
158
+ );
159
+
160
+ expect(next.getTime()).toBe(
161
+ OneUptimeDate.addRemoveYears(baseDate, 2).getTime(),
162
+ );
163
+ });
164
+
165
+ test("throws BadDataException for an unknown interval type", () => {
166
+ const recurring: Recurring = makeRecurring(EventInterval.Day, 1);
167
+ recurring.intervalType = "Decade" as unknown as EventInterval;
168
+
169
+ expect(() => {
170
+ Recurring.getNextDateInterval(baseDate, recurring);
171
+ }).toThrowError(BadDataException);
172
+ });
173
+ });
174
+
175
+ describe("getNextDate", () => {
176
+ test("returns a future start date unchanged", () => {
177
+ const futureDate: Date = OneUptimeDate.addRemoveDays(
178
+ OneUptimeDate.getCurrentDate(),
179
+ 5,
180
+ );
181
+
182
+ const next: Date = Recurring.getNextDate(
183
+ futureDate,
184
+ makeRecurring(EventInterval.Day, 1),
185
+ );
186
+
187
+ expect(next.getTime()).toBe(futureDate.getTime());
188
+ });
189
+
190
+ test("advances a past start date to the future", () => {
191
+ const pastDate: Date = OneUptimeDate.addRemoveDays(
192
+ OneUptimeDate.getCurrentDate(),
193
+ -10,
194
+ );
195
+
196
+ const next: Date = Recurring.getNextDate(
197
+ pastDate,
198
+ makeRecurring(EventInterval.Day, 1),
199
+ );
200
+
201
+ expect(next.getTime()).toBeGreaterThan(pastDate.getTime());
202
+ expect(next.getTime()).toBeGreaterThanOrEqual(
203
+ OneUptimeDate.getCurrentDate().getTime() - 1000,
204
+ );
205
+ });
206
+
207
+ test("advances a past start date by whole hourly intervals", () => {
208
+ const intervalMillis: number = 2 * 3600000;
209
+ const pastDate: Date = new Date(
210
+ OneUptimeDate.getCurrentDate().getTime() - intervalMillis * 5,
211
+ );
212
+
213
+ const next: Date = Recurring.getNextDate(
214
+ pastDate,
215
+ makeRecurring(EventInterval.Hour, 2),
216
+ );
217
+
218
+ expect(next.getTime()).toBeGreaterThanOrEqual(
219
+ OneUptimeDate.getCurrentDate().getTime() - 1000,
220
+ );
221
+ expect((next.getTime() - pastDate.getTime()) % intervalMillis).toBe(0);
222
+ });
223
+
224
+ test("advances a past start date by whole weekly intervals", () => {
225
+ const intervalMillis: number = 604800000;
226
+ const pastDate: Date = new Date(
227
+ OneUptimeDate.getCurrentDate().getTime() - intervalMillis * 3,
228
+ );
229
+
230
+ const next: Date = Recurring.getNextDate(
231
+ pastDate,
232
+ makeRecurring(EventInterval.Week, 1),
233
+ );
234
+
235
+ expect(next.getTime()).toBeGreaterThanOrEqual(
236
+ OneUptimeDate.getCurrentDate().getTime() - 1000,
237
+ );
238
+ expect((next.getTime() - pastDate.getTime()) % intervalMillis).toBe(0);
239
+ });
240
+
241
+ test("advances a past start date by whole monthly intervals", () => {
242
+ const intervalMillis: number = 2629800000;
243
+ const pastDate: Date = new Date(
244
+ OneUptimeDate.getCurrentDate().getTime() - intervalMillis * 4,
245
+ );
246
+
247
+ const next: Date = Recurring.getNextDate(
248
+ pastDate,
249
+ makeRecurring(EventInterval.Month, 1),
250
+ );
251
+
252
+ expect(next.getTime()).toBeGreaterThanOrEqual(
253
+ OneUptimeDate.getCurrentDate().getTime() - 1000,
254
+ );
255
+ expect((next.getTime() - pastDate.getTime()) % intervalMillis).toBe(0);
256
+ });
257
+
258
+ test("advances a past start date by whole yearly intervals", () => {
259
+ const intervalMillis: number = 31557600000;
260
+ const pastDate: Date = new Date(
261
+ OneUptimeDate.getCurrentDate().getTime() - intervalMillis * 2,
262
+ );
263
+
264
+ const next: Date = Recurring.getNextDate(
265
+ pastDate,
266
+ makeRecurring(EventInterval.Year, 1),
267
+ );
268
+
269
+ expect(next.getTime()).toBeGreaterThanOrEqual(
270
+ OneUptimeDate.getCurrentDate().getTime() - 1000,
271
+ );
272
+ expect((next.getTime() - pastDate.getTime()) % intervalMillis).toBe(0);
273
+ });
274
+
275
+ test("leaves a start date equal to now unchanged (zero intervals to add)", () => {
276
+ const now: Date = OneUptimeDate.getCurrentDate();
277
+
278
+ const next: Date = Recurring.getNextDate(
279
+ now,
280
+ makeRecurring(EventInterval.Day, 1),
281
+ );
282
+
283
+ // The <= branch is entered, diff is 0, so Math.ceil(0) === 0 intervals are added.
284
+ expect(next.getTime()).toBe(now.getTime());
285
+ });
286
+
287
+ test("throws BadDataException for an unknown interval type when the start date is in the past", () => {
288
+ const pastDate: Date = OneUptimeDate.addRemoveDays(
289
+ OneUptimeDate.getCurrentDate(),
290
+ -10,
291
+ );
292
+
293
+ const recurring: Recurring = makeRecurring(EventInterval.Day, 1);
294
+ recurring.intervalType = "Fortnight" as unknown as EventInterval;
295
+
296
+ expect(() => {
297
+ Recurring.getNextDate(pastDate, recurring);
298
+ }).toThrowError(BadDataException);
299
+ });
300
+
301
+ test("does not reach the interval switch for a future start date even with an unknown interval type", () => {
302
+ const futureDate: Date = OneUptimeDate.addRemoveDays(
303
+ OneUptimeDate.getCurrentDate(),
304
+ 5,
305
+ );
306
+
307
+ const recurring: Recurring = makeRecurring(EventInterval.Day, 1);
308
+ recurring.intervalType = "Fortnight" as unknown as EventInterval;
309
+
310
+ const next: Date = Recurring.getNextDate(futureDate, recurring);
311
+
312
+ expect(next.getTime()).toBe(futureDate.getTime());
313
+ });
314
+ });
315
+
316
+ describe("toString", () => {
317
+ test("formats as count and interval type", () => {
318
+ expect(makeRecurring(EventInterval.Day, 1).toString()).toBe("1 Day");
319
+ expect(makeRecurring(EventInterval.Week, 2).toString()).toBe("2 Week");
320
+ });
321
+ });
322
+
323
+ describe("toJSON / fromJSON", () => {
324
+ test("serializes with the Recurring object type", () => {
325
+ const json: ReturnType<Recurring["toJSON"]> = makeRecurring(
326
+ EventInterval.Week,
327
+ 2,
328
+ ).toJSON();
329
+
330
+ expect(json["_type"]).toBe(ObjectType.Recurring);
331
+ expect(json["value"]).toBeDefined();
332
+ });
333
+
334
+ test("round-trips interval type and count", () => {
335
+ const restored: Recurring = Recurring.fromJSON(
336
+ makeRecurring(EventInterval.Month, 4).toJSON(),
337
+ );
338
+
339
+ expect(restored).toBeInstanceOf(Recurring);
340
+ expect(restored.intervalType).toBe(EventInterval.Month);
341
+ expect(restored.intervalCount.toNumber()).toBe(4);
342
+ });
343
+
344
+ test("returns the same instance when given a Recurring", () => {
345
+ const recurring: Recurring = new Recurring();
346
+
347
+ expect(Recurring.fromJSON(recurring)).toBe(recurring);
348
+ });
349
+
350
+ test("throws BadDataException for a missing object type", () => {
351
+ expect(() => {
352
+ Recurring.fromJSON({});
353
+ }).toThrowError(BadDataException);
354
+ });
355
+
356
+ test("throws BadDataException when value is missing", () => {
357
+ expect(() => {
358
+ Recurring.fromJSON({ _type: ObjectType.Recurring });
359
+ }).toThrowError(BadDataException);
360
+ });
361
+
362
+ test("falls back to a daily interval when intervalType is missing", () => {
363
+ const restored: Recurring = Recurring.fromJSON({
364
+ _type: ObjectType.Recurring,
365
+ value: {
366
+ intervalCount: new PositiveNumber(5).toJSON(),
367
+ },
368
+ });
369
+
370
+ expect(restored.intervalType).toBe(EventInterval.Day);
371
+ expect(restored.intervalCount.toNumber()).toBe(5);
372
+ });
373
+
374
+ test("falls back to a count of one when intervalCount is missing", () => {
375
+ const restored: Recurring = Recurring.fromJSON({
376
+ _type: ObjectType.Recurring,
377
+ value: {
378
+ intervalType: EventInterval.Week,
379
+ },
380
+ });
381
+
382
+ expect(restored.intervalType).toBe(EventInterval.Week);
383
+ expect(restored.intervalCount.toNumber()).toBe(1);
384
+ });
385
+
386
+ test("applies both defaults when value is an empty object", () => {
387
+ const restored: Recurring = Recurring.fromJSON({
388
+ _type: ObjectType.Recurring,
389
+ value: {},
390
+ });
391
+
392
+ expect(restored.intervalType).toBe(EventInterval.Day);
393
+ expect(restored.intervalCount.toNumber()).toBe(1);
394
+ });
395
+ });
396
+
397
+ describe("getDatabaseTransformer (toDatabase / fromDatabase)", () => {
398
+ test("to/from round-trips a single Recurring instance", () => {
399
+ const transformer: ReturnType<typeof Recurring.getDatabaseTransformer> =
400
+ Recurring.getDatabaseTransformer();
401
+
402
+ const stored: unknown = transformer.to(
403
+ makeRecurring(EventInterval.Month, 4),
404
+ );
405
+
406
+ expect((stored as { _type: string })["_type"]).toBe(ObjectType.Recurring);
407
+
408
+ const restored: Recurring = transformer.from(stored) as Recurring;
409
+
410
+ expect(restored).toBeInstanceOf(Recurring);
411
+ expect(restored.intervalType).toBe(EventInterval.Month);
412
+ expect(restored.intervalCount.toNumber()).toBe(4);
413
+ });
414
+
415
+ test("to/from round-trips an array of Recurrings", () => {
416
+ const transformer: ReturnType<typeof Recurring.getDatabaseTransformer> =
417
+ Recurring.getDatabaseTransformer();
418
+
419
+ const stored: unknown = transformer.to([
420
+ makeRecurring(EventInterval.Day, 1),
421
+ makeRecurring(EventInterval.Year, 3),
422
+ ] as unknown as Recurring);
423
+
424
+ expect(Array.isArray(stored)).toBe(true);
425
+
426
+ const restored: Array<Recurring> = transformer.from(
427
+ stored,
428
+ ) as Array<Recurring>;
429
+
430
+ expect(restored.length).toBe(2);
431
+ expect(restored[0]!.intervalType).toBe(EventInterval.Day);
432
+ expect(restored[1]!.intervalType).toBe(EventInterval.Year);
433
+ expect(restored[1]!.intervalCount.toNumber()).toBe(3);
434
+ });
435
+
436
+ test("to returns null and from returns null for nullish values", () => {
437
+ const transformer: ReturnType<typeof Recurring.getDatabaseTransformer> =
438
+ Recurring.getDatabaseTransformer();
439
+
440
+ expect(transformer.to(null as unknown as Recurring)).toBeNull();
441
+ expect(transformer.from(null as unknown as JSONObject)).toBeNull();
442
+ });
443
+ });
444
+
445
+ describe("toJSONArray / fromJSONArray", () => {
446
+ test("round-trips an array of recurrings", () => {
447
+ const recurrings: Array<Recurring> = [
448
+ makeRecurring(EventInterval.Day, 1),
449
+ makeRecurring(EventInterval.Year, 3),
450
+ ];
451
+
452
+ const restored: Array<Recurring> = Recurring.fromJSONArray(
453
+ Recurring.toJSONArray(recurrings),
454
+ );
455
+
456
+ expect(restored.length).toBe(2);
457
+ expect(restored[0]!.intervalType).toBe(EventInterval.Day);
458
+ expect(restored[0]!.intervalCount.toNumber()).toBe(1);
459
+ expect(restored[1]!.intervalType).toBe(EventInterval.Year);
460
+ expect(restored[1]!.intervalCount.toNumber()).toBe(3);
461
+ });
462
+
463
+ test("round-trips an empty array to an empty array", () => {
464
+ const json: ReturnType<typeof Recurring.toJSONArray> =
465
+ Recurring.toJSONArray([]);
466
+
467
+ expect(json).toEqual([]);
468
+
469
+ const restored: Array<Recurring> = Recurring.fromJSONArray(json);
470
+
471
+ expect(restored).toEqual([]);
472
+ expect(restored.length).toBe(0);
473
+ });
474
+ });
475
+ });