@oneuptime/common 14.0.5 → 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/MetricService.ts +1 -1
- package/Server/Services/ScheduledMaintenanceService.ts +50 -46
- package/Server/Services/UserNotificationRuleService.ts +55 -53
- package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
- package/Server/Types/Database/QueryHelper.ts +54 -1
- package/Server/Utils/AI/Toolbox/MetricTools.ts +13 -2
- package/Server/Utils/AI/Toolbox/ResourceTools.ts +12 -1
- package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
- package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +1 -1
- package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +33 -0
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +4 -3
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +3 -1
- 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/NetworkTopologyExplorerSearch.test.tsx +1267 -0
- package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +4 -1
- package/Tests/App/Dashboard/RumOverviewPage.test.tsx +127 -0
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
- package/Tests/App/Dashboard/SiteSearchBox.test.tsx +2110 -0
- package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +168 -8
- 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/MetricServiceAggregate.test.ts +12 -0
- package/Tests/Server/Services/SavedViewDefaults.test.ts +325 -0
- package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
- package/Tests/Server/Services/StatusPageCertificateService.test.ts +169 -0
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +191 -0
- package/Tests/Server/Types/Database/QueryHelperSearchAllWords.test.ts +651 -0
- package/Tests/Server/Utils/AI/AlertWriteTools.test.ts +256 -0
- package/Tests/Server/Utils/AI/IncidentWriteTools.test.ts +482 -0
- package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
- package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +54 -0
- package/Tests/Server/Utils/Monitor/MetricDecimalThreshold.test.ts +417 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +96 -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/BaseDatabase/AggregationType.test.ts +5 -1
- package/Tests/Types/Dashboard/KubernetesDashboardTemplate.test.ts +184 -0
- package/Tests/Types/Dashboard/KubernetesDashboardTemplates.test.ts +107 -0
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +8 -14
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +4 -3
- package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +27 -6
- package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +76 -0
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +53 -0
- package/Tests/UI/Rum/ReplaySignals.test.ts +54 -0
- package/Tests/Utils/Dashboard/KubernetesClusterCpuTileRepair.test.ts +370 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
- package/Tests/Utils/Rules/MonitorTypeRuleCriteria.test.ts +181 -0
- package/Types/BaseDatabase/AggregationType.ts +7 -0
- package/Types/Dashboard/DashboardTemplates.ts +20 -7
- package/Types/Incident/IncidentAlertLink.ts +19 -0
- package/Types/Rum/SessionReplayCustomEvents.ts +29 -3
- package/Types/Rum/WebVitals.ts +13 -0
- package/Types/Telemetry/InventoryLiveness.ts +26 -0
- package/Types/Topology/TopologyApi.ts +428 -0
- package/Types/Topology/TopologyTypeRules.ts +191 -0
- package/UI/Components/Input/Input.tsx +11 -0
- package/Utils/Dashboard/KubernetesClusterCpuTileRepair.ts +147 -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/MetricService.js +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/Types/Database/QueryHelper.js +47 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +13 -2
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +12 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.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/AnalyticsDatabase/StatementGenerator.js +1 -1
- 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/Monitor/Criteria/CompareCriteria.js +31 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- 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/BaseDatabase/AggregationType.js +7 -0
- package/build/dist/Types/BaseDatabase/AggregationType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +20 -7
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayCustomEvents.js.map +1 -1
- package/build/dist/Types/Rum/WebVitals.js +12 -0
- package/build/dist/Types/Rum/WebVitals.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/UI/Components/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.js +110 -0
- package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.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
|
@@ -7,38 +7,50 @@ import {
|
|
|
7
7
|
jest,
|
|
8
8
|
test,
|
|
9
9
|
} from "@jest/globals";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
cleanup,
|
|
12
|
+
render,
|
|
13
|
+
RenderResult,
|
|
14
|
+
waitFor,
|
|
15
|
+
within,
|
|
16
|
+
} from "@testing-library/react";
|
|
11
17
|
import * as React from "react";
|
|
12
18
|
import { MemoryRouter } from "react-router-dom";
|
|
19
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
13
20
|
|
|
14
21
|
/*
|
|
15
|
-
* The incident and scheduled maintenance wizards end on a
|
|
16
|
-
* what each step picked. The "Resources Affected" step uses one
|
|
17
|
-
* writes to every resource relation the page hands it,
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* "
|
|
22
|
+
* The incident and scheduled maintenance wizards end on a review step that
|
|
23
|
+
* lists what each earlier step picked. The "Resources Affected" step uses one
|
|
24
|
+
* picker that writes to every resource relation the page hands it, and the
|
|
25
|
+
* form holds what it picked as bare IDs. Its summary used to be hand-written
|
|
26
|
+
* per page: it named monitors but only counted every other type ("3 Podman
|
|
27
|
+
* hosts"), so the one place meant to confirm the choice never said which
|
|
28
|
+
* hosts or services were picked - and a type the summary forgot was not even
|
|
29
|
+
* counted. Pick only Podman hosts and it once said "No resources affected",
|
|
30
|
+
* although the selection saved fine.
|
|
31
|
+
*
|
|
32
|
+
* The summary now renders the same picker, read-only, with the same resource
|
|
33
|
+
* types as the editor; the picker looks the names of bare IDs up, so every
|
|
34
|
+
* picked resource is named.
|
|
21
35
|
*
|
|
22
36
|
* The invariant pinned here: every resource prop the editor picker is given
|
|
23
|
-
*
|
|
24
|
-
* capture the fields it is handed (as
|
|
25
|
-
* does),
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
37
|
+
* is named in the summary. These tests drive the real pages, with ModelForm
|
|
38
|
+
* mocked to capture the fields it is handed (as
|
|
39
|
+
* CreateReviewStepSelectionSummary.test.tsx does), read the resource props
|
|
40
|
+
* off the picker element the step renders, and render the step's summary
|
|
41
|
+
* against a ModelAPI that knows a name for each resource under its own model
|
|
42
|
+
* type only. A resource type added to a page's picker later is picked up
|
|
43
|
+
* without edits here.
|
|
29
44
|
*/
|
|
30
45
|
|
|
46
|
+
type SummaryItem = Record<string, unknown>;
|
|
47
|
+
|
|
31
48
|
type CapturedField = {
|
|
32
49
|
stepId?: string | undefined;
|
|
33
50
|
getCustomElement?:
|
|
34
|
-
| ((
|
|
35
|
-
values: Record<string, unknown>,
|
|
36
|
-
elementProps: Record<string, unknown>,
|
|
37
|
-
) => React.ReactElement)
|
|
38
|
-
| undefined;
|
|
39
|
-
getSummaryElement?:
|
|
40
|
-
| ((item: Record<string, unknown>) => React.ReactElement)
|
|
51
|
+
| ((values: SummaryItem, elementProps: SummaryItem) => React.ReactElement)
|
|
41
52
|
| undefined;
|
|
53
|
+
getSummaryElement?: ((item: SummaryItem) => React.ReactElement) | undefined;
|
|
42
54
|
};
|
|
43
55
|
|
|
44
56
|
type CapturedFormProps = {
|
|
@@ -63,58 +75,85 @@ jest.mock("../../../UI/Components/Forms/ModelForm", () => {
|
|
|
63
75
|
};
|
|
64
76
|
});
|
|
65
77
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
default: (props: { monitorIds: Array<unknown> }): React.ReactElement => {
|
|
76
|
-
return <div>{`${props.monitorIds.length} monitor ids`}</div>;
|
|
78
|
+
const getListMock: MockFunction = getJestMockFunction();
|
|
79
|
+
const getItemMock: MockFunction = getJestMockFunction();
|
|
80
|
+
|
|
81
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
82
|
+
return {
|
|
83
|
+
__esModule: true,
|
|
84
|
+
default: {
|
|
85
|
+
getList: (...args: Array<unknown>) => {
|
|
86
|
+
return getListMock(...args);
|
|
77
87
|
},
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
getItem: (...args: Array<unknown>) => {
|
|
89
|
+
return getItemMock(...args);
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
});
|
|
81
94
|
|
|
82
95
|
import AffectedResourcesPicker, {
|
|
83
96
|
AffectedResourcesPayload,
|
|
97
|
+
NAME_LOADING_PLACEHOLDER,
|
|
84
98
|
} from "../../../../App/FeatureSet/Dashboard/src/Components/AffectedResources/AffectedResourcesPicker";
|
|
85
99
|
import IncidentCreate from "../../../../App/FeatureSet/Dashboard/src/Pages/Incidents/Create";
|
|
86
100
|
import ScheduledMaintenanceCreate from "../../../../App/FeatureSet/Dashboard/src/Pages/ScheduledMaintenanceEvents/Create";
|
|
87
101
|
import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
|
|
102
|
+
import CephCluster from "../../../Models/DatabaseModels/CephCluster";
|
|
103
|
+
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
104
|
+
import DatabaseServer from "../../../Models/DatabaseModels/DatabaseServer";
|
|
105
|
+
import DockerHost from "../../../Models/DatabaseModels/DockerHost";
|
|
106
|
+
import DockerSwarmCluster from "../../../Models/DatabaseModels/DockerSwarmCluster";
|
|
107
|
+
import Host from "../../../Models/DatabaseModels/Host";
|
|
108
|
+
import IoTFleet from "../../../Models/DatabaseModels/IoTFleet";
|
|
109
|
+
import KubernetesCluster from "../../../Models/DatabaseModels/KubernetesCluster";
|
|
110
|
+
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
111
|
+
import NetworkSite from "../../../Models/DatabaseModels/NetworkSite";
|
|
112
|
+
import PodmanHost from "../../../Models/DatabaseModels/PodmanHost";
|
|
88
113
|
import Project from "../../../Models/DatabaseModels/Project";
|
|
114
|
+
import ProxmoxCluster from "../../../Models/DatabaseModels/ProxmoxCluster";
|
|
115
|
+
import Service from "../../../Models/DatabaseModels/Service";
|
|
116
|
+
import VMwareVCenter from "../../../Models/DatabaseModels/VMwareVCenter";
|
|
89
117
|
import Route from "../../../Types/API/Route";
|
|
118
|
+
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
90
119
|
import ObjectID from "../../../Types/ObjectID";
|
|
91
120
|
import Navigation from "../../../UI/Utils/Navigation";
|
|
92
121
|
import ProjectUtil from "../../../UI/Utils/Project";
|
|
122
|
+
import User from "../../../UI/Utils/User";
|
|
123
|
+
|
|
124
|
+
const PROJECT_ID: string = "3b1d7c2e-5f4a-4e6b-9c8d-7a6f5e4d3c2b";
|
|
93
125
|
|
|
94
126
|
type ResourceProp = Exclude<
|
|
95
127
|
keyof AffectedResourcesPayload,
|
|
96
128
|
"__affectedResourcesPayload"
|
|
97
129
|
>;
|
|
98
130
|
|
|
131
|
+
type ResourceModel = BaseModel & {
|
|
132
|
+
name?: string | undefined;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
type ResourceModelType = { new (): BaseModel };
|
|
136
|
+
|
|
99
137
|
/*
|
|
100
|
-
* Every relation the picker can write
|
|
101
|
-
* fields, so a resource type added to
|
|
102
|
-
* it is listed
|
|
138
|
+
* Every relation the picker can write, and the model its names are looked up
|
|
139
|
+
* in. A Record keyed by the payload's own fields, so a resource type added to
|
|
140
|
+
* the picker fails to compile here until it is listed - and the API below
|
|
141
|
+
* can tell whether a lookup went to the right model.
|
|
103
142
|
*/
|
|
104
|
-
const RESOURCE_PROPS: Record<ResourceProp,
|
|
105
|
-
monitors:
|
|
106
|
-
hosts:
|
|
107
|
-
kubernetesClusters:
|
|
108
|
-
dockerHosts:
|
|
109
|
-
podmanHosts:
|
|
110
|
-
proxmoxClusters:
|
|
111
|
-
vmwareVCenters:
|
|
112
|
-
cephClusters:
|
|
113
|
-
dockerSwarmClusters:
|
|
114
|
-
iotFleets:
|
|
115
|
-
databaseServers:
|
|
116
|
-
networkSites:
|
|
117
|
-
services:
|
|
143
|
+
const RESOURCE_PROPS: Record<ResourceProp, ResourceModelType> = {
|
|
144
|
+
monitors: Monitor,
|
|
145
|
+
hosts: Host,
|
|
146
|
+
kubernetesClusters: KubernetesCluster,
|
|
147
|
+
dockerHosts: DockerHost,
|
|
148
|
+
podmanHosts: PodmanHost,
|
|
149
|
+
proxmoxClusters: ProxmoxCluster,
|
|
150
|
+
vmwareVCenters: VMwareVCenter,
|
|
151
|
+
cephClusters: CephCluster,
|
|
152
|
+
dockerSwarmClusters: DockerSwarmCluster,
|
|
153
|
+
iotFleets: IoTFleet,
|
|
154
|
+
databaseServers: DatabaseServer,
|
|
155
|
+
networkSites: NetworkSite,
|
|
156
|
+
services: Service,
|
|
118
157
|
};
|
|
119
158
|
|
|
120
159
|
const RESOURCE_PROP_NAMES: Array<ResourceProp> = Object.keys(
|
|
@@ -123,16 +162,163 @@ const RESOURCE_PROP_NAMES: Array<ResourceProp> = Object.keys(
|
|
|
123
162
|
|
|
124
163
|
const NOTHING_SELECTED_PATTERN: RegExp = /No resources affected/;
|
|
125
164
|
|
|
165
|
+
type KnownResource = {
|
|
166
|
+
modelType: ResourceModelType;
|
|
167
|
+
name: string;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/*
|
|
171
|
+
* The resources the API knows, by id. Each is known under the one model type
|
|
172
|
+
* it was registered with: a lookup of that id against any other model finds
|
|
173
|
+
* nothing, as the server would.
|
|
174
|
+
*/
|
|
175
|
+
const KNOWN_RESOURCES: Map<string, KnownResource> = new Map<
|
|
176
|
+
string,
|
|
177
|
+
KnownResource
|
|
178
|
+
>();
|
|
179
|
+
|
|
180
|
+
/*
|
|
181
|
+
* Fresh ids of one resource type, each registered with the API under that
|
|
182
|
+
* type's model and a name saying which prop and which pick it is, so a name
|
|
183
|
+
* on screen can only have come from looking up that very id.
|
|
184
|
+
*/
|
|
185
|
+
function knownResources(prop: ResourceProp, count: number): Array<string> {
|
|
186
|
+
const ids: Array<string> = [];
|
|
187
|
+
|
|
188
|
+
for (let index: number = 0; index < count; index++) {
|
|
189
|
+
const id: string = ObjectID.generate().toString();
|
|
190
|
+
KNOWN_RESOURCES.set(id, {
|
|
191
|
+
modelType: RESOURCE_PROPS[prop],
|
|
192
|
+
name: `${prop} resource ${index + 1} ${id.slice(0, 8)}`,
|
|
193
|
+
});
|
|
194
|
+
ids.push(id);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return ids;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function nameOf(id: string): string {
|
|
201
|
+
const known: KnownResource | undefined = KNOWN_RESOURCES.get(id);
|
|
202
|
+
|
|
203
|
+
expect(known).toBeDefined();
|
|
204
|
+
|
|
205
|
+
return known!.name;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
type ListRequest = {
|
|
209
|
+
modelType: unknown;
|
|
210
|
+
query?: Record<string, unknown> | undefined;
|
|
211
|
+
select?: Record<string, unknown> | undefined;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
type ItemRequest = {
|
|
215
|
+
modelType: unknown;
|
|
216
|
+
id: ObjectID;
|
|
217
|
+
select?: Record<string, unknown> | undefined;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
type ListAnswer = {
|
|
221
|
+
data: Array<ResourceModel>;
|
|
222
|
+
count: number;
|
|
223
|
+
skip: number;
|
|
224
|
+
limit: number;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
function listOf(data: Array<ResourceModel>): ListAnswer {
|
|
228
|
+
return { data: data, count: data.length, skip: 0, limit: data.length };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function answerList(request: ListRequest): Promise<ListAnswer> {
|
|
232
|
+
const idQuery: unknown = request.query?.["_id"];
|
|
233
|
+
|
|
234
|
+
/*
|
|
235
|
+
* What the pages ask for on mount (the first incident state and the like)
|
|
236
|
+
* has nothing to do with these summaries: an empty list.
|
|
237
|
+
*/
|
|
238
|
+
if (!(idQuery instanceof Includes)) {
|
|
239
|
+
return listOf([]);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const found: Array<ResourceModel> = [];
|
|
243
|
+
|
|
244
|
+
for (const value of idQuery.values) {
|
|
245
|
+
const id: string = value.toString();
|
|
246
|
+
const known: KnownResource | undefined = KNOWN_RESOURCES.get(id);
|
|
247
|
+
|
|
248
|
+
if (!known || known.modelType !== request.modelType) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const model: ResourceModel = new known.modelType() as ResourceModel;
|
|
253
|
+
model._id = id;
|
|
254
|
+
model.name = known.name;
|
|
255
|
+
found.push(model);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return listOf(found);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
type Lookup = {
|
|
262
|
+
modelType: unknown;
|
|
263
|
+
ids: Array<string>;
|
|
264
|
+
select: unknown;
|
|
265
|
+
};
|
|
266
|
+
|
|
126
267
|
/*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
268
|
+
* Every request made since the page finished mounting, whichever of getItem
|
|
269
|
+
* or getList carried it, as the model type, ids and columns it asked for.
|
|
129
270
|
*/
|
|
130
|
-
|
|
271
|
+
function lookupsMade(): Array<Lookup> {
|
|
272
|
+
const itemLookups: Array<Lookup> = getItemMock.mock.calls.map(
|
|
273
|
+
(call: Array<unknown>): Lookup => {
|
|
274
|
+
const request: ItemRequest = call[0] as ItemRequest;
|
|
275
|
+
return {
|
|
276
|
+
modelType: request.modelType,
|
|
277
|
+
ids: [request.id.toString()],
|
|
278
|
+
select: request.select,
|
|
279
|
+
};
|
|
280
|
+
},
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
const listLookups: Array<Lookup> = getListMock.mock.calls.map(
|
|
284
|
+
(call: Array<unknown>): Lookup => {
|
|
285
|
+
const request: ListRequest = call[0] as ListRequest;
|
|
286
|
+
const idQuery: unknown = request.query?.["_id"];
|
|
287
|
+
const ids: Array<string> = [];
|
|
288
|
+
|
|
289
|
+
if (idQuery instanceof Includes) {
|
|
290
|
+
for (const value of idQuery.values) {
|
|
291
|
+
ids.push(value.toString());
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
modelType: request.modelType,
|
|
297
|
+
ids: ids,
|
|
298
|
+
select: request.select,
|
|
299
|
+
};
|
|
300
|
+
},
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
return [...itemLookups, ...listLookups];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// The lookup the picker should make for a batch of ids of one type.
|
|
307
|
+
function nameLookupOf(prop: ResourceProp, ids: Array<string>): Lookup {
|
|
308
|
+
return {
|
|
309
|
+
modelType: RESOURCE_PROPS[prop],
|
|
310
|
+
ids: ids,
|
|
311
|
+
select: expect.objectContaining({ _id: true, name: true }),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
131
314
|
|
|
132
315
|
type PageUnderTest = {
|
|
133
316
|
name: string;
|
|
134
317
|
component: React.FunctionComponent<PageComponentProps>;
|
|
135
318
|
route: string;
|
|
319
|
+
nothingSelectedMessage: string;
|
|
320
|
+
// Offered on this page, so the checks below are not vacuous.
|
|
321
|
+
mustOffer: Array<ResourceProp>;
|
|
136
322
|
};
|
|
137
323
|
|
|
138
324
|
const PAGES: Array<PageUnderTest> = [
|
|
@@ -140,27 +326,30 @@ const PAGES: Array<PageUnderTest> = [
|
|
|
140
326
|
name: "incident create",
|
|
141
327
|
component: IncidentCreate,
|
|
142
328
|
route: "/dashboard/incidents/create",
|
|
329
|
+
nothingSelectedMessage: "No resources affected by this incident.",
|
|
330
|
+
mustOffer: ["monitors", "podmanHosts", "databaseServers"],
|
|
143
331
|
},
|
|
144
332
|
{
|
|
145
333
|
name: "scheduled maintenance create",
|
|
146
334
|
component: ScheduledMaintenanceCreate,
|
|
147
335
|
route: "/dashboard/scheduled-maintenance-events/create",
|
|
336
|
+
nothingSelectedMessage:
|
|
337
|
+
"No resources affected by this scheduled maintenance event.",
|
|
338
|
+
mustOffer: ["monitors", "podmanHosts", "databaseServers", "networkSites"],
|
|
148
339
|
},
|
|
149
340
|
];
|
|
150
341
|
|
|
151
342
|
type PickerStep = {
|
|
152
343
|
resourceProps: Array<ResourceProp>;
|
|
153
|
-
getSummaryElement: (item:
|
|
344
|
+
getSummaryElement: (item: SummaryItem) => React.ReactElement;
|
|
154
345
|
};
|
|
155
346
|
|
|
156
347
|
async function openForm(page: PageUnderTest): Promise<CapturedFormProps> {
|
|
157
|
-
const project: Project = new Project();
|
|
158
|
-
|
|
159
348
|
render(
|
|
160
349
|
<MemoryRouter>
|
|
161
350
|
<page.component
|
|
162
351
|
pageRoute={new Route(page.route)}
|
|
163
|
-
currentProject={
|
|
352
|
+
currentProject={new Project()}
|
|
164
353
|
hasPaymentMethod={true}
|
|
165
354
|
/>
|
|
166
355
|
</MemoryRouter>,
|
|
@@ -170,12 +359,19 @@ async function openForm(page: PageUnderTest): Promise<CapturedFormProps> {
|
|
|
170
359
|
expect(capturedForm).not.toBeNull();
|
|
171
360
|
});
|
|
172
361
|
|
|
362
|
+
/*
|
|
363
|
+
* Whatever the page asks for on mount has been asked by now, so from here
|
|
364
|
+
* on every request is one a summary made.
|
|
365
|
+
*/
|
|
366
|
+
getListMock.mockClear();
|
|
367
|
+
getItemMock.mockClear();
|
|
368
|
+
|
|
173
369
|
return capturedForm!;
|
|
174
370
|
}
|
|
175
371
|
|
|
176
372
|
/** The fields whose editor is the picker and that render a summary. */
|
|
177
373
|
function findPickerSteps(form: CapturedFormProps): Array<PickerStep> {
|
|
178
|
-
const emptySelection:
|
|
374
|
+
const emptySelection: SummaryItem = {};
|
|
179
375
|
for (const prop of RESOURCE_PROP_NAMES) {
|
|
180
376
|
emptySelection[prop] = [];
|
|
181
377
|
}
|
|
@@ -211,27 +407,95 @@ function findPickerSteps(form: CapturedFormProps): Array<PickerStep> {
|
|
|
211
407
|
return steps;
|
|
212
408
|
}
|
|
213
409
|
|
|
214
|
-
function
|
|
410
|
+
async function openPickerStep(page: PageUnderTest): Promise<PickerStep> {
|
|
411
|
+
const steps: Array<PickerStep> = findPickerSteps(await openForm(page));
|
|
412
|
+
|
|
413
|
+
expect(steps).toHaveLength(1);
|
|
414
|
+
|
|
415
|
+
return steps[0]!;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function renderStepSummary(step: PickerStep, item: SummaryItem): RenderResult {
|
|
419
|
+
return render(<MemoryRouter>{step.getSummaryElement(item)}</MemoryRouter>);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/*
|
|
423
|
+
* Whether the summary is still waiting on a name: the picker shows a
|
|
424
|
+
* placeholder on a busy chip until the lookup for it lands.
|
|
425
|
+
*/
|
|
426
|
+
function isLookingUpNames(container: HTMLElement): boolean {
|
|
427
|
+
return (
|
|
428
|
+
container.querySelectorAll('[aria-busy="true"]').length > 0 ||
|
|
429
|
+
(container.textContent || "").includes(NAME_LOADING_PLACEHOLDER)
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Renders the step's summary and waits for every lookup it started to land.
|
|
434
|
+
async function renderSummary(
|
|
215
435
|
step: PickerStep,
|
|
216
|
-
item:
|
|
217
|
-
):
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
436
|
+
item: SummaryItem,
|
|
437
|
+
): Promise<RenderResult> {
|
|
438
|
+
const result: RenderResult = renderStepSummary(step, item);
|
|
439
|
+
|
|
440
|
+
await waitFor(() => {
|
|
441
|
+
expect(isLookingUpNames(result.container)).toBe(false);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
return result;
|
|
222
445
|
}
|
|
223
446
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
447
|
+
/*
|
|
448
|
+
* Waits for every name to show in the summary. Resolves false rather than
|
|
449
|
+
* throwing when one never does, so a check over every resource type can
|
|
450
|
+
* report all the types that fail at once.
|
|
451
|
+
*/
|
|
452
|
+
async function showsEveryName(
|
|
453
|
+
container: HTMLElement,
|
|
454
|
+
names: Array<string>,
|
|
455
|
+
): Promise<boolean> {
|
|
456
|
+
try {
|
|
457
|
+
await waitFor(() => {
|
|
458
|
+
for (const name of names) {
|
|
459
|
+
expect(within(container).getByText(name)).toBeInTheDocument();
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
return true;
|
|
463
|
+
} catch {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Lets anything a summary might have started get as far as the API.
|
|
469
|
+
async function nextTick(): Promise<void> {
|
|
470
|
+
await new Promise<void>((resolve: () => void) => {
|
|
471
|
+
setTimeout(resolve, 0);
|
|
227
472
|
});
|
|
228
473
|
}
|
|
229
474
|
|
|
230
475
|
describe("the Resources Affected summary step", () => {
|
|
231
476
|
beforeEach(() => {
|
|
232
477
|
capturedForm = null;
|
|
233
|
-
|
|
478
|
+
KNOWN_RESOURCES.clear();
|
|
479
|
+
getListMock.mockReset();
|
|
480
|
+
getItemMock.mockReset();
|
|
481
|
+
getListMock.mockImplementation(answerList as never);
|
|
482
|
+
// Nothing these pages or summaries show is fetched one item at a time.
|
|
483
|
+
getItemMock.mockResolvedValue(null as never);
|
|
484
|
+
|
|
485
|
+
jest
|
|
486
|
+
.spyOn(ProjectUtil, "getCurrentProjectId")
|
|
487
|
+
.mockReturnValue(new ObjectID(PROJECT_ID));
|
|
234
488
|
jest.spyOn(Navigation, "getQueryStringByName").mockReturnValue(null);
|
|
489
|
+
|
|
490
|
+
/*
|
|
491
|
+
* The picker shows only the types the viewer may read
|
|
492
|
+
* (filterTypesByReadPermission); a type it may not is left out, with a
|
|
493
|
+
* note that something is hidden. What these tests pin is which types the
|
|
494
|
+
* summary is handed, not permissions, so the viewer is one who may read
|
|
495
|
+
* every type - a master admin - rather than whatever the cookies and
|
|
496
|
+
* localStorage of the test environment happen to say.
|
|
497
|
+
*/
|
|
498
|
+
jest.spyOn(User, "isMasterAdmin").mockReturnValue(true);
|
|
235
499
|
});
|
|
236
500
|
|
|
237
501
|
afterEach(() => {
|
|
@@ -245,82 +509,205 @@ describe("the Resources Affected summary step", () => {
|
|
|
245
509
|
|
|
246
510
|
expect(steps).toHaveLength(1);
|
|
247
511
|
expect(steps[0]!.resourceProps).toEqual(
|
|
248
|
-
expect.arrayContaining(
|
|
512
|
+
expect.arrayContaining(page.mustOffer),
|
|
249
513
|
);
|
|
250
514
|
});
|
|
251
515
|
|
|
252
|
-
test("
|
|
253
|
-
const
|
|
516
|
+
test("names a selection of every resource type the picker offers", async () => {
|
|
517
|
+
const step: PickerStep = await openPickerStep(page);
|
|
254
518
|
const missing: Array<string> = [];
|
|
255
519
|
|
|
256
|
-
for (const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
520
|
+
for (const prop of step.resourceProps) {
|
|
521
|
+
const selected: Array<string> = knownResources(prop, 3);
|
|
522
|
+
const result: RenderResult = renderStepSummary(step, {
|
|
523
|
+
[prop]: selected,
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
const named: boolean = await showsEveryName(
|
|
527
|
+
result.container,
|
|
528
|
+
selected.map(nameOf),
|
|
529
|
+
);
|
|
530
|
+
const text: string = result.container.textContent || "";
|
|
531
|
+
|
|
532
|
+
if (
|
|
533
|
+
!named ||
|
|
534
|
+
NOTHING_SELECTED_PATTERN.test(text) ||
|
|
535
|
+
isLookingUpNames(result.container) ||
|
|
536
|
+
within(result.container).queryByTestId(
|
|
537
|
+
"affected-resources-hidden-note",
|
|
538
|
+
)
|
|
539
|
+
) {
|
|
540
|
+
missing.push(`${prop}: "${text}"`);
|
|
266
541
|
}
|
|
542
|
+
|
|
543
|
+
result.unmount();
|
|
267
544
|
}
|
|
268
545
|
|
|
269
546
|
expect(missing).toEqual([]);
|
|
270
547
|
});
|
|
271
548
|
|
|
272
|
-
test("
|
|
273
|
-
const
|
|
549
|
+
test("looks each type's names up in its own model, for exactly the ids selected", async () => {
|
|
550
|
+
const step: PickerStep = await openPickerStep(page);
|
|
551
|
+
const lookupsByProp: Record<string, Array<Lookup>> = {};
|
|
552
|
+
const expectedByProp: Record<string, Array<Lookup>> = {};
|
|
553
|
+
|
|
554
|
+
for (const prop of step.resourceProps) {
|
|
555
|
+
const selected: Array<string> = knownResources(prop, 3);
|
|
556
|
+
getListMock.mockClear();
|
|
557
|
+
getItemMock.mockClear();
|
|
558
|
+
|
|
559
|
+
const result: RenderResult = await renderSummary(step, {
|
|
560
|
+
[prop]: selected,
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
lookupsByProp[prop] = lookupsMade();
|
|
564
|
+
expectedByProp[prop] = [nameLookupOf(prop, selected)];
|
|
565
|
+
|
|
566
|
+
result.unmount();
|
|
567
|
+
}
|
|
274
568
|
|
|
275
|
-
expect(
|
|
569
|
+
expect(lookupsByProp).toEqual(expectedByProp);
|
|
276
570
|
});
|
|
277
571
|
|
|
278
|
-
test("names
|
|
279
|
-
const
|
|
572
|
+
test("names every resource of a mixed selection, each under its type", async () => {
|
|
573
|
+
const step: PickerStep = await openPickerStep(page);
|
|
574
|
+
|
|
575
|
+
const picks: Array<{ prop: ResourceProp; label: string; count: number }> =
|
|
576
|
+
[
|
|
577
|
+
{ prop: "monitors", label: "Monitor", count: 1 },
|
|
578
|
+
{ prop: "dockerHosts", label: "Docker Host", count: 2 },
|
|
579
|
+
{ prop: "podmanHosts", label: "Podman Host", count: 4 },
|
|
580
|
+
{ prop: "services", label: "Service", count: 1 },
|
|
581
|
+
{ prop: "databaseServers", label: "Database", count: 2 },
|
|
582
|
+
];
|
|
583
|
+
|
|
584
|
+
const item: SummaryItem = {};
|
|
585
|
+
const expectedLookups: Array<Lookup> = [];
|
|
586
|
+
|
|
587
|
+
for (const pick of picks) {
|
|
588
|
+
const selected: Array<string> = knownResources(pick.prop, pick.count);
|
|
589
|
+
item[pick.prop] = selected;
|
|
590
|
+
expectedLookups.push(nameLookupOf(pick.prop, selected));
|
|
591
|
+
}
|
|
280
592
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
593
|
+
const { container } = await renderSummary(step, item);
|
|
594
|
+
|
|
595
|
+
for (const pick of picks) {
|
|
596
|
+
for (const id of item[pick.prop] as Array<string>) {
|
|
597
|
+
const name: HTMLElement = within(container).getByText(nameOf(id));
|
|
598
|
+
|
|
599
|
+
// The chip reads "<Type label><name>".
|
|
600
|
+
expect(name.parentElement).toHaveTextContent(
|
|
601
|
+
`${pick.label}${nameOf(id)}`,
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
expect(container.textContent).not.toMatch(NOTHING_SELECTED_PATTERN);
|
|
607
|
+
|
|
608
|
+
// One lookup per type, however many of that type were picked.
|
|
609
|
+
expect(lookupsMade()).toHaveLength(expectedLookups.length);
|
|
610
|
+
expect(lookupsMade()).toEqual(expect.arrayContaining(expectedLookups));
|
|
290
611
|
});
|
|
291
612
|
|
|
292
|
-
test("
|
|
293
|
-
const
|
|
613
|
+
test("keeps its nothing-selected sentence and looks nothing up when nothing is selected", async () => {
|
|
614
|
+
const step: PickerStep = await openPickerStep(page);
|
|
294
615
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
616
|
+
const allEmpty: SummaryItem = {};
|
|
617
|
+
for (const prop of RESOURCE_PROP_NAMES) {
|
|
618
|
+
allEmpty[prop] = [];
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const emptySelections: Array<SummaryItem> = [{}, allEmpty];
|
|
622
|
+
|
|
623
|
+
for (const item of emptySelections) {
|
|
624
|
+
const result: RenderResult = await renderSummary(step, item);
|
|
625
|
+
|
|
626
|
+
await nextTick();
|
|
627
|
+
|
|
628
|
+
expect(result.container).toHaveTextContent(page.nothingSelectedMessage);
|
|
629
|
+
result.unmount();
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
expect(lookupsMade()).toEqual([]);
|
|
308
633
|
});
|
|
309
634
|
|
|
310
|
-
test("
|
|
311
|
-
const
|
|
635
|
+
test("reads a resource the API cannot find as unknown instead of dropping it", async () => {
|
|
636
|
+
const step: PickerStep = await openPickerStep(page);
|
|
637
|
+
|
|
638
|
+
const found: string = knownResources("podmanHosts", 1)[0]!;
|
|
639
|
+
// Deleted, or not readable: the lookup does not return it.
|
|
640
|
+
const gone: string = ObjectID.generate().toString();
|
|
641
|
+
|
|
642
|
+
const { container } = await renderSummary(step, {
|
|
643
|
+
podmanHosts: [found, gone],
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
expect(within(container).getByText(nameOf(found))).toBeInTheDocument();
|
|
647
|
+
expect(
|
|
648
|
+
within(container).getByText("Unknown Podman Host"),
|
|
649
|
+
).toBeInTheDocument();
|
|
650
|
+
expect(container.textContent).not.toMatch(NOTHING_SELECTED_PATTERN);
|
|
651
|
+
expect(lookupsMade()).toEqual([
|
|
652
|
+
nameLookupOf("podmanHosts", [found, gone]),
|
|
653
|
+
]);
|
|
654
|
+
});
|
|
312
655
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
656
|
+
test("names resources that arrive with their name without looking them up", async () => {
|
|
657
|
+
/*
|
|
658
|
+
* The alert prefill and incident templates hand the form resources as
|
|
659
|
+
* { _id, name } objects rather than bare ids. Those are named as they
|
|
660
|
+
* come; only a bare id among them is looked up.
|
|
661
|
+
*/
|
|
662
|
+
const step: PickerStep = await openPickerStep(page);
|
|
663
|
+
|
|
664
|
+
const namedMonitor: { _id: string; name: string } = {
|
|
665
|
+
_id: ObjectID.generate().toString(),
|
|
666
|
+
name: "Checkout API monitor",
|
|
667
|
+
};
|
|
668
|
+
const namedPodmanHost: { _id: string; name: string } = {
|
|
669
|
+
_id: ObjectID.generate().toString(),
|
|
670
|
+
name: "podman-edge-01",
|
|
671
|
+
};
|
|
672
|
+
const bareId: string = knownResources("podmanHosts", 1)[0]!;
|
|
673
|
+
|
|
674
|
+
const { container } = await renderSummary(step, {
|
|
675
|
+
monitors: [namedMonitor],
|
|
676
|
+
podmanHosts: [namedPodmanHost, bareId],
|
|
318
677
|
});
|
|
319
678
|
|
|
320
|
-
expect(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
expect(
|
|
679
|
+
expect(
|
|
680
|
+
within(container).getByText(namedMonitor.name),
|
|
681
|
+
).toBeInTheDocument();
|
|
682
|
+
expect(
|
|
683
|
+
within(container).getByText(namedPodmanHost.name),
|
|
684
|
+
).toBeInTheDocument();
|
|
685
|
+
expect(within(container).getByText(nameOf(bareId))).toBeInTheDocument();
|
|
686
|
+
expect(lookupsMade()).toEqual([nameLookupOf("podmanHosts", [bareId])]);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
test("is read-only: no search input and no remove buttons", async () => {
|
|
690
|
+
const step: PickerStep = await openPickerStep(page);
|
|
691
|
+
|
|
692
|
+
const monitors: Array<string> = knownResources("monitors", 1);
|
|
693
|
+
const podmanHosts: Array<string> = knownResources("podmanHosts", 2);
|
|
694
|
+
|
|
695
|
+
const { container } = await renderSummary(step, {
|
|
696
|
+
monitors: monitors,
|
|
697
|
+
podmanHosts: podmanHosts,
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// Not vacuous: the chips are there.
|
|
701
|
+
for (const id of [...monitors, ...podmanHosts]) {
|
|
702
|
+
expect(within(container).getByText(nameOf(id))).toBeInTheDocument();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
expect(container.querySelectorAll("input")).toHaveLength(0);
|
|
706
|
+
expect(within(container).queryAllByRole("textbox")).toHaveLength(0);
|
|
707
|
+
expect(within(container).queryAllByRole("combobox")).toHaveLength(0);
|
|
708
|
+
expect(
|
|
709
|
+
within(container).queryAllByRole("button", { name: /^Remove\b/ }),
|
|
710
|
+
).toHaveLength(0);
|
|
324
711
|
});
|
|
325
712
|
});
|
|
326
713
|
});
|