@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.
- package/Models/DatabaseModels/InventoryItem.ts +23 -12
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/AlertService.ts +32 -36
- package/Server/Services/IncidentAlertService.ts +609 -2
- package/Server/Services/IncidentService.ts +134 -44
- package/Server/Services/InventoryItemService.ts +38 -0
- package/Server/Services/IoTDeviceCredentialService.ts +13 -4
- package/Server/Services/ScheduledMaintenanceService.ts +50 -46
- package/Server/Services/UserNotificationRuleService.ts +55 -53
- package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
- package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
- package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
- package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
- package/Server/Utils/Response.ts +31 -0
- package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
- package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
- package/Server/Utils/Topology/TopologyRequest.ts +463 -0
- package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
- package/Server/Utils/Topology/TopologySql.ts +1024 -0
- package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
- package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
- package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
- package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
- package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
- package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
- package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
- package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
- package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +292 -1
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
- package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
- package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
- package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
- package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
- package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
- package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
- package/Tests/Server/Services/DomainService.test.ts +525 -0
- package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
- package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
- package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
- package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
- package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
- package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
- package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
- package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
- package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
- package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
- package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
- package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
- package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
- package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
- package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
- package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
- package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
- package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
- package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
- package/Types/Incident/IncidentAlertLink.ts +19 -0
- package/Types/Telemetry/InventoryLiveness.ts +26 -0
- package/Types/Topology/TopologyApi.ts +428 -0
- package/Types/Topology/TopologyTypeRules.ts +191 -0
- package/Utils/Incident/IncidentFromAlerts.ts +78 -0
- package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +21 -33
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +457 -3
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +96 -42
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +31 -0
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
- package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +37 -40
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
- package/build/dist/Server/Utils/Response.js +30 -0
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
- package/build/dist/Types/Topology/TopologyApi.js +55 -0
- package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
- package/package.json +1 -1
|
@@ -17,21 +17,28 @@ import {
|
|
|
17
17
|
} from "@testing-library/react";
|
|
18
18
|
import * as React from "react";
|
|
19
19
|
import { MemoryRouter } from "react-router-dom";
|
|
20
|
-
import InventoryItem from "../../../Models/DatabaseModels/InventoryItem";
|
|
21
|
-
import InventoryItemRelationship from "../../../Models/DatabaseModels/InventoryItemRelationship";
|
|
22
20
|
import EntityType from "../../../Types/Telemetry/EntityType";
|
|
23
21
|
import EntityRelationshipType from "../../../Types/Telemetry/EntityRelationshipType";
|
|
24
22
|
import EntitySource from "../../../Types/Telemetry/EntitySource";
|
|
25
23
|
import TimeRange from "../../../Types/Time/TimeRange";
|
|
26
24
|
import { ServiceOperationalStatus } from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/OperationalOverlay";
|
|
27
25
|
import ServiceMapGraph from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/ServiceMapGraph";
|
|
26
|
+
import {
|
|
27
|
+
EntityDetailTarget,
|
|
28
|
+
TopologyEntity,
|
|
29
|
+
TopologyRelationship,
|
|
30
|
+
TopologyRunsOnCount,
|
|
31
|
+
TopologyRunsOnCounts,
|
|
32
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/TopologyData";
|
|
28
33
|
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
29
34
|
|
|
30
35
|
/*
|
|
31
36
|
* The Service Map component: what a person sees first, how they narrow it
|
|
32
37
|
* down, and how they get to details. Graph drawing itself (React Flow) is
|
|
33
|
-
* replaced by a stand-in that renders nodes and edges as buttons,
|
|
34
|
-
*
|
|
38
|
+
* replaced by a stand-in that renders nodes and edges as buttons, and the
|
|
39
|
+
* detail drawer (which fetches its own connections) by a stand-in that
|
|
40
|
+
* records the props it was given, so these tests exercise the component's
|
|
41
|
+
* decisions, not the canvas or the drawer.
|
|
35
42
|
*/
|
|
36
43
|
|
|
37
44
|
const mockFitView: MockFunction = getJestMockFunction();
|
|
@@ -52,6 +59,14 @@ let mockStatuses: Map<string, ServiceOperationalStatus> = new Map<
|
|
|
52
59
|
string,
|
|
53
60
|
ServiceOperationalStatus
|
|
54
61
|
>();
|
|
62
|
+
/*
|
|
63
|
+
* Every drawer instance, by the entity it was mounted for. The map keeps one
|
|
64
|
+
* drawer while the user moves between entities (a remount would replay the
|
|
65
|
+
* slide-in and drop keyboard focus), so a new entry means a new drawer.
|
|
66
|
+
*/
|
|
67
|
+
const mockDrawerMounts: Array<string> = [];
|
|
68
|
+
/* The props of the drawer as last rendered. */
|
|
69
|
+
let mockDrawerProps: Record<string, unknown> | null = null;
|
|
55
70
|
|
|
56
71
|
jest.mock("../../../UI/Utils/Translation", () => {
|
|
57
72
|
return {
|
|
@@ -94,41 +109,143 @@ jest.mock(
|
|
|
94
109
|
};
|
|
95
110
|
},
|
|
96
111
|
);
|
|
112
|
+
/*
|
|
113
|
+
* The drawer's props contract: a preview of the entity (it fetches the rest
|
|
114
|
+
* itself), the server's range start, and callbacks. Its connection rows are
|
|
115
|
+
* stood in for by buttons that call back the way the real rows do: a
|
|
116
|
+
* "runs on" row opens the Infrastructure view when it can, every other row
|
|
117
|
+
* asks the map to select what it points at.
|
|
118
|
+
*/
|
|
97
119
|
jest.mock(
|
|
98
120
|
"../../../../App/FeatureSet/Dashboard/src/Components/Topology/EntityDetailPanel",
|
|
99
121
|
() => {
|
|
122
|
+
type Target = {
|
|
123
|
+
entityKey: string;
|
|
124
|
+
entityType?: string;
|
|
125
|
+
displayName?: string;
|
|
126
|
+
};
|
|
100
127
|
return {
|
|
101
128
|
__esModule: true,
|
|
102
129
|
default: (props: {
|
|
103
|
-
entity:
|
|
130
|
+
entity: Target;
|
|
131
|
+
rangeStart?: Date | null;
|
|
104
132
|
traffic?: { statusLabel: string; subtitle: string };
|
|
133
|
+
incidentStatus?: { activeIncidentCount: number } | null;
|
|
105
134
|
onClose: () => void;
|
|
106
|
-
onFocus
|
|
107
|
-
|
|
135
|
+
onFocus?: (key: string) => void;
|
|
136
|
+
focusButtonLabel?: string;
|
|
137
|
+
onSelectEntity?: (target: Target) => void;
|
|
138
|
+
onOpenInfrastructure?: (key: string) => void;
|
|
108
139
|
}) => {
|
|
140
|
+
mockDrawerProps = props as unknown as Record<string, unknown>;
|
|
141
|
+
const [mountedFor] = React.useState<string>(props.entity.entityKey);
|
|
142
|
+
React.useEffect(() => {
|
|
143
|
+
mockDrawerMounts.push(props.entity.entityKey);
|
|
144
|
+
}, []);
|
|
145
|
+
const select: (target: Target) => void = (target: Target): void => {
|
|
146
|
+
props.onSelectEntity?.(target);
|
|
147
|
+
};
|
|
109
148
|
return (
|
|
110
|
-
<div
|
|
111
|
-
|
|
149
|
+
<div
|
|
150
|
+
role="dialog"
|
|
151
|
+
aria-label="Service details"
|
|
152
|
+
data-mounted-for={mountedFor}
|
|
153
|
+
data-entity-key={props.entity.entityKey}
|
|
154
|
+
>
|
|
155
|
+
<p data-testid="drawer-name">
|
|
156
|
+
{props.entity.displayName || props.entity.entityKey}
|
|
157
|
+
</p>
|
|
158
|
+
<p data-testid="drawer-type">{props.entity.entityType || ""}</p>
|
|
112
159
|
<p data-testid="drawer-status">{props.traffic?.statusLabel}</p>
|
|
113
160
|
<p data-testid="drawer-subtitle">{props.traffic?.subtitle}</p>
|
|
161
|
+
<p data-testid="drawer-incidents">
|
|
162
|
+
{String(props.incidentStatus?.activeIncidentCount ?? "none")}
|
|
163
|
+
</p>
|
|
114
164
|
<button onClick={props.onClose}>Close service</button>
|
|
165
|
+
{props.onFocus ? (
|
|
166
|
+
<button
|
|
167
|
+
onClick={() => {
|
|
168
|
+
props.onFocus?.(props.entity.entityKey);
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
{props.focusButtonLabel || "Focus"}
|
|
172
|
+
</button>
|
|
173
|
+
) : (
|
|
174
|
+
<></>
|
|
175
|
+
)}
|
|
115
176
|
<button
|
|
116
177
|
onClick={() => {
|
|
117
|
-
|
|
178
|
+
select({
|
|
179
|
+
entityKey: "postgres",
|
|
180
|
+
entityType: "database",
|
|
181
|
+
displayName: "postgres",
|
|
182
|
+
});
|
|
118
183
|
}}
|
|
119
184
|
>
|
|
120
|
-
|
|
185
|
+
View related dependency
|
|
121
186
|
</button>
|
|
122
187
|
<button
|
|
123
188
|
onClick={() => {
|
|
124
|
-
|
|
189
|
+
select({
|
|
190
|
+
entityKey: "web",
|
|
191
|
+
entityType: "service",
|
|
192
|
+
displayName: "web",
|
|
193
|
+
});
|
|
125
194
|
}}
|
|
126
195
|
>
|
|
127
|
-
View
|
|
196
|
+
View caller
|
|
197
|
+
</button>
|
|
198
|
+
<button
|
|
199
|
+
onClick={() => {
|
|
200
|
+
select({
|
|
201
|
+
entityKey: "legacy",
|
|
202
|
+
entityType: "service",
|
|
203
|
+
displayName: "legacy",
|
|
204
|
+
});
|
|
205
|
+
}}
|
|
206
|
+
>
|
|
207
|
+
View inactive caller
|
|
208
|
+
</button>
|
|
209
|
+
<button
|
|
210
|
+
onClick={() => {
|
|
211
|
+
select({
|
|
212
|
+
entityKey: "api-instance-1",
|
|
213
|
+
entityType: "service.instance",
|
|
214
|
+
displayName: "api-instance-1",
|
|
215
|
+
});
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
View related instance
|
|
219
|
+
</button>
|
|
220
|
+
<button
|
|
221
|
+
onClick={() => {
|
|
222
|
+
select({ entityKey: "mystery-key" });
|
|
223
|
+
}}
|
|
224
|
+
>
|
|
225
|
+
View untyped resource
|
|
226
|
+
</button>
|
|
227
|
+
<button
|
|
228
|
+
onClick={() => {
|
|
229
|
+
select({
|
|
230
|
+
entityKey: "node-7",
|
|
231
|
+
entityType: "k8s.node",
|
|
232
|
+
displayName: "node-7",
|
|
233
|
+
});
|
|
234
|
+
}}
|
|
235
|
+
>
|
|
236
|
+
View related node
|
|
128
237
|
</button>
|
|
129
238
|
<button
|
|
130
239
|
onClick={() => {
|
|
131
|
-
props.
|
|
240
|
+
if (props.onOpenInfrastructure) {
|
|
241
|
+
props.onOpenInfrastructure("pod-1");
|
|
242
|
+
} else {
|
|
243
|
+
select({
|
|
244
|
+
entityKey: "pod-1",
|
|
245
|
+
entityType: "k8s.pod",
|
|
246
|
+
displayName: "pod-1",
|
|
247
|
+
});
|
|
248
|
+
}
|
|
132
249
|
}}
|
|
133
250
|
>
|
|
134
251
|
View placement
|
|
@@ -146,8 +263,8 @@ jest.mock(
|
|
|
146
263
|
__esModule: true,
|
|
147
264
|
default: (props: {
|
|
148
265
|
onClose: () => void;
|
|
149
|
-
fromEntity:
|
|
150
|
-
toEntity:
|
|
266
|
+
fromEntity: TopologyEntity;
|
|
267
|
+
toEntity: TopologyEntity;
|
|
151
268
|
}) => {
|
|
152
269
|
return (
|
|
153
270
|
<div role="dialog" aria-label="Connection details">
|
|
@@ -281,9 +398,9 @@ const RANGE_START: Date = new Date("2026-09-06T10:00:00Z");
|
|
|
281
398
|
|
|
282
399
|
function item(
|
|
283
400
|
key: string,
|
|
284
|
-
type: EntityType,
|
|
285
|
-
overrides: Partial<
|
|
286
|
-
):
|
|
401
|
+
type: EntityType | string,
|
|
402
|
+
overrides: Partial<TopologyEntity> = {},
|
|
403
|
+
): TopologyEntity {
|
|
287
404
|
return {
|
|
288
405
|
entityKey: key,
|
|
289
406
|
displayName: key,
|
|
@@ -291,7 +408,7 @@ function item(
|
|
|
291
408
|
source: EntitySource.Discovered,
|
|
292
409
|
lastSeenAt: NOW,
|
|
293
410
|
...overrides,
|
|
294
|
-
}
|
|
411
|
+
};
|
|
295
412
|
}
|
|
296
413
|
|
|
297
414
|
function edge(
|
|
@@ -300,7 +417,7 @@ function edge(
|
|
|
300
417
|
calls?: number,
|
|
301
418
|
errors?: number,
|
|
302
419
|
type: EntityRelationshipType = EntityRelationshipType.DependsOn,
|
|
303
|
-
):
|
|
420
|
+
): TopologyRelationship {
|
|
304
421
|
return {
|
|
305
422
|
fromEntityKey: from,
|
|
306
423
|
toEntityKey: to,
|
|
@@ -308,32 +425,54 @@ function edge(
|
|
|
308
425
|
callCount: calls,
|
|
309
426
|
errorCount: errors,
|
|
310
427
|
avgDurationMs: calls === undefined ? undefined : 25,
|
|
311
|
-
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function runsOn(
|
|
432
|
+
entityType: string,
|
|
433
|
+
active: number,
|
|
434
|
+
total: number,
|
|
435
|
+
): TopologyRunsOnCount {
|
|
436
|
+
return { entityType, active, total };
|
|
312
437
|
}
|
|
313
438
|
|
|
314
|
-
|
|
439
|
+
/*
|
|
440
|
+
* What the Service Map payload carries: services, what they call and the
|
|
441
|
+
* depends-on rows between them — no pods or hosts. Where services run comes
|
|
442
|
+
* as counts.
|
|
443
|
+
*/
|
|
444
|
+
const ENTITIES: Array<TopologyEntity> = [
|
|
315
445
|
item("web", EntityType.Service),
|
|
316
446
|
item("api", EntityType.Service),
|
|
317
447
|
item("worker", EntityType.Service),
|
|
318
448
|
item("postgres", EntityType.Database, {
|
|
319
449
|
descriptiveAttributes: { "db.system.name": "postgresql" },
|
|
320
450
|
}),
|
|
321
|
-
item("pod-1", EntityType.KubernetesPod),
|
|
322
451
|
item("legacy", EntityType.Service, {
|
|
323
452
|
lastSeenAt: new Date("2026-08-01T00:00:00Z"),
|
|
324
453
|
}),
|
|
325
454
|
];
|
|
326
455
|
|
|
327
|
-
const RELATIONSHIPS: Array<
|
|
456
|
+
const RELATIONSHIPS: Array<TopologyRelationship> = [
|
|
328
457
|
edge("web", "api", 600, 60),
|
|
329
458
|
edge("api", "postgres", 900, 0),
|
|
330
|
-
edge("api", "pod-1", undefined, undefined, EntityRelationshipType.RunsOn),
|
|
331
459
|
];
|
|
332
460
|
|
|
461
|
+
const RUNS_ON_COUNTS: TopologyRunsOnCounts = new Map<
|
|
462
|
+
string,
|
|
463
|
+
Array<TopologyRunsOnCount>
|
|
464
|
+
>([
|
|
465
|
+
["api", [runsOn(EntityType.KubernetesPod, 1, 3)]],
|
|
466
|
+
["worker", [runsOn(EntityType.Host, 0, 2)]],
|
|
467
|
+
["legacy", [runsOn(EntityType.Host, 0, 1)]],
|
|
468
|
+
]);
|
|
469
|
+
|
|
333
470
|
async function renderGraph(
|
|
334
471
|
options: {
|
|
335
|
-
entities?: Array<
|
|
336
|
-
relationships?: Array<
|
|
472
|
+
entities?: Array<TopologyEntity>;
|
|
473
|
+
relationships?: Array<TopologyRelationship>;
|
|
474
|
+
/* null renders without counts, the way full rows are fed. */
|
|
475
|
+
runsOnCounts?: TopologyRunsOnCounts | null;
|
|
337
476
|
includeInactive?: boolean;
|
|
338
477
|
onOpenInfrastructure?: (key: string) => void;
|
|
339
478
|
} = {},
|
|
@@ -344,6 +483,11 @@ async function renderGraph(
|
|
|
344
483
|
<ServiceMapGraph
|
|
345
484
|
entities={options.entities || ENTITIES}
|
|
346
485
|
relationships={options.relationships || RELATIONSHIPS}
|
|
486
|
+
runsOnCounts={
|
|
487
|
+
options.runsOnCounts === undefined
|
|
488
|
+
? RUNS_ON_COUNTS
|
|
489
|
+
: options.runsOnCounts || undefined
|
|
490
|
+
}
|
|
347
491
|
metricsWindowSeconds={60}
|
|
348
492
|
timeRange={{ range: TimeRange.PAST_ONE_DAY }}
|
|
349
493
|
rangeStart={RANGE_START}
|
|
@@ -355,11 +499,21 @@ async function renderGraph(
|
|
|
355
499
|
});
|
|
356
500
|
}
|
|
357
501
|
|
|
502
|
+
function drawer(): HTMLElement {
|
|
503
|
+
return screen.getByRole("dialog", { name: "Service details" });
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function drawerTarget(): EntityDetailTarget {
|
|
507
|
+
return mockDrawerProps!["entity"] as EntityDetailTarget;
|
|
508
|
+
}
|
|
509
|
+
|
|
358
510
|
beforeEach(() => {
|
|
359
511
|
window.history.replaceState({}, "", "/");
|
|
360
512
|
mockStatuses = new Map<string, ServiceOperationalStatus>();
|
|
361
513
|
mockSetQuery.mockClear();
|
|
362
514
|
mockFitView.mockClear();
|
|
515
|
+
mockDrawerMounts.length = 0;
|
|
516
|
+
mockDrawerProps = null;
|
|
363
517
|
});
|
|
364
518
|
afterEach(() => {
|
|
365
519
|
cleanup();
|
|
@@ -430,8 +584,111 @@ describe("first impression", () => {
|
|
|
430
584
|
});
|
|
431
585
|
});
|
|
432
586
|
|
|
587
|
+
describe("where services run", () => {
|
|
588
|
+
test("comes from the server's counts of what reported in the range", async () => {
|
|
589
|
+
await renderGraph();
|
|
590
|
+
// api runs on 1 active pod of 3; web has no counts at all.
|
|
591
|
+
expect(screen.getByTestId("map-node-api")).toHaveAttribute(
|
|
592
|
+
"data-footer",
|
|
593
|
+
"Runs on 1 pod",
|
|
594
|
+
);
|
|
595
|
+
expect(screen.getByTestId("map-node-web")).toHaveAttribute(
|
|
596
|
+
"data-footer",
|
|
597
|
+
"",
|
|
598
|
+
);
|
|
599
|
+
// worker runs on 2 hosts, neither of which reported: nothing to say.
|
|
600
|
+
expect(
|
|
601
|
+
within(screen.getByTestId("service-map-unconnected")).getByRole(
|
|
602
|
+
"button",
|
|
603
|
+
{ name: /worker/ },
|
|
604
|
+
),
|
|
605
|
+
).toHaveTextContent(/^workerService$/);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test("counts every resource when inactive ones are shown", async () => {
|
|
609
|
+
await renderGraph({ includeInactive: true });
|
|
610
|
+
expect(screen.getByTestId("map-node-api")).toHaveAttribute(
|
|
611
|
+
"data-footer",
|
|
612
|
+
"Runs on 3 pods",
|
|
613
|
+
);
|
|
614
|
+
const tray: HTMLElement = screen.getByTestId("service-map-unconnected");
|
|
615
|
+
expect(
|
|
616
|
+
within(tray).getByRole("button", { name: /worker/ }),
|
|
617
|
+
).toHaveTextContent("Service · 2 hosts");
|
|
618
|
+
expect(
|
|
619
|
+
within(tray).getByRole("button", { name: /legacy/ }),
|
|
620
|
+
).toHaveTextContent("Service · 1 host");
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("reads several types most first, in the table too", async () => {
|
|
624
|
+
await renderGraph({
|
|
625
|
+
runsOnCounts: new Map<string, Array<TopologyRunsOnCount>>([
|
|
626
|
+
[
|
|
627
|
+
"api",
|
|
628
|
+
[
|
|
629
|
+
runsOn(EntityType.Host, 2, 2),
|
|
630
|
+
runsOn(EntityType.KubernetesPod, 12, 12),
|
|
631
|
+
],
|
|
632
|
+
],
|
|
633
|
+
]),
|
|
634
|
+
});
|
|
635
|
+
expect(screen.getByTestId("map-node-api")).toHaveAttribute(
|
|
636
|
+
"data-footer",
|
|
637
|
+
"Runs on 12 pods · 2 hosts",
|
|
638
|
+
);
|
|
639
|
+
fireEvent.click(screen.getByTestId("service-map-view-list"));
|
|
640
|
+
expect(
|
|
641
|
+
screen.getByRole("button", { name: "api", exact: true }).closest("tr"),
|
|
642
|
+
).toHaveTextContent("Service · 12 pods · 2 hosts");
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
test("without counts, is counted from runs-on rows to resources it was given", async () => {
|
|
646
|
+
await renderGraph({
|
|
647
|
+
entities: [
|
|
648
|
+
...ENTITIES,
|
|
649
|
+
item("pod-1", EntityType.KubernetesPod),
|
|
650
|
+
item("pod-2", EntityType.KubernetesPod),
|
|
651
|
+
item("old-host", EntityType.Host, {
|
|
652
|
+
lastSeenAt: new Date("2026-08-01T00:00:00Z"),
|
|
653
|
+
}),
|
|
654
|
+
],
|
|
655
|
+
relationships: [
|
|
656
|
+
...RELATIONSHIPS,
|
|
657
|
+
edge(
|
|
658
|
+
"api",
|
|
659
|
+
"pod-1",
|
|
660
|
+
undefined,
|
|
661
|
+
undefined,
|
|
662
|
+
EntityRelationshipType.RunsOn,
|
|
663
|
+
),
|
|
664
|
+
edge(
|
|
665
|
+
"api",
|
|
666
|
+
"pod-2",
|
|
667
|
+
undefined,
|
|
668
|
+
undefined,
|
|
669
|
+
EntityRelationshipType.RunsOn,
|
|
670
|
+
),
|
|
671
|
+
edge(
|
|
672
|
+
"api",
|
|
673
|
+
"old-host",
|
|
674
|
+
undefined,
|
|
675
|
+
undefined,
|
|
676
|
+
EntityRelationshipType.HostedOn,
|
|
677
|
+
),
|
|
678
|
+
],
|
|
679
|
+
runsOnCounts: null,
|
|
680
|
+
});
|
|
681
|
+
expect(screen.getByTestId("map-node-api")).toHaveAttribute(
|
|
682
|
+
"data-footer",
|
|
683
|
+
"Runs on 2 pods",
|
|
684
|
+
);
|
|
685
|
+
// Infrastructure is never a node of the Service Map.
|
|
686
|
+
expect(screen.queryByTestId("map-node-pod-1")).not.toBeInTheDocument();
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
|
|
433
690
|
describe("a project with services but no observed calls", () => {
|
|
434
|
-
const entities: Array<
|
|
691
|
+
const entities: Array<TopologyEntity> = [
|
|
435
692
|
item("dashboard", EntityType.Service),
|
|
436
693
|
item("probe", EntityType.Service),
|
|
437
694
|
];
|
|
@@ -681,32 +938,215 @@ describe("connections", () => {
|
|
|
681
938
|
});
|
|
682
939
|
|
|
683
940
|
describe("details", () => {
|
|
684
|
-
test("the drawer
|
|
941
|
+
test("the drawer opens on a preview of the node and the server's range start", async () => {
|
|
942
|
+
await renderGraph();
|
|
943
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
944
|
+
expect(drawer()).toHaveAttribute("data-entity-key", "api");
|
|
945
|
+
expect(drawerTarget()).toEqual({
|
|
946
|
+
entityKey: "api",
|
|
947
|
+
entityType: EntityType.Service,
|
|
948
|
+
displayName: "api",
|
|
949
|
+
});
|
|
950
|
+
expect(mockDrawerProps!["rangeStart"]).toBe(RANGE_START);
|
|
951
|
+
expect(mockDrawerProps!["metricsWindowSeconds"]).toBe(60);
|
|
952
|
+
expect(mockDrawerProps!["focusButtonLabel"]).toBe("Show its connections");
|
|
953
|
+
expect(screen.getByTestId("drawer-subtitle")).toHaveTextContent("Service");
|
|
954
|
+
expect(screen.getByTestId("drawer-status")).toHaveTextContent(
|
|
955
|
+
"High error rate",
|
|
956
|
+
);
|
|
957
|
+
// The drawer fetches connections itself: the map hands it no rows.
|
|
958
|
+
expect(mockDrawerProps).not.toHaveProperty("relationships");
|
|
959
|
+
expect(mockDrawerProps).not.toHaveProperty("entityByKey");
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
test("an unnamed node's preview leaves naming to the drawer", async () => {
|
|
963
|
+
await renderGraph({
|
|
964
|
+
entities: [
|
|
965
|
+
item("svc", EntityType.Service, { displayName: undefined }),
|
|
966
|
+
item("db", EntityType.Database, { displayName: undefined }),
|
|
967
|
+
],
|
|
968
|
+
relationships: [edge("svc", "db", 5, 0)],
|
|
969
|
+
});
|
|
970
|
+
fireEvent.click(screen.getByTestId("map-node-db"));
|
|
971
|
+
expect(drawerTarget()).toEqual({
|
|
972
|
+
entityKey: "db",
|
|
973
|
+
entityType: EntityType.Database,
|
|
974
|
+
displayName: undefined,
|
|
975
|
+
});
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
test("a row pointing at a node of the map selects that node", async () => {
|
|
685
979
|
const onOpenInfrastructure: MockFunction = getJestMockFunction();
|
|
686
980
|
await renderGraph({ onOpenInfrastructure });
|
|
687
981
|
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
688
|
-
expect(screen.getByTestId("drawer-subtitle")).toHaveTextContent("Service");
|
|
689
982
|
fireEvent.click(
|
|
690
983
|
screen.getByRole("button", { name: "View related dependency" }),
|
|
691
984
|
);
|
|
692
|
-
expect(
|
|
693
|
-
|
|
694
|
-
).toHaveTextContent("postgres");
|
|
985
|
+
expect(drawer()).toHaveAttribute("data-entity-key", "postgres");
|
|
986
|
+
expect(screen.getByTestId("drawer-name")).toHaveTextContent("postgres");
|
|
695
987
|
expect(screen.getByTestId("drawer-subtitle")).toHaveTextContent(
|
|
696
988
|
"Database · PostgreSQL",
|
|
697
989
|
);
|
|
990
|
+
// The open drawer carries on to the new node: never a remount.
|
|
991
|
+
expect(mockDrawerMounts).toEqual(["api"]);
|
|
992
|
+
expect(drawer()).toHaveAttribute("data-mounted-for", "api");
|
|
993
|
+
expect(onOpenInfrastructure).not.toHaveBeenCalled();
|
|
994
|
+
fireEvent.click(screen.getByRole("button", { name: "View caller" }));
|
|
995
|
+
expect(screen.getByTestId("drawer-name")).toHaveTextContent("web");
|
|
996
|
+
expect(screen.getByTestId("drawer-status")).toHaveTextContent(
|
|
997
|
+
"Entry point",
|
|
998
|
+
);
|
|
999
|
+
expect(mockDrawerMounts).toEqual(["api"]);
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
test("a runs-on row opens the Infrastructure view and closes the drawer", async () => {
|
|
1003
|
+
const onOpenInfrastructure: MockFunction = getJestMockFunction();
|
|
1004
|
+
await renderGraph({ onOpenInfrastructure });
|
|
1005
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
698
1006
|
fireEvent.click(screen.getByRole("button", { name: "View placement" }));
|
|
1007
|
+
expect(onOpenInfrastructure).toHaveBeenCalledTimes(1);
|
|
699
1008
|
expect(onOpenInfrastructure).toHaveBeenCalledWith("pod-1");
|
|
700
1009
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
701
1010
|
});
|
|
702
1011
|
|
|
1012
|
+
test("any other infrastructure a row points at opens in the Infrastructure view", async () => {
|
|
1013
|
+
const onOpenInfrastructure: MockFunction = getJestMockFunction();
|
|
1014
|
+
await renderGraph({ onOpenInfrastructure });
|
|
1015
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
1016
|
+
fireEvent.click(screen.getByRole("button", { name: "View related node" }));
|
|
1017
|
+
expect(onOpenInfrastructure).toHaveBeenCalledWith("node-7");
|
|
1018
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
test("without an Infrastructure view, infrastructure opens in the drawer", async () => {
|
|
1022
|
+
await renderGraph();
|
|
1023
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
1024
|
+
fireEvent.click(screen.getByRole("button", { name: "View placement" }));
|
|
1025
|
+
expect(drawer()).toHaveAttribute("data-entity-key", "pod-1");
|
|
1026
|
+
expect(drawerTarget()).toEqual({
|
|
1027
|
+
entityKey: "pod-1",
|
|
1028
|
+
entityType: EntityType.KubernetesPod,
|
|
1029
|
+
displayName: "pod-1",
|
|
1030
|
+
});
|
|
1031
|
+
// Not a node of this map: no traffic, and nothing to focus on.
|
|
1032
|
+
expect(screen.getByTestId("drawer-status")).toBeEmptyDOMElement();
|
|
1033
|
+
expect(mockDrawerProps!["onFocus"]).toBeUndefined();
|
|
1034
|
+
expect(mockDrawerProps!["onOpenInfrastructure"]).toBeUndefined();
|
|
1035
|
+
expect(
|
|
1036
|
+
screen.queryByRole("button", { name: "Show its connections" }),
|
|
1037
|
+
).not.toBeInTheDocument();
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
test("a service that is not on the map opens in the drawer, not elsewhere", async () => {
|
|
1041
|
+
const onOpenInfrastructure: MockFunction = getJestMockFunction();
|
|
1042
|
+
await renderGraph({ onOpenInfrastructure });
|
|
1043
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
1044
|
+
fireEvent.click(
|
|
1045
|
+
screen.getByRole("button", { name: "View inactive caller" }),
|
|
1046
|
+
);
|
|
1047
|
+
expect(onOpenInfrastructure).not.toHaveBeenCalled();
|
|
1048
|
+
expect(drawer()).toHaveAttribute("data-entity-key", "legacy");
|
|
1049
|
+
expect(drawerTarget()).toEqual({
|
|
1050
|
+
entityKey: "legacy",
|
|
1051
|
+
entityType: EntityType.Service,
|
|
1052
|
+
displayName: "legacy",
|
|
1053
|
+
});
|
|
1054
|
+
expect(screen.getByTestId("drawer-status")).toBeEmptyDOMElement();
|
|
1055
|
+
expect(screen.getByTestId("drawer-incidents")).toHaveTextContent("none");
|
|
1056
|
+
expect(
|
|
1057
|
+
screen.queryByRole("button", { name: "Show its connections" }),
|
|
1058
|
+
).not.toBeInTheDocument();
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
test("application types and untyped resources open in the drawer", async () => {
|
|
1062
|
+
const onOpenInfrastructure: MockFunction = getJestMockFunction();
|
|
1063
|
+
await renderGraph({ onOpenInfrastructure });
|
|
1064
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
1065
|
+
fireEvent.click(
|
|
1066
|
+
screen.getByRole("button", { name: "View related instance" }),
|
|
1067
|
+
);
|
|
1068
|
+
expect(drawer()).toHaveAttribute("data-entity-key", "api-instance-1");
|
|
1069
|
+
fireEvent.click(
|
|
1070
|
+
screen.getByRole("button", { name: "View untyped resource" }),
|
|
1071
|
+
);
|
|
1072
|
+
expect(drawer()).toHaveAttribute("data-entity-key", "mystery-key");
|
|
1073
|
+
expect(drawerTarget()).toEqual({ entityKey: "mystery-key" });
|
|
1074
|
+
expect(onOpenInfrastructure).not.toHaveBeenCalled();
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
test("from a drawer off the map, rows and the map lead back onto it", async () => {
|
|
1078
|
+
await renderGraph();
|
|
1079
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
1080
|
+
fireEvent.click(
|
|
1081
|
+
screen.getByRole("button", { name: "View inactive caller" }),
|
|
1082
|
+
);
|
|
1083
|
+
fireEvent.click(
|
|
1084
|
+
screen.getByRole("button", { name: "View related dependency" }),
|
|
1085
|
+
);
|
|
1086
|
+
expect(drawer()).toHaveAttribute("data-entity-key", "postgres");
|
|
1087
|
+
expect(
|
|
1088
|
+
screen.getByRole("button", { name: "Show its connections" }),
|
|
1089
|
+
).toBeInTheDocument();
|
|
1090
|
+
fireEvent.click(
|
|
1091
|
+
screen.getByRole("button", { name: "View inactive caller" }),
|
|
1092
|
+
);
|
|
1093
|
+
fireEvent.click(screen.getByTestId("map-node-web"));
|
|
1094
|
+
expect(drawer()).toHaveAttribute("data-entity-key", "web");
|
|
1095
|
+
expect(screen.getByTestId("drawer-status")).toHaveTextContent(
|
|
1096
|
+
"Entry point",
|
|
1097
|
+
);
|
|
1098
|
+
/* Rows and map clicks alike moved the one open drawer along. */
|
|
1099
|
+
expect(mockDrawerMounts).toEqual(["api"]);
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
test("a connection closes a drawer that is open off the map", async () => {
|
|
1103
|
+
await renderGraph();
|
|
1104
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
1105
|
+
fireEvent.click(
|
|
1106
|
+
screen.getByRole("button", { name: "View inactive caller" }),
|
|
1107
|
+
);
|
|
1108
|
+
fireEvent.click(screen.getByTestId("map-edge-api->postgres"));
|
|
1109
|
+
expect(
|
|
1110
|
+
screen.queryByRole("dialog", { name: "Service details" }),
|
|
1111
|
+
).not.toBeInTheDocument();
|
|
1112
|
+
expect(
|
|
1113
|
+
screen.getByRole("dialog", { name: "Connection details" }),
|
|
1114
|
+
).toBeInTheDocument();
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
test("with inactive services shown, the same service is a node again", async () => {
|
|
1118
|
+
await renderGraph({ includeInactive: true });
|
|
1119
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
1120
|
+
fireEvent.click(
|
|
1121
|
+
screen.getByRole("button", { name: "View inactive caller" }),
|
|
1122
|
+
);
|
|
1123
|
+
expect(drawer()).toHaveAttribute("data-entity-key", "legacy");
|
|
1124
|
+
expect(screen.getByTestId("drawer-status")).toHaveTextContent(
|
|
1125
|
+
"No calls observed",
|
|
1126
|
+
);
|
|
1127
|
+
expect(
|
|
1128
|
+
screen.getByRole("button", { name: "Show its connections" }),
|
|
1129
|
+
).toBeInTheDocument();
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
test("closing the drawer clears the selection", async () => {
|
|
1133
|
+
await renderGraph();
|
|
1134
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
1135
|
+
fireEvent.click(screen.getByRole("button", { name: "Close service" }));
|
|
1136
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
1137
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
1138
|
+
expect(mockDrawerMounts).toEqual(["api", "api"]);
|
|
1139
|
+
});
|
|
1140
|
+
|
|
703
1141
|
test("focusing from the drawer opens that node's connections on the map", async () => {
|
|
704
1142
|
await renderGraph();
|
|
705
1143
|
fireEvent.click(screen.getByTestId("service-map-view-list"));
|
|
706
1144
|
fireEvent.click(
|
|
707
1145
|
screen.getByRole("button", { name: "postgres", exact: true }),
|
|
708
1146
|
);
|
|
709
|
-
fireEvent.click(
|
|
1147
|
+
fireEvent.click(
|
|
1148
|
+
screen.getByRole("button", { name: "Show its connections" }),
|
|
1149
|
+
);
|
|
710
1150
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
711
1151
|
expect(screen.getByTestId("service-map-view-map")).toHaveAttribute(
|
|
712
1152
|
"aria-pressed",
|
|
@@ -716,6 +1156,38 @@ describe("details", () => {
|
|
|
716
1156
|
expect(screen.queryByTestId("map-node-web")).not.toBeInTheDocument();
|
|
717
1157
|
});
|
|
718
1158
|
|
|
1159
|
+
test("a service's incidents reach its drawer; a dependency's never do", async () => {
|
|
1160
|
+
mockStatuses.set("api", {
|
|
1161
|
+
serviceId: "api-id",
|
|
1162
|
+
activeIncidentCount: 2,
|
|
1163
|
+
worstIncidentSeverityName: "Critical",
|
|
1164
|
+
worstIncidentSeverityColor: "#dc2626",
|
|
1165
|
+
incidents: [],
|
|
1166
|
+
activeAlertCount: 0,
|
|
1167
|
+
worstAlertSeverityName: null,
|
|
1168
|
+
worstAlertSeverityColor: null,
|
|
1169
|
+
alerts: [],
|
|
1170
|
+
});
|
|
1171
|
+
mockStatuses.set("postgres", {
|
|
1172
|
+
serviceId: "postgres-id",
|
|
1173
|
+
activeIncidentCount: 5,
|
|
1174
|
+
worstIncidentSeverityName: "Critical",
|
|
1175
|
+
worstIncidentSeverityColor: "#dc2626",
|
|
1176
|
+
incidents: [],
|
|
1177
|
+
activeAlertCount: 0,
|
|
1178
|
+
worstAlertSeverityName: null,
|
|
1179
|
+
worstAlertSeverityColor: null,
|
|
1180
|
+
alerts: [],
|
|
1181
|
+
});
|
|
1182
|
+
await renderGraph();
|
|
1183
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
1184
|
+
expect(screen.getByTestId("drawer-incidents")).toHaveTextContent("2");
|
|
1185
|
+
fireEvent.click(
|
|
1186
|
+
screen.getByRole("button", { name: "View related dependency" }),
|
|
1187
|
+
);
|
|
1188
|
+
expect(screen.getByTestId("drawer-incidents")).toHaveTextContent("none");
|
|
1189
|
+
});
|
|
1190
|
+
|
|
719
1191
|
test("incidents take precedence over traffic in status", async () => {
|
|
720
1192
|
mockStatuses.set("worker", {
|
|
721
1193
|
serviceId: "worker-id",
|
|
@@ -755,7 +1227,7 @@ describe("table", () => {
|
|
|
755
1227
|
});
|
|
756
1228
|
|
|
757
1229
|
test("a large catalog opens as a paged table with a hint to narrow it", async () => {
|
|
758
|
-
const many: Array<
|
|
1230
|
+
const many: Array<TopologyEntity> = Array.from(
|
|
759
1231
|
{ length: 130 },
|
|
760
1232
|
(_value: unknown, index: number) => {
|
|
761
1233
|
return item(
|