@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,570 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The evaluator's import chain pulls the native isolated-vm addon
|
|
3
|
+
* (MonitorCriteriaEvaluator → VMAPI → VMRunner). Nothing under test here
|
|
4
|
+
* touches the sandbox, and the prebuilt binary cannot always dlopen in the
|
|
5
|
+
* test environment — so stub the module out before anything imports it.
|
|
6
|
+
*/
|
|
7
|
+
jest.mock("isolated-vm", () => {
|
|
8
|
+
return {};
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
import MonitorCriteriaEvaluator from "../../../../Server/Utils/Monitor/MonitorCriteriaEvaluator";
|
|
12
|
+
import CompareCriteria from "../../../../Server/Utils/Monitor/Criteria/CompareCriteria";
|
|
13
|
+
import Monitor from "../../../../Models/DatabaseModels/Monitor";
|
|
14
|
+
import MonitorStep from "../../../../Types/Monitor/MonitorStep";
|
|
15
|
+
import MonitorCriteriaInstance from "../../../../Types/Monitor/MonitorCriteriaInstance";
|
|
16
|
+
import MetricCriteriaContext from "../../../../Types/Monitor/MetricMonitor/MetricCriteriaContext";
|
|
17
|
+
import {
|
|
18
|
+
CheckOn,
|
|
19
|
+
CriteriaFilter,
|
|
20
|
+
FilterType,
|
|
21
|
+
} from "../../../../Types/Monitor/CriteriaFilter";
|
|
22
|
+
import FilterCondition from "../../../../Types/Filter/FilterCondition";
|
|
23
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
24
|
+
import { describe, expect, test } from "@jest/globals";
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* HUMAN-READABLE METRIC VALUES IN THE ROOT CAUSE.
|
|
28
|
+
*
|
|
29
|
+
* The metric root cause is what an alert or incident email shows under
|
|
30
|
+
* "Root Cause" — MonitorCriteriaEvaluator writes it, AlertService and
|
|
31
|
+
* IncidentService copy it onto the row, and the owner-notification jobs
|
|
32
|
+
* run it through Markdown.convertToHTML for the email body.
|
|
33
|
+
*
|
|
34
|
+
* Before this suite existed, nothing anywhere pinned the Breaching
|
|
35
|
+
* Samples table or the "- Unit:" line, and both rendered raw digits with
|
|
36
|
+
* the exporter's raw UCUM code glued on: a memory breach that the
|
|
37
|
+
* dashboard drew as "1.07 GB" arrived in the inbox as "1073741824 By",
|
|
38
|
+
* and a ratio metric's Value column read "0.06 1".
|
|
39
|
+
*
|
|
40
|
+
* These tests pin:
|
|
41
|
+
* - the Value column at human scale, in the unit each row landed on,
|
|
42
|
+
* - component columns each in their OWN unit, with no unit in the header
|
|
43
|
+
* (auto-scaling means neighbouring rows disagree with any one header),
|
|
44
|
+
* - the "- Unit:" line spelled out, and dropped when there is no unit,
|
|
45
|
+
* - the formula guard: a formula expression is not a metric name,
|
|
46
|
+
* - that the table and the "Filter Conditions Met" sentence in the same
|
|
47
|
+
* email render the same sample identically.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
type EvaluatorPrivate = {
|
|
51
|
+
buildMetricRootCauseContext: (input: {
|
|
52
|
+
criteriaInstance: MonitorCriteriaInstance;
|
|
53
|
+
monitor: Monitor;
|
|
54
|
+
monitorStep?: MonitorStep | undefined;
|
|
55
|
+
}) => string | null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const Evaluator: EvaluatorPrivate =
|
|
59
|
+
MonitorCriteriaEvaluator as unknown as EvaluatorPrivate;
|
|
60
|
+
|
|
61
|
+
const BREACH_TIME: Date = new Date("2026-08-14T10:30:00.000Z");
|
|
62
|
+
const BREACH_TIME_ISO: string = "2026-08-14T10:30:00.000Z";
|
|
63
|
+
|
|
64
|
+
function makeContext(
|
|
65
|
+
overrides: Partial<MetricCriteriaContext> = {},
|
|
66
|
+
): MetricCriteriaContext {
|
|
67
|
+
return {
|
|
68
|
+
metricName: "k8s.pod.memory.usage",
|
|
69
|
+
alias: "a",
|
|
70
|
+
unit: null,
|
|
71
|
+
aggregationType: null,
|
|
72
|
+
isFormula: false,
|
|
73
|
+
filterAttributes: {},
|
|
74
|
+
groupBy: [],
|
|
75
|
+
breachingSamples: [
|
|
76
|
+
{
|
|
77
|
+
value: 1073741824,
|
|
78
|
+
timestamp: BREACH_TIME,
|
|
79
|
+
attributes: {},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
...overrides,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function makeCriteriaInstance(
|
|
87
|
+
ctx: MetricCriteriaContext,
|
|
88
|
+
): MonitorCriteriaInstance {
|
|
89
|
+
const filter: CriteriaFilter = {
|
|
90
|
+
checkOn: CheckOn.MetricValue,
|
|
91
|
+
metricCriteriaContext: ctx,
|
|
92
|
+
} as CriteriaFilter;
|
|
93
|
+
|
|
94
|
+
const instance: MonitorCriteriaInstance = new MonitorCriteriaInstance();
|
|
95
|
+
instance.data = {
|
|
96
|
+
monitorStatusId: undefined,
|
|
97
|
+
filterCondition: FilterCondition.All,
|
|
98
|
+
filters: [filter],
|
|
99
|
+
incidents: [],
|
|
100
|
+
alerts: [],
|
|
101
|
+
name: "Memory breach",
|
|
102
|
+
description: "Memory breach",
|
|
103
|
+
id: ObjectID.generate().toString(),
|
|
104
|
+
};
|
|
105
|
+
return instance;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function rootCause(ctx: MetricCriteriaContext): string {
|
|
109
|
+
const context: string | null = Evaluator.buildMetricRootCauseContext({
|
|
110
|
+
criteriaInstance: makeCriteriaInstance(ctx),
|
|
111
|
+
monitor: new Monitor(),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(context).not.toBeNull();
|
|
115
|
+
return context as string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The one data row of the Breaching Samples table. */
|
|
119
|
+
function sampleRow(context: string): string {
|
|
120
|
+
const row: string | undefined = context.split("\n").find((line: string) => {
|
|
121
|
+
return line.includes(BREACH_TIME_ISO);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(row).toBeDefined();
|
|
125
|
+
return row as string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The header row of the Breaching Samples table. */
|
|
129
|
+
function headerRow(context: string): string {
|
|
130
|
+
const row: string | undefined = context.split("\n").find((line: string) => {
|
|
131
|
+
return line.startsWith("| Timestamp |");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(row).toBeDefined();
|
|
135
|
+
return row as string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
describe("MonitorCriteriaEvaluator - Breaching Samples value column", () => {
|
|
139
|
+
test("renders bytes at human scale instead of raw digits", () => {
|
|
140
|
+
const context: string = rootCause(makeContext({ unit: "By" }));
|
|
141
|
+
|
|
142
|
+
expect(sampleRow(context)).toBe(
|
|
143
|
+
`| \`${BREACH_TIME_ISO}\` | \`k8s.pod.memory.usage\` | \`a\` | 1.07 GB |`,
|
|
144
|
+
);
|
|
145
|
+
expect(context).not.toContain("1073741824");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("renders a duration at human scale", () => {
|
|
149
|
+
const context: string = rootCause(
|
|
150
|
+
makeContext({
|
|
151
|
+
metricName: "http.server.duration",
|
|
152
|
+
unit: "ms",
|
|
153
|
+
breachingSamples: [
|
|
154
|
+
{ value: 1500, timestamp: BREACH_TIME, attributes: {} },
|
|
155
|
+
],
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
expect(sampleRow(context)).toContain("| 1.5 sec |");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("renders a value the user typed a GB threshold against in GB terms", () => {
|
|
163
|
+
/*
|
|
164
|
+
* When the criteria carries a thresholdUnit, samples arrive already
|
|
165
|
+
* converted into it — so ctx.unit is "GB" and the values are counts
|
|
166
|
+
* of gigabytes. ValueFormatter has no ladder for "GB" on its own;
|
|
167
|
+
* MetricValueFormatter restates the value in bytes first.
|
|
168
|
+
*/
|
|
169
|
+
const context: string = rootCause(
|
|
170
|
+
makeContext({
|
|
171
|
+
unit: "GB",
|
|
172
|
+
breachingSamples: [
|
|
173
|
+
{ value: 2500, timestamp: BREACH_TIME, attributes: {} },
|
|
174
|
+
],
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
expect(sampleRow(context)).toContain("| 2.5 TB |");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("each row names the unit IT landed on", () => {
|
|
182
|
+
const context: string = rootCause(
|
|
183
|
+
makeContext({
|
|
184
|
+
unit: "By",
|
|
185
|
+
breachingSamples: [
|
|
186
|
+
{
|
|
187
|
+
value: 921600,
|
|
188
|
+
timestamp: new Date("2026-08-14T10:29:00.000Z"),
|
|
189
|
+
attributes: {},
|
|
190
|
+
},
|
|
191
|
+
{ value: 1258291, timestamp: BREACH_TIME, attributes: {} },
|
|
192
|
+
],
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
* 922 KB and 1.26 MB in the same table. This is exactly why the unit
|
|
198
|
+
* rides each cell instead of the column header — no single header
|
|
199
|
+
* could describe both rows.
|
|
200
|
+
*/
|
|
201
|
+
expect(context).toContain("| 922 KB |");
|
|
202
|
+
expect(context).toContain("| 1.26 MB |");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("a ratio metric's fraction is shown as the percentage it means", () => {
|
|
206
|
+
const context: string = rootCause(
|
|
207
|
+
makeContext({
|
|
208
|
+
metricName: "system.cpu.utilization",
|
|
209
|
+
unit: "1",
|
|
210
|
+
breachingSamples: [
|
|
211
|
+
{ value: 0.0585, timestamp: BREACH_TIME, attributes: {} },
|
|
212
|
+
],
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
expect(sampleRow(context)).toContain("| 5.85% |");
|
|
217
|
+
// The old rendering: a bare "0.06", which reads as 0.06%.
|
|
218
|
+
expect(context).not.toContain("| 0.06 |");
|
|
219
|
+
expect(context).not.toContain("0.06 1");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("a UCUM annotation-only unit is dropped rather than printed", () => {
|
|
223
|
+
const context: string = rootCause(
|
|
224
|
+
makeContext({
|
|
225
|
+
metricName: "process.threads",
|
|
226
|
+
unit: "{thread}",
|
|
227
|
+
breachingSamples: [
|
|
228
|
+
{ value: 512, timestamp: BREACH_TIME, attributes: {} },
|
|
229
|
+
],
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
expect(sampleRow(context)).toContain("| 512 |");
|
|
234
|
+
expect(context).not.toContain("{thread}");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
/*
|
|
238
|
+
* BACKWARD COMPATIBILITY. A metric with no unit must keep every digit —
|
|
239
|
+
* a counter at 5000 is not "5K" in an alert, because the reader may be
|
|
240
|
+
* scanning the table for the exact value that crossed the threshold.
|
|
241
|
+
*/
|
|
242
|
+
test("a unitless value keeps its exact digits", () => {
|
|
243
|
+
const context: string = rootCause(
|
|
244
|
+
makeContext({
|
|
245
|
+
metricName: "http.server.request.count",
|
|
246
|
+
unit: null,
|
|
247
|
+
breachingSamples: [
|
|
248
|
+
{ value: 5000, timestamp: BREACH_TIME, attributes: {} },
|
|
249
|
+
],
|
|
250
|
+
}),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
expect(sampleRow(context)).toContain("| 5000 |");
|
|
254
|
+
expect(context).not.toContain("5K");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("a unitless non-integer is rounded to two decimals", () => {
|
|
258
|
+
const context: string = rootCause(
|
|
259
|
+
makeContext({
|
|
260
|
+
unit: null,
|
|
261
|
+
breachingSamples: [
|
|
262
|
+
{ value: 2.3333333333333335, timestamp: BREACH_TIME, attributes: {} },
|
|
263
|
+
],
|
|
264
|
+
}),
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
expect(sampleRow(context)).toContain("| 2.33 |");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
/*
|
|
271
|
+
* ValueFormatter renders Infinity as "InfinityP PB" — its
|
|
272
|
+
* formatLargeNumber divides by 1e15, gets Infinity back and appends the
|
|
273
|
+
* "P". A broken metric must not produce that in an on-call email.
|
|
274
|
+
*/
|
|
275
|
+
test("a non-finite sample is reported verbatim", () => {
|
|
276
|
+
const context: string = rootCause(
|
|
277
|
+
makeContext({
|
|
278
|
+
unit: "By",
|
|
279
|
+
breachingSamples: [
|
|
280
|
+
{ value: Infinity, timestamp: BREACH_TIME, attributes: {} },
|
|
281
|
+
],
|
|
282
|
+
}),
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
expect(sampleRow(context)).toContain("| Infinity |");
|
|
286
|
+
expect(context).not.toContain("InfinityP");
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("attribute columns are unaffected", () => {
|
|
290
|
+
const context: string = rootCause(
|
|
291
|
+
makeContext({
|
|
292
|
+
unit: "By",
|
|
293
|
+
groupBy: ["k8s.pod.name"],
|
|
294
|
+
breachingSamples: [
|
|
295
|
+
{
|
|
296
|
+
value: 1073741824,
|
|
297
|
+
timestamp: BREACH_TIME,
|
|
298
|
+
attributes: { "k8s.pod.name": "web-1" },
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
expect(headerRow(context)).toBe(
|
|
305
|
+
"| Timestamp | Metric | Alias | Value | k8s.pod.name |",
|
|
306
|
+
);
|
|
307
|
+
expect(sampleRow(context)).toContain("| web-1 |");
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe("MonitorCriteriaEvaluator - Breaching Samples component columns", () => {
|
|
312
|
+
function formulaContext(): MetricCriteriaContext {
|
|
313
|
+
return makeContext({
|
|
314
|
+
metricName: "a + b",
|
|
315
|
+
alias: "c",
|
|
316
|
+
isFormula: true,
|
|
317
|
+
formulaExpression: "a + b",
|
|
318
|
+
unit: "By",
|
|
319
|
+
components: [
|
|
320
|
+
{
|
|
321
|
+
alias: "a",
|
|
322
|
+
name: "container.memory.usage",
|
|
323
|
+
unit: "By",
|
|
324
|
+
isFormula: false,
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
alias: "b",
|
|
328
|
+
name: "container.memory.cache",
|
|
329
|
+
unit: "ms",
|
|
330
|
+
isFormula: false,
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
breachingSamples: [
|
|
334
|
+
{
|
|
335
|
+
value: 1073741824,
|
|
336
|
+
timestamp: BREACH_TIME,
|
|
337
|
+
attributes: {},
|
|
338
|
+
componentValues: [
|
|
339
|
+
{ alias: "a", value: 536870912 },
|
|
340
|
+
{ alias: "b", value: 1500 },
|
|
341
|
+
],
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
test("each component cell is formatted in its own unit", () => {
|
|
348
|
+
const context: string = rootCause(formulaContext());
|
|
349
|
+
|
|
350
|
+
expect(sampleRow(context)).toBe(
|
|
351
|
+
`| \`${BREACH_TIME_ISO}\` | \`a + b\` | \`c\` | 1.07 GB | 537 MB | 1.5 sec |`,
|
|
352
|
+
);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
/*
|
|
356
|
+
* The header used to read "a (By)". Now that a cell says which scale it
|
|
357
|
+
* landed on, a header unit is a claim two rows of the same column can
|
|
358
|
+
* contradict — and the Components bullet list above the table still
|
|
359
|
+
* records the configured unit, so nothing is lost.
|
|
360
|
+
*/
|
|
361
|
+
test("component headers carry the alias only, not the configured unit", () => {
|
|
362
|
+
const context: string = rootCause(formulaContext());
|
|
363
|
+
|
|
364
|
+
expect(headerRow(context)).toBe(
|
|
365
|
+
"| Timestamp | Metric | Alias | Value | a | b |",
|
|
366
|
+
);
|
|
367
|
+
expect(context).not.toContain("| a (By) |");
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("the Components bullet list spells each component's unit out", () => {
|
|
371
|
+
const context: string = rootCause(formulaContext());
|
|
372
|
+
|
|
373
|
+
expect(context).toContain("- `a` = `container.memory.usage` — unit: Bytes");
|
|
374
|
+
expect(context).toContain(
|
|
375
|
+
"- `b` = `container.memory.cache` — unit: Milliseconds",
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("a component with no value for this sample still renders a dash", () => {
|
|
380
|
+
const ctx: MetricCriteriaContext = formulaContext();
|
|
381
|
+
ctx.breachingSamples![0]!.componentValues = [
|
|
382
|
+
{ alias: "a", value: 536870912 },
|
|
383
|
+
{ alias: "b", value: null },
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
expect(sampleRow(rootCause(ctx))).toContain("| 537 MB | - |");
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
describe("MonitorCriteriaEvaluator - the Unit line", () => {
|
|
391
|
+
test("spells the UCUM code out", () => {
|
|
392
|
+
expect(rootCause(makeContext({ unit: "By" }))).toContain("- Unit: Bytes");
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("names the percent a fraction metric carries", () => {
|
|
396
|
+
const context: string = rootCause(
|
|
397
|
+
makeContext({ metricName: "system.cpu.utilization", unit: "1" }),
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
expect(context).toContain("- Unit: Percent");
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
/*
|
|
404
|
+
* The line used to read "- Unit: 1" for every ratio metric — a unit
|
|
405
|
+
* that names nothing, printed as if it did.
|
|
406
|
+
*/
|
|
407
|
+
test("is dropped entirely when the unit names no dimension", () => {
|
|
408
|
+
expect(rootCause(makeContext({ unit: "1" }))).not.toContain("- Unit:");
|
|
409
|
+
expect(rootCause(makeContext({ unit: "{thread}" }))).not.toContain(
|
|
410
|
+
"- Unit:",
|
|
411
|
+
);
|
|
412
|
+
expect(rootCause(makeContext({ unit: null }))).not.toContain("- Unit:");
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
/*
|
|
417
|
+
* THE FORMULA GUARD.
|
|
418
|
+
*
|
|
419
|
+
* MetricCriteriaContext.metricName is the FORMULA EXPRESSION when the
|
|
420
|
+
* criteria targets a formula. The only use a name has downstream is
|
|
421
|
+
* ValueFormatter.isFractionMetric, whose regex asks whether the name ends
|
|
422
|
+
* in `.utilization` / `_ratio` / `_percent`. A formula written as
|
|
423
|
+
* `a / b_ratio` ends in `_ratio`, so feeding it through would silently
|
|
424
|
+
* report every value at 100× its real magnitude.
|
|
425
|
+
*/
|
|
426
|
+
describe("MonitorCriteriaEvaluator - formula names are not metric names", () => {
|
|
427
|
+
test("a formula ending in _ratio is not multiplied by 100", () => {
|
|
428
|
+
const context: string = rootCause(
|
|
429
|
+
makeContext({
|
|
430
|
+
metricName: "a / b_ratio",
|
|
431
|
+
alias: "c",
|
|
432
|
+
isFormula: true,
|
|
433
|
+
formulaExpression: "a / b_ratio",
|
|
434
|
+
unit: "1",
|
|
435
|
+
breachingSamples: [
|
|
436
|
+
{ value: 0.42, timestamp: BREACH_TIME, attributes: {} },
|
|
437
|
+
],
|
|
438
|
+
}),
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
expect(sampleRow(context)).toContain("| 0.42 |");
|
|
442
|
+
expect(context).not.toContain("42.00%");
|
|
443
|
+
expect(context).not.toContain("- Unit:");
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test("a formula COMPONENT ending in _ratio is not multiplied by 100 either", () => {
|
|
447
|
+
const context: string = rootCause(
|
|
448
|
+
makeContext({
|
|
449
|
+
metricName: "c",
|
|
450
|
+
alias: "c",
|
|
451
|
+
isFormula: true,
|
|
452
|
+
formulaExpression: "x",
|
|
453
|
+
unit: null,
|
|
454
|
+
components: [
|
|
455
|
+
{
|
|
456
|
+
alias: "x",
|
|
457
|
+
name: "p / q_utilization",
|
|
458
|
+
unit: "1",
|
|
459
|
+
isFormula: true,
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
breachingSamples: [
|
|
463
|
+
{
|
|
464
|
+
value: 1,
|
|
465
|
+
timestamp: BREACH_TIME,
|
|
466
|
+
attributes: {},
|
|
467
|
+
componentValues: [{ alias: "x", value: 0.42 }],
|
|
468
|
+
},
|
|
469
|
+
],
|
|
470
|
+
}),
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
expect(sampleRow(context)).toContain("| 0.42 |");
|
|
474
|
+
expect(context).not.toContain("42.00%");
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
/*
|
|
478
|
+
* A plain metric criteria DOES get the heuristic — this is the control
|
|
479
|
+
* that shows the guard is narrow rather than a blanket opt-out.
|
|
480
|
+
*/
|
|
481
|
+
test("a plain metric criteria still gets the fraction heuristic", () => {
|
|
482
|
+
const context: string = rootCause(
|
|
483
|
+
makeContext({
|
|
484
|
+
metricName: "system.memory.utilization",
|
|
485
|
+
isFormula: false,
|
|
486
|
+
unit: "1",
|
|
487
|
+
breachingSamples: [
|
|
488
|
+
{ value: 0.42, timestamp: BREACH_TIME, attributes: {} },
|
|
489
|
+
],
|
|
490
|
+
}),
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
expect(sampleRow(context)).toContain("| 42.00% |");
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
/*
|
|
498
|
+
* ONE EMAIL, ONE ANSWER.
|
|
499
|
+
*
|
|
500
|
+
* The unit-suppression rule was hand-rolled three separate times before
|
|
501
|
+
* this change — in CompareCriteria's "Filter Conditions Met" sentence, in
|
|
502
|
+
* MonitorCriteriaObservationBuilder, and nowhere at all in the Breaching
|
|
503
|
+
* Samples table — with the result that a single evaluation read
|
|
504
|
+
* differently in its own two halves: "0.31" in the sentence and "0.31 1"
|
|
505
|
+
* in the table directly beneath it.
|
|
506
|
+
*
|
|
507
|
+
* Both sections of one email now go through MetricValueFormatter. These
|
|
508
|
+
* tests assert the two strings are IDENTICAL rather than merely both
|
|
509
|
+
* plausible, so a fourth copy of the rule cannot quietly appear.
|
|
510
|
+
*/
|
|
511
|
+
describe("MonitorCriteriaEvaluator - the table and the sentence agree", () => {
|
|
512
|
+
function bothRenderings(input: {
|
|
513
|
+
value: number;
|
|
514
|
+
unit: string | null;
|
|
515
|
+
metricName: string;
|
|
516
|
+
}): { cell: string; sentence: string } {
|
|
517
|
+
const ctx: MetricCriteriaContext = makeContext({
|
|
518
|
+
metricName: input.metricName,
|
|
519
|
+
unit: input.unit,
|
|
520
|
+
breachingSamples: [
|
|
521
|
+
{ value: input.value, timestamp: BREACH_TIME, attributes: {} },
|
|
522
|
+
],
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
const row: string = sampleRow(rootCause(ctx));
|
|
526
|
+
const cells: Array<string> = row.split("|").map((cell: string) => {
|
|
527
|
+
return cell.trim();
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
return {
|
|
531
|
+
// | ts | metric | alias | VALUE | → index 4 once the leading "" is counted.
|
|
532
|
+
cell: cells[4] as string,
|
|
533
|
+
sentence: CompareCriteria.getCompareMessage({
|
|
534
|
+
values: input.value,
|
|
535
|
+
threshold: input.value,
|
|
536
|
+
criteriaFilter: {
|
|
537
|
+
checkOn: CheckOn.MetricValue,
|
|
538
|
+
filterType: FilterType.GreaterThan,
|
|
539
|
+
} as CriteriaFilter,
|
|
540
|
+
metricDisplayName: input.metricName,
|
|
541
|
+
metricName: input.metricName,
|
|
542
|
+
unit: input.unit || undefined,
|
|
543
|
+
}),
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const cases: Array<{
|
|
548
|
+
value: number;
|
|
549
|
+
unit: string | null;
|
|
550
|
+
metricName: string;
|
|
551
|
+
}> = [
|
|
552
|
+
{ value: 1073741824, unit: "By", metricName: "k8s.pod.memory.usage" },
|
|
553
|
+
{ value: 1500, unit: "ms", metricName: "http.server.duration" },
|
|
554
|
+
{ value: 0.0585, unit: "1", metricName: "system.cpu.utilization" },
|
|
555
|
+
{ value: 0.31, unit: "1", metricName: "browser.cls" },
|
|
556
|
+
{ value: 5000, unit: null, metricName: "http.server.request.count" },
|
|
557
|
+
{ value: 2500, unit: "GB", metricName: "system.filesystem.usage" },
|
|
558
|
+
];
|
|
559
|
+
|
|
560
|
+
for (const testCase of cases) {
|
|
561
|
+
test(`${testCase.metricName} @ ${testCase.unit ?? "no unit"} reads the same in both`, () => {
|
|
562
|
+
const rendered: { cell: string; sentence: string } =
|
|
563
|
+
bothRenderings(testCase);
|
|
564
|
+
|
|
565
|
+
expect(rendered.cell.length).toBeGreaterThan(0);
|
|
566
|
+
expect(rendered.sentence).toContain(`is ${rendered.cell} which is`);
|
|
567
|
+
expect(rendered.sentence).toContain(`greater than ${rendered.cell}.`);
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
});
|
|
@@ -517,7 +517,18 @@ describe("MonitorCriteriaExpectationBuilder.describeCriteriaExpectation", () =>
|
|
|
517
517
|
* template string, so a negative sign and any decimals survive verbatim
|
|
518
518
|
* (no toFixed / number formatting), and the unit suffix still follows.
|
|
519
519
|
*/
|
|
520
|
-
|
|
520
|
+
/*
|
|
521
|
+
* A threshold carrying a unit is rendered by the same formatter as the
|
|
522
|
+
* observed value beside it, so both halves of one sentence describe the
|
|
523
|
+
* quantity the same way. That means it can be RESCALED — "-0.5 sec" is
|
|
524
|
+
* written "-500 ms", the same number at the scale a reader reads it at,
|
|
525
|
+
* and the observation in that sentence uses milliseconds too.
|
|
526
|
+
*
|
|
527
|
+
* What must never happen is decimal PADDING: a threshold the user typed
|
|
528
|
+
* as "-5" must not come back as "-5.00", which is what the old raw
|
|
529
|
+
* interpolation was protecting against and what these still assert.
|
|
530
|
+
*/
|
|
531
|
+
describe("negative thresholds with a unit are formatted, never padded", () => {
|
|
521
532
|
test("GreaterThan '-5' with { unit: 'sec' } => 'to be greater than -5 sec'", () => {
|
|
522
533
|
const filter: CriteriaFilter = makeFilter({
|
|
523
534
|
filterType: FilterType.GreaterThan,
|
|
@@ -530,11 +541,11 @@ describe("MonitorCriteriaExpectationBuilder.describeCriteriaExpectation", () =>
|
|
|
530
541
|
});
|
|
531
542
|
|
|
532
543
|
expect(result).toBe("to be greater than -5 sec");
|
|
533
|
-
//
|
|
544
|
+
// Integers keep their digits — never padded to "-5.00".
|
|
534
545
|
expect(result).not.toContain("-5.00");
|
|
535
546
|
});
|
|
536
547
|
|
|
537
|
-
test("LessThan '-0.5' with { unit: 'sec' }
|
|
548
|
+
test("LessThan '-0.5' with { unit: 'sec' } rescales to milliseconds", () => {
|
|
538
549
|
const filter: CriteriaFilter = makeFilter({
|
|
539
550
|
filterType: FilterType.LessThan,
|
|
540
551
|
value: "-0.5",
|
|
@@ -545,9 +556,23 @@ describe("MonitorCriteriaExpectationBuilder.describeCriteriaExpectation", () =>
|
|
|
545
556
|
unit: "sec",
|
|
546
557
|
});
|
|
547
558
|
|
|
548
|
-
expect(result).toBe("to be less than -
|
|
549
|
-
// Raw interpolation — the decimal is kept as-is, not padded to "-0.50".
|
|
559
|
+
expect(result).toBe("to be less than -500 ms");
|
|
550
560
|
expect(result).not.toContain("-0.50");
|
|
551
561
|
});
|
|
562
|
+
|
|
563
|
+
/*
|
|
564
|
+
* The boundary the formatter is gated on: with no unit resolved — every
|
|
565
|
+
* non-metric checkOn — the threshold is interpolated exactly as before.
|
|
566
|
+
*/
|
|
567
|
+
test("a threshold with no unit is still interpolated raw", () => {
|
|
568
|
+
const filter: CriteriaFilter = makeFilter({
|
|
569
|
+
filterType: FilterType.LessThan,
|
|
570
|
+
value: "-0.5",
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
expect(
|
|
574
|
+
MonitorCriteriaExpectationBuilder.describeCriteriaExpectation(filter),
|
|
575
|
+
).toBe("to be less than -0.5");
|
|
576
|
+
});
|
|
552
577
|
});
|
|
553
578
|
});
|