@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
|
@@ -312,7 +312,7 @@ describe("CompareCriteria aggregation semantics", () => {
|
|
|
312
312
|
});
|
|
313
313
|
|
|
314
314
|
expect(message).toBe(
|
|
315
|
-
"Any value of CPU is 95
|
|
315
|
+
"Any value of CPU is 95.00%, 96.00%, 97.00% which is greater than 90.00%.",
|
|
316
316
|
);
|
|
317
317
|
});
|
|
318
318
|
|
|
@@ -328,7 +328,7 @@ describe("CompareCriteria aggregation semantics", () => {
|
|
|
328
328
|
unit: "%",
|
|
329
329
|
});
|
|
330
330
|
|
|
331
|
-
expect(message).toContain("80
|
|
331
|
+
expect(message).toContain("80.00%, 70.00%");
|
|
332
332
|
expect(message).not.toContain("95");
|
|
333
333
|
});
|
|
334
334
|
|
|
@@ -345,7 +345,7 @@ describe("CompareCriteria aggregation semantics", () => {
|
|
|
345
345
|
});
|
|
346
346
|
|
|
347
347
|
expect(message).toBe(
|
|
348
|
-
"All values of CPU is 95
|
|
348
|
+
"All values of CPU is 95.00%, 96.00% which is greater than 90.00%.",
|
|
349
349
|
);
|
|
350
350
|
});
|
|
351
351
|
|
|
@@ -513,15 +513,21 @@ describe("CompareCriteria aggregation semantics", () => {
|
|
|
513
513
|
/*
|
|
514
514
|
* The guard against over-suppression. If this ever goes quiet the
|
|
515
515
|
* fix has widened into a general unit filter.
|
|
516
|
+
*
|
|
517
|
+
* The unit now rides each NUMBER at the scale that number landed on,
|
|
518
|
+
* so a sub-byte value reports "B" and 0.85 ms is written out as the
|
|
519
|
+
* 850 µs it is. Both halves of the sentence go through the same
|
|
520
|
+
* renderer, which is the property that matters here: the observed
|
|
521
|
+
* value and the threshold can never be quoted in different units.
|
|
516
522
|
*/
|
|
517
523
|
expect(unitMessage("By")).toBe(
|
|
518
|
-
"Any value of Metric Value is 0.85
|
|
524
|
+
"Any value of Metric Value is 0.85 B which is greater than 0.8 B.",
|
|
519
525
|
);
|
|
520
526
|
expect(unitMessage("%")).toBe(
|
|
521
|
-
"Any value of Metric Value is 0.85
|
|
527
|
+
"Any value of Metric Value is 0.85% which is greater than 0.80%.",
|
|
522
528
|
);
|
|
523
529
|
expect(unitMessage("ms")).toBe(
|
|
524
|
-
"Any value of Metric Value is
|
|
530
|
+
"Any value of Metric Value is 850 µs which is greater than 800 µs.",
|
|
525
531
|
);
|
|
526
532
|
// "1" is suppressed; a unit that merely CONTAINS a 1 is not.
|
|
527
533
|
expect(unitMessage("m/s2")).toContain("0.85 m/s2");
|
|
@@ -532,7 +538,7 @@ describe("CompareCriteria aggregation semantics", () => {
|
|
|
532
538
|
const message: string = unitMessage(" ms ");
|
|
533
539
|
|
|
534
540
|
expect(message).toBe(
|
|
535
|
-
"Any value of Metric Value is
|
|
541
|
+
"Any value of Metric Value is 850 µs which is greater than 800 µs.",
|
|
536
542
|
);
|
|
537
543
|
expect(message).not.toContain(" ");
|
|
538
544
|
});
|
|
@@ -178,6 +178,35 @@ describe("DatabaseMonitorCriteria.isMonitorInstanceCriteriaFilterMet", () => {
|
|
|
178
178
|
expect(result).toContain("95");
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
+
/*
|
|
182
|
+
* A Database Health metric carries an explicit catalog unit, and
|
|
183
|
+
* DatabaseMonitorCriteria hands that unit to CompareCriteria for the
|
|
184
|
+
* alert email. The observation beside it used to render bytes with a
|
|
185
|
+
* 1024 divisor while labelling the rungs SI, so one byte count read
|
|
186
|
+
* three ways across the product: "1.07 GB" in the email, "1.00 GB" in
|
|
187
|
+
* the evaluation log, and "1000000000 bytes" in the expectation
|
|
188
|
+
* clause. All three now agree.
|
|
189
|
+
*/
|
|
190
|
+
test("a bytes metric is rescaled decimally, matching the alert email", async () => {
|
|
191
|
+
const result: string | null = await evaluate(
|
|
192
|
+
buildDataToProcess({
|
|
193
|
+
metrics: {
|
|
194
|
+
[MonitorMetricType.DatabaseSizeBytes]: 1073741824,
|
|
195
|
+
},
|
|
196
|
+
}),
|
|
197
|
+
metricFilter(
|
|
198
|
+
MonitorMetricType.DatabaseSizeBytes,
|
|
199
|
+
FilterType.GreaterThan,
|
|
200
|
+
"1000000000",
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
expect(result).toContain("1.07 GB");
|
|
205
|
+
// The 1024-based rendering this used to produce.
|
|
206
|
+
expect(result).not.toContain("1.00 GB");
|
|
207
|
+
expect(result).not.toContain("1073741824");
|
|
208
|
+
});
|
|
209
|
+
|
|
181
210
|
test("percent metric 40 > 90 -> not met", async () => {
|
|
182
211
|
const result: string | null = await evaluate(
|
|
183
212
|
buildDataToProcess({
|
|
@@ -111,8 +111,15 @@ describe("MetricMonitorCriteria.isMonitorInstanceCriteriaFilterMet", () => {
|
|
|
111
111
|
await MetricMonitorCriteria.isMonitorInstanceCriteriaFilterMet(inputs);
|
|
112
112
|
|
|
113
113
|
expect(message).toBeTruthy();
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
/*
|
|
115
|
+
* The COMPARISON happens in the metric's native ms — that is what this
|
|
116
|
+
* test is about, and the context below pins it. The MESSAGE renders
|
|
117
|
+
* those same milliseconds at the scale a human reads them at, so 2500
|
|
118
|
+
* ms is written "2.5 sec"; the threshold gets the same treatment so
|
|
119
|
+
* the two halves of the sentence cannot disagree.
|
|
120
|
+
*/
|
|
121
|
+
expect(message).toContain("greater than 2 sec");
|
|
122
|
+
expect(message).toContain("2.5 sec");
|
|
116
123
|
expect(criteriaFilter.metricCriteriaContext?.unit).toBe("ms");
|
|
117
124
|
expect(criteriaFilter.metricCriteriaContext?.breachingSample?.value).toBe(
|
|
118
125
|
2500,
|
|
@@ -228,11 +235,23 @@ describe("MetricMonitorCriteria.isMonitorInstanceCriteriaFilterMet", () => {
|
|
|
228
235
|
await MetricMonitorCriteria.isMonitorInstanceCriteriaFilterMet(inputs);
|
|
229
236
|
|
|
230
237
|
/*
|
|
231
|
-
* No conversion possible, so the
|
|
232
|
-
*
|
|
238
|
+
* No conversion possible, so the sample value is passed through
|
|
239
|
+
* untouched and the criteria still speaks the user's chosen MB. Those
|
|
240
|
+
* two facts are the subject of this test and are asserted directly on
|
|
241
|
+
* the context.
|
|
242
|
+
*
|
|
243
|
+
* The rendered sentence is deliberately NOT asserted on: the user has
|
|
244
|
+
* mislabelled a 5000 ms sample as 5000 MB, and the renderer takes that
|
|
245
|
+
* label at its word and reads it out as "5 GB". Any string this
|
|
246
|
+
* sentence can produce is wrong, because the input is wrong — pinning
|
|
247
|
+
* one spelling of a wrong answer would only make the misconfiguration
|
|
248
|
+
* look supported.
|
|
233
249
|
*/
|
|
234
250
|
expect(message).toBeTruthy();
|
|
235
|
-
expect(
|
|
251
|
+
expect(criteriaFilter.metricCriteriaContext?.unit).toBe("MB");
|
|
252
|
+
expect(criteriaFilter.metricCriteriaContext?.breachingSample?.value).toBe(
|
|
253
|
+
5000,
|
|
254
|
+
);
|
|
236
255
|
});
|
|
237
256
|
|
|
238
257
|
test("no data + NoDataPolicy.Trigger triggers regardless of threshold", async () => {
|
|
@@ -495,7 +514,12 @@ describe("MetricMonitorCriteria.isMonitorInstanceCriteriaFilterMet", () => {
|
|
|
495
514
|
await MetricMonitorCriteria.isMonitorInstanceCriteriaFilterMet(inputs);
|
|
496
515
|
|
|
497
516
|
expect(message).toBeTruthy();
|
|
498
|
-
|
|
517
|
+
/*
|
|
518
|
+
* Both ends of the summary carry the unit, because either end can
|
|
519
|
+
* land on a different scale than the other once values spread far
|
|
520
|
+
* enough apart.
|
|
521
|
+
*/
|
|
522
|
+
expect(message).toContain("10 samples between 110 ms and 200 ms");
|
|
499
523
|
expect(message).toContain("greater than 100 ms");
|
|
500
524
|
// The raw comma-joined dump is no longer in the message
|
|
501
525
|
expect(message).not.toContain("110, 120, 130, 140, 150, 160");
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The baseline lookup is the only thing standing between this test and
|
|
3
|
+
* ClickHouse. Stub it so the anomaly branch runs against a known baseline
|
|
4
|
+
* and the assertions are about the SENTENCE, which is what an alert email
|
|
5
|
+
* shows.
|
|
6
|
+
*/
|
|
7
|
+
jest.mock("../../../../../Server/Services/MetricBaselineService", () => {
|
|
8
|
+
const actual: Record<string, unknown> = jest.requireActual(
|
|
9
|
+
"../../../../../Server/Services/MetricBaselineService",
|
|
10
|
+
) as Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
__esModule: true,
|
|
15
|
+
default: {
|
|
16
|
+
getBaseline: jest.fn(),
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
import MetricMonitorCriteria from "../../../../../Server/Utils/Monitor/Criteria/MetricMonitorCriteria";
|
|
22
|
+
import MetricBaselineService, {
|
|
23
|
+
BaselineSummary,
|
|
24
|
+
} from "../../../../../Server/Services/MetricBaselineService";
|
|
25
|
+
import AggregateModel from "../../../../../Types/BaseDatabase/AggregatedModel";
|
|
26
|
+
import AggregatedResult from "../../../../../Types/BaseDatabase/AggregatedResult";
|
|
27
|
+
import {
|
|
28
|
+
CheckOn,
|
|
29
|
+
CriteriaFilter,
|
|
30
|
+
FilterType,
|
|
31
|
+
} from "../../../../../Types/Monitor/CriteriaFilter";
|
|
32
|
+
import MetricAliasData from "../../../../../Types/Metrics/MetricAliasData";
|
|
33
|
+
import MetricQueryConfigData from "../../../../../Types/Metrics/MetricQueryConfigData";
|
|
34
|
+
import MetricQueryData from "../../../../../Types/Metrics/MetricQueryData";
|
|
35
|
+
import MetricsViewConfig from "../../../../../Types/Metrics/MetricsViewConfig";
|
|
36
|
+
import MetricMonitorResponse from "../../../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
|
|
37
|
+
import MonitorStep from "../../../../../Types/Monitor/MonitorStep";
|
|
38
|
+
import RollingTime from "../../../../../Types/RollingTime/RollingTime";
|
|
39
|
+
import ObjectID from "../../../../../Types/ObjectID";
|
|
40
|
+
import { beforeEach, describe, expect, jest, test } from "@jest/globals";
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
* THE ANOMALY SENTENCE.
|
|
44
|
+
*
|
|
45
|
+
* An anomaly breach produces its own root cause — it never reaches
|
|
46
|
+
* CompareCriteria — and it used to print `value.toFixed(3)` with the raw
|
|
47
|
+
* exporter unit glued on:
|
|
48
|
+
*
|
|
49
|
+
* "container.memory.usage value 1073741824.000 By is 4.20σ above ..."
|
|
50
|
+
*
|
|
51
|
+
* The quantities in it are now rendered exactly as the Breaching Samples
|
|
52
|
+
* table renders them. σ is NOT one of those quantities: it is a count of
|
|
53
|
+
* standard deviations, dimensionless by construction, and must stay a
|
|
54
|
+
* bare number no matter what unit the metric carries.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
const BASELINE_MEAN: number = 268435456;
|
|
58
|
+
const BASELINE_STDDEV: number = 134217728;
|
|
59
|
+
const BREACHING_VALUE: number = 1073741824;
|
|
60
|
+
|
|
61
|
+
function baseline(): BaselineSummary {
|
|
62
|
+
return {
|
|
63
|
+
sampleCount: 480,
|
|
64
|
+
mean: BASELINE_MEAN,
|
|
65
|
+
stddev: BASELINE_STDDEV,
|
|
66
|
+
median: BASELINE_MEAN,
|
|
67
|
+
p95: BASELINE_MEAN,
|
|
68
|
+
minObserved: 0,
|
|
69
|
+
maxObserved: BREACHING_VALUE,
|
|
70
|
+
isReliable: true,
|
|
71
|
+
windowDays: 14,
|
|
72
|
+
hourOfWeek: 0,
|
|
73
|
+
} as BaselineSummary;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildInputs(input: {
|
|
77
|
+
metricNativeUnit: string;
|
|
78
|
+
metricName: string;
|
|
79
|
+
sampleValue: number;
|
|
80
|
+
}): {
|
|
81
|
+
criteriaFilter: CriteriaFilter;
|
|
82
|
+
monitorStep: MonitorStep;
|
|
83
|
+
dataToProcess: MetricMonitorResponse;
|
|
84
|
+
projectId: ObjectID;
|
|
85
|
+
} {
|
|
86
|
+
const aliasData: MetricAliasData = {
|
|
87
|
+
metricVariable: "a",
|
|
88
|
+
title: input.metricName,
|
|
89
|
+
description: undefined,
|
|
90
|
+
legend: undefined,
|
|
91
|
+
legendUnit: input.metricNativeUnit,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const queryConfig: MetricQueryConfigData = {
|
|
95
|
+
metricAliasData: aliasData,
|
|
96
|
+
metricQueryData: {
|
|
97
|
+
filterData: {
|
|
98
|
+
metricName: input.metricName,
|
|
99
|
+
},
|
|
100
|
+
} as unknown as MetricQueryData,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const metricViewConfig: MetricsViewConfig = {
|
|
104
|
+
queryConfigs: [queryConfig],
|
|
105
|
+
formulaConfigs: [],
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const monitorStep: MonitorStep = new MonitorStep();
|
|
109
|
+
monitorStep.data = {
|
|
110
|
+
id: ObjectID.generate().toString(),
|
|
111
|
+
monitorCriteria: { data: undefined } as never,
|
|
112
|
+
} as unknown as MonitorStep["data"];
|
|
113
|
+
monitorStep.data!.metricMonitor = {
|
|
114
|
+
metricViewConfig,
|
|
115
|
+
rollingTime: RollingTime.Past1Minute,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const aggregated: AggregatedResult = {
|
|
119
|
+
data: [
|
|
120
|
+
{
|
|
121
|
+
timestamp: new Date("2026-08-14T10:30:00.000Z"),
|
|
122
|
+
value: input.sampleValue,
|
|
123
|
+
} as AggregateModel,
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
criteriaFilter: {
|
|
131
|
+
checkOn: CheckOn.MetricValue,
|
|
132
|
+
filterType: FilterType.AnomalouslyHigh,
|
|
133
|
+
metricMonitorOptions: {
|
|
134
|
+
metricAlias: "a",
|
|
135
|
+
},
|
|
136
|
+
} as CriteriaFilter,
|
|
137
|
+
monitorStep,
|
|
138
|
+
dataToProcess: {
|
|
139
|
+
projectId: projectId,
|
|
140
|
+
metricResult: [aggregated],
|
|
141
|
+
metricViewConfig,
|
|
142
|
+
monitorId: ObjectID.generate(),
|
|
143
|
+
},
|
|
144
|
+
projectId,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function anomalyRootCause(input: {
|
|
149
|
+
metricNativeUnit: string;
|
|
150
|
+
metricName: string;
|
|
151
|
+
sampleValue: number;
|
|
152
|
+
}): Promise<string> {
|
|
153
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs(input);
|
|
154
|
+
|
|
155
|
+
const message: string | null =
|
|
156
|
+
await MetricMonitorCriteria.isMonitorInstanceCriteriaFilterMet({
|
|
157
|
+
criteriaFilter: inputs.criteriaFilter,
|
|
158
|
+
monitorStep: inputs.monitorStep,
|
|
159
|
+
dataToProcess: inputs.dataToProcess,
|
|
160
|
+
projectId: inputs.projectId,
|
|
161
|
+
} as never);
|
|
162
|
+
|
|
163
|
+
expect(message).toBeTruthy();
|
|
164
|
+
return message as string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
describe("MetricMonitorCriteria - anomaly root cause units", () => {
|
|
168
|
+
beforeEach(() => {
|
|
169
|
+
(MetricBaselineService.getBaseline as unknown as jest.Mock).mockReset();
|
|
170
|
+
(
|
|
171
|
+
MetricBaselineService.getBaseline as unknown as jest.Mock
|
|
172
|
+
).mockResolvedValue(baseline() as never);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("value, mean and stddev are all rendered at human scale", async () => {
|
|
176
|
+
const rootCause: string = await anomalyRootCause({
|
|
177
|
+
metricName: "container.memory.usage",
|
|
178
|
+
metricNativeUnit: "By",
|
|
179
|
+
sampleValue: BREACHING_VALUE,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(rootCause).toContain("value 1.07 GB");
|
|
183
|
+
expect(rootCause).toContain("mean 268 MB");
|
|
184
|
+
expect(rootCause).toContain("σ 134 MB");
|
|
185
|
+
|
|
186
|
+
// The old rendering, in full.
|
|
187
|
+
expect(rootCause).not.toContain("1073741824.000");
|
|
188
|
+
expect(rootCause).not.toContain("268435456.000");
|
|
189
|
+
expect(rootCause).not.toContain("By");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
/*
|
|
193
|
+
* The σ COUNT is not a quantity in the metric's unit — "6.00σ above the
|
|
194
|
+
* baseline" is six standard deviations, and rendering it as "6 GB"
|
|
195
|
+
* would be nonsense. It keeps its own two-decimal format.
|
|
196
|
+
*/
|
|
197
|
+
test("the sigma count stays a bare number", async () => {
|
|
198
|
+
const rootCause: string = await anomalyRootCause({
|
|
199
|
+
metricName: "container.memory.usage",
|
|
200
|
+
metricNativeUnit: "By",
|
|
201
|
+
sampleValue: BREACHING_VALUE,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(rootCause).toMatch(/is \d+\.\d{2}σ above the same-hour baseline/);
|
|
205
|
+
expect(rootCause).not.toMatch(/is [\d.]+ [A-Za-z]+σ/);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("a metric with no unit keeps its exact digits", async () => {
|
|
209
|
+
(
|
|
210
|
+
MetricBaselineService.getBaseline as unknown as jest.Mock
|
|
211
|
+
).mockResolvedValue({
|
|
212
|
+
...baseline(),
|
|
213
|
+
mean: 100,
|
|
214
|
+
stddev: 20,
|
|
215
|
+
} as never);
|
|
216
|
+
|
|
217
|
+
const rootCause: string = await anomalyRootCause({
|
|
218
|
+
metricName: "http.server.request.count",
|
|
219
|
+
metricNativeUnit: "",
|
|
220
|
+
sampleValue: 5000,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
expect(rootCause).toContain("value 5000 ");
|
|
224
|
+
expect(rootCause).not.toContain("5K");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("a ratio metric reads as a percentage", async () => {
|
|
228
|
+
(
|
|
229
|
+
MetricBaselineService.getBaseline as unknown as jest.Mock
|
|
230
|
+
).mockResolvedValue({
|
|
231
|
+
...baseline(),
|
|
232
|
+
mean: 0.2,
|
|
233
|
+
stddev: 0.05,
|
|
234
|
+
} as never);
|
|
235
|
+
|
|
236
|
+
const rootCause: string = await anomalyRootCause({
|
|
237
|
+
metricName: "system.cpu.utilization",
|
|
238
|
+
metricNativeUnit: "1",
|
|
239
|
+
sampleValue: 0.85,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(rootCause).toContain("value 85.00%");
|
|
243
|
+
expect(rootCause).toContain("mean 20.00%");
|
|
244
|
+
expect(rootCause).not.toContain("0.85 1");
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import MonitorCriteriaMessageBuilder from "../../../../Server/Utils/Monitor/MonitorCriteriaMessageBuilder";
|
|
2
|
+
import CompareCriteria from "../../../../Server/Utils/Monitor/Criteria/CompareCriteria";
|
|
3
|
+
import Monitor from "../../../../Models/DatabaseModels/Monitor";
|
|
4
|
+
import {
|
|
5
|
+
CheckOn,
|
|
6
|
+
CriteriaFilter,
|
|
7
|
+
EvaluateOverTimeType,
|
|
8
|
+
FilterType,
|
|
9
|
+
} from "../../../../Types/Monitor/CriteriaFilter";
|
|
10
|
+
import AggregateModel from "../../../../Types/BaseDatabase/AggregatedModel";
|
|
11
|
+
import AggregatedResult from "../../../../Types/BaseDatabase/AggregatedResult";
|
|
12
|
+
import MetricAliasData from "../../../../Types/Metrics/MetricAliasData";
|
|
13
|
+
import MetricQueryConfigData from "../../../../Types/Metrics/MetricQueryConfigData";
|
|
14
|
+
import MetricQueryData from "../../../../Types/Metrics/MetricQueryData";
|
|
15
|
+
import MetricsViewConfig from "../../../../Types/Metrics/MetricsViewConfig";
|
|
16
|
+
import MetricMonitorResponse from "../../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
|
|
17
|
+
import MonitorStep from "../../../../Types/Monitor/MonitorStep";
|
|
18
|
+
import RollingTime from "../../../../Types/RollingTime/RollingTime";
|
|
19
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
20
|
+
import { describe, expect, test } from "@jest/globals";
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
* ONE SAMPLE, ONE DESCRIPTION.
|
|
24
|
+
*
|
|
25
|
+
* A metric breach is written twice, by two different code paths, and a
|
|
26
|
+
* user compares them side by side:
|
|
27
|
+
*
|
|
28
|
+
* - the ALERT EMAIL's "Filter Conditions Met" line, produced by
|
|
29
|
+
* CompareCriteria when the filter MATCHED, and
|
|
30
|
+
* - the monitor's EVALUATION LOG line, produced by the observation and
|
|
31
|
+
* expectation builders when the filter did NOT match.
|
|
32
|
+
*
|
|
33
|
+
* Only the first was migrated to MetricValueFormatter, so the same
|
|
34
|
+
* 1073741824-byte sample read "1.07 GB" in the inbox and
|
|
35
|
+
* "1073741824.00 By" on the monitor page — and a ratio metric read "6.00%"
|
|
36
|
+
* in one and "0.06" in the other.
|
|
37
|
+
*
|
|
38
|
+
* These tests assert the two paths render the same quantity the same way.
|
|
39
|
+
* They are deliberately about AGREEMENT rather than about either exact
|
|
40
|
+
* wording: the sentences around the number differ by design, the number
|
|
41
|
+
* and its unit must not.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
function buildInputs(input: {
|
|
45
|
+
metricNativeUnit: string;
|
|
46
|
+
metricName: string;
|
|
47
|
+
sampleValues: Array<number>;
|
|
48
|
+
criteriaFilter: CriteriaFilter;
|
|
49
|
+
}): {
|
|
50
|
+
monitor: Monitor;
|
|
51
|
+
criteriaFilter: CriteriaFilter;
|
|
52
|
+
monitorStep: MonitorStep;
|
|
53
|
+
dataToProcess: MetricMonitorResponse;
|
|
54
|
+
} {
|
|
55
|
+
const aliasData: MetricAliasData = {
|
|
56
|
+
metricVariable: "a",
|
|
57
|
+
title: input.metricName,
|
|
58
|
+
description: undefined,
|
|
59
|
+
legend: undefined,
|
|
60
|
+
legendUnit: input.metricNativeUnit,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const queryConfig: MetricQueryConfigData = {
|
|
64
|
+
metricAliasData: aliasData,
|
|
65
|
+
metricQueryData: {
|
|
66
|
+
filterData: {
|
|
67
|
+
metricName: input.metricName,
|
|
68
|
+
},
|
|
69
|
+
} as unknown as MetricQueryData,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const metricViewConfig: MetricsViewConfig = {
|
|
73
|
+
queryConfigs: [queryConfig],
|
|
74
|
+
formulaConfigs: [],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const monitorStep: MonitorStep = new MonitorStep();
|
|
78
|
+
monitorStep.data = {
|
|
79
|
+
id: ObjectID.generate().toString(),
|
|
80
|
+
monitorCriteria: { data: undefined } as never,
|
|
81
|
+
} as unknown as MonitorStep["data"];
|
|
82
|
+
monitorStep.data!.metricMonitor = {
|
|
83
|
+
metricViewConfig,
|
|
84
|
+
rollingTime: RollingTime.Past1Minute,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const aggregated: AggregatedResult = {
|
|
88
|
+
data: input.sampleValues.map((v: number) => {
|
|
89
|
+
return {
|
|
90
|
+
timestamp: new Date(),
|
|
91
|
+
value: v,
|
|
92
|
+
} as AggregateModel;
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
monitor: new Monitor(),
|
|
98
|
+
criteriaFilter: input.criteriaFilter,
|
|
99
|
+
monitorStep,
|
|
100
|
+
dataToProcess: {
|
|
101
|
+
projectId: ObjectID.generate(),
|
|
102
|
+
metricResult: [aggregated],
|
|
103
|
+
metricViewConfig,
|
|
104
|
+
monitorId: ObjectID.generate(),
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface AgreementCase {
|
|
110
|
+
name: string;
|
|
111
|
+
metricName: string;
|
|
112
|
+
nativeUnit: string;
|
|
113
|
+
sample: number;
|
|
114
|
+
threshold: string;
|
|
115
|
+
/** The rendering both paths must produce for `sample`. */
|
|
116
|
+
expectedValue: string;
|
|
117
|
+
/** The rendering both paths must produce for `threshold`. */
|
|
118
|
+
expectedThreshold: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const CASES: Array<AgreementCase> = [
|
|
122
|
+
{
|
|
123
|
+
name: "bytes rescale to gigabytes",
|
|
124
|
+
metricName: "k8s.pod.memory.usage",
|
|
125
|
+
nativeUnit: "By",
|
|
126
|
+
sample: 1073741824,
|
|
127
|
+
threshold: "1000000000",
|
|
128
|
+
expectedValue: "1.07 GB",
|
|
129
|
+
expectedThreshold: "1 GB",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "milliseconds rescale to seconds",
|
|
133
|
+
metricName: "http.server.request.duration",
|
|
134
|
+
nativeUnit: "ms",
|
|
135
|
+
sample: 1500,
|
|
136
|
+
threshold: "1000",
|
|
137
|
+
expectedValue: "1.5 sec",
|
|
138
|
+
expectedThreshold: "1 sec",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "a fraction metric reads as a percentage",
|
|
142
|
+
metricName: "system.cpu.utilization",
|
|
143
|
+
nativeUnit: "1",
|
|
144
|
+
sample: 0.0585,
|
|
145
|
+
threshold: "0.05",
|
|
146
|
+
expectedValue: "5.85%",
|
|
147
|
+
expectedThreshold: "5.00%",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "a non-fraction metric carrying '1' stays bare",
|
|
151
|
+
metricName: "browser.cumulative_layout_shift",
|
|
152
|
+
nativeUnit: "1",
|
|
153
|
+
sample: 0.31,
|
|
154
|
+
threshold: "0.25",
|
|
155
|
+
expectedValue: "0.31",
|
|
156
|
+
expectedThreshold: "0.25",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "a metric with no unit keeps its exact digits",
|
|
160
|
+
metricName: "http.server.request.count",
|
|
161
|
+
nativeUnit: "",
|
|
162
|
+
sample: 5000,
|
|
163
|
+
threshold: "4900",
|
|
164
|
+
expectedValue: "5000",
|
|
165
|
+
expectedThreshold: "4900",
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
describe("the alert email and the evaluation log describe one sample identically", () => {
|
|
170
|
+
for (const testCase of CASES) {
|
|
171
|
+
test(testCase.name, () => {
|
|
172
|
+
const criteriaFilter: CriteriaFilter = {
|
|
173
|
+
checkOn: CheckOn.MetricValue,
|
|
174
|
+
filterType: FilterType.GreaterThan,
|
|
175
|
+
value: testCase.threshold,
|
|
176
|
+
metricMonitorOptions: {
|
|
177
|
+
metricAlias: "a",
|
|
178
|
+
metricAggregationType: EvaluateOverTimeType.AnyValue,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
183
|
+
metricNativeUnit: testCase.nativeUnit,
|
|
184
|
+
metricName: testCase.metricName,
|
|
185
|
+
sampleValues: [testCase.sample],
|
|
186
|
+
criteriaFilter,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
/*
|
|
190
|
+
* What the alert email says — CompareCriteria's sentence, which is
|
|
191
|
+
* returned verbatim as the root cause when the filter matched.
|
|
192
|
+
*/
|
|
193
|
+
const emailLine: string = CompareCriteria.getCompareMessage({
|
|
194
|
+
values: testCase.sample,
|
|
195
|
+
threshold: Number(testCase.threshold),
|
|
196
|
+
criteriaFilter: criteriaFilter,
|
|
197
|
+
metricDisplayName: testCase.metricName,
|
|
198
|
+
metricName: testCase.metricName,
|
|
199
|
+
unit: testCase.nativeUnit || undefined,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// What the monitor's evaluation log says for the same sample.
|
|
203
|
+
const evalLogLine: string =
|
|
204
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
205
|
+
...inputs,
|
|
206
|
+
didMeetCriteria: false,
|
|
207
|
+
matchMessage: null,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(emailLine).toContain(testCase.expectedValue);
|
|
211
|
+
expect(evalLogLine).toContain(testCase.expectedValue);
|
|
212
|
+
|
|
213
|
+
expect(emailLine).toContain(testCase.expectedThreshold);
|
|
214
|
+
expect(evalLogLine).toContain(testCase.expectedThreshold);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/*
|
|
219
|
+
* The specific strings the drift used to produce. Asserting their
|
|
220
|
+
* absence is what makes this suite fail loudly if either path is
|
|
221
|
+
* reverted to its own formatter.
|
|
222
|
+
*/
|
|
223
|
+
test("neither path emits the pre-migration renderings", () => {
|
|
224
|
+
const criteriaFilter: CriteriaFilter = {
|
|
225
|
+
checkOn: CheckOn.MetricValue,
|
|
226
|
+
filterType: FilterType.GreaterThan,
|
|
227
|
+
value: "1000000000",
|
|
228
|
+
metricMonitorOptions: {
|
|
229
|
+
metricAlias: "a",
|
|
230
|
+
metricAggregationType: EvaluateOverTimeType.AnyValue,
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
235
|
+
metricNativeUnit: "By",
|
|
236
|
+
metricName: "k8s.pod.memory.usage",
|
|
237
|
+
sampleValues: [1073741824],
|
|
238
|
+
criteriaFilter,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const evalLogLine: string =
|
|
242
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
243
|
+
...inputs,
|
|
244
|
+
didMeetCriteria: false,
|
|
245
|
+
matchMessage: null,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect(evalLogLine).not.toContain("1073741824");
|
|
249
|
+
expect(evalLogLine).not.toContain(" By");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
/*
|
|
253
|
+
* A matched filter short-circuits: the message builder returns
|
|
254
|
+
* CompareCriteria's sentence untouched. Pinning this keeps the two
|
|
255
|
+
* paths from being confused for one another — the agreement above is a
|
|
256
|
+
* property of two DIFFERENT renderers, not of one shared call.
|
|
257
|
+
*/
|
|
258
|
+
test("a matched filter returns the email sentence verbatim", () => {
|
|
259
|
+
const criteriaFilter: CriteriaFilter = {
|
|
260
|
+
checkOn: CheckOn.MetricValue,
|
|
261
|
+
filterType: FilterType.GreaterThan,
|
|
262
|
+
value: "1000000000",
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const inputs: ReturnType<typeof buildInputs> = buildInputs({
|
|
266
|
+
metricNativeUnit: "By",
|
|
267
|
+
metricName: "k8s.pod.memory.usage",
|
|
268
|
+
sampleValues: [1073741824],
|
|
269
|
+
criteriaFilter,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
expect(
|
|
273
|
+
MonitorCriteriaMessageBuilder.buildCriteriaFilterMessage({
|
|
274
|
+
...inputs,
|
|
275
|
+
didMeetCriteria: true,
|
|
276
|
+
matchMessage: "Metric Value is 1.07 GB which is greater than 1 GB.",
|
|
277
|
+
}),
|
|
278
|
+
).toBe("Metric Value is 1.07 GB which is greater than 1 GB.");
|
|
279
|
+
});
|
|
280
|
+
});
|