@oneuptime/common 12.0.34 → 13.0.0
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/GlobalConfig.ts +12 -12
- package/Models/DatabaseModels/InventoryItem.ts +44 -1
- package/Models/DatabaseModels/NetworkDevice.ts +126 -0
- package/Server/EnvironmentConfig.ts +47 -16
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Infrastructure/Redis.ts +8 -8
- package/Server/Infrastructure/Status.ts +10 -4
- package/Server/Services/DatabaseService.ts +65 -13
- package/Server/Services/InventoryItemService.ts +31 -1
- package/Server/Services/MonitorTemplateService.ts +140 -19
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
- package/Server/Services/NetworkDeviceService.ts +69 -0
- package/Server/Services/UserNotificationRuleService.ts +76 -25
- package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
- package/Server/Types/Database/QueryUtil.ts +56 -0
- package/Server/Types/Markdown.ts +93 -1
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
- package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
- package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
- package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
- package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
- package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
- package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
- package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
- package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
- package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
- package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
- package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
- package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
- package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
- package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
- package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
- package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
- package/Tests/Models/InventoryItemStatus.test.ts +147 -0
- package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
- package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
- package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
- package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
- package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
- package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
- package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
- package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
- package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
- package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
- package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
- package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
- package/Tests/Server/Types/Markdown.test.ts +194 -1
- package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
- package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
- package/Tests/Server/Utils/EventLoop.test.ts +359 -0
- package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
- package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
- package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
- package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
- package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
- package/Tests/Types/JSONFunctions.test.ts +40 -0
- package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
- package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
- package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
- package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
- package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
- package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
- package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
- package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
- package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
- package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
- package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
- package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
- package/Types/Dashboard/DashboardVariable.ts +8 -0
- package/Types/JSON.ts +4 -0
- package/Types/JSONFunctions.ts +16 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
- package/Types/SerializableObjectDictionary.ts +4 -0
- package/Types/Telemetry/InventoryLiveness.ts +56 -0
- package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
- package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
- package/UI/esbuild-config.js +6 -0
- package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
- package/Utils/Dashboard/LabelVariable.ts +152 -0
- package/Utils/Dashboard/VariableUrlState.ts +27 -5
- package/Utils/MetricUnitUtil.ts +34 -0
- package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
- package/Utils/Monitor/MetricValueFormatter.ts +342 -0
- package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
- package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
- package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +44 -12
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.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/Infrastructure/Redis.js +8 -8
- package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
- package/build/dist/Server/Infrastructure/Status.js +10 -4
- package/build/dist/Server/Infrastructure/Status.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +58 -13
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +28 -1
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +75 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
- package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
- package/build/dist/Types/JSON.js +1 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +13 -0
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +4 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
- package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
- package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
- package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +26 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/test-setup.sh +9 -9
- package/tsconfig.json +51 -11
- package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
3
|
+
import {
|
|
4
|
+
act,
|
|
5
|
+
cleanup,
|
|
6
|
+
fireEvent,
|
|
7
|
+
render,
|
|
8
|
+
screen,
|
|
9
|
+
waitFor,
|
|
10
|
+
} from "@testing-library/react";
|
|
11
|
+
import React from "react";
|
|
12
|
+
import { MemoryRouter } from "react-router-dom";
|
|
13
|
+
import DiscoveryPage from "../../../../App/FeatureSet/Dashboard/src/Pages/NetworkDevice/Discovery";
|
|
14
|
+
import ProbeUtil from "../../../../App/FeatureSet/Dashboard/src/Utils/Probe";
|
|
15
|
+
import NetworkDeviceDiscoveryScan, {
|
|
16
|
+
DiscoveredNetworkDevice,
|
|
17
|
+
} from "../../../Models/DatabaseModels/NetworkDeviceDiscoveryScan";
|
|
18
|
+
import NetworkDevice from "../../../Models/DatabaseModels/NetworkDevice";
|
|
19
|
+
import Project from "../../../Models/DatabaseModels/Project";
|
|
20
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
21
|
+
import Route from "../../../Types/API/Route";
|
|
22
|
+
import Permission from "../../../Types/Permission";
|
|
23
|
+
import { VoidFunction } from "../../../Types/FunctionTypes";
|
|
24
|
+
import ModelAPI from "../../../UI/Utils/ModelAPI/ModelAPI";
|
|
25
|
+
import ProjectUtil from "../../../UI/Utils/Project";
|
|
26
|
+
import PermissionUtil from "../../../UI/Utils/Permission";
|
|
27
|
+
import { ComponentProps as ModalProps } from "../../../UI/Components/Modal/Modal";
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* Keep the page's review, selection and import handlers real. The table mock
|
|
31
|
+
* exposes its actual row action; the modal mock removes animation and portals.
|
|
32
|
+
*/
|
|
33
|
+
interface TableProps {
|
|
34
|
+
actionButtons: Array<{
|
|
35
|
+
title: string;
|
|
36
|
+
onClick: (
|
|
37
|
+
scan: NetworkDeviceDiscoveryScan,
|
|
38
|
+
onComplete: VoidFunction,
|
|
39
|
+
) => Promise<void>;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let capturedTable: TableProps | null = null;
|
|
44
|
+
let capturedModal: ModalProps | null = null;
|
|
45
|
+
|
|
46
|
+
jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
|
|
47
|
+
return {
|
|
48
|
+
__esModule: true,
|
|
49
|
+
default: (props: TableProps) => {
|
|
50
|
+
capturedTable = props;
|
|
51
|
+
return null;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
jest.mock("../../../UI/Components/Modal/Modal", () => {
|
|
57
|
+
return {
|
|
58
|
+
__esModule: true,
|
|
59
|
+
ModalWidth: { Medium: 1 },
|
|
60
|
+
default: (props: ModalProps) => {
|
|
61
|
+
capturedModal = props;
|
|
62
|
+
return (
|
|
63
|
+
<div role="dialog" aria-label={props.title}>
|
|
64
|
+
<p>{props.description}</p>
|
|
65
|
+
{props.error && <p role="alert">{props.error}</p>}
|
|
66
|
+
{props.isBodyLoading ? <p>Refreshing inventory</p> : props.children}
|
|
67
|
+
<button onClick={props.onClose}>Close review</button>
|
|
68
|
+
<button
|
|
69
|
+
disabled={props.disableSubmitButton || props.isLoading}
|
|
70
|
+
onClick={props.onSubmit}
|
|
71
|
+
>
|
|
72
|
+
{props.submitButtonText}
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
81
|
+
"11111111-1111-4111-8111-111111111111",
|
|
82
|
+
);
|
|
83
|
+
const SCAN_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
|
|
84
|
+
const OTHER_SCAN_ID: ObjectID = new ObjectID(
|
|
85
|
+
"33333333-3333-4333-8333-333333333333",
|
|
86
|
+
);
|
|
87
|
+
const PROBE_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
88
|
+
|
|
89
|
+
let getItemSpy: jest.SpyInstance;
|
|
90
|
+
let createSpy: jest.SpyInstance;
|
|
91
|
+
|
|
92
|
+
interface Deferred<T> {
|
|
93
|
+
promise: Promise<T>;
|
|
94
|
+
resolve: (value: T) => void;
|
|
95
|
+
reject: (error: Error) => void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function deferred<T>(): Deferred<T> {
|
|
99
|
+
let resolve: (value: T) => void = () => {};
|
|
100
|
+
let reject: (error: Error) => void = () => {};
|
|
101
|
+
const promise: Promise<T> = new Promise<T>(
|
|
102
|
+
(
|
|
103
|
+
resolvePromise: (value: T | PromiseLike<T>) => void,
|
|
104
|
+
rejectPromise: (reason?: unknown) => void,
|
|
105
|
+
) => {
|
|
106
|
+
resolve = resolvePromise;
|
|
107
|
+
reject = rejectPromise;
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
return { promise, resolve, reject };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function host(
|
|
114
|
+
ipAddress: string,
|
|
115
|
+
isAlreadyRegistered: boolean,
|
|
116
|
+
): DiscoveredNetworkDevice {
|
|
117
|
+
return { ipAddress, isAlreadyRegistered, snmpReachable: false };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function scan(
|
|
121
|
+
hosts: Array<DiscoveredNetworkDevice>,
|
|
122
|
+
id: ObjectID = SCAN_ID,
|
|
123
|
+
): NetworkDeviceDiscoveryScan {
|
|
124
|
+
const value: NetworkDeviceDiscoveryScan = new NetworkDeviceDiscoveryScan();
|
|
125
|
+
value.id = id;
|
|
126
|
+
value.projectId = PROJECT_ID;
|
|
127
|
+
value.probeId = PROBE_ID;
|
|
128
|
+
value.name =
|
|
129
|
+
id.toString() === SCAN_ID.toString() ? "Primary scan" : "Other scan";
|
|
130
|
+
value.cidr = "10.0.0.0/24";
|
|
131
|
+
value.status = "Completed";
|
|
132
|
+
value.discoveredDevices = hosts;
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function renderPage(): Promise<void> {
|
|
137
|
+
const project: Project = new Project();
|
|
138
|
+
project.id = PROJECT_ID;
|
|
139
|
+
render(
|
|
140
|
+
<MemoryRouter>
|
|
141
|
+
<DiscoveryPage
|
|
142
|
+
pageRoute={new Route("/dashboard/network-devices/discovery")}
|
|
143
|
+
currentProject={project}
|
|
144
|
+
hasPaymentMethod={true}
|
|
145
|
+
/>
|
|
146
|
+
</MemoryRouter>,
|
|
147
|
+
);
|
|
148
|
+
await waitFor(() => {
|
|
149
|
+
expect(capturedTable).not.toBeNull();
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function reviewAction(): TableProps["actionButtons"][number] {
|
|
154
|
+
const action: TableProps["actionButtons"][number] | undefined =
|
|
155
|
+
capturedTable?.actionButtons.find(
|
|
156
|
+
(button: TableProps["actionButtons"][number]) => {
|
|
157
|
+
return button.title === "Review Results";
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
if (!action) {
|
|
161
|
+
throw new Error("Review Results action was not rendered");
|
|
162
|
+
}
|
|
163
|
+
return action;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function openReview(value: NetworkDeviceDiscoveryScan): Promise<void> {
|
|
167
|
+
await act(async () => {
|
|
168
|
+
await reviewAction().onClick(value, () => {});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function checkbox(ipAddress: string): HTMLElement {
|
|
173
|
+
return screen.getByTestId(`discovered-device-checkbox-${ipAddress}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function submit(): HTMLElement {
|
|
177
|
+
return screen.getByRole("button", { name: /Import Selected/ });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function importSelected(): Promise<void> {
|
|
181
|
+
fireEvent.click(submit());
|
|
182
|
+
await waitFor(() => {
|
|
183
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
beforeEach(() => {
|
|
188
|
+
capturedTable = null;
|
|
189
|
+
capturedModal = null;
|
|
190
|
+
jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
|
|
191
|
+
jest.spyOn(ProbeUtil, "getAllProbes").mockResolvedValue([]);
|
|
192
|
+
jest
|
|
193
|
+
.spyOn(PermissionUtil, "getAllPermissions")
|
|
194
|
+
.mockReturnValue([Permission.ProjectAdmin]);
|
|
195
|
+
getItemSpy = jest.spyOn(ModelAPI, "getItem");
|
|
196
|
+
createSpy = jest
|
|
197
|
+
.spyOn(ModelAPI, "create")
|
|
198
|
+
.mockResolvedValue(undefined as never);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
afterEach(() => {
|
|
202
|
+
cleanup();
|
|
203
|
+
jest.restoreAllMocks();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe("Discovery review refreshes registration from current inventory", () => {
|
|
207
|
+
test("a deleted device becomes selectable, while a newly registered device cannot be imported", async () => {
|
|
208
|
+
const oldScan: NetworkDeviceDiscoveryScan = scan([
|
|
209
|
+
host("10.0.0.1", true),
|
|
210
|
+
host("10.0.0.2", false),
|
|
211
|
+
]);
|
|
212
|
+
getItemSpy.mockResolvedValue(
|
|
213
|
+
scan([host("10.0.0.1", false), host("10.0.0.2", true)]),
|
|
214
|
+
);
|
|
215
|
+
await renderPage();
|
|
216
|
+
await openReview(oldScan);
|
|
217
|
+
|
|
218
|
+
expect(checkbox("10.0.0.1")).toBeEnabled();
|
|
219
|
+
expect(checkbox("10.0.0.1")).toBeChecked();
|
|
220
|
+
expect(checkbox("10.0.0.2")).toBeDisabled();
|
|
221
|
+
expect(checkbox("10.0.0.2")).not.toBeChecked();
|
|
222
|
+
expect(submit()).toHaveTextContent("Import Selected (1)");
|
|
223
|
+
|
|
224
|
+
await importSelected();
|
|
225
|
+
|
|
226
|
+
expect(createSpy).toHaveBeenCalledTimes(1);
|
|
227
|
+
expect(createSpy.mock.calls[0]![0].model.hostname).toBe("10.0.0.1");
|
|
228
|
+
expect(createSpy.mock.calls[0]![0].modelType).toBe(NetworkDevice);
|
|
229
|
+
expect(oldScan.discoveredDevices![0]!.isAlreadyRegistered).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("requests the scan identity, current results, probe and all import credentials", async () => {
|
|
233
|
+
const freshScan: NetworkDeviceDiscoveryScan = scan([
|
|
234
|
+
{ ...host("10.0.0.1", false), snmpReachable: true },
|
|
235
|
+
]);
|
|
236
|
+
freshScan.snmpCommunityString = "refreshed-community";
|
|
237
|
+
freshScan.snmpVersion = "2c";
|
|
238
|
+
getItemSpy.mockResolvedValue(freshScan);
|
|
239
|
+
await renderPage();
|
|
240
|
+
await openReview(scan([host("10.0.0.1", true)]));
|
|
241
|
+
|
|
242
|
+
expect(getItemSpy).toHaveBeenCalledTimes(1);
|
|
243
|
+
expect(getItemSpy.mock.calls[0]![0]).toEqual({
|
|
244
|
+
modelType: NetworkDeviceDiscoveryScan,
|
|
245
|
+
id: SCAN_ID,
|
|
246
|
+
select: {
|
|
247
|
+
_id: true,
|
|
248
|
+
projectId: true,
|
|
249
|
+
name: true,
|
|
250
|
+
cidr: true,
|
|
251
|
+
status: true,
|
|
252
|
+
statusMessage: true,
|
|
253
|
+
discoveredDevices: true,
|
|
254
|
+
probeId: true,
|
|
255
|
+
isSnmpEnabled: true,
|
|
256
|
+
snmpConfigs: true,
|
|
257
|
+
snmpVersion: true,
|
|
258
|
+
snmpCommunityString: true,
|
|
259
|
+
snmpPort: true,
|
|
260
|
+
snmpV3SecurityLevel: true,
|
|
261
|
+
snmpV3Username: true,
|
|
262
|
+
snmpV3AuthProtocol: true,
|
|
263
|
+
snmpV3AuthKey: true,
|
|
264
|
+
snmpV3PrivProtocol: true,
|
|
265
|
+
snmpV3PrivKey: true,
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
await importSelected();
|
|
269
|
+
const device: NetworkDevice = createSpy.mock.calls[0]![0].model;
|
|
270
|
+
expect(device.probeId?.toString()).toBe(PROBE_ID.toString());
|
|
271
|
+
expect(device.snmpCommunityString).toBe("refreshed-community");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("reopening on the same page allows reimport after deleting a device imported during this visit", async () => {
|
|
275
|
+
const oldScan: NetworkDeviceDiscoveryScan = scan([host("10.0.0.1", false)]);
|
|
276
|
+
getItemSpy.mockResolvedValue(scan([host("10.0.0.1", false)]));
|
|
277
|
+
await renderPage();
|
|
278
|
+
await openReview(oldScan);
|
|
279
|
+
await importSelected();
|
|
280
|
+
|
|
281
|
+
/*
|
|
282
|
+
* The inventory no longer contains the device; the page still remembers
|
|
283
|
+
* importing it, but a successful new read supersedes that old record.
|
|
284
|
+
*/
|
|
285
|
+
await openReview(oldScan);
|
|
286
|
+
expect(checkbox("10.0.0.1")).toBeEnabled();
|
|
287
|
+
expect(checkbox("10.0.0.1")).toBeChecked();
|
|
288
|
+
await importSelected();
|
|
289
|
+
expect(createSpy).toHaveBeenCalledTimes(2);
|
|
290
|
+
expect(getItemSpy).toHaveBeenCalledTimes(2);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("reopening keeps an existing device disabled even when its monitor was removed", async () => {
|
|
294
|
+
const oldScan: NetworkDeviceDiscoveryScan = scan([host("10.0.0.1", false)]);
|
|
295
|
+
getItemSpy.mockResolvedValueOnce(oldScan);
|
|
296
|
+
await renderPage();
|
|
297
|
+
await openReview(oldScan);
|
|
298
|
+
await importSelected();
|
|
299
|
+
|
|
300
|
+
getItemSpy.mockResolvedValue(scan([host("10.0.0.1", true)]));
|
|
301
|
+
await openReview(oldScan);
|
|
302
|
+
|
|
303
|
+
expect(checkbox("10.0.0.1")).toBeDisabled();
|
|
304
|
+
expect(screen.getByText("Already added")).toBeInTheDocument();
|
|
305
|
+
expect(submit()).toBeDisabled();
|
|
306
|
+
fireEvent.click(submit());
|
|
307
|
+
expect(createSpy).toHaveBeenCalledTimes(1);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("keeps a successful batch retired while allowing the failed host to be retried", async () => {
|
|
311
|
+
getItemSpy.mockResolvedValue(
|
|
312
|
+
scan([host("10.0.0.1", false), host("10.0.0.2", false)]),
|
|
313
|
+
);
|
|
314
|
+
createSpy
|
|
315
|
+
.mockResolvedValueOnce(undefined)
|
|
316
|
+
.mockRejectedValueOnce(new Error("Device creation failed"))
|
|
317
|
+
.mockRejectedValueOnce(new Error("Device creation failed"));
|
|
318
|
+
await renderPage();
|
|
319
|
+
await openReview(scan([]));
|
|
320
|
+
fireEvent.click(submit());
|
|
321
|
+
await waitFor(() => {
|
|
322
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
323
|
+
"Device creation failed",
|
|
324
|
+
);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
expect(checkbox("10.0.0.1")).toBeDisabled();
|
|
328
|
+
expect(checkbox("10.0.0.2")).toBeEnabled();
|
|
329
|
+
expect(checkbox("10.0.0.2")).toBeChecked();
|
|
330
|
+
expect(submit()).toHaveTextContent("Import Selected (1)");
|
|
331
|
+
await importSelected();
|
|
332
|
+
expect(createSpy).toHaveBeenCalledTimes(4);
|
|
333
|
+
expect(createSpy.mock.calls[3]![0].model.hostname).toBe("10.0.0.2");
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe("Discovery review waits for a successful current response", () => {
|
|
338
|
+
test("does not expose stale selection or import while the inventory read is pending", async () => {
|
|
339
|
+
const pending: Deferred<NetworkDeviceDiscoveryScan | null> = deferred();
|
|
340
|
+
getItemSpy.mockReturnValue(pending.promise);
|
|
341
|
+
await renderPage();
|
|
342
|
+
let opening: Promise<void> = Promise.resolve();
|
|
343
|
+
await act(async () => {
|
|
344
|
+
opening = reviewAction().onClick(
|
|
345
|
+
scan([host("10.0.0.1", false)]),
|
|
346
|
+
() => {},
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
expect(screen.getByText("Refreshing inventory")).toBeInTheDocument();
|
|
351
|
+
expect(submit()).toBeDisabled();
|
|
352
|
+
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
|
|
353
|
+
await act(async () => {
|
|
354
|
+
capturedModal?.onSubmit?.();
|
|
355
|
+
});
|
|
356
|
+
expect(createSpy).not.toHaveBeenCalled();
|
|
357
|
+
|
|
358
|
+
await act(async () => {
|
|
359
|
+
pending.resolve(scan([host("10.0.0.1", true)]));
|
|
360
|
+
await opening;
|
|
361
|
+
});
|
|
362
|
+
expect(checkbox("10.0.0.1")).toBeDisabled();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("a failed refresh cannot fall back to a stale importable row and can be retried by reopening", async () => {
|
|
366
|
+
getItemSpy.mockRejectedValueOnce(new Error("Inventory refresh failed"));
|
|
367
|
+
await renderPage();
|
|
368
|
+
const oldScan: NetworkDeviceDiscoveryScan = scan([host("10.0.0.1", false)]);
|
|
369
|
+
await openReview(oldScan);
|
|
370
|
+
|
|
371
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
372
|
+
"Inventory refresh failed",
|
|
373
|
+
);
|
|
374
|
+
expect(submit()).toBeDisabled();
|
|
375
|
+
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
|
|
376
|
+
await act(async () => {
|
|
377
|
+
capturedModal?.onSubmit?.();
|
|
378
|
+
});
|
|
379
|
+
expect(createSpy).not.toHaveBeenCalled();
|
|
380
|
+
|
|
381
|
+
fireEvent.click(screen.getByText("Close review"));
|
|
382
|
+
getItemSpy.mockResolvedValue(scan([host("10.0.0.1", false)]));
|
|
383
|
+
await openReview(oldScan);
|
|
384
|
+
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
|
385
|
+
expect(checkbox("10.0.0.1")).toBeChecked();
|
|
386
|
+
expect(submit()).toBeEnabled();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("a scan deleted since the table loaded leaves import disabled", async () => {
|
|
390
|
+
getItemSpy.mockResolvedValue(null);
|
|
391
|
+
await renderPage();
|
|
392
|
+
await openReview(scan([host("10.0.0.1", false)]));
|
|
393
|
+
|
|
394
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
395
|
+
"This discovery scan could not be found.",
|
|
396
|
+
);
|
|
397
|
+
expect(submit()).toBeDisabled();
|
|
398
|
+
expect(createSpy).not.toHaveBeenCalled();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("closing during a pending read does not reopen the dialog when the read completes", async () => {
|
|
402
|
+
const pending: Deferred<NetworkDeviceDiscoveryScan | null> = deferred();
|
|
403
|
+
getItemSpy.mockReturnValue(pending.promise);
|
|
404
|
+
const completed: jest.Mock = jest.fn();
|
|
405
|
+
await renderPage();
|
|
406
|
+
let opening: Promise<void> = Promise.resolve();
|
|
407
|
+
await act(async () => {
|
|
408
|
+
opening = reviewAction().onClick(scan([]), completed);
|
|
409
|
+
});
|
|
410
|
+
fireEvent.click(screen.getByText("Close review"));
|
|
411
|
+
|
|
412
|
+
await act(async () => {
|
|
413
|
+
pending.resolve(scan([host("10.0.0.1", false)]));
|
|
414
|
+
await opening;
|
|
415
|
+
});
|
|
416
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
417
|
+
expect(completed).toHaveBeenCalledTimes(1);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test.each([false, true])(
|
|
421
|
+
"an older response cannot replace a newer review (same scan: %s)",
|
|
422
|
+
async (sameScan: boolean) => {
|
|
423
|
+
const older: Deferred<NetworkDeviceDiscoveryScan | null> = deferred();
|
|
424
|
+
getItemSpy.mockReturnValueOnce(older.promise);
|
|
425
|
+
getItemSpy.mockResolvedValueOnce(
|
|
426
|
+
scan([host("10.0.0.2", true)], sameScan ? SCAN_ID : OTHER_SCAN_ID),
|
|
427
|
+
);
|
|
428
|
+
await renderPage();
|
|
429
|
+
let opening: Promise<void> = Promise.resolve();
|
|
430
|
+
await act(async () => {
|
|
431
|
+
opening = reviewAction().onClick(scan([]), () => {});
|
|
432
|
+
});
|
|
433
|
+
await openReview(scan([], sameScan ? SCAN_ID : OTHER_SCAN_ID));
|
|
434
|
+
|
|
435
|
+
await act(async () => {
|
|
436
|
+
older.resolve(scan([host("10.0.0.1", false)]));
|
|
437
|
+
await opening;
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
expect(
|
|
441
|
+
screen.queryByTestId("discovered-device-checkbox-10.0.0.1"),
|
|
442
|
+
).not.toBeInTheDocument();
|
|
443
|
+
expect(checkbox("10.0.0.2")).toBeDisabled();
|
|
444
|
+
expect(submit()).toBeDisabled();
|
|
445
|
+
},
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
test("an older failure cannot show an error on the current review", async () => {
|
|
449
|
+
const older: Deferred<NetworkDeviceDiscoveryScan | null> = deferred();
|
|
450
|
+
getItemSpy.mockReturnValueOnce(older.promise);
|
|
451
|
+
getItemSpy.mockResolvedValueOnce(
|
|
452
|
+
scan([host("10.0.0.2", false)], OTHER_SCAN_ID),
|
|
453
|
+
);
|
|
454
|
+
await renderPage();
|
|
455
|
+
let opening: Promise<void> = Promise.resolve();
|
|
456
|
+
await act(async () => {
|
|
457
|
+
opening = reviewAction().onClick(scan([]), () => {});
|
|
458
|
+
});
|
|
459
|
+
await openReview(scan([], OTHER_SCAN_ID));
|
|
460
|
+
await act(async () => {
|
|
461
|
+
older.reject(new Error("Old request failed"));
|
|
462
|
+
await opening;
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
|
466
|
+
expect(checkbox("10.0.0.2")).toBeChecked();
|
|
467
|
+
expect(submit()).toBeEnabled();
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test("an import finishing during a reopened review's read remains retired even if the read predates it", async () => {
|
|
471
|
+
const importPending: Deferred<undefined> = deferred();
|
|
472
|
+
const refreshPending: Deferred<NetworkDeviceDiscoveryScan | null> =
|
|
473
|
+
deferred();
|
|
474
|
+
const oldScan: NetworkDeviceDiscoveryScan = scan([host("10.0.0.1", false)]);
|
|
475
|
+
getItemSpy
|
|
476
|
+
.mockResolvedValueOnce(oldScan)
|
|
477
|
+
.mockReturnValueOnce(refreshPending.promise);
|
|
478
|
+
createSpy.mockReturnValueOnce(importPending.promise);
|
|
479
|
+
await renderPage();
|
|
480
|
+
await openReview(oldScan);
|
|
481
|
+
fireEvent.click(submit());
|
|
482
|
+
await waitFor(() => {
|
|
483
|
+
expect(createSpy).toHaveBeenCalledTimes(1);
|
|
484
|
+
});
|
|
485
|
+
fireEvent.click(screen.getByText("Close review"));
|
|
486
|
+
let opening: Promise<void> = Promise.resolve();
|
|
487
|
+
await act(async () => {
|
|
488
|
+
opening = reviewAction().onClick(oldScan, () => {});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
await act(async () => {
|
|
492
|
+
importPending.resolve(undefined);
|
|
493
|
+
});
|
|
494
|
+
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
|
495
|
+
expect(screen.getByText("Refreshing inventory")).toBeInTheDocument();
|
|
496
|
+
|
|
497
|
+
await act(async () => {
|
|
498
|
+
refreshPending.resolve(scan([host("10.0.0.1", false)]));
|
|
499
|
+
await opening;
|
|
500
|
+
});
|
|
501
|
+
expect(checkbox("10.0.0.1")).toBeDisabled();
|
|
502
|
+
expect(submit()).toBeDisabled();
|
|
503
|
+
expect(createSpy).toHaveBeenCalledTimes(1);
|
|
504
|
+
});
|
|
505
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
3
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import InventoryItem from "../../../Models/DatabaseModels/InventoryItem";
|
|
6
|
+
import InventoryItemRelationship from "../../../Models/DatabaseModels/InventoryItemRelationship";
|
|
7
|
+
import EntityType from "../../../Types/Telemetry/EntityType";
|
|
8
|
+
import EntityRelationshipType from "../../../Types/Telemetry/EntityRelationshipType";
|
|
9
|
+
import EntityDetailPanel from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/EntityDetailPanel";
|
|
10
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
11
|
+
|
|
12
|
+
jest.mock("../../../UI/Utils/Translation", () => {
|
|
13
|
+
return {
|
|
14
|
+
__esModule: true,
|
|
15
|
+
default: () => {
|
|
16
|
+
return {
|
|
17
|
+
translateString: (value: string) => {
|
|
18
|
+
return value;
|
|
19
|
+
},
|
|
20
|
+
translateValue: (value: React.ReactNode) => {
|
|
21
|
+
return value;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
jest.mock("../../../UI/Components/SideOver/SideOver", () => {
|
|
28
|
+
return {
|
|
29
|
+
__esModule: true,
|
|
30
|
+
SideOverSize: { Small: "small" },
|
|
31
|
+
default: (props: { title: string; children: React.ReactNode }) => {
|
|
32
|
+
return <section aria-label={props.title}>{props.children}</section>;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function entity(key: string, label: string): InventoryItem {
|
|
38
|
+
return {
|
|
39
|
+
entityKey: key,
|
|
40
|
+
displayName: label,
|
|
41
|
+
entityType: EntityType.Container,
|
|
42
|
+
} as InventoryItem;
|
|
43
|
+
}
|
|
44
|
+
const API: InventoryItem = entity("api", "Checkout API");
|
|
45
|
+
const DATABASE: InventoryItem = entity("db", "Orders database");
|
|
46
|
+
const WEB: InventoryItem = entity("web", "Web frontend");
|
|
47
|
+
const RELATIONSHIPS: Array<InventoryItemRelationship> = [
|
|
48
|
+
{
|
|
49
|
+
fromEntityKey: "api",
|
|
50
|
+
toEntityKey: "db",
|
|
51
|
+
relationshipType: EntityRelationshipType.DependsOn,
|
|
52
|
+
callCount: 100,
|
|
53
|
+
errorCount: 2,
|
|
54
|
+
avgDurationMs: 30,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
fromEntityKey: "web",
|
|
58
|
+
toEntityKey: "api",
|
|
59
|
+
relationshipType: EntityRelationshipType.DependsOn,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
fromEntityKey: "api",
|
|
63
|
+
toEntityKey: "missing",
|
|
64
|
+
relationshipType: EntityRelationshipType.DependsOn,
|
|
65
|
+
},
|
|
66
|
+
] as Array<InventoryItemRelationship>;
|
|
67
|
+
|
|
68
|
+
function renderPanel(
|
|
69
|
+
options: {
|
|
70
|
+
onSelectEntity?: (key: string) => void;
|
|
71
|
+
onFocus?: (key: string) => void;
|
|
72
|
+
focusButtonLabel?: string;
|
|
73
|
+
} = {},
|
|
74
|
+
): void {
|
|
75
|
+
render(
|
|
76
|
+
<EntityDetailPanel
|
|
77
|
+
entity={API}
|
|
78
|
+
relationships={RELATIONSHIPS}
|
|
79
|
+
entityByKey={
|
|
80
|
+
new Map<string, InventoryItem>([
|
|
81
|
+
["api", API],
|
|
82
|
+
["db", DATABASE],
|
|
83
|
+
["web", WEB],
|
|
84
|
+
])
|
|
85
|
+
}
|
|
86
|
+
metricsWindowSeconds={60}
|
|
87
|
+
onClose={() => {
|
|
88
|
+
return undefined;
|
|
89
|
+
}}
|
|
90
|
+
onFocus={
|
|
91
|
+
options.onFocus ||
|
|
92
|
+
(() => {
|
|
93
|
+
return undefined;
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
onSelectEntity={options.onSelectEntity}
|
|
97
|
+
focusButtonLabel={options.focusButtonLabel}
|
|
98
|
+
/>,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
afterEach(() => {
|
|
103
|
+
cleanup();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("topology connection details", () => {
|
|
107
|
+
test("offers a clear explore-connections action for the selected resource", () => {
|
|
108
|
+
const onFocus: MockFunction = getJestMockFunction();
|
|
109
|
+
renderPanel({ onFocus });
|
|
110
|
+
fireEvent.click(
|
|
111
|
+
screen.getByRole("button", { name: "Explore connections" }),
|
|
112
|
+
);
|
|
113
|
+
expect(onFocus).toHaveBeenCalledWith("api");
|
|
114
|
+
expect(screen.queryByText("Focus on this node")).not.toBeInTheDocument();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("infrastructure can describe its resource exploration action", () => {
|
|
118
|
+
renderPanel({ focusButtonLabel: "Explore this resource" });
|
|
119
|
+
expect(
|
|
120
|
+
screen.getByRole("button", { name: "Explore this resource" }),
|
|
121
|
+
).toBeInTheDocument();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("known dependencies and callers can be explored without closing the drawer", () => {
|
|
125
|
+
const onSelectEntity: MockFunction = getJestMockFunction();
|
|
126
|
+
renderPanel({ onSelectEntity });
|
|
127
|
+
fireEvent.click(
|
|
128
|
+
screen.getByRole("button", { name: "View details for Orders database" }),
|
|
129
|
+
);
|
|
130
|
+
expect(onSelectEntity).toHaveBeenLastCalledWith("db");
|
|
131
|
+
fireEvent.click(
|
|
132
|
+
screen.getByRole("button", { name: "View details for Web frontend" }),
|
|
133
|
+
);
|
|
134
|
+
expect(onSelectEntity).toHaveBeenLastCalledWith("web");
|
|
135
|
+
expect(screen.getByText(/100\/min/)).toHaveTextContent("2.0% errors");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("unresolved connections are explained and never presented as broken actions", () => {
|
|
139
|
+
renderPanel({
|
|
140
|
+
onSelectEntity: () => {
|
|
141
|
+
return undefined;
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
expect(
|
|
145
|
+
screen.getByText("Checkout API depends on Undiscovered resource"),
|
|
146
|
+
).toBeInTheDocument();
|
|
147
|
+
expect(
|
|
148
|
+
screen.queryByRole("button", { name: /View details for Undiscovered/ }),
|
|
149
|
+
).not.toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("callers without navigation support retain readable static connections", () => {
|
|
153
|
+
renderPanel();
|
|
154
|
+
expect(
|
|
155
|
+
screen.getByText("Checkout API depends on Orders database"),
|
|
156
|
+
).toBeInTheDocument();
|
|
157
|
+
expect(
|
|
158
|
+
screen.queryByRole("button", {
|
|
159
|
+
name: "View details for Orders database",
|
|
160
|
+
}),
|
|
161
|
+
).not.toBeInTheDocument();
|
|
162
|
+
});
|
|
163
|
+
});
|