@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,178 @@
|
|
|
1
|
+
import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
|
|
2
|
+
import InventoryItem from "../../../Models/DatabaseModels/InventoryItem";
|
|
3
|
+
import InventoryItemService from "../../../Server/Services/InventoryItemService";
|
|
4
|
+
import Service from "../../../Models/DatabaseModels/Service";
|
|
5
|
+
import ServiceService from "../../../Server/Services/ServiceService";
|
|
6
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
7
|
+
import PositiveNumber from "../../../Types/PositiveNumber";
|
|
8
|
+
import EntitySource from "../../../Types/Telemetry/EntitySource";
|
|
9
|
+
import EntityType from "../../../Types/Telemetry/EntityType";
|
|
10
|
+
import { ExtractedEntity } from "../../../Server/Utils/Telemetry/TelemetryEntity";
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* What may become a row in the Inventory registry.
|
|
15
|
+
*
|
|
16
|
+
* Two gates decide it, and they answer different questions. The TYPE gate asks
|
|
17
|
+
* whether this kind of thing belongs in the registry at all — containers,
|
|
18
|
+
* processes, service instances and SDKs are high-churn, so their keys travel on
|
|
19
|
+
* signals but never mint rows. The per-entity `membershipOnly` flag asks
|
|
20
|
+
* whether this particular observation is the resource's entity of that type or
|
|
21
|
+
* a duplicate the producer sent alongside it: one OTLP resource describes one
|
|
22
|
+
* pod, on one node, in one cluster, but nothing stops a producer emitting two
|
|
23
|
+
* `entity_refs` of the same type, and promoting both put two rows in Inventory
|
|
24
|
+
* from a single batch.
|
|
25
|
+
*
|
|
26
|
+
* `reconcileEntities` is public, so it repeats both gates rather than trusting
|
|
27
|
+
* its caller — the cost of a missed gate here is a row that should never have
|
|
28
|
+
* existed, and once created nothing removes it until its TTL expires.
|
|
29
|
+
*
|
|
30
|
+
* Everything external is mocked — no Postgres, no Redis.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const PROJECT_ID: ObjectID = ObjectID.generate();
|
|
34
|
+
const SERVICE_ROW_ID: ObjectID = ObjectID.generate();
|
|
35
|
+
|
|
36
|
+
let createdRows: Array<InventoryItem>;
|
|
37
|
+
let bumps: Array<ObjectID>;
|
|
38
|
+
|
|
39
|
+
function entity(
|
|
40
|
+
entityType: EntityType,
|
|
41
|
+
overrides: Partial<ExtractedEntity> = {},
|
|
42
|
+
): ExtractedEntity {
|
|
43
|
+
return {
|
|
44
|
+
entityType,
|
|
45
|
+
entityKey: "0123456789abcdef",
|
|
46
|
+
identifyingAttributes: { "some.name": "thing" },
|
|
47
|
+
...overrides,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function reconcile(entities: Array<ExtractedEntity>): Promise<void> {
|
|
52
|
+
return InventoryItemService.reconcileEntities({
|
|
53
|
+
projectId: PROJECT_ID,
|
|
54
|
+
entities,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
createdRows = [];
|
|
60
|
+
bumps = [];
|
|
61
|
+
|
|
62
|
+
// Both fences open, so every reconcile actually does its work.
|
|
63
|
+
jest
|
|
64
|
+
.spyOn(GlobalCache, "setStringIfNotExists")
|
|
65
|
+
.mockImplementation(async () => {
|
|
66
|
+
return true;
|
|
67
|
+
});
|
|
68
|
+
jest.spyOn(GlobalCache, "deleteKey").mockImplementation(async () => {
|
|
69
|
+
return undefined as never;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const serviceRow: Service = new Service();
|
|
73
|
+
serviceRow.id = SERVICE_ROW_ID;
|
|
74
|
+
serviceRow._id = SERVICE_ROW_ID.toString();
|
|
75
|
+
jest
|
|
76
|
+
.spyOn(ServiceService, "findOneBy")
|
|
77
|
+
.mockImplementation(async (): Promise<Service | null> => {
|
|
78
|
+
return serviceRow;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
jest
|
|
82
|
+
.spyOn(InventoryItemService, "findOneBy")
|
|
83
|
+
.mockImplementation(async (): Promise<InventoryItem | null> => {
|
|
84
|
+
return null;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
jest
|
|
88
|
+
.spyOn(InventoryItemService, "countBy")
|
|
89
|
+
.mockImplementation(async (): Promise<PositiveNumber> => {
|
|
90
|
+
return new PositiveNumber(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
jest
|
|
94
|
+
.spyOn(InventoryItemService, "create")
|
|
95
|
+
.mockImplementation(async (createBy: unknown): Promise<InventoryItem> => {
|
|
96
|
+
const data: InventoryItem = (createBy as { data: InventoryItem }).data;
|
|
97
|
+
createdRows.push(data);
|
|
98
|
+
return data;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
jest
|
|
102
|
+
.spyOn(InventoryItemService, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
103
|
+
.mockImplementation(async (data: unknown): Promise<boolean> => {
|
|
104
|
+
bumps.push((data as { id: ObjectID }).id);
|
|
105
|
+
return true;
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
afterEach(() => {
|
|
110
|
+
jest.restoreAllMocks();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("membership-only entities never become rows", () => {
|
|
114
|
+
test.each([
|
|
115
|
+
EntityType.Container,
|
|
116
|
+
EntityType.Process,
|
|
117
|
+
EntityType.ServiceInstance,
|
|
118
|
+
EntityType.TelemetrySdk,
|
|
119
|
+
])("%s is not promoted by type", async (entityType: EntityType) => {
|
|
120
|
+
await reconcile([entity(entityType)]);
|
|
121
|
+
|
|
122
|
+
expect(createdRows).toHaveLength(0);
|
|
123
|
+
expect(bumps).toHaveLength(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test.each([
|
|
127
|
+
EntityType.KubernetesPod,
|
|
128
|
+
EntityType.KubernetesNode,
|
|
129
|
+
EntityType.Service,
|
|
130
|
+
EntityType.Host,
|
|
131
|
+
])(
|
|
132
|
+
"%s flagged membershipOnly is not promoted even though its type is",
|
|
133
|
+
async (entityType: EntityType) => {
|
|
134
|
+
await reconcile([entity(entityType, { membershipOnly: true })]);
|
|
135
|
+
|
|
136
|
+
expect(createdRows).toHaveLength(0);
|
|
137
|
+
expect(bumps).toHaveLength(0);
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
test("a demoted duplicate does not suppress the promoted entity beside it", async () => {
|
|
142
|
+
/*
|
|
143
|
+
* The shape `entitiesFromRefs` produces when a producer sends two refs of
|
|
144
|
+
* one type: the first is the entity, the rest are membership keys.
|
|
145
|
+
*/
|
|
146
|
+
await reconcile([
|
|
147
|
+
entity(EntityType.KubernetesPod, { entityKey: "aaaaaaaaaaaaaaaa" }),
|
|
148
|
+
entity(EntityType.KubernetesPod, {
|
|
149
|
+
entityKey: "bbbbbbbbbbbbbbbb",
|
|
150
|
+
membershipOnly: true,
|
|
151
|
+
}),
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
expect(createdRows).toHaveLength(1);
|
|
155
|
+
expect(createdRows[0]!.entityKey).toBe("aaaaaaaaaaaaaaaa");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("promoted entities still become rows", () => {
|
|
160
|
+
test.each([
|
|
161
|
+
EntityType.Service,
|
|
162
|
+
EntityType.Host,
|
|
163
|
+
EntityType.KubernetesPod,
|
|
164
|
+
EntityType.KubernetesNode,
|
|
165
|
+
EntityType.KubernetesCluster,
|
|
166
|
+
EntityType.KubernetesNamespace,
|
|
167
|
+
EntityType.KubernetesDeployment,
|
|
168
|
+
EntityType.ProxmoxCluster,
|
|
169
|
+
EntityType.CephCluster,
|
|
170
|
+
EntityType.DockerSwarmCluster,
|
|
171
|
+
])("%s is created as a discovered row", async (entityType: EntityType) => {
|
|
172
|
+
await reconcile([entity(entityType)]);
|
|
173
|
+
|
|
174
|
+
expect(createdRows).toHaveLength(1);
|
|
175
|
+
expect(createdRows[0]!.source).toBe(EntitySource.Discovered);
|
|
176
|
+
expect(createdRows[0]!.entityType).toBe(entityType);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
2
|
+
import MonitorTemplate from "../../../Models/DatabaseModels/MonitorTemplate";
|
|
3
|
+
import MonitorService from "../../../Server/Services/MonitorService";
|
|
4
|
+
import MonitorTemplateService, {
|
|
5
|
+
SyncLinkedMonitorsResult,
|
|
6
|
+
} from "../../../Server/Services/MonitorTemplateService";
|
|
7
|
+
import FindBy from "../../../Server/Types/Database/FindBy";
|
|
8
|
+
import Query from "../../../Server/Types/Database/Query";
|
|
9
|
+
import Select from "../../../Server/Types/Database/Select";
|
|
10
|
+
import ModelPermission from "../../../Server/Types/Database/Permissions/Index";
|
|
11
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
12
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
13
|
+
import MonitorType from "../../../Types/Monitor/MonitorType";
|
|
14
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
15
|
+
import { afterEach, describe, expect, it, jest } from "@jest/globals";
|
|
16
|
+
import type { SpyInstance } from "jest-mock";
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* Contract under test — pushing a monitor template's CUSTOM FIELD DEFAULTS
|
|
20
|
+
* onto the monitors already made from it (issue #3548).
|
|
21
|
+
*
|
|
22
|
+
* Provisioning has always copied a template's `customFields` onto the monitors
|
|
23
|
+
* it creates, so setting defaults on a template covers everything imported
|
|
24
|
+
* after the edit. The fleet that makes this a problem is on the other side of
|
|
25
|
+
* that line: a thousand devices an auto-import rule already turned into
|
|
26
|
+
* monitors with every custom field empty. Sync is what reaches them, and three
|
|
27
|
+
* things about it are easy to get wrong and impossible to undo:
|
|
28
|
+
*
|
|
29
|
+
* - it OVERLAYS. A template holds a key for every custom field the project
|
|
30
|
+
* has defined the moment its edit form is saved, so an assignment would
|
|
31
|
+
* blank every field the template happens not to default across the whole
|
|
32
|
+
* fleet;
|
|
33
|
+
* - it is per-monitor, because the value written depends on what that
|
|
34
|
+
* monitor already holds — which rules out the bulk `updateBy` every other
|
|
35
|
+
* syncable field rides;
|
|
36
|
+
* - it is opt-in by name. An unscoped sync pushes criteria, interval and
|
|
37
|
+
* labels and deliberately leaves operator-entered custom field values
|
|
38
|
+
* alone.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
42
|
+
"11111111-1111-4111-8111-111111111111",
|
|
43
|
+
);
|
|
44
|
+
const TEMPLATE_ID: ObjectID = new ObjectID(
|
|
45
|
+
"22222222-2222-4222-8222-222222222222",
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
function buildTemplate(data?: {
|
|
49
|
+
customFields?: JSONObject | undefined;
|
|
50
|
+
monitorType?: MonitorType | undefined;
|
|
51
|
+
}): MonitorTemplate {
|
|
52
|
+
const template: MonitorTemplate = new MonitorTemplate();
|
|
53
|
+
template.id = TEMPLATE_ID;
|
|
54
|
+
template.projectId = PROJECT_ID;
|
|
55
|
+
/*
|
|
56
|
+
* Ping rather than Network Device, so nothing but custom fields can push a
|
|
57
|
+
* sync onto the per-monitor path.
|
|
58
|
+
*/
|
|
59
|
+
template.monitorType = data?.monitorType || MonitorType.Ping;
|
|
60
|
+
template.monitoringInterval = "*/10 * * * *";
|
|
61
|
+
template.minimumProbeAgreement = 2;
|
|
62
|
+
const customFields: JSONObject | undefined =
|
|
63
|
+
data && "customFields" in data
|
|
64
|
+
? data.customFields
|
|
65
|
+
: {
|
|
66
|
+
Vendor: "Cisco",
|
|
67
|
+
"Configuration Item": "",
|
|
68
|
+
};
|
|
69
|
+
if (customFields) {
|
|
70
|
+
template.customFields = customFields;
|
|
71
|
+
}
|
|
72
|
+
return template;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildLinkedMonitor(customFields?: JSONObject | undefined): Monitor {
|
|
76
|
+
const monitor: Monitor = new Monitor();
|
|
77
|
+
monitor.id = ObjectID.generate();
|
|
78
|
+
monitor.projectId = PROJECT_ID;
|
|
79
|
+
monitor.monitorType = MonitorType.Ping;
|
|
80
|
+
monitor.monitorTemplateId = TEMPLATE_ID;
|
|
81
|
+
if (customFields) {
|
|
82
|
+
monitor.customFields = customFields;
|
|
83
|
+
}
|
|
84
|
+
return monitor;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface MockedSync {
|
|
88
|
+
updateOneSpy: SpyInstance<typeof MonitorService.updateOneById>;
|
|
89
|
+
bulkUpdateSpy: SpyInstance<typeof MonitorService.updateBy>;
|
|
90
|
+
findBySpy: SpyInstance<typeof MonitorService.findBy>;
|
|
91
|
+
permissionSpy: SpyInstance<
|
|
92
|
+
typeof ModelPermission.checkUpdateQueryPermissions
|
|
93
|
+
>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function mockSync(data: {
|
|
97
|
+
template: MonitorTemplate;
|
|
98
|
+
monitors: Array<Monitor>;
|
|
99
|
+
}): MockedSync {
|
|
100
|
+
jest
|
|
101
|
+
.spyOn(MonitorTemplateService, "findOneById")
|
|
102
|
+
.mockResolvedValue(data.template);
|
|
103
|
+
jest
|
|
104
|
+
.spyOn(MonitorTemplateService, "countLinkedMonitors")
|
|
105
|
+
.mockResolvedValue(data.monitors.length);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
updateOneSpy: jest
|
|
109
|
+
.spyOn(MonitorService, "updateOneById")
|
|
110
|
+
.mockResolvedValue(1),
|
|
111
|
+
bulkUpdateSpy: jest.spyOn(MonitorService, "updateBy").mockResolvedValue(0),
|
|
112
|
+
findBySpy: jest
|
|
113
|
+
.spyOn(MonitorService, "findBy")
|
|
114
|
+
.mockResolvedValue(data.monitors),
|
|
115
|
+
permissionSpy: jest
|
|
116
|
+
.spyOn(ModelPermission, "checkUpdateQueryPermissions")
|
|
117
|
+
.mockImplementation(
|
|
118
|
+
async (
|
|
119
|
+
_modelType: any,
|
|
120
|
+
query: Query<Monitor>,
|
|
121
|
+
): Promise<Query<Monitor>> => {
|
|
122
|
+
return query;
|
|
123
|
+
},
|
|
124
|
+
) as SpyInstance<typeof ModelPermission.checkUpdateQueryPermissions>,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function writtenCustomFields(
|
|
129
|
+
updateOneSpy: SpyInstance<typeof MonitorService.updateOneById>,
|
|
130
|
+
callIndex: number,
|
|
131
|
+
): JSONObject {
|
|
132
|
+
return updateOneSpy.mock.calls[callIndex]![0].data
|
|
133
|
+
.customFields as unknown as JSONObject;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
afterEach(() => {
|
|
137
|
+
jest.restoreAllMocks();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("MonitorTemplateService custom field default synchronization", () => {
|
|
141
|
+
it("is not pushed by an unscoped sync", async () => {
|
|
142
|
+
const template: MonitorTemplate = buildTemplate();
|
|
143
|
+
const monitor: Monitor = buildLinkedMonitor({ Vendor: "Juniper" });
|
|
144
|
+
const mocks: MockedSync = mockSync({ template, monitors: [monitor] });
|
|
145
|
+
|
|
146
|
+
const result: SyncLinkedMonitorsResult =
|
|
147
|
+
await MonitorTemplateService.syncLinkedMonitors({
|
|
148
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
149
|
+
props: { isRoot: true },
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(result).toEqual({ totalLinkedMonitors: 1, syncedMonitors: 0 });
|
|
153
|
+
|
|
154
|
+
/*
|
|
155
|
+
* The bulk path, and the payload it carries has no customFields key at
|
|
156
|
+
* all — not an empty one, which would still clear the column. (`labels` is
|
|
157
|
+
* absent because this template has none, not because it is unsyncable.)
|
|
158
|
+
*/
|
|
159
|
+
expect(mocks.updateOneSpy).not.toHaveBeenCalled();
|
|
160
|
+
expect(mocks.bulkUpdateSpy).toHaveBeenCalledTimes(1);
|
|
161
|
+
|
|
162
|
+
const bulkPayloadKeys: Array<string> = Object.keys(
|
|
163
|
+
mocks.bulkUpdateSpy.mock.calls[0]![0].data as unknown as JSONObject,
|
|
164
|
+
);
|
|
165
|
+
expect(bulkPayloadKeys).not.toContain("customFields");
|
|
166
|
+
expect(bulkPayloadKeys).toEqual([
|
|
167
|
+
"monitoringInterval",
|
|
168
|
+
"minimumProbeAgreement",
|
|
169
|
+
]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("is accepted as a scoped field, and an unknown field still is not", async () => {
|
|
173
|
+
const template: MonitorTemplate = buildTemplate();
|
|
174
|
+
mockSync({ template, monitors: [buildLinkedMonitor()] });
|
|
175
|
+
|
|
176
|
+
await expect(
|
|
177
|
+
MonitorTemplateService.syncLinkedMonitors({
|
|
178
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
179
|
+
fields: ["customFields"],
|
|
180
|
+
props: { isRoot: true },
|
|
181
|
+
}),
|
|
182
|
+
).resolves.toEqual({ totalLinkedMonitors: 1, syncedMonitors: 1 });
|
|
183
|
+
|
|
184
|
+
await expect(
|
|
185
|
+
MonitorTemplateService.syncLinkedMonitors({
|
|
186
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
187
|
+
fields: ["name"],
|
|
188
|
+
props: { isRoot: true },
|
|
189
|
+
}),
|
|
190
|
+
).rejects.toThrow(BadDataException);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
/*
|
|
194
|
+
* The behaviour the whole feature turns on: only the fields the template
|
|
195
|
+
* actually defaults are written, and every other value on the monitor —
|
|
196
|
+
* including one on a field the template holds an empty string for — survives.
|
|
197
|
+
*/
|
|
198
|
+
it("overlays the template's defaults and leaves every other value on the monitor alone", async () => {
|
|
199
|
+
const template: MonitorTemplate = buildTemplate({
|
|
200
|
+
customFields: {
|
|
201
|
+
Vendor: "Cisco",
|
|
202
|
+
Duration: 30,
|
|
203
|
+
"Configuration Item": "",
|
|
204
|
+
Services: [],
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
const monitor: Monitor = buildLinkedMonitor({
|
|
208
|
+
Vendor: "Juniper",
|
|
209
|
+
"Configuration Item": "CI-4417",
|
|
210
|
+
Services: ["billing"],
|
|
211
|
+
Notes: "replaced 2026-01-04",
|
|
212
|
+
});
|
|
213
|
+
const mocks: MockedSync = mockSync({ template, monitors: [monitor] });
|
|
214
|
+
|
|
215
|
+
const result: SyncLinkedMonitorsResult =
|
|
216
|
+
await MonitorTemplateService.syncLinkedMonitors({
|
|
217
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
218
|
+
fields: ["customFields"],
|
|
219
|
+
props: { isRoot: true },
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(result).toEqual({ totalLinkedMonitors: 1, syncedMonitors: 1 });
|
|
223
|
+
expect(writtenCustomFields(mocks.updateOneSpy, 0)).toEqual({
|
|
224
|
+
Vendor: "Cisco",
|
|
225
|
+
Duration: 30,
|
|
226
|
+
"Configuration Item": "CI-4417",
|
|
227
|
+
Services: ["billing"],
|
|
228
|
+
Notes: "replaced 2026-01-04",
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Scoped to custom fields, so nothing else about the monitor is rewritten.
|
|
232
|
+
expect(
|
|
233
|
+
Object.keys(
|
|
234
|
+
mocks.updateOneSpy.mock.calls[0]![0].data as unknown as JSONObject,
|
|
235
|
+
),
|
|
236
|
+
).toEqual(["customFields"]);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
/*
|
|
240
|
+
* Each monitor's payload is computed from that monitor's own bag, which is
|
|
241
|
+
* exactly why this cannot ride the shared bulk update the other syncable
|
|
242
|
+
* fields use.
|
|
243
|
+
*/
|
|
244
|
+
it("writes a different payload per monitor, one row at a time", async () => {
|
|
245
|
+
const template: MonitorTemplate = buildTemplate({
|
|
246
|
+
customFields: { Vendor: "Cisco" },
|
|
247
|
+
});
|
|
248
|
+
const alreadyTagged: Monitor = buildLinkedMonitor({
|
|
249
|
+
Vendor: "Juniper",
|
|
250
|
+
"Configuration Item": "CI-1",
|
|
251
|
+
});
|
|
252
|
+
const untouched: Monitor = buildLinkedMonitor();
|
|
253
|
+
const mocks: MockedSync = mockSync({
|
|
254
|
+
template,
|
|
255
|
+
monitors: [alreadyTagged, untouched],
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const result: SyncLinkedMonitorsResult =
|
|
259
|
+
await MonitorTemplateService.syncLinkedMonitors({
|
|
260
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
261
|
+
fields: ["customFields"],
|
|
262
|
+
props: { isRoot: true },
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(result).toEqual({ totalLinkedMonitors: 2, syncedMonitors: 2 });
|
|
266
|
+
expect(mocks.bulkUpdateSpy).not.toHaveBeenCalled();
|
|
267
|
+
expect(mocks.updateOneSpy).toHaveBeenCalledTimes(2);
|
|
268
|
+
|
|
269
|
+
expect(writtenCustomFields(mocks.updateOneSpy, 0)).toEqual({
|
|
270
|
+
Vendor: "Cisco",
|
|
271
|
+
"Configuration Item": "CI-1",
|
|
272
|
+
});
|
|
273
|
+
expect(writtenCustomFields(mocks.updateOneSpy, 1)).toEqual({
|
|
274
|
+
Vendor: "Cisco",
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
/*
|
|
279
|
+
* Whatever else it pushes, a per-monitor sync has to READ the column it is
|
|
280
|
+
* about to overlay — a select that omits it hands the merge an undefined bag
|
|
281
|
+
* and turns the overlay into an assignment, silently blanking the fields the
|
|
282
|
+
* template does not default.
|
|
283
|
+
*/
|
|
284
|
+
it("reads each monitor's existing custom fields before overlaying them", async () => {
|
|
285
|
+
const template: MonitorTemplate = buildTemplate({
|
|
286
|
+
customFields: { Vendor: "Cisco" },
|
|
287
|
+
});
|
|
288
|
+
const mocks: MockedSync = mockSync({
|
|
289
|
+
template,
|
|
290
|
+
monitors: [buildLinkedMonitor()],
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
await MonitorTemplateService.syncLinkedMonitors({
|
|
294
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
295
|
+
fields: ["customFields"],
|
|
296
|
+
props: { isRoot: true },
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const select: Select<Monitor> = (
|
|
300
|
+
mocks.findBySpy.mock.calls[0]![0] as FindBy<Monitor>
|
|
301
|
+
).select as Select<Monitor>;
|
|
302
|
+
expect((select as JSONObject)["customFields"]).toBe(true);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
/*
|
|
306
|
+
* The read of the template itself, for the same reason. Selecting the column
|
|
307
|
+
* is what makes the difference between "this template defaults nothing" and
|
|
308
|
+
* "nobody asked for the column", and the two are indistinguishable
|
|
309
|
+
* afterwards.
|
|
310
|
+
*/
|
|
311
|
+
it("selects customFields on the template it reads", async () => {
|
|
312
|
+
const template: MonitorTemplate = buildTemplate();
|
|
313
|
+
mockSync({ template, monitors: [buildLinkedMonitor()] });
|
|
314
|
+
const templateReadSpy: SpyInstance<
|
|
315
|
+
typeof MonitorTemplateService.findOneById
|
|
316
|
+
> = jest.spyOn(MonitorTemplateService, "findOneById");
|
|
317
|
+
|
|
318
|
+
await MonitorTemplateService.syncLinkedMonitors({
|
|
319
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
320
|
+
fields: ["customFields"],
|
|
321
|
+
props: { isRoot: true },
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
expect(
|
|
325
|
+
(templateReadSpy.mock.calls[0]![0].select as JSONObject)["customFields"],
|
|
326
|
+
).toBe(true);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
/*
|
|
330
|
+
* A caller who may not update Monitor.customFields must be refused before
|
|
331
|
+
* the first write, not on whichever row the loop happens to reach when the
|
|
332
|
+
* column check finally runs. The per-monitor values are not known at that
|
|
333
|
+
* point and only the KEYS decide a column check, so the probe names the
|
|
334
|
+
* column with an empty bag.
|
|
335
|
+
*/
|
|
336
|
+
it("names customFields in the up-front column permission probe", async () => {
|
|
337
|
+
const template: MonitorTemplate = buildTemplate({
|
|
338
|
+
customFields: { Vendor: "Cisco" },
|
|
339
|
+
});
|
|
340
|
+
const mocks: MockedSync = mockSync({
|
|
341
|
+
template,
|
|
342
|
+
monitors: [buildLinkedMonitor()],
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
await MonitorTemplateService.syncLinkedMonitors({
|
|
346
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
347
|
+
fields: ["customFields"],
|
|
348
|
+
props: { isRoot: true },
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
expect(mocks.permissionSpy).toHaveBeenCalledTimes(1);
|
|
352
|
+
expect(
|
|
353
|
+
Object.keys(mocks.permissionSpy.mock.calls[0]![2] as JSONObject),
|
|
354
|
+
).toContain("customFields");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
/*
|
|
358
|
+
* "0 of 200 synced" is the summary the dashboard reads as a permissions
|
|
359
|
+
* problem and tells the operator to run the sync again as somebody else. A
|
|
360
|
+
* template with nothing to push must therefore write nothing and say so,
|
|
361
|
+
* rather than walk the fleet.
|
|
362
|
+
*/
|
|
363
|
+
it.each([
|
|
364
|
+
["an absent bag", undefined],
|
|
365
|
+
["an empty bag", {}],
|
|
366
|
+
["a bag holding only blanks", { Vendor: "", Services: [], Duration: null }],
|
|
367
|
+
])(
|
|
368
|
+
"writes nothing when the template has %s",
|
|
369
|
+
async (
|
|
370
|
+
_label: string,
|
|
371
|
+
customFields: JSONObject | undefined,
|
|
372
|
+
): Promise<void> => {
|
|
373
|
+
const template: MonitorTemplate = buildTemplate({ customFields });
|
|
374
|
+
const mocks: MockedSync = mockSync({
|
|
375
|
+
template,
|
|
376
|
+
monitors: [buildLinkedMonitor({ Vendor: "Juniper" })],
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const result: SyncLinkedMonitorsResult =
|
|
380
|
+
await MonitorTemplateService.syncLinkedMonitors({
|
|
381
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
382
|
+
fields: ["customFields"],
|
|
383
|
+
props: { isRoot: true },
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
expect(result).toEqual({ totalLinkedMonitors: 1, syncedMonitors: 0 });
|
|
387
|
+
expect(mocks.updateOneSpy).not.toHaveBeenCalled();
|
|
388
|
+
expect(mocks.bulkUpdateSpy).not.toHaveBeenCalled();
|
|
389
|
+
expect(mocks.findBySpy).not.toHaveBeenCalled();
|
|
390
|
+
},
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
/*
|
|
394
|
+
* Custom fields ride alongside the other per-monitor field rather than
|
|
395
|
+
* displacing it: a Network Device template pushing criteria AND defaults has
|
|
396
|
+
* to rebind the steps to each monitor's own device in the same write.
|
|
397
|
+
*/
|
|
398
|
+
it("carries a Network Device criteria rebind and the defaults in one write", async () => {
|
|
399
|
+
const template: MonitorTemplate = buildTemplate({
|
|
400
|
+
monitorType: MonitorType.NetworkDevice,
|
|
401
|
+
customFields: { Vendor: "Cisco" },
|
|
402
|
+
});
|
|
403
|
+
const monitor: Monitor = buildLinkedMonitor({ Duration: 30 });
|
|
404
|
+
const mocks: MockedSync = mockSync({ template, monitors: [monitor] });
|
|
405
|
+
|
|
406
|
+
const result: SyncLinkedMonitorsResult =
|
|
407
|
+
await MonitorTemplateService.syncLinkedMonitors({
|
|
408
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
409
|
+
fields: ["customFields", "monitoringInterval"],
|
|
410
|
+
props: { isRoot: true },
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
expect(result).toEqual({ totalLinkedMonitors: 1, syncedMonitors: 1 });
|
|
414
|
+
expect(mocks.updateOneSpy).toHaveBeenCalledTimes(1);
|
|
415
|
+
expect(
|
|
416
|
+
Object.keys(
|
|
417
|
+
mocks.updateOneSpy.mock.calls[0]![0].data as unknown as JSONObject,
|
|
418
|
+
).sort(),
|
|
419
|
+
).toEqual(["customFields", "monitoringInterval"]);
|
|
420
|
+
expect(writtenCustomFields(mocks.updateOneSpy, 0)).toEqual({
|
|
421
|
+
Vendor: "Cisco",
|
|
422
|
+
Duration: 30,
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe("syncToMonitor", () => {
|
|
427
|
+
it("overlays the defaults onto that one monitor's values", async () => {
|
|
428
|
+
const template: MonitorTemplate = buildTemplate({
|
|
429
|
+
customFields: { Vendor: "Cisco", "Configuration Item": "" },
|
|
430
|
+
});
|
|
431
|
+
const monitor: Monitor = buildLinkedMonitor({
|
|
432
|
+
Vendor: "Juniper",
|
|
433
|
+
"Configuration Item": "CI-4417",
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
jest
|
|
437
|
+
.spyOn(MonitorTemplateService, "findOneById")
|
|
438
|
+
.mockResolvedValue(template);
|
|
439
|
+
jest.spyOn(MonitorService, "findOneById").mockResolvedValue(monitor);
|
|
440
|
+
const updateOneSpy: SpyInstance<typeof MonitorService.updateOneById> =
|
|
441
|
+
jest.spyOn(MonitorService, "updateOneById").mockResolvedValue(1);
|
|
442
|
+
|
|
443
|
+
await MonitorTemplateService.syncToMonitor({
|
|
444
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
445
|
+
monitorId: monitor.id!,
|
|
446
|
+
fields: ["customFields"],
|
|
447
|
+
props: { isRoot: true },
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
expect(writtenCustomFields(updateOneSpy, 0)).toEqual({
|
|
451
|
+
Vendor: "Cisco",
|
|
452
|
+
"Configuration Item": "CI-4417",
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("reads the monitor's existing custom fields before overlaying them", async () => {
|
|
457
|
+
const template: MonitorTemplate = buildTemplate({
|
|
458
|
+
customFields: { Vendor: "Cisco" },
|
|
459
|
+
});
|
|
460
|
+
const monitor: Monitor = buildLinkedMonitor();
|
|
461
|
+
|
|
462
|
+
jest
|
|
463
|
+
.spyOn(MonitorTemplateService, "findOneById")
|
|
464
|
+
.mockResolvedValue(template);
|
|
465
|
+
const monitorReadSpy: SpyInstance<typeof MonitorService.findOneById> =
|
|
466
|
+
jest.spyOn(MonitorService, "findOneById").mockResolvedValue(monitor);
|
|
467
|
+
jest.spyOn(MonitorService, "updateOneById").mockResolvedValue(1);
|
|
468
|
+
|
|
469
|
+
await MonitorTemplateService.syncToMonitor({
|
|
470
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
471
|
+
monitorId: monitor.id!,
|
|
472
|
+
fields: ["customFields"],
|
|
473
|
+
props: { isRoot: true },
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
expect(
|
|
477
|
+
(monitorReadSpy.mock.calls[0]![0].select as JSONObject)["customFields"],
|
|
478
|
+
).toBe(true);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("writes nothing when the template defaults nothing", async () => {
|
|
482
|
+
const template: MonitorTemplate = buildTemplate({ customFields: {} });
|
|
483
|
+
const monitor: Monitor = buildLinkedMonitor({ Vendor: "Juniper" });
|
|
484
|
+
|
|
485
|
+
jest
|
|
486
|
+
.spyOn(MonitorTemplateService, "findOneById")
|
|
487
|
+
.mockResolvedValue(template);
|
|
488
|
+
jest.spyOn(MonitorService, "findOneById").mockResolvedValue(monitor);
|
|
489
|
+
const updateOneSpy: SpyInstance<typeof MonitorService.updateOneById> =
|
|
490
|
+
jest.spyOn(MonitorService, "updateOneById").mockResolvedValue(1);
|
|
491
|
+
|
|
492
|
+
await MonitorTemplateService.syncToMonitor({
|
|
493
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
494
|
+
monitorId: monitor.id!,
|
|
495
|
+
fields: ["customFields"],
|
|
496
|
+
props: { isRoot: true },
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
expect(updateOneSpy).not.toHaveBeenCalled();
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
});
|