@oneuptime/common 14.0.6 → 14.0.7

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 (127) hide show
  1. package/Models/DatabaseModels/InventoryItem.ts +23 -12
  2. package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  4. package/Server/Services/AlertService.ts +32 -36
  5. package/Server/Services/IncidentAlertService.ts +609 -2
  6. package/Server/Services/IncidentService.ts +134 -44
  7. package/Server/Services/InventoryItemService.ts +38 -0
  8. package/Server/Services/IoTDeviceCredentialService.ts +13 -4
  9. package/Server/Services/ScheduledMaintenanceService.ts +50 -46
  10. package/Server/Services/UserNotificationRuleService.ts +55 -53
  11. package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
  12. package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
  13. package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
  14. package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
  15. package/Server/Utils/Response.ts +31 -0
  16. package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
  17. package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
  18. package/Server/Utils/Topology/TopologyRequest.ts +463 -0
  19. package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
  20. package/Server/Utils/Topology/TopologySql.ts +1024 -0
  21. package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
  22. package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
  23. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
  24. package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
  25. package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
  26. package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
  27. package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
  28. package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
  29. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
  30. package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
  31. package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
  32. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +292 -1
  33. package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
  34. package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
  35. package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
  36. package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
  37. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
  38. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
  39. package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
  40. package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
  41. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
  42. package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
  43. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
  44. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
  45. package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
  46. package/Tests/Server/Services/DomainService.test.ts +525 -0
  47. package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
  48. package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
  49. package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
  50. package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
  51. package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
  52. package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
  53. package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
  54. package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
  55. package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
  56. package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
  57. package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
  58. package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
  59. package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
  60. package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
  61. package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
  62. package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
  63. package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
  64. package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
  65. package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
  66. package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
  67. package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
  68. package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
  69. package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
  70. package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
  71. package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
  72. package/Types/Incident/IncidentAlertLink.ts +19 -0
  73. package/Types/Telemetry/InventoryLiveness.ts +26 -0
  74. package/Types/Topology/TopologyApi.ts +428 -0
  75. package/Types/Topology/TopologyTypeRules.ts +191 -0
  76. package/Utils/Incident/IncidentFromAlerts.ts +78 -0
  77. package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
  78. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
  79. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
  80. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js.map +1 -0
  81. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  82. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  83. package/build/dist/Server/Services/AlertService.js +21 -33
  84. package/build/dist/Server/Services/AlertService.js.map +1 -1
  85. package/build/dist/Server/Services/IncidentAlertService.js +457 -3
  86. package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
  87. package/build/dist/Server/Services/IncidentService.js +96 -42
  88. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  89. package/build/dist/Server/Services/InventoryItemService.js +31 -0
  90. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  91. package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
  92. package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
  93. package/build/dist/Server/Services/ScheduledMaintenanceService.js +37 -40
  94. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  95. package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
  96. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  97. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
  98. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
  99. package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
  100. package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
  101. package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
  102. package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
  103. package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
  104. package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
  105. package/build/dist/Server/Utils/Response.js +30 -0
  106. package/build/dist/Server/Utils/Response.js.map +1 -1
  107. package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
  108. package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
  109. package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
  110. package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
  111. package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
  112. package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
  113. package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
  114. package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
  115. package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
  116. package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
  117. package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
  118. package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
  119. package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
  120. package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
  121. package/build/dist/Types/Topology/TopologyApi.js +55 -0
  122. package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
  123. package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
  124. package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
  125. package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
  126. package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
  127. package/package.json +1 -1
@@ -0,0 +1,305 @@
1
+ import TopologyResponseCacheInstance, {
2
+ TOPOLOGY_RESPONSE_CACHE_MAX_BYTES,
3
+ TOPOLOGY_RESPONSE_CACHE_TTL_MS,
4
+ TopologyResponseCache,
5
+ topologyResponseCacheKey,
6
+ } from "../../../../Server/Utils/Topology/TopologyResponseCache";
7
+ import { describe, expect, test } from "@jest/globals";
8
+
9
+ /*
10
+ * The Topology maps' response cache: a short TTL, a byte budget rather than
11
+ * an entry count (one infrastructure payload can be tens of megabytes), and
12
+ * single flight so a burst of cold requests builds once. Failures are never
13
+ * cached. A steerable clock keeps the TTL tests from sleeping.
14
+ */
15
+
16
+ type Clock = { nowMs: number; now: () => number };
17
+
18
+ function makeClock(): Clock {
19
+ const clock: Clock = {
20
+ nowMs: 1_000_000,
21
+ now: (): number => {
22
+ return clock.nowMs;
23
+ },
24
+ };
25
+ return clock;
26
+ }
27
+
28
+ interface Deferred {
29
+ promise: Promise<string>;
30
+ resolve: (value: string) => void;
31
+ reject: (error: Error) => void;
32
+ }
33
+
34
+ function deferred(): Deferred {
35
+ let resolve: (value: string) => void = (): void => {
36
+ return undefined;
37
+ };
38
+ let reject: (error: Error) => void = (): void => {
39
+ return undefined;
40
+ };
41
+ const promise: Promise<string> = new Promise<string>(
42
+ (
43
+ resolveFn: (value: string) => void,
44
+ rejectFn: (error: Error) => void,
45
+ ): void => {
46
+ resolve = resolveFn;
47
+ reject = rejectFn;
48
+ },
49
+ );
50
+ return { promise, resolve, reject };
51
+ }
52
+
53
+ describe("TopologyResponseCache", () => {
54
+ test("defaults: a minute, ~256 MB", () => {
55
+ expect(TOPOLOGY_RESPONSE_CACHE_TTL_MS).toBe(60_000);
56
+ expect(TOPOLOGY_RESPONSE_CACHE_MAX_BYTES).toBe(256 * 1024 * 1024);
57
+ expect(TopologyResponseCacheInstance).toBeInstanceOf(TopologyResponseCache);
58
+ });
59
+
60
+ test("the key is (route, project, range start)", () => {
61
+ const base: { route: string; projectId: string; rangeStart: Date } = {
62
+ route: "/telemetry/topology/service-map",
63
+ projectId: "p-1",
64
+ rangeStart: new Date("2026-09-26T11:00:00.000Z"),
65
+ };
66
+ const key: string = topologyResponseCacheKey(base);
67
+ expect(key).toBe(
68
+ `/telemetry/topology/service-map|p-1|${Date.parse("2026-09-26T11:00:00.000Z")}`,
69
+ );
70
+ expect(topologyResponseCacheKey({ ...base, projectId: "p-2" })).not.toBe(
71
+ key,
72
+ );
73
+ expect(
74
+ topologyResponseCacheKey({
75
+ ...base,
76
+ route: "/telemetry/topology/infrastructure",
77
+ }),
78
+ ).not.toBe(key);
79
+ expect(
80
+ topologyResponseCacheKey({
81
+ ...base,
82
+ rangeStart: new Date("2026-09-26T11:01:00.000Z"),
83
+ }),
84
+ ).not.toBe(key);
85
+ });
86
+
87
+ test("a hit returns the cached string without building", async () => {
88
+ const cache: TopologyResponseCache = new TopologyResponseCache();
89
+ let builds: number = 0;
90
+ const build: () => Promise<string> = async (): Promise<string> => {
91
+ builds++;
92
+ return `{"build":${builds}}`;
93
+ };
94
+ expect(await cache.getOrBuild("k", build)).toBe(`{"build":1}`);
95
+ expect(await cache.getOrBuild("k", build)).toBe(`{"build":1}`);
96
+ expect(builds).toBe(1);
97
+ });
98
+
99
+ test("entries expire after the TTL", async () => {
100
+ const clock: Clock = makeClock();
101
+ const cache: TopologyResponseCache = new TopologyResponseCache(
102
+ 60_000,
103
+ 1_000_000,
104
+ clock.now,
105
+ );
106
+ cache.set("k", "v1");
107
+ clock.nowMs += 60_000;
108
+ expect(cache.get("k")).toBe("v1");
109
+ clock.nowMs += 1;
110
+ expect(cache.get("k")).toBeUndefined();
111
+ expect(cache.size()).toBe(0);
112
+ expect(cache.byteSize()).toBe(0);
113
+ });
114
+
115
+ test("the byte budget evicts oldest first and skips a value larger than it", () => {
116
+ const cache: TopologyResponseCache = new TopologyResponseCache(60_000, 40);
117
+ cache.set("a", "0123456789"); // 20 bytes
118
+ cache.set("b", "0123456789"); // 20 bytes, budget full
119
+ cache.set("c", "01234"); // 10 bytes: evicts "a"
120
+ expect(cache.get("a")).toBeUndefined();
121
+ expect(cache.get("b")).toBe("0123456789");
122
+ expect(cache.get("c")).toBe("01234");
123
+ expect(cache.byteSize()).toBe(30);
124
+
125
+ cache.set("huge", "x".repeat(21)); // 42 bytes > budget: not kept
126
+ expect(cache.get("huge")).toBeUndefined();
127
+ expect(cache.byteSize()).toBe(30);
128
+
129
+ // Replacing a key releases its old bytes.
130
+ cache.set("b", "01");
131
+ expect(cache.byteSize()).toBe(14);
132
+ });
133
+
134
+ test("expired entries are swept on write", () => {
135
+ const clock: Clock = makeClock();
136
+ const cache: TopologyResponseCache = new TopologyResponseCache(
137
+ 10,
138
+ 1_000,
139
+ clock.now,
140
+ );
141
+ cache.set("old", "v");
142
+ clock.nowMs += 11;
143
+ cache.set("new", "v");
144
+ expect(cache.size()).toBe(1);
145
+ });
146
+
147
+ test("concurrent cold requests share one build", async () => {
148
+ const cache: TopologyResponseCache = new TopologyResponseCache();
149
+ const pending: Deferred = deferred();
150
+ let builds: number = 0;
151
+ const build: () => Promise<string> = (): Promise<string> => {
152
+ builds++;
153
+ return pending.promise;
154
+ };
155
+ const first: Promise<string> = cache.getOrBuild("k", build);
156
+ const second: Promise<string> = cache.getOrBuild("k", build);
157
+ expect(cache.inFlightCount()).toBe(1);
158
+ pending.resolve("payload");
159
+ expect(await Promise.all([first, second])).toEqual(["payload", "payload"]);
160
+ expect(builds).toBe(1);
161
+ expect(cache.inFlightCount()).toBe(0);
162
+ expect(cache.get("k")).toBe("payload");
163
+ });
164
+
165
+ test("a failed build is shared by its followers and never cached", async () => {
166
+ const cache: TopologyResponseCache = new TopologyResponseCache();
167
+ const pending: Deferred = deferred();
168
+ const first: Promise<string> = cache.getOrBuild("k", () => {
169
+ return pending.promise;
170
+ });
171
+ const second: Promise<string> = cache.getOrBuild("k", () => {
172
+ return Promise.resolve("never");
173
+ });
174
+ pending.reject(new Error("statement timeout"));
175
+ await expect(first).rejects.toThrow("statement timeout");
176
+ await expect(second).rejects.toThrow("statement timeout");
177
+ expect(cache.get("k")).toBeUndefined();
178
+ expect(cache.inFlightCount()).toBe(0);
179
+
180
+ // The next request starts over.
181
+ expect(
182
+ await cache.getOrBuild("k", async () => {
183
+ return "fresh";
184
+ }),
185
+ ).toBe("fresh");
186
+ });
187
+
188
+ test("a build that throws synchronously still releases its key", async () => {
189
+ const cache: TopologyResponseCache = new TopologyResponseCache();
190
+ await expect(
191
+ cache.getOrBuild("k", (): Promise<string> => {
192
+ throw new Error("boom");
193
+ }),
194
+ ).rejects.toThrow("boom");
195
+ expect(cache.inFlightCount()).toBe(0);
196
+ });
197
+
198
+ test("different keys build independently", async () => {
199
+ const cache: TopologyResponseCache = new TopologyResponseCache();
200
+ const results: Array<string> = await Promise.all([
201
+ cache.getOrBuild("a", async () => {
202
+ return "A";
203
+ }),
204
+ cache.getOrBuild("b", async () => {
205
+ return "B";
206
+ }),
207
+ ]);
208
+ expect(results).toEqual(["A", "B"]);
209
+ cache.clear();
210
+ expect(cache.size()).toBe(0);
211
+ expect(cache.byteSize()).toBe(0);
212
+ });
213
+
214
+ describe("fresh (an explicit refresh)", () => {
215
+ test("after a warm hit, rebuilds and replaces the entry for everyone", async () => {
216
+ const cache: TopologyResponseCache = new TopologyResponseCache();
217
+ let builds: number = 0;
218
+ const build: () => Promise<string> = async (): Promise<string> => {
219
+ builds++;
220
+ return `{"build":${builds}}`;
221
+ };
222
+ expect(await cache.getOrBuild("k", build)).toBe(`{"build":1}`);
223
+ expect(await cache.getOrBuild("k", build)).toBe(`{"build":1}`);
224
+
225
+ expect(await cache.getOrBuild("k", build, { fresh: true })).toBe(
226
+ `{"build":2}`,
227
+ );
228
+ expect(builds).toBe(2);
229
+ // The refreshed copy is what the next ordinary request gets.
230
+ expect(await cache.getOrBuild("k", build)).toBe(`{"build":2}`);
231
+ expect(builds).toBe(2);
232
+ expect(cache.size()).toBe(1);
233
+ expect(cache.byteSize()).toBe(`{"build":2}`.length * 2);
234
+ });
235
+
236
+ test("joins a build already in flight instead of starting a second", async () => {
237
+ const cache: TopologyResponseCache = new TopologyResponseCache();
238
+ const pending: Deferred = deferred();
239
+ let builds: number = 0;
240
+ const build: () => Promise<string> = (): Promise<string> => {
241
+ builds++;
242
+ return pending.promise;
243
+ };
244
+ const cold: Promise<string> = cache.getOrBuild("k", build);
245
+ const fresh: Promise<string> = cache.getOrBuild("k", build, {
246
+ fresh: true,
247
+ });
248
+ expect(cache.inFlightCount()).toBe(1);
249
+ pending.resolve("payload");
250
+ expect(await Promise.all([cold, fresh])).toEqual(["payload", "payload"]);
251
+ expect(builds).toBe(1);
252
+ });
253
+
254
+ test("ordinary requests during a refresh still get the cached copy, then the new one", async () => {
255
+ const cache: TopologyResponseCache = new TopologyResponseCache();
256
+ cache.set("k", "old");
257
+ const pending: Deferred = deferred();
258
+ const refresh: Promise<string> = cache.getOrBuild(
259
+ "k",
260
+ () => {
261
+ return pending.promise;
262
+ },
263
+ { fresh: true },
264
+ );
265
+ expect(
266
+ await cache.getOrBuild("k", async () => {
267
+ return "unexpected";
268
+ }),
269
+ ).toBe("old");
270
+ pending.resolve("new");
271
+ expect(await refresh).toBe("new");
272
+ expect(cache.get("k")).toBe("new");
273
+ });
274
+
275
+ test("a failed refresh is not cached and leaves the previous copy in place", async () => {
276
+ const cache: TopologyResponseCache = new TopologyResponseCache();
277
+ cache.set("k", "old");
278
+ await expect(
279
+ cache.getOrBuild(
280
+ "k",
281
+ async (): Promise<string> => {
282
+ throw new Error("statement timeout");
283
+ },
284
+ { fresh: true },
285
+ ),
286
+ ).rejects.toThrow("statement timeout");
287
+ expect(cache.get("k")).toBe("old");
288
+ expect(cache.inFlightCount()).toBe(0);
289
+ });
290
+
291
+ test("fresh: false behaves like no option", async () => {
292
+ const cache: TopologyResponseCache = new TopologyResponseCache();
293
+ cache.set("k", "cached");
294
+ expect(
295
+ await cache.getOrBuild(
296
+ "k",
297
+ async () => {
298
+ return "built";
299
+ },
300
+ { fresh: false },
301
+ ),
302
+ ).toBe("cached");
303
+ });
304
+ });
305
+ });