@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
package/Server/Types/Markdown.ts
CHANGED
|
@@ -119,7 +119,15 @@ export default class Markdown {
|
|
|
119
119
|
return htmlBody;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Escape the five characters that can break out of HTML text or an
|
|
124
|
+
* attribute value.
|
|
125
|
+
*
|
|
126
|
+
* Public because it is not only marked's business: code that hand-builds
|
|
127
|
+
* an email fragment by string interpolation needs the same escaping, and
|
|
128
|
+
* a second implementation is a second thing to get wrong.
|
|
129
|
+
*/
|
|
130
|
+
public static escapeHtml(text: string): string {
|
|
123
131
|
return text
|
|
124
132
|
.replace(/&/g, "&")
|
|
125
133
|
.replace(/</g, "<")
|
|
@@ -128,6 +136,31 @@ export default class Markdown {
|
|
|
128
136
|
.replace(/'/g, "'");
|
|
129
137
|
}
|
|
130
138
|
|
|
139
|
+
/*
|
|
140
|
+
* The email renderer.
|
|
141
|
+
*
|
|
142
|
+
* Everything here is an INLINE style, and that is not a preference.
|
|
143
|
+
* MailService compiles the Handlebars template and sends it — there is no
|
|
144
|
+
* CSS inlining step anywhere in the pipeline — and Gmail strips <style>
|
|
145
|
+
* blocks from the document head, so a class name emitted here reaches the
|
|
146
|
+
* reader as nothing at all. That is why the Docs/Blog renderers' Tailwind
|
|
147
|
+
* classes cannot be copied even though the override shape can.
|
|
148
|
+
*
|
|
149
|
+
* Until this existed the renderer was a bare `new Renderer()`. Alert and
|
|
150
|
+
* incident root causes carry a GitHub-flavoured table of breaching
|
|
151
|
+
* samples, and marked emitted it as a naked <table> with no borders, no
|
|
152
|
+
* padding and no alignment: every row ran into the next, which is
|
|
153
|
+
* precisely the part of the email an on-call engineer reads first.
|
|
154
|
+
*
|
|
155
|
+
* Outlook renders through Word, which ignores border-radius, box-shadow
|
|
156
|
+
* and — importantly — overflow, so the scrolling wrapper the Docs and
|
|
157
|
+
* Blog renderers use does not transfer. The table has to FIT instead:
|
|
158
|
+
* the DetailBox card is 472px wide with 28px of padding either side,
|
|
159
|
+
* leaving roughly 416px for a root-cause table that routinely runs to
|
|
160
|
+
* four or more columns. Hence `width:100%` with `table-layout:auto` and
|
|
161
|
+
* `word-break:break-word` on the cells — a long metric name wraps inside
|
|
162
|
+
* its column rather than pushing the table past the body width.
|
|
163
|
+
*/
|
|
131
164
|
private static getEmailRenderer(): Renderer {
|
|
132
165
|
if (this.emailRenderer !== null) {
|
|
133
166
|
return this.emailRenderer;
|
|
@@ -135,6 +168,65 @@ export default class Markdown {
|
|
|
135
168
|
|
|
136
169
|
const renderer: Renderer = new Renderer();
|
|
137
170
|
|
|
171
|
+
const cellFont: string =
|
|
172
|
+
"font-family:'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;font-size:13px;line-height:20px;";
|
|
173
|
+
|
|
174
|
+
renderer.table = function (header: string, body: string): string {
|
|
175
|
+
return (
|
|
176
|
+
`<table cellpadding="0" cellspacing="0" border="0" width="100%" ` +
|
|
177
|
+
`style="border-collapse:collapse;width:100%;table-layout:auto;` +
|
|
178
|
+
`margin:12px 0;border:1px solid #e2e8f0;">` +
|
|
179
|
+
`<thead>${header}</thead><tbody>${body}</tbody></table>`
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
renderer.tablerow = function (content: string): string {
|
|
184
|
+
return `<tr>${content}</tr>`;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
renderer.tablecell = function (
|
|
188
|
+
content: string,
|
|
189
|
+
flags: { header?: boolean; align?: string | null },
|
|
190
|
+
): string {
|
|
191
|
+
const tag: string = flags.header ? "th" : "td";
|
|
192
|
+
const align: string = flags.align ? `text-align:${flags.align};` : "";
|
|
193
|
+
|
|
194
|
+
/*
|
|
195
|
+
* A header cell gets the tinted band and the heavier bottom rule; a
|
|
196
|
+
* body cell gets a hairline so consecutive rows stay separable. Both
|
|
197
|
+
* keep the same padding so the columns line up.
|
|
198
|
+
*/
|
|
199
|
+
const tone: string = flags.header
|
|
200
|
+
? "background-color:#f8fafc;color:#475569;font-weight:600;border-bottom:1px solid #cbd5e1;"
|
|
201
|
+
: "color:#1e293b;font-weight:400;border-bottom:1px solid #f1f5f9;";
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
`<${tag} style="padding:8px 10px;${cellFont}${tone}` +
|
|
205
|
+
`word-break:break-word;${align || "text-align:left;"}">` +
|
|
206
|
+
`${content}</${tag}>`
|
|
207
|
+
);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/*
|
|
211
|
+
* The root cause backticks metric names, aliases and timestamps. Left
|
|
212
|
+
* bare they were indistinguishable from the prose around them.
|
|
213
|
+
*
|
|
214
|
+
* `code` arrives ALREADY escaped — marked escapes codespan text before
|
|
215
|
+
* it reaches the renderer — so escaping again here would turn a typed
|
|
216
|
+
* "<img>" into the literal "<img>" on screen rather than the
|
|
217
|
+
* "<img>" the author wrote. (The Docs and BlogValidation renderers do
|
|
218
|
+
* escape a second time; that is a separate defect in those surfaces,
|
|
219
|
+
* not a pattern to copy.)
|
|
220
|
+
*/
|
|
221
|
+
renderer.codespan = function (code: string): string {
|
|
222
|
+
return (
|
|
223
|
+
`<code style="background-color:#f1f5f9;color:#0f172a;` +
|
|
224
|
+
`padding:1px 5px;border-radius:4px;` +
|
|
225
|
+
`font-family:'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;` +
|
|
226
|
+
`font-size:12px;">${code}</code>`
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
|
|
138
230
|
this.emailRenderer = renderer;
|
|
139
231
|
|
|
140
232
|
return renderer;
|
|
@@ -21,6 +21,7 @@ import DashboardModelQueryInterpolation, {
|
|
|
21
21
|
AttributeToColumnMap,
|
|
22
22
|
} from "../../../Utils/Dashboard/ModelQueryVariableInterpolation";
|
|
23
23
|
import DashboardVariableInterpolation from "../../../Utils/Dashboard/VariableInterpolation";
|
|
24
|
+
import DashboardLabelVariable from "../../../Utils/Dashboard/LabelVariable";
|
|
24
25
|
|
|
25
26
|
export interface PublicDashboardResourceListPolicyResult {
|
|
26
27
|
resourceType: string;
|
|
@@ -180,6 +181,7 @@ export default class PublicDashboardResourceListPolicy {
|
|
|
180
181
|
componentType: componentType as DashboardComponentType,
|
|
181
182
|
argumentsObject,
|
|
182
183
|
requestedQuery: data.requestedQuery,
|
|
184
|
+
variables,
|
|
183
185
|
});
|
|
184
186
|
|
|
185
187
|
let query: Record<string, unknown> = draft.query;
|
|
@@ -505,8 +507,9 @@ export default class PublicDashboardResourceListPolicy {
|
|
|
505
507
|
componentType: DashboardComponentType;
|
|
506
508
|
argumentsObject: Record<string, unknown>;
|
|
507
509
|
requestedQuery: unknown;
|
|
510
|
+
variables: Array<DashboardVariable>;
|
|
508
511
|
}): PolicyDraft {
|
|
509
|
-
const { componentType, argumentsObject, requestedQuery } = data;
|
|
512
|
+
const { componentType, argumentsObject, requestedQuery, variables } = data;
|
|
510
513
|
|
|
511
514
|
switch (componentType) {
|
|
512
515
|
case DashboardComponentType.IncidentList:
|
|
@@ -520,6 +523,7 @@ export default class PublicDashboardResourceListPolicy {
|
|
|
520
523
|
case DashboardComponentType.MonitorList:
|
|
521
524
|
return PublicDashboardResourceListPolicy.buildMonitorPolicy(
|
|
522
525
|
argumentsObject,
|
|
526
|
+
variables,
|
|
523
527
|
);
|
|
524
528
|
case DashboardComponentType.NetworkMap:
|
|
525
529
|
return PublicDashboardResourceListPolicy.buildNetworkMapPolicy(
|
|
@@ -814,6 +818,7 @@ export default class PublicDashboardResourceListPolicy {
|
|
|
814
818
|
|
|
815
819
|
private static buildMonitorPolicy(
|
|
816
820
|
argumentsObject: Record<string, unknown>,
|
|
821
|
+
variables: Array<DashboardVariable>,
|
|
817
822
|
): PolicyDraft {
|
|
818
823
|
const query: Record<string, unknown> = {};
|
|
819
824
|
const statusFilter: string | undefined =
|
|
@@ -841,12 +846,22 @@ export default class PublicDashboardResourceListPolicy {
|
|
|
841
846
|
argumentsObject,
|
|
842
847
|
argumentKey: "monitorTypes",
|
|
843
848
|
});
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
849
|
+
const labels: ReturnType<typeof DashboardLabelVariable.getFilter> =
|
|
850
|
+
DashboardLabelVariable.getFilter({
|
|
851
|
+
labelIds: PublicDashboardResourceListPolicy.optionalStringArray(
|
|
852
|
+
argumentsObject,
|
|
853
|
+
"labelIds",
|
|
854
|
+
),
|
|
855
|
+
labelVariableId: PublicDashboardResourceListPolicy.optionalString(
|
|
856
|
+
argumentsObject,
|
|
857
|
+
"labelVariableId",
|
|
858
|
+
false,
|
|
859
|
+
),
|
|
860
|
+
variables,
|
|
861
|
+
});
|
|
862
|
+
if (labels) {
|
|
863
|
+
query["labels"] = labels;
|
|
864
|
+
}
|
|
850
865
|
|
|
851
866
|
return PublicDashboardResourceListPolicy.listDraft({
|
|
852
867
|
resourceType: "monitor",
|
|
@@ -1653,19 +1668,19 @@ export default class PublicDashboardResourceListPolicy {
|
|
|
1653
1668
|
storedValue,
|
|
1654
1669
|
"Stored dashboard variable",
|
|
1655
1670
|
);
|
|
1656
|
-
|
|
1671
|
+
const type: unknown = stored["type"];
|
|
1672
|
+
if (
|
|
1673
|
+
type !== DashboardVariableType.TelemetryAttribute &&
|
|
1674
|
+
type !== DashboardVariableType.ProjectLabel
|
|
1675
|
+
) {
|
|
1657
1676
|
continue;
|
|
1658
1677
|
}
|
|
1659
1678
|
|
|
1660
1679
|
const id: unknown = stored["id"];
|
|
1661
|
-
const attributeKey: unknown = stored["attributeKey"];
|
|
1662
1680
|
if (
|
|
1663
1681
|
typeof id !== "string" ||
|
|
1664
1682
|
id.length === 0 ||
|
|
1665
|
-
id.length > MAX_VARIABLE_ID_LENGTH
|
|
1666
|
-
typeof attributeKey !== "string" ||
|
|
1667
|
-
attributeKey.trim().length === 0 ||
|
|
1668
|
-
attributeKey.length > MAX_VARIABLE_VALUE_LENGTH
|
|
1683
|
+
id.length > MAX_VARIABLE_ID_LENGTH
|
|
1669
1684
|
) {
|
|
1670
1685
|
throw new BadDataException("Stored dashboard variable is malformed.");
|
|
1671
1686
|
}
|
|
@@ -1700,11 +1715,10 @@ export default class PublicDashboardResourceListPolicy {
|
|
|
1700
1715
|
const name: unknown = stored["name"];
|
|
1701
1716
|
const storedIsMultiSelect: boolean = isMultiSelect === true;
|
|
1702
1717
|
|
|
1703
|
-
|
|
1718
|
+
const variable: DashboardVariable = {
|
|
1704
1719
|
id,
|
|
1705
1720
|
name: typeof name === "string" ? name : "",
|
|
1706
|
-
type
|
|
1707
|
-
attributeKey: attributeKey.trim(),
|
|
1721
|
+
type,
|
|
1708
1722
|
isMultiSelect: storedIsMultiSelect,
|
|
1709
1723
|
defaultValue:
|
|
1710
1724
|
typeof defaultValue === "string" ? defaultValue : undefined,
|
|
@@ -1716,12 +1730,63 @@ export default class PublicDashboardResourceListPolicy {
|
|
|
1716
1730
|
storedIsMultiSelect && Array.isArray(selectedValues)
|
|
1717
1731
|
? (selectedValues as Array<string>)
|
|
1718
1732
|
: undefined,
|
|
1719
|
-
}
|
|
1733
|
+
};
|
|
1734
|
+
|
|
1735
|
+
if (type === DashboardVariableType.ProjectLabel) {
|
|
1736
|
+
variable.labelOptions =
|
|
1737
|
+
PublicDashboardResourceListPolicy.readLabelOptions(stored);
|
|
1738
|
+
} else {
|
|
1739
|
+
const attributeKey: unknown = stored["attributeKey"];
|
|
1740
|
+
if (
|
|
1741
|
+
typeof attributeKey !== "string" ||
|
|
1742
|
+
attributeKey.trim().length === 0 ||
|
|
1743
|
+
attributeKey.length > MAX_VARIABLE_VALUE_LENGTH
|
|
1744
|
+
) {
|
|
1745
|
+
throw new BadDataException("Stored dashboard variable is malformed.");
|
|
1746
|
+
}
|
|
1747
|
+
variable.attributeKey = attributeKey.trim();
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
variables.push(variable);
|
|
1720
1751
|
}
|
|
1721
1752
|
|
|
1722
1753
|
return variables;
|
|
1723
1754
|
}
|
|
1724
1755
|
|
|
1756
|
+
private static readLabelOptions(
|
|
1757
|
+
stored: Record<string, unknown>,
|
|
1758
|
+
): NonNullable<DashboardVariable["labelOptions"]> {
|
|
1759
|
+
const value: unknown = stored["labelOptions"];
|
|
1760
|
+
if (!Array.isArray(value)) {
|
|
1761
|
+
throw new BadDataException(
|
|
1762
|
+
"Stored dashboard label options are malformed.",
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
return value.map((entry: unknown): { label: string; value: string } => {
|
|
1767
|
+
const option: Record<string, unknown> =
|
|
1768
|
+
PublicDashboardResourceListPolicy.requireObject(
|
|
1769
|
+
entry,
|
|
1770
|
+
"Stored dashboard label option",
|
|
1771
|
+
);
|
|
1772
|
+
const label: unknown = option["label"];
|
|
1773
|
+
const labelId: unknown = option["value"];
|
|
1774
|
+
if (
|
|
1775
|
+
typeof label !== "string" ||
|
|
1776
|
+
label.length === 0 ||
|
|
1777
|
+
label.length > MAX_VARIABLE_VALUE_LENGTH ||
|
|
1778
|
+
typeof labelId !== "string" ||
|
|
1779
|
+
labelId.length === 0 ||
|
|
1780
|
+
labelId.length > MAX_VARIABLE_VALUE_LENGTH
|
|
1781
|
+
) {
|
|
1782
|
+
throw new BadDataException(
|
|
1783
|
+
"Stored dashboard label options are malformed.",
|
|
1784
|
+
);
|
|
1785
|
+
}
|
|
1786
|
+
return { label, value: labelId };
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1725
1790
|
private static validateRequestedVariableSelections(
|
|
1726
1791
|
requestedVariables: unknown,
|
|
1727
1792
|
): Map<string, Record<string, unknown>> {
|
|
@@ -751,9 +751,18 @@ export default class EmailRollupFlushRunner {
|
|
|
751
751
|
const projectHomeLink: string = URL.fromString(dashboardUrl.toString())
|
|
752
752
|
.addRoute(`/${bucket.projectId.toString()}/home`)
|
|
753
753
|
.toString();
|
|
754
|
+
/*
|
|
755
|
+
* Email Preferences, not Notification Settings: this footer exists to
|
|
756
|
+
* offer the reader a way out of BATCHING, and the rollup switch lives on
|
|
757
|
+
* Email Preferences. Notification Settings is one link further on, from
|
|
758
|
+
* the page this lands on. The segment is duplicated from the Dashboard's
|
|
759
|
+
* UserSettingsRoutePath for the reason OnCallSetupReminderService gives -
|
|
760
|
+
* Common/Server cannot import App sources - so a route rename there has
|
|
761
|
+
* to be repeated here.
|
|
762
|
+
*/
|
|
754
763
|
const preferencesLink: string = URL.fromString(dashboardUrl.toString())
|
|
755
764
|
.addRoute(
|
|
756
|
-
`/${bucket.projectId.toString()}/user-settings/
|
|
765
|
+
`/${bucket.projectId.toString()}/user-settings/email-preferences`,
|
|
757
766
|
)
|
|
758
767
|
.toString();
|
|
759
768
|
|
|
@@ -629,8 +629,8 @@ export const buildRollupEmail: BuildRollupEmailFunction = (
|
|
|
629
629
|
*/
|
|
630
630
|
const preferencesText: string =
|
|
631
631
|
"OneUptime groups owner notifications into a single email when several arrive at once for the same project. " +
|
|
632
|
-
"To go back to one email per notification, sent the moment it happens, turn off email rollup for this project in your
|
|
633
|
-
"The same page
|
|
632
|
+
"To go back to one email per notification, sent the moment it happens, turn off email rollup for this project in your email preferences. " +
|
|
633
|
+
"The same page can also switch off routine emails in bulk, and it links on to the per-event settings:";
|
|
634
634
|
|
|
635
635
|
/*
|
|
636
636
|
* ONE FLAT ARRAY, WITH THE SECTION HEADING CARRIED ON ITS FIRST ROW.
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "../../../../Types/Monitor/CriteriaFilter";
|
|
8
8
|
import Typeof from "../../../../Types/Typeof";
|
|
9
9
|
import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
10
|
+
import MetricValueFormatter from "../../../../Utils/Monitor/MetricValueFormatter";
|
|
10
11
|
|
|
11
12
|
export default class CompareCriteria {
|
|
12
13
|
/*
|
|
@@ -474,6 +475,15 @@ export default class CompareCriteria {
|
|
|
474
475
|
criteriaFilter: CriteriaFilter;
|
|
475
476
|
metricDisplayName?: string | undefined;
|
|
476
477
|
unit?: string | undefined;
|
|
478
|
+
/**
|
|
479
|
+
* The metric NAME, when there is a real one. Distinct from
|
|
480
|
+
* metricDisplayName, which is the formula expression for a formula
|
|
481
|
+
* criteria and is only ever used as a label: this one feeds
|
|
482
|
+
* ValueFormatter.isFractionMetric, which would read the trailing
|
|
483
|
+
* `_ratio` of a formula like `a / b_ratio` as a signal to multiply
|
|
484
|
+
* the value by 100. Callers pass it only for plain metric criteria.
|
|
485
|
+
*/
|
|
486
|
+
metricName?: string | undefined;
|
|
477
487
|
}): string | null {
|
|
478
488
|
if (data.value === null || data.value === undefined) {
|
|
479
489
|
return null;
|
|
@@ -499,6 +509,7 @@ export default class CompareCriteria {
|
|
|
499
509
|
criteriaFilter: data.criteriaFilter,
|
|
500
510
|
metricDisplayName: data.metricDisplayName,
|
|
501
511
|
unit: data.unit,
|
|
512
|
+
metricName: data.metricName,
|
|
502
513
|
});
|
|
503
514
|
}
|
|
504
515
|
|
|
@@ -521,6 +532,7 @@ export default class CompareCriteria {
|
|
|
521
532
|
criteriaFilter: data.criteriaFilter,
|
|
522
533
|
metricDisplayName: data.metricDisplayName,
|
|
523
534
|
unit: data.unit,
|
|
535
|
+
metricName: data.metricName,
|
|
524
536
|
});
|
|
525
537
|
}
|
|
526
538
|
|
|
@@ -543,6 +555,7 @@ export default class CompareCriteria {
|
|
|
543
555
|
criteriaFilter: data.criteriaFilter,
|
|
544
556
|
metricDisplayName: data.metricDisplayName,
|
|
545
557
|
unit: data.unit,
|
|
558
|
+
metricName: data.metricName,
|
|
546
559
|
});
|
|
547
560
|
}
|
|
548
561
|
|
|
@@ -565,6 +578,7 @@ export default class CompareCriteria {
|
|
|
565
578
|
criteriaFilter: data.criteriaFilter,
|
|
566
579
|
metricDisplayName: data.metricDisplayName,
|
|
567
580
|
unit: data.unit,
|
|
581
|
+
metricName: data.metricName,
|
|
568
582
|
});
|
|
569
583
|
}
|
|
570
584
|
|
|
@@ -587,6 +601,7 @@ export default class CompareCriteria {
|
|
|
587
601
|
criteriaFilter: data.criteriaFilter,
|
|
588
602
|
metricDisplayName: data.metricDisplayName,
|
|
589
603
|
unit: data.unit,
|
|
604
|
+
metricName: data.metricName,
|
|
590
605
|
});
|
|
591
606
|
}
|
|
592
607
|
|
|
@@ -609,6 +624,7 @@ export default class CompareCriteria {
|
|
|
609
624
|
criteriaFilter: data.criteriaFilter,
|
|
610
625
|
metricDisplayName: data.metricDisplayName,
|
|
611
626
|
unit: data.unit,
|
|
627
|
+
metricName: data.metricName,
|
|
612
628
|
});
|
|
613
629
|
}
|
|
614
630
|
|
|
@@ -625,6 +641,15 @@ export default class CompareCriteria {
|
|
|
625
641
|
criteriaFilter: CriteriaFilter;
|
|
626
642
|
metricDisplayName?: string | undefined;
|
|
627
643
|
unit?: string | undefined;
|
|
644
|
+
/**
|
|
645
|
+
* The metric NAME, when there is a real one. Distinct from
|
|
646
|
+
* metricDisplayName, which is the formula expression for a formula
|
|
647
|
+
* criteria and is only ever used as a label: this one feeds
|
|
648
|
+
* ValueFormatter.isFractionMetric, which would read the trailing
|
|
649
|
+
* `_ratio` of a formula like `a / b_ratio` as a signal to multiply
|
|
650
|
+
* the value by 100. Callers pass it only for plain metric criteria.
|
|
651
|
+
*/
|
|
652
|
+
metricName?: string | undefined;
|
|
628
653
|
}): string {
|
|
629
654
|
// CPU Percent over the last 5 minutes is 10 which is less than the threshold of 20
|
|
630
655
|
let message: string = "";
|
|
@@ -695,30 +720,36 @@ export default class CompareCriteria {
|
|
|
695
720
|
}
|
|
696
721
|
|
|
697
722
|
/*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
* real unit passes through unchanged, so "%" stays "%" and "ms"
|
|
706
|
-
* stays "ms".
|
|
723
|
+
* A metric value is written the way the dashboard writes it —
|
|
724
|
+
* "1.07 GB", "1.5 sec", "25.34%" — rather than as raw digits with a
|
|
725
|
+
* raw UCUM code glued on. Only two of the ~35 callers pass a unit at
|
|
726
|
+
* all (MetricMonitorCriteria and DatabaseMonitorCriteria); everything
|
|
727
|
+
* else carries its unit inside the CheckOn label ("Response Time (in
|
|
728
|
+
* ms)") and must keep its exact digits, so an absent unit leaves this
|
|
729
|
+
* sentence byte-for-byte as it was.
|
|
707
730
|
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
731
|
+
* MetricValueFormatter also subsumes the unit-suppression rules this
|
|
732
|
+
* block used to spell out by hand: OTel's dimensionless "1" (which
|
|
733
|
+
* made a CLS breach read "is 0.31 1 which is greater than or equal to
|
|
734
|
+
* 0.25 1") and UCUM's annotation-only "{cpu}" / "{packets}" both
|
|
735
|
+
* disappear, and a fraction metric still carrying "1" is rendered as
|
|
736
|
+
* the percentage it means.
|
|
737
|
+
*
|
|
738
|
+
* The unit rides each NUMBER instead of the sentence, because
|
|
739
|
+
* auto-scaling can land two samples in the same sentence on different
|
|
740
|
+
* scales — "is 900 KB, 1.2 MB" — which one trailing suffix cannot
|
|
741
|
+
* express.
|
|
713
742
|
*/
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
743
|
+
const withUnit: ((value: number) => string) | undefined =
|
|
744
|
+
MetricValueFormatter.hasDisplayableUnit(data.unit, data.metricName)
|
|
745
|
+
? (value: number): string => {
|
|
746
|
+
return MetricValueFormatter.format({
|
|
747
|
+
value: value,
|
|
748
|
+
unit: data.unit,
|
|
749
|
+
metricName: data.metricName,
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
: undefined;
|
|
722
753
|
|
|
723
754
|
if (
|
|
724
755
|
data.criteriaFilter.filterType !== FilterType.True &&
|
|
@@ -750,10 +781,12 @@ export default class CompareCriteria {
|
|
|
750
781
|
evaluationType: evaluationType,
|
|
751
782
|
});
|
|
752
783
|
|
|
753
|
-
const formattedValues: string =
|
|
754
|
-
|
|
784
|
+
const formattedValues: string = CompareCriteria.formatCriteriaValues(
|
|
785
|
+
reportedValues,
|
|
786
|
+
withUnit,
|
|
787
|
+
);
|
|
755
788
|
|
|
756
|
-
message += ` is ${formattedValues}
|
|
789
|
+
message += ` is ${formattedValues}`;
|
|
757
790
|
|
|
758
791
|
message += " which is";
|
|
759
792
|
|
|
@@ -778,22 +811,22 @@ export default class CompareCriteria {
|
|
|
778
811
|
|
|
779
812
|
switch (data.criteriaFilter.filterType) {
|
|
780
813
|
case FilterType.GreaterThan:
|
|
781
|
-
message += ` greater than ${CompareCriteria.formatSingleValue(data.threshold)}
|
|
814
|
+
message += ` greater than ${CompareCriteria.formatSingleValue(data.threshold, withUnit)}. `;
|
|
782
815
|
break;
|
|
783
816
|
case FilterType.GreaterThanOrEqualTo:
|
|
784
|
-
message += ` greater than or equal to ${CompareCriteria.formatSingleValue(data.threshold)}
|
|
817
|
+
message += ` greater than or equal to ${CompareCriteria.formatSingleValue(data.threshold, withUnit)}. `;
|
|
785
818
|
break;
|
|
786
819
|
case FilterType.LessThan:
|
|
787
|
-
message += ` less than ${CompareCriteria.formatSingleValue(data.threshold)}
|
|
820
|
+
message += ` less than ${CompareCriteria.formatSingleValue(data.threshold, withUnit)}. `;
|
|
788
821
|
break;
|
|
789
822
|
case FilterType.LessThanOrEqualTo:
|
|
790
|
-
message += ` less than or equal to ${CompareCriteria.formatSingleValue(data.threshold)}
|
|
823
|
+
message += ` less than or equal to ${CompareCriteria.formatSingleValue(data.threshold, withUnit)}. `;
|
|
791
824
|
break;
|
|
792
825
|
case FilterType.NotEqualTo:
|
|
793
|
-
message += ` not equal to ${CompareCriteria.formatSingleValue(data.threshold)}
|
|
826
|
+
message += ` not equal to ${CompareCriteria.formatSingleValue(data.threshold, withUnit)}. `;
|
|
794
827
|
break;
|
|
795
828
|
case FilterType.EqualTo:
|
|
796
|
-
message += ` equal to ${CompareCriteria.formatSingleValue(data.threshold)}
|
|
829
|
+
message += ` equal to ${CompareCriteria.formatSingleValue(data.threshold, withUnit)}. `;
|
|
797
830
|
break;
|
|
798
831
|
case FilterType.Contains:
|
|
799
832
|
message += ` contains ${CompareCriteria.formatSingleValue(data.threshold)}. `;
|
|
@@ -918,8 +951,16 @@ export default class CompareCriteria {
|
|
|
918
951
|
}
|
|
919
952
|
}
|
|
920
953
|
|
|
954
|
+
/*
|
|
955
|
+
* `withUnit`, when supplied, is how a NUMBER is rendered — unit
|
|
956
|
+
* included. It is threaded down rather than applied to the finished
|
|
957
|
+
* string because the string is not always one number: a large window
|
|
958
|
+
* collapses to "N samples between X and Y", and both X and Y need the
|
|
959
|
+
* unit of their own scale.
|
|
960
|
+
*/
|
|
921
961
|
private static formatCriteriaValues(
|
|
922
962
|
values: Array<number | boolean> | number | boolean | string,
|
|
963
|
+
withUnit?: ((value: number) => string) | undefined,
|
|
923
964
|
): string {
|
|
924
965
|
if (Array.isArray(values)) {
|
|
925
966
|
/*
|
|
@@ -934,7 +975,7 @@ export default class CompareCriteria {
|
|
|
934
975
|
if (values.length <= MAX_INLINE) {
|
|
935
976
|
return values
|
|
936
977
|
.map((value: number | boolean) => {
|
|
937
|
-
return CompareCriteria.formatSingleValue(value);
|
|
978
|
+
return CompareCriteria.formatSingleValue(value, withUnit);
|
|
938
979
|
})
|
|
939
980
|
.join(", ");
|
|
940
981
|
}
|
|
@@ -950,24 +991,26 @@ export default class CompareCriteria {
|
|
|
950
991
|
const max: number = Math.max(...numericValues);
|
|
951
992
|
return `${numericValues.length} samples between ${CompareCriteria.formatSingleValue(
|
|
952
993
|
min,
|
|
953
|
-
|
|
994
|
+
withUnit,
|
|
995
|
+
)} and ${CompareCriteria.formatSingleValue(max, withUnit)}`;
|
|
954
996
|
}
|
|
955
997
|
|
|
956
998
|
// Fall back to a truncated list when not all values are numeric
|
|
957
999
|
const head: string = values
|
|
958
1000
|
.slice(0, MAX_INLINE)
|
|
959
1001
|
.map((value: number | boolean) => {
|
|
960
|
-
return CompareCriteria.formatSingleValue(value);
|
|
1002
|
+
return CompareCriteria.formatSingleValue(value, withUnit);
|
|
961
1003
|
})
|
|
962
1004
|
.join(", ");
|
|
963
1005
|
return `${head}, … (${values.length} values total)`;
|
|
964
1006
|
}
|
|
965
1007
|
|
|
966
|
-
return CompareCriteria.formatSingleValue(values);
|
|
1008
|
+
return CompareCriteria.formatSingleValue(values, withUnit);
|
|
967
1009
|
}
|
|
968
1010
|
|
|
969
1011
|
private static formatSingleValue(
|
|
970
1012
|
value: number | boolean | string | null | undefined,
|
|
1013
|
+
withUnit?: ((value: number) => string) | undefined,
|
|
971
1014
|
): string {
|
|
972
1015
|
if (value === null || value === undefined) {
|
|
973
1016
|
return "unknown";
|
|
@@ -976,6 +1019,10 @@ export default class CompareCriteria {
|
|
|
976
1019
|
if (typeof value === Typeof.Number) {
|
|
977
1020
|
const numericValue: number = value as number;
|
|
978
1021
|
|
|
1022
|
+
if (withUnit) {
|
|
1023
|
+
return withUnit(numericValue);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
979
1026
|
if (Number.isInteger(numericValue)) {
|
|
980
1027
|
return numericValue.toString();
|
|
981
1028
|
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from "../../../../Types/Monitor/CriteriaFilter";
|
|
27
27
|
import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
28
28
|
import MetricUnitUtil from "../../../../Utils/MetricUnitUtil";
|
|
29
|
+
import MetricValueFormatter from "../../../../Utils/Monitor/MetricValueFormatter";
|
|
29
30
|
import MetricFormulaEvaluator from "../../../../Utils/Metrics/MetricFormulaEvaluator";
|
|
30
31
|
import MetricBaselineService, {
|
|
31
32
|
BaselineSummary,
|
|
@@ -369,6 +370,15 @@ export default class MetricMonitorCriteria {
|
|
|
369
370
|
criteriaFilter: input.criteriaFilter,
|
|
370
371
|
metricDisplayName: metricContext.metricName,
|
|
371
372
|
unit: displayUnit,
|
|
373
|
+
/*
|
|
374
|
+
* Only a plain metric criteria has a real metric NAME —
|
|
375
|
+
* metricContext.metricName is the formula expression when the
|
|
376
|
+
* criteria targets a formula, and a formula ending in `_ratio`
|
|
377
|
+
* would trip the fraction heuristic into a 100× render.
|
|
378
|
+
*/
|
|
379
|
+
metricName: metricContext.isFormula
|
|
380
|
+
? undefined
|
|
381
|
+
: metricContext.metricName,
|
|
372
382
|
});
|
|
373
383
|
|
|
374
384
|
if (!comparisonMessage) {
|
|
@@ -989,13 +999,29 @@ export default class MetricMonitorCriteria {
|
|
|
989
999
|
: "below";
|
|
990
1000
|
|
|
991
1001
|
const sigmaAbs: number = Math.abs(firstBreach.sigma);
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1002
|
+
|
|
1003
|
+
/*
|
|
1004
|
+
* Same rendering as the breaching-samples table and the comparison
|
|
1005
|
+
* sentence — an anomaly on a memory metric reads "1.07 GB", not
|
|
1006
|
+
* "1073741824.000 By". σ is a count of standard deviations, not a
|
|
1007
|
+
* quantity in the metric's unit, so it stays a bare number; the
|
|
1008
|
+
* standard deviation itself is in the metric's unit and does not.
|
|
1009
|
+
*/
|
|
1010
|
+
const inMetricUnit: (value: number) => string = (value: number): string => {
|
|
1011
|
+
return MetricValueFormatter.format({
|
|
1012
|
+
value: value,
|
|
1013
|
+
unit: metricContext.unit,
|
|
1014
|
+
// Formula expressions are not metric names — see the guard above.
|
|
1015
|
+
metricName: metricContext.isFormula
|
|
1016
|
+
? undefined
|
|
1017
|
+
: metricContext.metricName,
|
|
1018
|
+
});
|
|
1019
|
+
};
|
|
1020
|
+
|
|
995
1021
|
const rootCause: string =
|
|
996
|
-
`${metricContext.metricName} value ${firstBreach.sample.value
|
|
1022
|
+
`${metricContext.metricName} value ${inMetricUnit(firstBreach.sample.value)} ` +
|
|
997
1023
|
`is ${sigmaAbs.toFixed(2)}σ ${direction} the same-hour baseline ` +
|
|
998
|
-
`(mean ${baseline.mean
|
|
1024
|
+
`(mean ${inMetricUnit(baseline.mean)}, σ ${inMetricUnit(baseline.stddev)}, ` +
|
|
999
1025
|
`${baseline.sampleCount} samples over ${baseline.windowDays} days, sensitivity ${sensitivity}).`;
|
|
1000
1026
|
|
|
1001
1027
|
return {
|