@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
|
@@ -124,7 +124,7 @@ const build: BuildFunction = (
|
|
|
124
124
|
projectName: projectName,
|
|
125
125
|
projectHomeLink: "https://oneuptime.example.com/dashboard/p1/home",
|
|
126
126
|
preferencesLink:
|
|
127
|
-
"https://oneuptime.example.com/dashboard/p1/user-settings/
|
|
127
|
+
"https://oneuptime.example.com/dashboard/p1/user-settings/email-preferences",
|
|
128
128
|
items: sortAscending(items),
|
|
129
129
|
});
|
|
130
130
|
};
|
|
@@ -661,7 +661,7 @@ describe("buildRollupEmail escaping contract", () => {
|
|
|
661
661
|
"https://oneuptime.example.com/dashboard/p1/home",
|
|
662
662
|
);
|
|
663
663
|
expect(varString(email, "preferencesLink")).toBe(
|
|
664
|
-
"https://oneuptime.example.com/dashboard/p1/user-settings/
|
|
664
|
+
"https://oneuptime.example.com/dashboard/p1/user-settings/email-preferences",
|
|
665
665
|
);
|
|
666
666
|
});
|
|
667
667
|
});
|
|
@@ -858,7 +858,14 @@ describe("buildRollupEmail variable set", () => {
|
|
|
858
858
|
* conclude batching is mandatory.
|
|
859
859
|
*/
|
|
860
860
|
expect(preferences).toContain("turn off email rollup for this project");
|
|
861
|
-
|
|
861
|
+
/*
|
|
862
|
+
* "email preferences", not "notification settings": the switch this
|
|
863
|
+
* paragraph promises is on the Email Preferences page, and naming the
|
|
864
|
+
* wrong page is the same failure as omitting the paragraph - the reader
|
|
865
|
+
* goes looking, does not find it, and concludes batching is mandatory.
|
|
866
|
+
*/
|
|
867
|
+
expect(preferences).toContain("email preferences");
|
|
868
|
+
expect(preferences).not.toContain("notification settings");
|
|
862
869
|
|
|
863
870
|
/*
|
|
864
871
|
* The template renders preferencesLink as a bare URL on the line below
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import timers from "timers";
|
|
3
|
+
import EventLoop from "../../../Server/Utils/EventLoop";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* Common's jest environment is jsdom, which does not expose setImmediate -
|
|
7
|
+
* and setImmediate is the entire subject of this file. A `@jest-environment
|
|
8
|
+
* node` docblock is not an option (Common's shared jest.setup.ts touches
|
|
9
|
+
* `window`), so lend jsdom the real one from Node, exactly as
|
|
10
|
+
* MultipartFormData.test.ts does.
|
|
11
|
+
*
|
|
12
|
+
* This matters beyond the plumbing: the helper is server-only code, so jsdom
|
|
13
|
+
* not having setImmediate is correct - but it does mean nothing under
|
|
14
|
+
* Common's default environment can reach this function at all, which is
|
|
15
|
+
* most of why it went untested.
|
|
16
|
+
*/
|
|
17
|
+
if (
|
|
18
|
+
typeof (globalThis as unknown as { setImmediate?: unknown }).setImmediate !==
|
|
19
|
+
"function"
|
|
20
|
+
) {
|
|
21
|
+
(globalThis as unknown as { setImmediate: unknown }).setImmediate =
|
|
22
|
+
timers.setImmediate;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
* The whole value of this one-line helper is the DIFFERENCE between it and
|
|
27
|
+
* `await Promise.resolve()`, and that difference is invisible in the source.
|
|
28
|
+
*
|
|
29
|
+
* The failure it exists to prevent: a telemetry ingest job transforms tens of
|
|
30
|
+
* thousands of OTLP records in one synchronous run. While it does, the
|
|
31
|
+
* single-threaded event loop never reaches its poll phase, so the HTTP handler
|
|
32
|
+
* for /status/live never gets scheduled, the kubelet's liveness probe times
|
|
33
|
+
* out, and Kubernetes restarts a pod that was working perfectly - mid-ingest.
|
|
34
|
+
*
|
|
35
|
+
* Awaiting a resolved promise does NOT fix that. It drains the microtask
|
|
36
|
+
* queue and hands control straight back, so a loop that awaits one on every
|
|
37
|
+
* iteration still starves the probe handler exactly as badly. Only a macrotask
|
|
38
|
+
* - setImmediate, scheduled in the check phase, which runs after the poll
|
|
39
|
+
* phase has had its turn - actually lets pending I/O and timers through.
|
|
40
|
+
*
|
|
41
|
+
* So the tests below are comparative on purpose. Asserting that
|
|
42
|
+
* yieldToEventLoop resolves would pass equally well for the broken
|
|
43
|
+
* implementation this file was written to rule out; asserting that a timer
|
|
44
|
+
* already past its deadline runs across it, and does NOT run across a
|
|
45
|
+
* microtask drain, is what distinguishes them.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Hold the event loop for at least `milliseconds` without ever yielding -
|
|
50
|
+
* exactly what a chunk of synchronous OTLP transformation does to it. There
|
|
51
|
+
* is no await in here, so no callback of any kind can run while it spins,
|
|
52
|
+
* which is what lets the tests below assert on a timer being OVERDUE rather
|
|
53
|
+
* than on how much wall clock a handful of loop turns happens to cover.
|
|
54
|
+
*
|
|
55
|
+
* Timed off the monotonic clock rather than Date.now(), because that is the
|
|
56
|
+
* clock family libuv computes timer deadlines from: a wall-clock reading can
|
|
57
|
+
* be stepped by NTP mid-spin, which would quietly turn "the timer is due now"
|
|
58
|
+
* back into the guess this file exists to get rid of.
|
|
59
|
+
*/
|
|
60
|
+
function blockEventLoopFor(milliseconds: number): void {
|
|
61
|
+
const startedAtNs: bigint = process.hrtime.bigint();
|
|
62
|
+
const spinForNs: number = milliseconds * 1000 * 1000;
|
|
63
|
+
|
|
64
|
+
while (Number(process.hrtime.bigint() - startedAtNs) < spinForNs) {
|
|
65
|
+
// Holding the thread IS the work here.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* Five milliseconds, and the arithmetic behind it:
|
|
71
|
+
*
|
|
72
|
+
* setTimeout(fn, 0) is not a zero millisecond timer. Node's timers
|
|
73
|
+
* documentation is explicit that when the delay is "less than 1 [...] the
|
|
74
|
+
* delay will be set to 1", so the deadline these tests block past is 1 ms.
|
|
75
|
+
*
|
|
76
|
+
* libuv checks that deadline against the loop's cached clock rather than a
|
|
77
|
+
* fresh reading, and it only adopts the coarse monotonic clock when
|
|
78
|
+
* clock_getres reports a resolution of 1 ms or better - otherwise it keeps
|
|
79
|
+
* fine-grained CLOCK_MONOTONIC. So the worst case granularity of the loop
|
|
80
|
+
* clock is 1 ms; it is NOT the kernel tick (4 ms at HZ=250) that the word
|
|
81
|
+
* "coarse" suggests.
|
|
82
|
+
*
|
|
83
|
+
* 1 ms of deadline plus 1 ms of clock granularity is a 2 ms worst case. Five
|
|
84
|
+
* is more than double that, and a 5 ms spin costs a test nothing.
|
|
85
|
+
*/
|
|
86
|
+
const BLOCK_UNTIL_TIMER_IS_OVERDUE_MS: number = 5;
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
* A ceiling for the starvation test below - a bound on how long that test may
|
|
90
|
+
* spend failing, not a prediction of how many turns it needs. The reasoning
|
|
91
|
+
* for the number, and for why the loop is bounded at all, is with the test.
|
|
92
|
+
*/
|
|
93
|
+
const MAX_YIELDS_BEFORE_A_TIMER_MUST_HAVE_FIRED: number = 100000;
|
|
94
|
+
|
|
95
|
+
describe("yieldToEventLoop actually advances the event loop", () => {
|
|
96
|
+
/*
|
|
97
|
+
* This test used to schedule a 0 ms timer and yield up to ten times hoping
|
|
98
|
+
* to catch it, which was a wall-clock bet that lost on CI. Node floors
|
|
99
|
+
* setTimeout(fn, 0) at 1 ms, while a setImmediate ping-pong turns the loop
|
|
100
|
+
* over in single-digit microseconds, so the timer needs however many turns
|
|
101
|
+
* fit inside a millisecond: measured on this machine (Node 22, idle) that
|
|
102
|
+
* is 44 to 324 turns, median around 170. The exact figure is a property of
|
|
103
|
+
* the hardware, not of Node - the point is that it is hundreds of turns and
|
|
104
|
+
* ten was never going to be enough. The old version only passed when the
|
|
105
|
+
* process happened to be descheduled, or the loop's cached time happened to
|
|
106
|
+
* be stale, inside those ten turns.
|
|
107
|
+
*
|
|
108
|
+
* The defect was in the test, not in the helper. yieldToEventLoop hands
|
|
109
|
+
* control back to the loop, and that is all it can do: it cannot make
|
|
110
|
+
* wall-clock time pass, and the one thing that could - setTimeout(resolve,
|
|
111
|
+
* 1) - would be a real pessimisation, capping an ingest loop at a thousand
|
|
112
|
+
* chunks a second to satisfy a test. So this test asserts the guarantee the
|
|
113
|
+
* helper genuinely provides, which is also the one the liveness probe
|
|
114
|
+
* needs: work that is ALREADY due - a timer past its deadline, a queued I/O
|
|
115
|
+
* callback - runs across a yield instead of waiting out the synchronous
|
|
116
|
+
* batch.
|
|
117
|
+
*
|
|
118
|
+
* Two yields rather than one, because the phase the test body resumes in is
|
|
119
|
+
* not ours to choose. The documented rule for setImmediate is that "if an
|
|
120
|
+
* immediate timer is queued from inside an executing callback, that timer
|
|
121
|
+
* will not be triggered until the next event loop iteration", so the second
|
|
122
|
+
* yield's immediate cannot run until an iteration boundary has been crossed
|
|
123
|
+
* - and every iteration runs its timers phase exactly once. Note that this
|
|
124
|
+
* argument deliberately does not depend on WHERE in the iteration timers
|
|
125
|
+
* sit: libuv 1.45 (Node 20) moved them to after the poll phase, and the
|
|
126
|
+
* guarantee survives that move because it only counts iterations. It is a
|
|
127
|
+
* phase-ordering guarantee rather than a timing one, so it holds on a
|
|
128
|
+
* loaded CI runner exactly as well as on an idle laptop. Measured here, one
|
|
129
|
+
* yield was in fact enough 500 times out of 500; two is the bound that
|
|
130
|
+
* needs no assumption about where the loop was when the test resumed.
|
|
131
|
+
*/
|
|
132
|
+
test("lets a timer that came due during blocking work run", async () => {
|
|
133
|
+
let timerFired: boolean = false;
|
|
134
|
+
|
|
135
|
+
setTimeout((): void => {
|
|
136
|
+
timerFired = true;
|
|
137
|
+
}, 0);
|
|
138
|
+
|
|
139
|
+
blockEventLoopFor(BLOCK_UNTIL_TIMER_IS_OVERDUE_MS);
|
|
140
|
+
|
|
141
|
+
/*
|
|
142
|
+
* Nothing ran while the loop was blocked - that starvation is the whole
|
|
143
|
+
* problem the helper exists to relieve, and pinning it here is what keeps
|
|
144
|
+
* the assertion below about the yield rather than about elapsed time.
|
|
145
|
+
*/
|
|
146
|
+
expect(timerFired).toBe(false);
|
|
147
|
+
|
|
148
|
+
await EventLoop.yieldToEventLoop();
|
|
149
|
+
await EventLoop.yieldToEventLoop();
|
|
150
|
+
|
|
151
|
+
expect(timerFired).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/*
|
|
155
|
+
* The control case, and the reason the helper is not just `await
|
|
156
|
+
* Promise.resolve()`. Draining the microtask queue - however many times -
|
|
157
|
+
* never reaches the phase where timers and I/O are serviced.
|
|
158
|
+
*
|
|
159
|
+
* There is deliberately no "and now yield it through" tail here any more:
|
|
160
|
+
* that used to repeat the previous test byte for byte, and the property it
|
|
161
|
+
* was standing in for - that the timer is genuinely still pending rather
|
|
162
|
+
* than lost - is pinned by the starvation test below, which drives the same
|
|
163
|
+
* construct until it fires. Only the first assertion is this test's own.
|
|
164
|
+
*/
|
|
165
|
+
test("a microtask drain, by contrast, does not", async () => {
|
|
166
|
+
let timerFired: boolean = false;
|
|
167
|
+
|
|
168
|
+
const timer: NodeJS.Timeout = setTimeout((): void => {
|
|
169
|
+
timerFired = true;
|
|
170
|
+
}, 0);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
for (let i: number = 0; i < 100000; i++) {
|
|
174
|
+
await Promise.resolve();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
expect(timerFired).toBe(false);
|
|
178
|
+
} finally {
|
|
179
|
+
// Nothing else in the file should inherit a live timer from this one.
|
|
180
|
+
clearTimeout(timer);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
/*
|
|
185
|
+
* The weaker but still real property the original ten-iteration test was
|
|
186
|
+
* reaching for, restored with a bound it can actually keep: a yield loop
|
|
187
|
+
* does not STARVE a timer that is not yet due. A setImmediate ping-pong
|
|
188
|
+
* could in principle keep re-entering the check phase and never let the
|
|
189
|
+
* loop clock advance past the deadline; it does not, and an ingest job that
|
|
190
|
+
* yields in a tight loop depends on it not doing so.
|
|
191
|
+
*
|
|
192
|
+
* The bound here is temporal, not countable. Each yield turns the loop over
|
|
193
|
+
* once, so the timer comes due after however many turns fit in its 1 ms
|
|
194
|
+
* deadline - and that count goes DOWN on a slower or busier machine,
|
|
195
|
+
* because each turn then costs more wall clock. The direction of error is
|
|
196
|
+
* the friendly one: 100,000 is roughly three hundred times the worst case
|
|
197
|
+
* measured here, and CI load only makes it more generous.
|
|
198
|
+
*
|
|
199
|
+
* The loop is capped rather than written `while (!timerFired)` for a
|
|
200
|
+
* reason that is easy to get backwards. Against a microtask-based
|
|
201
|
+
* implementation - the exact regression this file exists to catch - an
|
|
202
|
+
* uncapped loop would never advance the loop clock, so the timer would
|
|
203
|
+
* never fire AND jest's own testTimeout, which is itself a timer, could
|
|
204
|
+
* never fire either: the worker would hang until the job timeout instead of
|
|
205
|
+
* going red. Measured: the uncapped form outlived a 5 second watchdog and
|
|
206
|
+
* had to be killed, while the capped form exhausts 100,000 microtask turns
|
|
207
|
+
* and fails in about 6 ms.
|
|
208
|
+
*/
|
|
209
|
+
test("a yield loop lets a timer that is not yet due through", async () => {
|
|
210
|
+
let timerFired: boolean = false;
|
|
211
|
+
|
|
212
|
+
setTimeout((): void => {
|
|
213
|
+
timerFired = true;
|
|
214
|
+
}, 0);
|
|
215
|
+
|
|
216
|
+
let yields: number = 0;
|
|
217
|
+
|
|
218
|
+
while (!timerFired && yields < MAX_YIELDS_BEFORE_A_TIMER_MUST_HAVE_FIRED) {
|
|
219
|
+
await EventLoop.yieldToEventLoop();
|
|
220
|
+
yields++;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// False here means the cap was exhausted: the yield never let it through.
|
|
224
|
+
expect(timerFired).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
/*
|
|
228
|
+
* What separates setImmediate from setTimeout(resolve, 0), which every
|
|
229
|
+
* other test in this file is blind to - a mutant helper built on a 0 ms
|
|
230
|
+
* timer passes all of them, and the comment above argues against exactly
|
|
231
|
+
* that substitution, so something has to hold the line.
|
|
232
|
+
*
|
|
233
|
+
* setImmediate resolves in the check phase of the loop iteration it is
|
|
234
|
+
* queued for, and immediates run in the order they were created, so an
|
|
235
|
+
* immediate queued AFTER the yield cannot have run by the time the yield's
|
|
236
|
+
* continuation does. A timer-based helper cannot manage that: its resolve
|
|
237
|
+
* waits out a 1 ms deadline, which is several hundred check phases away, so
|
|
238
|
+
* the later immediate wins.
|
|
239
|
+
*
|
|
240
|
+
* Repeated a handful of times because a single probe is not quite decisive:
|
|
241
|
+
* measured, setTimeout(resolve, 0) survives one probe about 3 times in 200
|
|
242
|
+
* (when the process is descheduled past the 1 ms floor between the two
|
|
243
|
+
* scheduling calls), but never survives five in a row - 0/200 for both
|
|
244
|
+
* setTimeout(resolve, 0) and setTimeout(resolve, 1), against 200/200 for
|
|
245
|
+
* the real implementation. Repetition costs five loop turns and buys the
|
|
246
|
+
* distinction outright.
|
|
247
|
+
*/
|
|
248
|
+
test("resolves ahead of an immediate queued after it", async () => {
|
|
249
|
+
const probes: number = 5;
|
|
250
|
+
|
|
251
|
+
for (let probe: number = 0; probe < probes; probe++) {
|
|
252
|
+
let laterImmediateFired: boolean = false;
|
|
253
|
+
|
|
254
|
+
const yielded: Promise<void> = EventLoop.yieldToEventLoop();
|
|
255
|
+
|
|
256
|
+
setImmediate((): void => {
|
|
257
|
+
laterImmediateFired = true;
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
await yielded;
|
|
261
|
+
|
|
262
|
+
expect(laterImmediateFired).toBe(false);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("lets a pending immediate callback run", async () => {
|
|
267
|
+
let immediateFired: boolean = false;
|
|
268
|
+
|
|
269
|
+
setImmediate((): void => {
|
|
270
|
+
immediateFired = true;
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
await EventLoop.yieldToEventLoop();
|
|
274
|
+
|
|
275
|
+
expect(immediateFired).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
/*
|
|
279
|
+
* The real usage: a CPU-bound loop that yields every N items. Each yield
|
|
280
|
+
* has to give the loop a turn, not just the first one.
|
|
281
|
+
*/
|
|
282
|
+
test("gives the loop a turn on every iteration of a chunked loop", async () => {
|
|
283
|
+
const ticks: Array<number> = [];
|
|
284
|
+
let chunk: number = 0;
|
|
285
|
+
|
|
286
|
+
const interval: NodeJS.Timeout = setInterval((): void => {
|
|
287
|
+
ticks.push(chunk);
|
|
288
|
+
}, 1);
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
for (chunk = 0; chunk < 5; chunk++) {
|
|
292
|
+
// Stand in for a chunk of synchronous transformation work.
|
|
293
|
+
for (let i: number = 0; i < 10000; i++) {
|
|
294
|
+
void i;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
await EventLoop.yieldToEventLoop();
|
|
298
|
+
await new Promise<void>((resolve: () => void) => {
|
|
299
|
+
setTimeout(resolve, 2);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
} finally {
|
|
303
|
+
clearInterval(interval);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// The interval got scheduled repeatedly, across several different chunks.
|
|
307
|
+
expect(ticks.length).toBeGreaterThan(1);
|
|
308
|
+
expect(new Set(ticks).size).toBeGreaterThan(1);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe("the shape of the helper itself", () => {
|
|
313
|
+
test("resolves with nothing", async () => {
|
|
314
|
+
await expect(EventLoop.yieldToEventLoop()).resolves.toBeUndefined();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("returns a promise rather than running synchronously", () => {
|
|
318
|
+
let settled: boolean = false;
|
|
319
|
+
|
|
320
|
+
const pending: Promise<void> = EventLoop.yieldToEventLoop().then(
|
|
321
|
+
(): void => {
|
|
322
|
+
settled = true;
|
|
323
|
+
},
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
// Still pending on the very next line - it is not a synchronous no-op.
|
|
327
|
+
expect(settled).toBe(false);
|
|
328
|
+
|
|
329
|
+
return pending;
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
/*
|
|
333
|
+
* The real subject here is termination - a yield that never resolved would
|
|
334
|
+
* hang this loop until jest's testTimeout, not fail an assertion - so the
|
|
335
|
+
* count is asserted rather than a bare `expect(true)`: it at least pins
|
|
336
|
+
* that every one of the fifty awaits settled and the loop body ran each
|
|
337
|
+
* time.
|
|
338
|
+
*/
|
|
339
|
+
test("can be awaited many times in a row", async () => {
|
|
340
|
+
let completedYields: number = 0;
|
|
341
|
+
|
|
342
|
+
for (let i: number = 0; i < 50; i++) {
|
|
343
|
+
await EventLoop.yieldToEventLoop();
|
|
344
|
+
completedYields++;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
expect(completedYields).toBe(50);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("several concurrent yields all resolve", async () => {
|
|
351
|
+
await expect(
|
|
352
|
+
Promise.all([
|
|
353
|
+
EventLoop.yieldToEventLoop(),
|
|
354
|
+
EventLoop.yieldToEventLoop(),
|
|
355
|
+
EventLoop.yieldToEventLoop(),
|
|
356
|
+
]),
|
|
357
|
+
).resolves.toEqual([undefined, undefined, undefined]);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
@@ -51,6 +51,36 @@ describe("JSONWebToken", () => {
|
|
|
51
51
|
return JSONWebToken.decodeJsonPayload(token);
|
|
52
52
|
}).toThrow();
|
|
53
53
|
});
|
|
54
|
+
|
|
55
|
+
/*
|
|
56
|
+
* jwt.sign accepts a bare string as a payload, and such a token verifies
|
|
57
|
+
* perfectly well - it is simply not a claims object. decodeJsonPayload
|
|
58
|
+
* used to hand that string back typed as a JSONObject, and every caller
|
|
59
|
+
* of decode() then read undefined out of userId, email and name and built
|
|
60
|
+
* a session object out of nothing. Refuse the shape where it is parsed,
|
|
61
|
+
* so the caller sees a decode failure rather than an empty identity.
|
|
62
|
+
*/
|
|
63
|
+
test("should refuse a token whose payload is a bare string, not claims", () => {
|
|
64
|
+
const token: string = jwt.sign(
|
|
65
|
+
"just-a-string",
|
|
66
|
+
EncryptionSecret.toString(),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(() => {
|
|
70
|
+
return JSONWebToken.decodeJsonPayload(token);
|
|
71
|
+
}).toThrow("Expected JSONObject");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("should refuse a bare-string payload through decode() too", () => {
|
|
75
|
+
const token: string = jwt.sign(
|
|
76
|
+
"just-a-string",
|
|
77
|
+
EncryptionSecret.toString(),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(() => {
|
|
81
|
+
return JSONWebToken.decode(token);
|
|
82
|
+
}).toThrow(BadDataException);
|
|
83
|
+
});
|
|
54
84
|
});
|
|
55
85
|
|
|
56
86
|
describe("signUserLoginToken / decode", () => {
|
|
@@ -774,6 +774,248 @@ describe("CompareCriteria", () => {
|
|
|
774
774
|
).toBe("Metric Value is 100 ms which is greater than 50 ms.");
|
|
775
775
|
});
|
|
776
776
|
|
|
777
|
+
/*
|
|
778
|
+
* HUMAN-READABLE UNITS.
|
|
779
|
+
*
|
|
780
|
+
* This sentence is the "Filter Conditions Met" line of the alert and
|
|
781
|
+
* incident email. It used to glue the exporter's raw UCUM code onto
|
|
782
|
+
* raw digits, so a memory breach the dashboard drew as "1.07 GB"
|
|
783
|
+
* arrived in the inbox as "1073741824 By".
|
|
784
|
+
*
|
|
785
|
+
* The unit rides each NUMBER rather than the sentence, because
|
|
786
|
+
* auto-scaling can land two samples of one window on different scales
|
|
787
|
+
* and a single trailing suffix cannot describe both.
|
|
788
|
+
*/
|
|
789
|
+
test("renders a byte value and its threshold at human scale", () => {
|
|
790
|
+
const filter: CriteriaFilter = makeFilter({
|
|
791
|
+
checkOn: CheckOn.MetricValue,
|
|
792
|
+
filterType: FilterType.GreaterThan,
|
|
793
|
+
});
|
|
794
|
+
expect(
|
|
795
|
+
CompareCriteria.getCompareMessage({
|
|
796
|
+
values: 1073741824,
|
|
797
|
+
threshold: 1000000000,
|
|
798
|
+
criteriaFilter: filter,
|
|
799
|
+
unit: "By",
|
|
800
|
+
}),
|
|
801
|
+
).toBe("Metric Value is 1.07 GB which is greater than 1 GB.");
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
test("rescales a duration", () => {
|
|
805
|
+
const filter: CriteriaFilter = makeFilter({
|
|
806
|
+
checkOn: CheckOn.MetricValue,
|
|
807
|
+
filterType: FilterType.GreaterThan,
|
|
808
|
+
});
|
|
809
|
+
expect(
|
|
810
|
+
CompareCriteria.getCompareMessage({
|
|
811
|
+
values: 1500,
|
|
812
|
+
threshold: 1000,
|
|
813
|
+
criteriaFilter: filter,
|
|
814
|
+
unit: "ms",
|
|
815
|
+
}),
|
|
816
|
+
).toBe("Metric Value is 1.5 sec which is greater than 1 sec.");
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
test("gives every listed sample its own scale", () => {
|
|
820
|
+
const filter: CriteriaFilter = makeFilter({
|
|
821
|
+
checkOn: CheckOn.MetricValue,
|
|
822
|
+
filterType: FilterType.GreaterThan,
|
|
823
|
+
evaluateOverTime: true,
|
|
824
|
+
evaluateOverTimeOptions: {
|
|
825
|
+
timeValueInMinutes: 5,
|
|
826
|
+
evaluateOverTimeType: EvaluateOverTimeType.AllValues,
|
|
827
|
+
},
|
|
828
|
+
});
|
|
829
|
+
expect(
|
|
830
|
+
CompareCriteria.getCompareMessage({
|
|
831
|
+
values: [921600, 1258291],
|
|
832
|
+
threshold: 900000,
|
|
833
|
+
criteriaFilter: filter,
|
|
834
|
+
unit: "By",
|
|
835
|
+
}),
|
|
836
|
+
).toBe(
|
|
837
|
+
"All values of Metric Value over the last 5 minutes is 922 KB, 1.26 MB which is greater than 900 KB.",
|
|
838
|
+
);
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
test("units ride both ends of the 'N samples between X and Y' summary", () => {
|
|
842
|
+
const filter: CriteriaFilter = makeFilter({
|
|
843
|
+
checkOn: CheckOn.MetricValue,
|
|
844
|
+
filterType: FilterType.GreaterThan,
|
|
845
|
+
evaluateOverTimeOptions: {
|
|
846
|
+
timeValueInMinutes: 5,
|
|
847
|
+
evaluateOverTimeType: EvaluateOverTimeType.AllValues,
|
|
848
|
+
},
|
|
849
|
+
});
|
|
850
|
+
const message: string = CompareCriteria.getCompareMessage({
|
|
851
|
+
values: [1e9, 2e9, 3e9, 4e9, 5e9, 6e9],
|
|
852
|
+
threshold: 5e8,
|
|
853
|
+
criteriaFilter: filter,
|
|
854
|
+
unit: "By",
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
expect(message).toContain("6 samples between 1 GB and 6 GB");
|
|
858
|
+
expect(message).toContain("greater than 500 MB");
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
/*
|
|
862
|
+
* OTel's dimensionless "1" used to be printed: a CLS breach read
|
|
863
|
+
* "is 0.31 1 which is greater than or equal to 0.25 1".
|
|
864
|
+
*/
|
|
865
|
+
test("the dimensionless '1' is suppressed on a non-fraction metric", () => {
|
|
866
|
+
const filter: CriteriaFilter = makeFilter({
|
|
867
|
+
checkOn: CheckOn.MetricValue,
|
|
868
|
+
filterType: FilterType.GreaterThanOrEqualTo,
|
|
869
|
+
});
|
|
870
|
+
const message: string = CompareCriteria.getCompareMessage({
|
|
871
|
+
values: 0.31,
|
|
872
|
+
threshold: 0.25,
|
|
873
|
+
criteriaFilter: filter,
|
|
874
|
+
metricDisplayName: "browser.cumulative_layout_shift",
|
|
875
|
+
metricName: "browser.cumulative_layout_shift",
|
|
876
|
+
unit: "1",
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
expect(message).toBe(
|
|
880
|
+
"browser.cumulative_layout_shift is 0.31 which is greater than or equal to 0.25.",
|
|
881
|
+
);
|
|
882
|
+
expect(message).not.toContain("0.31 1");
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
test("a fraction metric still carrying '1' reads as a percentage", () => {
|
|
886
|
+
const filter: CriteriaFilter = makeFilter({
|
|
887
|
+
checkOn: CheckOn.MetricValue,
|
|
888
|
+
filterType: FilterType.GreaterThan,
|
|
889
|
+
});
|
|
890
|
+
const message: string = CompareCriteria.getCompareMessage({
|
|
891
|
+
values: 0.0585,
|
|
892
|
+
threshold: 0.05,
|
|
893
|
+
criteriaFilter: filter,
|
|
894
|
+
metricDisplayName: "system.cpu.utilization",
|
|
895
|
+
metricName: "system.cpu.utilization",
|
|
896
|
+
unit: "1",
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
expect(message).toBe(
|
|
900
|
+
"system.cpu.utilization is 5.85% which is greater than 5.00%.",
|
|
901
|
+
);
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
test("a UCUM annotation-only unit is suppressed", () => {
|
|
905
|
+
const filter: CriteriaFilter = makeFilter({
|
|
906
|
+
checkOn: CheckOn.MetricValue,
|
|
907
|
+
filterType: FilterType.GreaterThan,
|
|
908
|
+
});
|
|
909
|
+
const message: string = CompareCriteria.getCompareMessage({
|
|
910
|
+
values: 512,
|
|
911
|
+
threshold: 500,
|
|
912
|
+
criteriaFilter: filter,
|
|
913
|
+
unit: "{thread}",
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
expect(message).toBe("Metric Value is 512 which is greater than 500.");
|
|
917
|
+
expect(message).not.toContain("{thread}");
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
/*
|
|
921
|
+
* THE FORMULA GUARD. metricDisplayName is the formula EXPRESSION for a
|
|
922
|
+
* formula criteria, and `a / b_ratio` ends in `_ratio` — the suffix
|
|
923
|
+
* the fraction heuristic keys on. Only `metricName`, which callers
|
|
924
|
+
* pass exclusively for plain metric criteria, may reach that
|
|
925
|
+
* heuristic; a formula reported at 100× its value would be a far worse
|
|
926
|
+
* bug than an unlabelled number.
|
|
927
|
+
*/
|
|
928
|
+
test("a formula expression is never read as a fraction metric name", () => {
|
|
929
|
+
const filter: CriteriaFilter = makeFilter({
|
|
930
|
+
checkOn: CheckOn.MetricValue,
|
|
931
|
+
filterType: FilterType.GreaterThan,
|
|
932
|
+
});
|
|
933
|
+
const message: string = CompareCriteria.getCompareMessage({
|
|
934
|
+
values: 0.42,
|
|
935
|
+
threshold: 0.4,
|
|
936
|
+
criteriaFilter: filter,
|
|
937
|
+
metricDisplayName: "a / b_ratio",
|
|
938
|
+
unit: "1",
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
expect(message).toBe("a / b_ratio is 0.42 which is greater than 0.4.");
|
|
942
|
+
expect(message).not.toContain("42.00%");
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
/*
|
|
946
|
+
* BACKWARD COMPATIBILITY. Only MetricMonitorCriteria and
|
|
947
|
+
* DatabaseMonitorCriteria pass a unit; every other monitor type
|
|
948
|
+
* carries its unit inside the CheckOn label ("Response Time (in ms)")
|
|
949
|
+
* and must keep byte-identical, unabbreviated output.
|
|
950
|
+
*/
|
|
951
|
+
test("a message with no unit is unchanged, digits and all", () => {
|
|
952
|
+
const filter: CriteriaFilter = makeFilter({
|
|
953
|
+
checkOn: CheckOn.ResponseTime,
|
|
954
|
+
filterType: FilterType.GreaterThan,
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
const expected: string =
|
|
958
|
+
"Response Time (in ms) is 5000 which is greater than 4900.";
|
|
959
|
+
|
|
960
|
+
expect(
|
|
961
|
+
CompareCriteria.getCompareMessage({
|
|
962
|
+
values: 5000,
|
|
963
|
+
threshold: 4900,
|
|
964
|
+
criteriaFilter: filter,
|
|
965
|
+
}),
|
|
966
|
+
).toBe(expected);
|
|
967
|
+
expect(
|
|
968
|
+
CompareCriteria.getCompareMessage({
|
|
969
|
+
values: 5000,
|
|
970
|
+
threshold: 4900,
|
|
971
|
+
criteriaFilter: filter,
|
|
972
|
+
unit: undefined,
|
|
973
|
+
}),
|
|
974
|
+
).toBe(expected);
|
|
975
|
+
expect(
|
|
976
|
+
CompareCriteria.getCompareMessage({
|
|
977
|
+
values: 5000,
|
|
978
|
+
threshold: 4900,
|
|
979
|
+
criteriaFilter: filter,
|
|
980
|
+
unit: "",
|
|
981
|
+
}),
|
|
982
|
+
).toBe(expected);
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
test("non-numeric filter types ignore a supplied unit", () => {
|
|
986
|
+
const filter: CriteriaFilter = makeFilter({
|
|
987
|
+
checkOn: CheckOn.MetricValue,
|
|
988
|
+
filterType: FilterType.Contains,
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
const message: string = CompareCriteria.getCompareMessage({
|
|
992
|
+
values: "abc",
|
|
993
|
+
threshold: "b",
|
|
994
|
+
criteriaFilter: filter,
|
|
995
|
+
unit: "By",
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
expect(message).not.toContain("By");
|
|
999
|
+
expect(message).not.toContain("GB");
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
test("a non-finite value never reaches the scaling ladder", () => {
|
|
1003
|
+
const filter: CriteriaFilter = makeFilter({
|
|
1004
|
+
checkOn: CheckOn.MetricValue,
|
|
1005
|
+
filterType: FilterType.GreaterThan,
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
const message: string = CompareCriteria.getCompareMessage({
|
|
1009
|
+
values: Infinity,
|
|
1010
|
+
threshold: 1000,
|
|
1011
|
+
criteriaFilter: filter,
|
|
1012
|
+
unit: "By",
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
expect(message).toContain("is Infinity which is");
|
|
1016
|
+
expect(message).not.toContain("InfinityP");
|
|
1017
|
+
});
|
|
1018
|
+
|
|
777
1019
|
test("includes the disk path for disk usage checks", () => {
|
|
778
1020
|
const withPath: CriteriaFilter = makeFilter({
|
|
779
1021
|
checkOn: CheckOn.DiskUsagePercent,
|