@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
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
jest,
|
|
8
|
+
test,
|
|
9
|
+
} from "@jest/globals";
|
|
10
|
+
import { act, cleanup, render, screen } from "@testing-library/react";
|
|
11
|
+
import React, { ReactElement, ReactNode } from "react";
|
|
12
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* The alerts list (Components/Alert/AlertsTable.tsx) backs the Alerts,
|
|
16
|
+
* Unresolved and Home "Active Alerts" pages and the Alerts tab of monitors,
|
|
17
|
+
* services, hosts, clusters and SLOs. An alert is raised on one monitor
|
|
18
|
+
* (Alert.monitor, singular), and the alert overview's Affected Resources card
|
|
19
|
+
* lists it. The list's Affected Resources column never passed it to the cell,
|
|
20
|
+
* so every alert raised on a monitor and linked to nothing else read "No
|
|
21
|
+
* resources." right next to a Monitor column naming that monitor.
|
|
22
|
+
*
|
|
23
|
+
* The table is replaced by a recorder so the columns and facets the list
|
|
24
|
+
* builds can be read and run; each cell is then rendered for real. AppLink is
|
|
25
|
+
* a plain anchor so hrefs can be read.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const modelTableMock: MockFunction = getJestMockFunction();
|
|
29
|
+
const resourceOwnersMock: MockFunction = getJestMockFunction();
|
|
30
|
+
const getListMock: MockFunction = getJestMockFunction();
|
|
31
|
+
|
|
32
|
+
jest.mock("react-i18next", () => {
|
|
33
|
+
return {
|
|
34
|
+
useTranslation: () => {
|
|
35
|
+
return {
|
|
36
|
+
t: (key: string, opts?: { defaultValue?: string }): string => {
|
|
37
|
+
return opts?.defaultValue ?? key;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
45
|
+
return {
|
|
46
|
+
__esModule: true,
|
|
47
|
+
default: {
|
|
48
|
+
getList: (...args: Array<unknown>): unknown => {
|
|
49
|
+
return getListMock(...args);
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
|
|
56
|
+
return {
|
|
57
|
+
__esModule: true,
|
|
58
|
+
default: (props: unknown): ReactElement => {
|
|
59
|
+
modelTableMock(props);
|
|
60
|
+
return <div data-testid="model-table" />;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
jest.mock(
|
|
66
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/AppLink/AppLink",
|
|
67
|
+
() => {
|
|
68
|
+
return {
|
|
69
|
+
__esModule: true,
|
|
70
|
+
default: (props: {
|
|
71
|
+
to?: { toString: () => string };
|
|
72
|
+
children: ReactNode;
|
|
73
|
+
className?: string;
|
|
74
|
+
}): ReactElement => {
|
|
75
|
+
return React.createElement(
|
|
76
|
+
"a",
|
|
77
|
+
{ href: props.to?.toString(), className: props.className },
|
|
78
|
+
props.children,
|
|
79
|
+
);
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
jest.mock(
|
|
86
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/ResourceOwners/useResourceOwners",
|
|
87
|
+
() => {
|
|
88
|
+
const actual: Record<string, unknown> = jest.requireActual(
|
|
89
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/ResourceOwners/useResourceOwners",
|
|
90
|
+
) as Record<string, unknown>;
|
|
91
|
+
return {
|
|
92
|
+
...actual,
|
|
93
|
+
__esModule: true,
|
|
94
|
+
default: (options: unknown) => {
|
|
95
|
+
resourceOwnersMock(options);
|
|
96
|
+
return {
|
|
97
|
+
getOwnersForResource: () => {
|
|
98
|
+
return [];
|
|
99
|
+
},
|
|
100
|
+
isLoadingOwners: false,
|
|
101
|
+
onResourcesFetched: () => {},
|
|
102
|
+
filterBar: null,
|
|
103
|
+
mergeFiltersIntoQuery: (query: unknown) => {
|
|
104
|
+
return query;
|
|
105
|
+
},
|
|
106
|
+
facetSaveState: undefined,
|
|
107
|
+
restoreFacetState: () => {},
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
jest.mock(
|
|
115
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/CustomFields/useCustomFieldFacets",
|
|
116
|
+
() => {
|
|
117
|
+
return {
|
|
118
|
+
__esModule: true,
|
|
119
|
+
default: () => {
|
|
120
|
+
return { facets: [], isLoading: false };
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
jest.mock("../../../UI/Components/BulkUpdate/BulkLabelActions", () => {
|
|
127
|
+
return {
|
|
128
|
+
__esModule: true,
|
|
129
|
+
default: () => {
|
|
130
|
+
return { bulkActions: [], modals: null };
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
jest.mock("../../../UI/Components/BulkUpdate/BulkOwnerActions", () => {
|
|
136
|
+
return {
|
|
137
|
+
__esModule: true,
|
|
138
|
+
default: () => {
|
|
139
|
+
return { bulkActions: [], modals: null };
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
jest.mock(
|
|
145
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Alert/BulkIncidentLinkActions",
|
|
146
|
+
() => {
|
|
147
|
+
return {
|
|
148
|
+
__esModule: true,
|
|
149
|
+
default: () => {
|
|
150
|
+
return { bulkActions: [], modals: null };
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
import AlertsTable from "../../../../App/FeatureSet/Dashboard/src/Components/Alert/AlertsTable";
|
|
157
|
+
import { ResourceFacet } from "../../../../App/FeatureSet/Dashboard/src/Components/ResourceOwners/ResourceFacet";
|
|
158
|
+
import { FilterChipDropdownOption } from "../../../../App/FeatureSet/Dashboard/src/Components/ResourceOwners/FilterChipDropdownTypes";
|
|
159
|
+
import Alert from "../../../Models/DatabaseModels/Alert";
|
|
160
|
+
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
161
|
+
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
162
|
+
import Service from "../../../Models/DatabaseModels/Service";
|
|
163
|
+
import ServiceLevelObjective from "../../../Models/DatabaseModels/ServiceLevelObjective";
|
|
164
|
+
import Color from "../../../Types/Color";
|
|
165
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
166
|
+
import { getExportKeysFromColumn } from "../../../UI/Components/ModelTable/ExportFromColumns";
|
|
167
|
+
/*
|
|
168
|
+
* Two different column types are in play, and getExportKeysFromColumn takes
|
|
169
|
+
* the ModelTable one: it reads `field`, which only ModelTable columns declare.
|
|
170
|
+
* The Table column below is what TableColumnsToCsv consumes.
|
|
171
|
+
*/
|
|
172
|
+
import ModelTableColumn from "../../../UI/Components/ModelTable/Column";
|
|
173
|
+
import Column from "../../../UI/Components/Table/Types/Column";
|
|
174
|
+
import FieldType from "../../../UI/Components/Types/FieldType";
|
|
175
|
+
import TableColumnsToCsv from "../../../UI/Utils/TableColumnsToCsv";
|
|
176
|
+
import { PROJECT_ID, goTo } from "./SideMenuHarness";
|
|
177
|
+
|
|
178
|
+
const MONITOR_ID: string = "0193c0de-9999-4aaa-8bbb-000000000001";
|
|
179
|
+
const SERVICE_ID: string = "0193c0de-9999-4aaa-8bbb-000000000002";
|
|
180
|
+
const SLO_ID: string = "0193c0de-9999-4aaa-8bbb-000000000003";
|
|
181
|
+
|
|
182
|
+
type CapturedColumn = {
|
|
183
|
+
field: Record<string, unknown>;
|
|
184
|
+
title: string;
|
|
185
|
+
type: FieldType;
|
|
186
|
+
getElement?: ((item: Alert) => ReactElement) | undefined;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
type ListResult = {
|
|
190
|
+
data: Array<unknown>;
|
|
191
|
+
count: number;
|
|
192
|
+
skip: number;
|
|
193
|
+
limit: number;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
function listResult(data: Array<unknown>): ListResult {
|
|
197
|
+
return { data: data, count: data.length, skip: 0, limit: data.length };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function monitorHref(id: string): string {
|
|
201
|
+
return `/dashboard/${PROJECT_ID}/monitors/${id}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function buildMonitor(name: string): Monitor {
|
|
205
|
+
const monitor: Monitor = new Monitor();
|
|
206
|
+
monitor._id = MONITOR_ID;
|
|
207
|
+
monitor.name = name;
|
|
208
|
+
return monitor;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function buildService(name: string): Service {
|
|
212
|
+
const service: Service = new Service();
|
|
213
|
+
service._id = SERVICE_ID;
|
|
214
|
+
service.name = name;
|
|
215
|
+
service.serviceColor = new Color("#6366f1");
|
|
216
|
+
return service;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function buildSlo(name: string): ServiceLevelObjective {
|
|
220
|
+
const slo: ServiceLevelObjective = new ServiceLevelObjective();
|
|
221
|
+
slo._id = SLO_ID;
|
|
222
|
+
slo.name = name;
|
|
223
|
+
return slo;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function flush(): Promise<void> {
|
|
227
|
+
for (let i: number = 0; i < 8; i++) {
|
|
228
|
+
await act(async () => {
|
|
229
|
+
await Promise.resolve();
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function renderTable(): Promise<void> {
|
|
235
|
+
render(<AlertsTable />);
|
|
236
|
+
await flush();
|
|
237
|
+
|
|
238
|
+
expect(modelTableMock).toHaveBeenCalled();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function capturedColumns(): Array<CapturedColumn> {
|
|
242
|
+
const props: { columns: Array<CapturedColumn> } = modelTableMock.mock.calls[
|
|
243
|
+
modelTableMock.mock.calls.length - 1
|
|
244
|
+
]![0] as { columns: Array<CapturedColumn> };
|
|
245
|
+
|
|
246
|
+
return props.columns;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function column(title: string): CapturedColumn {
|
|
250
|
+
const found: Array<CapturedColumn> = capturedColumns().filter(
|
|
251
|
+
(c: CapturedColumn): boolean => {
|
|
252
|
+
return c.title === title;
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
expect(found).toHaveLength(1);
|
|
257
|
+
|
|
258
|
+
return found[0]!;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function renderCell(title: string, alert: Alert): void {
|
|
262
|
+
cleanup();
|
|
263
|
+
render(column(title).getElement!(alert));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function facet(key: string): ResourceFacet {
|
|
267
|
+
const options: { extraFacets: Array<ResourceFacet> } = resourceOwnersMock.mock
|
|
268
|
+
.calls[resourceOwnersMock.mock.calls.length - 1]![0] as {
|
|
269
|
+
extraFacets: Array<ResourceFacet>;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const found: Array<ResourceFacet> = options.extraFacets.filter(
|
|
273
|
+
(f: ResourceFacet): boolean => {
|
|
274
|
+
return f.key === key;
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
expect(found).toHaveLength(1);
|
|
279
|
+
|
|
280
|
+
return found[0]!;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function linkTexts(): Array<string> {
|
|
284
|
+
return screen.getAllByRole("link").map((link: HTMLElement): string => {
|
|
285
|
+
return link.textContent?.trim() || "";
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
beforeEach(() => {
|
|
290
|
+
modelTableMock.mockReset();
|
|
291
|
+
resourceOwnersMock.mockReset();
|
|
292
|
+
getListMock.mockReset();
|
|
293
|
+
|
|
294
|
+
/*
|
|
295
|
+
* One named row of whatever is asked for, so a facet that searches a
|
|
296
|
+
* resource type gets an option back for it.
|
|
297
|
+
*/
|
|
298
|
+
getListMock.mockImplementation(async (...args: Array<unknown>) => {
|
|
299
|
+
const request: { modelType: { new (): BaseModel } } = args[0] as {
|
|
300
|
+
modelType: { new (): BaseModel };
|
|
301
|
+
};
|
|
302
|
+
const row: BaseModel = new request.modelType();
|
|
303
|
+
row._id = ObjectID.generate().toString();
|
|
304
|
+
(row as unknown as { name: string }).name =
|
|
305
|
+
`${row.singularName || "Row"} one`;
|
|
306
|
+
return listResult([row]);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
goTo(`/dashboard/${PROJECT_ID}/alerts`);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
afterEach(() => {
|
|
313
|
+
cleanup();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe("the alerts list's Affected Resources column", () => {
|
|
317
|
+
test("lists the monitor an alert was raised on, never 'No resources.'", async () => {
|
|
318
|
+
await renderTable();
|
|
319
|
+
|
|
320
|
+
const alert: Alert = new Alert();
|
|
321
|
+
alert.monitor = buildMonitor("Developer portal");
|
|
322
|
+
|
|
323
|
+
renderCell("Affected Resources", alert);
|
|
324
|
+
|
|
325
|
+
expect(screen.queryByText("No resources.")).toBeNull();
|
|
326
|
+
|
|
327
|
+
const link: HTMLElement = screen.getByRole("link");
|
|
328
|
+
|
|
329
|
+
expect(link).toHaveAttribute("href", monitorHref(MONITOR_ID));
|
|
330
|
+
expect(link).toHaveTextContent("Developer portal");
|
|
331
|
+
// The same type icon every other resource row carries.
|
|
332
|
+
expect(link.querySelector("svg")).not.toBeNull();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("names the same monitor the Monitor column does", async () => {
|
|
336
|
+
await renderTable();
|
|
337
|
+
|
|
338
|
+
const alert: Alert = new Alert();
|
|
339
|
+
alert.monitor = buildMonitor("Developer portal");
|
|
340
|
+
|
|
341
|
+
renderCell("Monitor", alert);
|
|
342
|
+
const monitorColumnHref: string | null = screen
|
|
343
|
+
.getByRole("link")
|
|
344
|
+
.getAttribute("href");
|
|
345
|
+
|
|
346
|
+
renderCell("Affected Resources", alert);
|
|
347
|
+
|
|
348
|
+
expect(screen.getByRole("link")).toHaveAttribute(
|
|
349
|
+
"href",
|
|
350
|
+
monitorColumnHref!,
|
|
351
|
+
);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("lists the monitor first, ahead of the service and SLO linked to the alert", async () => {
|
|
355
|
+
await renderTable();
|
|
356
|
+
|
|
357
|
+
const alert: Alert = new Alert();
|
|
358
|
+
alert.monitor = buildMonitor("Developer portal");
|
|
359
|
+
alert.services = [buildService("checkout-api")];
|
|
360
|
+
alert.serviceLevelObjectives = [buildSlo("Checkout availability")];
|
|
361
|
+
|
|
362
|
+
renderCell("Affected Resources", alert);
|
|
363
|
+
|
|
364
|
+
expect(linkTexts()).toEqual([
|
|
365
|
+
"Developer portal",
|
|
366
|
+
"checkout-api",
|
|
367
|
+
"Checkout availability",
|
|
368
|
+
]);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test("an alert with no monitor and nothing attached still reads 'No resources.'", async () => {
|
|
372
|
+
await renderTable();
|
|
373
|
+
|
|
374
|
+
renderCell("Affected Resources", new Alert());
|
|
375
|
+
|
|
376
|
+
expect(screen.getByText("No resources.")).toBeInTheDocument();
|
|
377
|
+
expect(screen.queryByRole("link")).toBeNull();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
/*
|
|
381
|
+
* Declared on this column rather than borrowed from the Monitor column, so
|
|
382
|
+
* the cell still gets the monitor if that column goes. It is a secondary
|
|
383
|
+
* key: a role that cannot read monitors has it dropped from the select
|
|
384
|
+
* instead of the whole list failing, and hosts stays the column's primary
|
|
385
|
+
* field (its sort and export key).
|
|
386
|
+
*/
|
|
387
|
+
test("reads the monitor itself, after hosts", async () => {
|
|
388
|
+
await renderTable();
|
|
389
|
+
|
|
390
|
+
const field: Record<string, unknown> = column("Affected Resources").field;
|
|
391
|
+
|
|
392
|
+
expect(Object.keys(field)[0]).toBe("hosts");
|
|
393
|
+
expect(field["monitor"]).toEqual({
|
|
394
|
+
name: true,
|
|
395
|
+
_id: true,
|
|
396
|
+
projectId: true,
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test("exports the monitor under Affected Resources", async () => {
|
|
401
|
+
await renderTable();
|
|
402
|
+
|
|
403
|
+
const affected: CapturedColumn = column("Affected Resources");
|
|
404
|
+
const exportColumn: Column<Alert> = {
|
|
405
|
+
title: affected.title,
|
|
406
|
+
type: affected.type,
|
|
407
|
+
key: "hosts",
|
|
408
|
+
exportKeys: getExportKeysFromColumn<Alert>({
|
|
409
|
+
column: affected as unknown as ModelTableColumn<Alert>,
|
|
410
|
+
columnKey: "hosts",
|
|
411
|
+
}),
|
|
412
|
+
} as unknown as Column<Alert>;
|
|
413
|
+
|
|
414
|
+
const alert: Alert = new Alert();
|
|
415
|
+
alert.monitor = buildMonitor("Developer portal");
|
|
416
|
+
|
|
417
|
+
expect(TableColumnsToCsv.getCellValue(alert, exportColumn)).toBe(
|
|
418
|
+
"Developer portal",
|
|
419
|
+
);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
/*
|
|
424
|
+
* The column lists the monitor, but filtering by it stays with the dedicated
|
|
425
|
+
* Monitor chip, which queries Alert.monitorId directly. Offering monitors in
|
|
426
|
+
* the Affected Resources chip too would list every monitor twice and filter
|
|
427
|
+
* through an ID lookup capped at LIMIT_PER_PROJECT alerts.
|
|
428
|
+
*/
|
|
429
|
+
describe("the alerts list's filters", () => {
|
|
430
|
+
test("the Affected Resources chip offers every resource type but monitors", async () => {
|
|
431
|
+
await renderTable();
|
|
432
|
+
getListMock.mockClear();
|
|
433
|
+
|
|
434
|
+
const options: Array<FilterChipDropdownOption> = await facet(
|
|
435
|
+
"affectedResources",
|
|
436
|
+
).loadOptions!(new ObjectID(PROJECT_ID), "");
|
|
437
|
+
|
|
438
|
+
const requestedTypes: Array<unknown> = getListMock.mock.calls.map(
|
|
439
|
+
(call: Array<unknown>): unknown => {
|
|
440
|
+
return (call[0] as { modelType: unknown }).modelType;
|
|
441
|
+
},
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
expect(requestedTypes).not.toContain(Monitor);
|
|
445
|
+
expect(requestedTypes).toContain(Service);
|
|
446
|
+
expect(requestedTypes).toContain(ServiceLevelObjective);
|
|
447
|
+
|
|
448
|
+
const groups: Array<string | undefined> = options.map(
|
|
449
|
+
(option: FilterChipDropdownOption): string | undefined => {
|
|
450
|
+
return option.group;
|
|
451
|
+
},
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
expect(groups).not.toContain("Monitors");
|
|
455
|
+
expect(groups).toContain("Services");
|
|
456
|
+
expect(groups).toContain("SLOs");
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("a monitor key reaching the Affected Resources chip is not looked up", async () => {
|
|
460
|
+
await renderTable();
|
|
461
|
+
getListMock.mockClear();
|
|
462
|
+
|
|
463
|
+
const ids: Array<string> = await facet("affectedResources")
|
|
464
|
+
.computeMatchingResourceIds!(
|
|
465
|
+
new ObjectID(PROJECT_ID),
|
|
466
|
+
[`monitor:${MONITOR_ID}`],
|
|
467
|
+
"is",
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
expect(ids).toEqual([]);
|
|
471
|
+
expect(getListMock).not.toHaveBeenCalled();
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test("the Monitor chip filters on the alert's monitorId", async () => {
|
|
475
|
+
await renderTable();
|
|
476
|
+
getListMock.mockClear();
|
|
477
|
+
|
|
478
|
+
const monitorFacet: ResourceFacet = facet("monitor");
|
|
479
|
+
|
|
480
|
+
expect(monitorFacet.queryField).toBe("monitorId");
|
|
481
|
+
|
|
482
|
+
const options: Array<FilterChipDropdownOption> =
|
|
483
|
+
await monitorFacet.loadOptions!(new ObjectID(PROJECT_ID), "");
|
|
484
|
+
|
|
485
|
+
expect(
|
|
486
|
+
(getListMock.mock.calls[0]![0] as { modelType: unknown }).modelType,
|
|
487
|
+
).toBe(Monitor);
|
|
488
|
+
expect(options).toHaveLength(1);
|
|
489
|
+
});
|
|
490
|
+
});
|