@oneuptime/common 12.0.13 → 12.0.14
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/OnCallDutyPolicySchedule.ts +14 -0
- package/Models/DatabaseModels/UserCall.ts +94 -3
- package/Models/DatabaseModels/UserEmail.ts +94 -3
- package/Models/DatabaseModels/UserIncomingCallNumber.ts +94 -3
- package/Models/DatabaseModels/UserPush.ts +37 -0
- package/Models/DatabaseModels/UserSMS.ts +94 -3
- package/Models/DatabaseModels/UserWhatsApp.ts +93 -2
- package/Server/API/BaseAPI.ts +79 -14
- package/Server/API/UserCallAPI.ts +53 -45
- package/Server/API/UserEmailAPI.ts +53 -44
- package/Server/API/UserIncomingCallNumberAPI.ts +48 -38
- package/Server/API/UserNotificationMethodAdminAPI.ts +481 -0
- package/Server/API/UserPushAPI.ts +126 -2
- package/Server/API/UserSmsAPI.ts +53 -44
- package/Server/API/UserWhatsAppAPI.ts +53 -50
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.ts +154 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.ts +45 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/VerificationCodeRateLimit.ts +586 -0
- package/Server/Services/AnalyticsDatabaseService.ts +169 -0
- package/Server/Services/DatabaseService.ts +101 -0
- package/Server/Services/LogAggregationService.ts +5 -0
- package/Server/Services/PushNotificationService.ts +127 -12
- package/Server/Services/TeamMemberService.ts +53 -13
- package/Server/Services/UserCallService.ts +55 -24
- package/Server/Services/UserEmailService.ts +83 -17
- package/Server/Services/UserIncomingCallNumberService.ts +49 -85
- package/Server/Services/UserNotificationMethodAdminService.ts +1527 -0
- package/Server/Services/UserNotificationRuleService.ts +26 -0
- package/Server/Services/UserPushService.ts +89 -0
- package/Server/Services/UserSmsService.ts +50 -17
- package/Server/Services/UserWhatsAppService.ts +48 -15
- package/Server/Types/Database/QueryHelper.ts +30 -0
- package/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.ts +59 -0
- package/Server/Utils/ChannelVerification.ts +510 -0
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +13 -16
- package/Server/Utils/Monitor/MonitorAlert.ts +24 -21
- package/Server/Utils/Monitor/MonitorIncident.ts +21 -18
- package/Server/Utils/Monitor/MonitorResourceContext.ts +107 -0
- package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +442 -0
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +3 -3
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +96 -28
- package/Server/Utils/TotpAuth.ts +137 -9
- package/Server/Utils/UserRegistrationToken.ts +182 -0
- package/Server/Utils/VerificationCode.ts +134 -0
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +173 -94
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +1151 -0
- package/Tests/App/Dashboard/AdminUserOnCallPages.test.tsx +905 -0
- package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +338 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1 -1
- package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +28 -0
- package/Tests/Models/UserPushCriticalAlertColumn.test.ts +172 -0
- package/Tests/Server/API/BaseAPIGetListParallel.test.ts +519 -0
- package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +543 -0
- package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +108 -18
- package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +963 -0
- package/Tests/Server/API/UserPushCriticalAlertsApi.test.ts +114 -0
- package/Tests/Server/API/UserSmsApi.test.ts +107 -14
- package/Tests/Server/API/UserTotpAuthAPI.test.ts +427 -0
- package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +315 -0
- package/Tests/Server/Middleware/VerificationCodeRateLimit.test.ts +775 -0
- package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +10 -62
- package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +33 -13
- package/Tests/Server/Services/AnalyticsDatabaseSortKeyBoundary.test.ts +658 -0
- package/Tests/Server/Services/CriticalOnCallAlertDelivery.test.ts +458 -0
- package/Tests/Server/Services/CriticalPushAlertPayload.test.ts +301 -0
- package/Tests/Server/Services/DatabaseServiceAtomicIncrement.test.ts +143 -0
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1 -0
- package/Tests/Server/Services/LogAggregationScanMemory.test.ts +421 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
- package/Tests/Server/Services/NotificationChannelCodeIssuance.test.ts +401 -0
- package/Tests/Server/Services/PushNotificationDeliveryOptions.test.ts +204 -0
- package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +21 -1
- package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +290 -0
- package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +948 -0
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1 -0
- package/Tests/Server/Services/UserPushCriticalAlertToggle.test.ts +255 -0
- package/Tests/Server/Services/UserTotpAuthEnrolment.test.ts +218 -0
- package/Tests/Server/TestingUtils/AuthenticatorApp.ts +248 -0
- package/Tests/Server/Types/Database/Permissions/NotificationChannelVerificationColumns.test.ts +135 -0
- package/Tests/Server/Types/Database/Permissions/OnCallScheduleRelationSelect.test.ts +504 -0
- package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAnyOf.test.ts +91 -0
- package/Tests/Server/Types/Workflow/Components/JsonToText.test.ts +384 -0
- package/Tests/Server/Types/Workflow/Components/MergeJson.test.ts +403 -0
- package/Tests/Server/Utils/Browser.test.ts +162 -0
- package/Tests/Server/Utils/ChannelVerification.test.ts +689 -0
- package/Tests/Server/Utils/Express.test.ts +371 -0
- package/Tests/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.test.ts +707 -0
- package/Tests/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.test.ts +698 -0
- package/Tests/Server/Utils/Monitor/Criteria/IncomingEmailBodyCriteria.test.ts +243 -0
- package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestBodyCriteria.test.ts +278 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +160 -20
- package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +13 -8
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +162 -18
- package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +349 -0
- package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +690 -0
- package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +7 -2
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +92 -18
- package/Tests/Server/Utils/PushNotificationUtilCreators.test.ts +267 -0
- package/Tests/Server/Utils/Telemetry/OneuptimeLabel.test.ts +439 -0
- package/Tests/Server/Utils/TotpAuth.test.ts +719 -0
- package/Tests/Server/Utils/UserRegistrationToken.test.ts +322 -0
- package/Tests/Server/Utils/VerificationCode.test.ts +296 -0
- package/Tests/Types/AutoRemediation/AutoRemediationSuggestionStatus.test.ts +192 -0
- package/Tests/Types/Call/CallRequest.test.ts +189 -0
- package/Tests/Types/Dashboard/DashboardSize.test.ts +226 -0
- package/Tests/Types/Incident/IncidentSlaStatus.test.ts +191 -0
- package/Tests/Types/Metrics/MetricDashboardMetricType.test.ts +278 -0
- package/Tests/Types/Metrics/MetricPipelineRuleType.test.ts +517 -0
- package/Tests/Types/Monitor/IncomingMonitorDefaultCriteria.test.ts +470 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +6 -4
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +262 -5
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +1 -1
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +1 -1
- package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +905 -0
- package/Tests/Types/Service/ServiceLanguage.test.ts +255 -0
- package/Tests/Types/TextRandomGeneration.test.ts +211 -0
- package/Tests/UI/Components/Button.test.tsx +175 -1
- package/Tests/UI/Components/CodeBlockLanguages.test.tsx +236 -0
- package/Tests/UI/Components/CommandPalette/CommandPalette.test.tsx +568 -0
- package/Tests/UI/Components/CommandPalette/CommandPaletteKeyboard.test.tsx +239 -0
- package/Tests/UI/Components/CommandPalette/CommandPaletteProviders.test.tsx +400 -0
- package/Tests/UI/Components/CommandPalette/PaletteFilter.test.ts +234 -0
- package/Tests/UI/Components/CommandPalette/RecentCommands.test.ts +97 -0
- package/Tests/UI/Components/CountModelSideMenuItemGuard.test.tsx +198 -0
- package/Tests/UI/Components/FeedItemSafeMode.test.tsx +1 -1
- package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +267 -0
- package/Tests/UI/Components/List.test.tsx +9 -2
- package/Tests/UI/Components/ListLoadingStates.test.tsx +173 -0
- package/Tests/UI/Components/MarkdownLazy.test.tsx +113 -0
- package/Tests/UI/Components/MarkdownMermaidRetry.test.tsx +110 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +8 -0
- package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +692 -0
- package/Tests/UI/Components/MoreMenuMotion.test.tsx +118 -0
- package/Tests/UI/Components/NavBarCommandKGating.test.tsx +178 -0
- package/Tests/UI/Components/OrderedStatesList.test.tsx +19 -2
- package/Tests/UI/Components/SideOverMotion.test.tsx +109 -0
- package/Tests/UI/Components/Skeleton.test.tsx +71 -0
- package/Tests/UI/Components/TableLoadingStates.test.tsx +279 -0
- package/Tests/UI/Components/Tabs.test.tsx +24 -0
- package/Tests/UI/Components/Toast.test.tsx +130 -7
- package/Tests/UI/Components/ToastStacking.test.tsx +198 -0
- package/Tests/UI/Utils/Dropdown.test.ts +423 -0
- package/Tests/UI/Utils/ModelAPITenantHeader.test.ts +122 -0
- package/Tests/UI/Utils/ModelListCache.test.ts +338 -0
- package/Tests/UI/Utils/ProjectListCacheInvalidation.test.ts +225 -0
- package/Tests/Utils/API.test.ts +16 -2
- package/Tests/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.test.ts +347 -0
- package/Tests/Utils/Dashboard/Components/DashboardListSharedArgs.test.ts +259 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +466 -0
- package/Tests/Utils/Rum/UrlScrubberSegments.test.ts +362 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +41 -20
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +131 -8
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +48 -8
- package/Types/Monitor/ServiceAlertTemplates.ts +1352 -0
- package/Types/Permission.ts +35 -0
- package/Types/PushNotification/AndroidNotificationChannel.ts +26 -0
- package/Types/PushNotification/PushNotificationMessage.ts +15 -0
- package/Types/PushNotification/PushNotificationRequest.ts +8 -16
- package/Types/Service/ServiceLanguage.ts +144 -0
- package/Types/Text.ts +111 -19
- package/UI/Components/Button/Button.tsx +27 -14
- package/UI/Components/CodeBlock/CodeBlock.tsx +54 -7
- package/UI/Components/CodeBlock/LanguageRegistry.ts +103 -0
- package/UI/Components/CommandPalette/CommandPalette.tsx +790 -0
- package/UI/Components/CommandPalette/PaletteFilter.ts +196 -0
- package/UI/Components/CommandPalette/PaletteRow.tsx +173 -0
- package/UI/Components/CommandPalette/RecentCommands.ts +57 -0
- package/UI/Components/CommandPalette/Types.ts +49 -0
- package/UI/Components/CommandPalette/UseProviderSearch.ts +129 -0
- package/UI/Components/Dropdown/Dropdown.tsx +6 -1
- package/UI/Components/Feed/FeedItem.tsx +1 -1
- package/UI/Components/List/List.tsx +37 -5
- package/UI/Components/List/ListSkeleton.tsx +54 -0
- package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +6 -1
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +71 -26
- package/UI/Components/Modal/Modal.tsx +2 -2
- package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +11 -5
- package/UI/Components/MoreMenu/MoreMenu.tsx +39 -1
- package/UI/Components/Navbar/NavBar.tsx +14 -1
- package/UI/Components/OrderedStatesList/OrderedStatesList.tsx +50 -5
- package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +40 -1
- package/UI/Components/SideOver/SideOver.tsx +37 -2
- package/UI/Components/Skeleton/Skeleton.tsx +53 -0
- package/UI/Components/Table/Table.tsx +74 -44
- package/UI/Components/Table/TableSkeletonRows.tsx +141 -0
- package/UI/Components/Tabs/Tab.tsx +1 -1
- package/UI/Components/Toast/Toast.tsx +118 -53
- package/UI/Components/Toast/ToastInit.tsx +82 -32
- package/UI/Styles/Theme.css +40 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +20 -10
- package/UI/Utils/ModelListCache.ts +188 -0
- package/UI/Utils/Project.ts +29 -0
- package/Utils/API.ts +6 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +14 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserCall.js +101 -3
- package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserEmail.js +101 -3
- package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js +101 -3
- package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserPush.js +38 -0
- package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSMS.js +101 -3
- package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js +100 -2
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +69 -13
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/UserCallAPI.js +42 -38
- package/build/dist/Server/API/UserCallAPI.js.map +1 -1
- package/build/dist/Server/API/UserEmailAPI.js +42 -38
- package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js +37 -32
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
- package/build/dist/Server/API/UserNotificationMethodAdminAPI.js +341 -0
- package/build/dist/Server/API/UserNotificationMethodAdminAPI.js.map +1 -0
- package/build/dist/Server/API/UserPushAPI.js +95 -2
- package/build/dist/Server/API/UserPushAPI.js.map +1 -1
- package/build/dist/Server/API/UserSmsAPI.js +42 -38
- package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
- package/build/dist/Server/API/UserWhatsAppAPI.js +42 -40
- package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js +89 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/VerificationCodeRateLimit.js +393 -0
- package/build/dist/Server/Middleware/VerificationCodeRateLimit.js.map +1 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +136 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +74 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +5 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/PushNotificationService.js +69 -30
- package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +43 -7
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserCallService.js +54 -22
- package/build/dist/Server/Services/UserCallService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +78 -17
- package/build/dist/Server/Services/UserEmailService.js.map +1 -1
- package/build/dist/Server/Services/UserIncomingCallNumberService.js +45 -74
- package/build/dist/Server/Services/UserIncomingCallNumberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js +1117 -0
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js +20 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserPushService.js +77 -0
- package/build/dist/Server/Services/UserPushService.js.map +1 -1
- package/build/dist/Server/Services/UserSmsService.js +50 -17
- package/build/dist/Server/Services/UserSmsService.js.map +1 -1
- package/build/dist/Server/Services/UserWhatsAppService.js +48 -15
- package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +27 -0
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js +46 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js.map +1 -1
- package/build/dist/Server/Utils/ChannelVerification.js +348 -0
- package/build/dist/Server/Utils/ChannelVerification.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +13 -12
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +20 -17
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +17 -14
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +95 -0
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +313 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +3 -3
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +67 -26
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
- package/build/dist/Server/Utils/TotpAuth.js +127 -8
- package/build/dist/Server/Utils/TotpAuth.js.map +1 -1
- package/build/dist/Server/Utils/UserRegistrationToken.js +170 -0
- package/build/dist/Server/Utils/UserRegistrationToken.js.map +1 -0
- package/build/dist/Server/Utils/VerificationCode.js +120 -0
- package/build/dist/Server/Utils/VerificationCode.js.map +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +42 -21
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +77 -5
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +18 -6
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js +1028 -0
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -0
- package/build/dist/Types/Permission.js +33 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/PushNotification/AndroidNotificationChannel.js +27 -0
- package/build/dist/Types/PushNotification/AndroidNotificationChannel.js.map +1 -0
- package/build/dist/Types/Service/ServiceLanguage.js +96 -0
- package/build/dist/Types/Service/ServiceLanguage.js.map +1 -0
- package/build/dist/Types/Text.js +82 -17
- package/build/dist/Types/Text.js.map +1 -1
- package/build/dist/UI/Components/Button/Button.js +22 -14
- package/build/dist/UI/Components/Button/Button.js.map +1 -1
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js +35 -3
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
- package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js +57 -0
- package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js +439 -0
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/PaletteFilter.js +135 -0
- package/build/dist/UI/Components/CommandPalette/PaletteFilter.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/PaletteRow.js +83 -0
- package/build/dist/UI/Components/CommandPalette/PaletteRow.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/RecentCommands.js +47 -0
- package/build/dist/UI/Components/CommandPalette/RecentCommands.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/Types.js +2 -0
- package/build/dist/UI/Components/CommandPalette/Types.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js +88 -0
- package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js.map +1 -0
- package/build/dist/UI/Components/Dropdown/Dropdown.js +6 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
- package/build/dist/UI/Components/List/List.js +19 -5
- package/build/dist/UI/Components/List/List.js.map +1 -1
- package/build/dist/UI/Components/List/ListSkeleton.js +21 -0
- package/build/dist/UI/Components/List/ListSkeleton.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +52 -25
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +2 -2
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +9 -2
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +32 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +5 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js +29 -5
- package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +23 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideOver/SideOver.js +23 -2
- package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
- package/build/dist/UI/Components/Skeleton/Skeleton.js +23 -0
- package/build/dist/UI/Components/Skeleton/Skeleton.js.map +1 -0
- package/build/dist/UI/Components/Table/Table.js +35 -24
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableSkeletonRows.js +52 -0
- package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -0
- package/build/dist/UI/Components/Tabs/Tab.js +1 -1
- package/build/dist/UI/Components/Tabs/Tab.js.map +1 -1
- package/build/dist/UI/Components/Toast/Toast.js +77 -23
- package/build/dist/UI/Components/Toast/Toast.js.map +1 -1
- package/build/dist/UI/Components/Toast/ToastInit.js +37 -15
- package/build/dist/UI/Components/Toast/ToastInit.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -9
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/ModelListCache.js +126 -0
- package/build/dist/UI/Utils/ModelListCache.js.map +1 -0
- package/build/dist/UI/Utils/Project.js +24 -1
- package/build/dist/UI/Utils/Project.js.map +1 -1
- package/build/dist/Utils/API.js +6 -1
- package/build/dist/Utils/API.js.map +1 -1
- package/jest.config.json +1 -1
- package/package.json +2 -3
- package/Server/Utils/Monitor/MonitorClusterContext.ts +0 -182
- package/Tests/__mocks__/otpauth.js +0 -30
- package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +0 -141
- package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js.map +0 -1
|
@@ -0,0 +1,905 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ServiceAlertTemplate,
|
|
3
|
+
ServiceAlertTemplateArgs,
|
|
4
|
+
getAllServiceAlertTemplates,
|
|
5
|
+
getLanguagesWithServiceAlertTemplates,
|
|
6
|
+
getServiceAlertTemplateById,
|
|
7
|
+
getServiceAlertTemplates,
|
|
8
|
+
} from "../../../Types/Monitor/ServiceAlertTemplates";
|
|
9
|
+
import MonitorStep from "../../../Types/Monitor/MonitorStep";
|
|
10
|
+
import MonitorCriteriaInstance from "../../../Types/Monitor/MonitorCriteriaInstance";
|
|
11
|
+
import MonitorSteps from "../../../Types/Monitor/MonitorSteps";
|
|
12
|
+
import MonitorType from "../../../Types/Monitor/MonitorType";
|
|
13
|
+
import MonitorStepMetricMonitor from "../../../Types/Monitor/MonitorStepMetricMonitor";
|
|
14
|
+
import MonitorStepTraceMonitor from "../../../Types/Monitor/MonitorStepTraceMonitor";
|
|
15
|
+
import MonitorStepExceptionMonitor from "../../../Types/Monitor/MonitorStepExceptionMonitor";
|
|
16
|
+
import MetricQueryConfigData from "../../../Types/Metrics/MetricQueryConfigData";
|
|
17
|
+
import MetricFormulaConfigData from "../../../Types/Metrics/MetricFormulaConfigData";
|
|
18
|
+
import {
|
|
19
|
+
CheckOn,
|
|
20
|
+
CriteriaFilter,
|
|
21
|
+
FilterType,
|
|
22
|
+
} from "../../../Types/Monitor/CriteriaFilter";
|
|
23
|
+
import { ServiceLanguage } from "../../../Types/Service/ServiceLanguage";
|
|
24
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* The service alert library, and the only template module whose answer depends
|
|
28
|
+
* on the resource rather than only on its type.
|
|
29
|
+
*
|
|
30
|
+
* The failure modes worth spending a test file on are all silent — a template
|
|
31
|
+
* that is registered, renders a card, creates a monitor, and then never fires:
|
|
32
|
+
*
|
|
33
|
+
* 1. A `checkOn` the evaluator does not implement for that monitor type.
|
|
34
|
+
* Only `MetricValue` / `SpanCount` / `ExceptionCount` are evaluated, one
|
|
35
|
+
* per monitor type, and anything else returns null forever.
|
|
36
|
+
* 2. A `metricAlias` that matches no query and no formula. The evaluator
|
|
37
|
+
* silently falls back to result slot 0, so a ratio template with a typo'd
|
|
38
|
+
* alias thresholds raw bytes against "90" and fires permanently.
|
|
39
|
+
* 3. A monitor step that is not scoped to the service, which would evaluate
|
|
40
|
+
* against every service in the project.
|
|
41
|
+
* 4. The language filter letting one runtime's templates through to another
|
|
42
|
+
* runtime, which is how you get a JVM monitor on a Go service.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
const ARGS: ServiceAlertTemplateArgs = {
|
|
46
|
+
serviceId: ObjectID.generate().toString(),
|
|
47
|
+
onlineMonitorStatusId: ObjectID.generate(),
|
|
48
|
+
offlineMonitorStatusId: ObjectID.generate(),
|
|
49
|
+
defaultIncidentSeverityId: ObjectID.generate(),
|
|
50
|
+
defaultAlertSeverityId: ObjectID.generate(),
|
|
51
|
+
monitorName: "payments-api",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// The counts are canaries. Update them deliberately when adding a template.
|
|
55
|
+
const AGNOSTIC_COUNT: number = 8;
|
|
56
|
+
|
|
57
|
+
const TEMPLATE_COUNT_BY_LANGUAGE: Partial<Record<ServiceLanguage, number>> = {
|
|
58
|
+
java: 6,
|
|
59
|
+
dotnet: 4,
|
|
60
|
+
nodejs: 4,
|
|
61
|
+
python: 3,
|
|
62
|
+
go: 4,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
* Languages the module deliberately ships nothing extra for. They are not an
|
|
67
|
+
* oversight: none of these ecosystems has a default OpenTelemetry runtime
|
|
68
|
+
* metrics source, so any runtime template written for them would query a
|
|
69
|
+
* metric name that nobody emits.
|
|
70
|
+
*/
|
|
71
|
+
const LANGUAGES_WITHOUT_TEMPLATES: Array<ServiceLanguage> = [
|
|
72
|
+
"ruby",
|
|
73
|
+
"php",
|
|
74
|
+
"rust",
|
|
75
|
+
"erlang",
|
|
76
|
+
"swift",
|
|
77
|
+
"cpp",
|
|
78
|
+
"webjs",
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const ALL_TEMPLATES: Array<ServiceAlertTemplate> =
|
|
82
|
+
getAllServiceAlertTemplates();
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
* The one CheckOn the evaluator implements for each monitor type. Anything
|
|
86
|
+
* else is accepted by the form, saved to the database, and then never
|
|
87
|
+
* compared against anything.
|
|
88
|
+
*/
|
|
89
|
+
const CHECK_ON_BY_MONITOR_TYPE: Record<string, CheckOn> = {
|
|
90
|
+
[MonitorType.Metrics]: CheckOn.MetricValue,
|
|
91
|
+
[MonitorType.Traces]: CheckOn.SpanCount,
|
|
92
|
+
[MonitorType.Exceptions]: CheckOn.ExceptionCount,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// The comparators `CompareCriteria.compareCriteriaNumbers` actually implements.
|
|
96
|
+
const NUMERIC_FILTER_TYPES: Array<FilterType> = [
|
|
97
|
+
FilterType.GreaterThan,
|
|
98
|
+
FilterType.LessThan,
|
|
99
|
+
FilterType.EqualTo,
|
|
100
|
+
FilterType.NotEqualTo,
|
|
101
|
+
FilterType.GreaterThanOrEqualTo,
|
|
102
|
+
FilterType.LessThanOrEqualTo,
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
function getCriteriaInstances(
|
|
106
|
+
step: MonitorStep,
|
|
107
|
+
): Array<MonitorCriteriaInstance> {
|
|
108
|
+
return step.data?.monitorCriteria?.data?.monitorCriteriaInstanceArray || [];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getFilters(step: MonitorStep): Array<CriteriaFilter> {
|
|
112
|
+
return getCriteriaInstances(step).flatMap(
|
|
113
|
+
(instance: MonitorCriteriaInstance) => {
|
|
114
|
+
return instance.data?.filters || [];
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function buildStep(template: ServiceAlertTemplate): MonitorStep {
|
|
120
|
+
return template.getMonitorStep(ARGS);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
describe("ServiceAlertTemplates", () => {
|
|
124
|
+
describe("the library itself", () => {
|
|
125
|
+
it("ships the expected number of templates", () => {
|
|
126
|
+
const languageCount: number = (
|
|
127
|
+
Object.values(TEMPLATE_COUNT_BY_LANGUAGE) as Array<number>
|
|
128
|
+
).reduce((total: number, count: number) => {
|
|
129
|
+
return total + count;
|
|
130
|
+
}, 0);
|
|
131
|
+
|
|
132
|
+
expect(ALL_TEMPLATES.length).toBe(AGNOSTIC_COUNT + languageCount);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("gives every template a unique id", () => {
|
|
136
|
+
const ids: Array<string> = ALL_TEMPLATES.map(
|
|
137
|
+
(template: ServiceAlertTemplate) => {
|
|
138
|
+
return template.id;
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(new Set<string>(ids).size).toBe(ids.length);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/*
|
|
146
|
+
* Template ids are only guaranteed unique WITHIN a module, but the
|
|
147
|
+
* registry-wide id is `<resourceType>:<templateId>` and every module
|
|
148
|
+
* self-prefixes by convention. Keeping the convention here means a
|
|
149
|
+
* `service-` id can never be confused for anyone else's in a log line or a
|
|
150
|
+
* dismissal row.
|
|
151
|
+
*/
|
|
152
|
+
it("prefixes every id with service-", () => {
|
|
153
|
+
for (const template of ALL_TEMPLATES) {
|
|
154
|
+
expect(template.id.startsWith("service-")).toBe(true);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("gives every template a name and a description", () => {
|
|
159
|
+
for (const template of ALL_TEMPLATES) {
|
|
160
|
+
expect(template.name.trim().length).toBeGreaterThan(0);
|
|
161
|
+
expect(template.description.trim().length).toBeGreaterThan(0);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("uses only the three telemetry monitor types", () => {
|
|
166
|
+
for (const template of ALL_TEMPLATES) {
|
|
167
|
+
expect([
|
|
168
|
+
MonitorType.Metrics,
|
|
169
|
+
MonitorType.Traces,
|
|
170
|
+
MonitorType.Exceptions,
|
|
171
|
+
]).toContain(template.monitorType);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("uses only the two recommendation severities", () => {
|
|
176
|
+
for (const template of ALL_TEMPLATES) {
|
|
177
|
+
expect(["Critical", "Warning"]).toContain(template.severity);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
/*
|
|
182
|
+
* The accessors hand out copies. A caller that sorts or splices the result
|
|
183
|
+
* — the recommendations page sorts for display — must not be able to
|
|
184
|
+
* reorder the library for everyone else, which would make the category
|
|
185
|
+
* order the page renders depend on who rendered first.
|
|
186
|
+
*/
|
|
187
|
+
it("does not leak the internal array", () => {
|
|
188
|
+
const first: Array<ServiceAlertTemplate> = getAllServiceAlertTemplates();
|
|
189
|
+
first.pop();
|
|
190
|
+
|
|
191
|
+
expect(getAllServiceAlertTemplates().length).toBe(ALL_TEMPLATES.length);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("looks a template up by id, and answers undefined for an unknown one", () => {
|
|
195
|
+
for (const template of ALL_TEMPLATES) {
|
|
196
|
+
expect(getServiceAlertTemplateById(template.id)).toBe(template);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
expect(
|
|
200
|
+
getServiceAlertTemplateById("service-does-not-exist"),
|
|
201
|
+
).toBeUndefined();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe("selecting by language", () => {
|
|
206
|
+
it("returns exactly the agnostic set when the language is unknown", () => {
|
|
207
|
+
const forNull: Array<ServiceAlertTemplate> =
|
|
208
|
+
getServiceAlertTemplates(null);
|
|
209
|
+
|
|
210
|
+
expect(forNull.length).toBe(AGNOSTIC_COUNT);
|
|
211
|
+
expect(getServiceAlertTemplates(undefined).length).toBe(AGNOSTIC_COUNT);
|
|
212
|
+
expect(getServiceAlertTemplates().length).toBe(AGNOSTIC_COUNT);
|
|
213
|
+
|
|
214
|
+
for (const template of forNull) {
|
|
215
|
+
expect(template.language).toBeUndefined();
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it.each(
|
|
220
|
+
Object.entries(TEMPLATE_COUNT_BY_LANGUAGE) as Array<
|
|
221
|
+
[ServiceLanguage, number]
|
|
222
|
+
>,
|
|
223
|
+
)(
|
|
224
|
+
"gives %s the agnostic set plus %s of its own",
|
|
225
|
+
(language: ServiceLanguage, ownCount: number) => {
|
|
226
|
+
const templates: Array<ServiceAlertTemplate> =
|
|
227
|
+
getServiceAlertTemplates(language);
|
|
228
|
+
|
|
229
|
+
expect(templates.length).toBe(AGNOSTIC_COUNT + ownCount);
|
|
230
|
+
|
|
231
|
+
const own: Array<ServiceAlertTemplate> = templates.filter(
|
|
232
|
+
(template: ServiceAlertTemplate) => {
|
|
233
|
+
return Boolean(template.language);
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
expect(own.length).toBe(ownCount);
|
|
238
|
+
|
|
239
|
+
for (const template of own) {
|
|
240
|
+
expect(template.language).toBe(language);
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
/*
|
|
246
|
+
* The whole point of the feature: one runtime's templates must never
|
|
247
|
+
* appear for another. A leak here is not a rendering bug — it creates
|
|
248
|
+
* monitors that query metrics the service does not emit and sit green
|
|
249
|
+
* forever.
|
|
250
|
+
*/
|
|
251
|
+
it("never mixes one runtime's templates into another's", () => {
|
|
252
|
+
const languages: Array<ServiceLanguage> =
|
|
253
|
+
getLanguagesWithServiceAlertTemplates();
|
|
254
|
+
|
|
255
|
+
expect(languages.length).toBe(
|
|
256
|
+
Object.keys(TEMPLATE_COUNT_BY_LANGUAGE).length,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
for (const language of languages) {
|
|
260
|
+
for (const template of getServiceAlertTemplates(language)) {
|
|
261
|
+
if (template.language) {
|
|
262
|
+
expect(template.language).toBe(language);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it.each(LANGUAGES_WITHOUT_TEMPLATES)(
|
|
269
|
+
"gives %s exactly the agnostic set, rather than nothing",
|
|
270
|
+
(language: ServiceLanguage) => {
|
|
271
|
+
const templates: Array<ServiceAlertTemplate> =
|
|
272
|
+
getServiceAlertTemplates(language);
|
|
273
|
+
|
|
274
|
+
expect(templates.length).toBe(AGNOSTIC_COUNT);
|
|
275
|
+
|
|
276
|
+
for (const template of templates) {
|
|
277
|
+
expect(template.language).toBeUndefined();
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
/*
|
|
283
|
+
* Order matters downstream: `MonitorRecommendationCatalog.getCategories`
|
|
284
|
+
* derives its section order from the order templates are returned in, so
|
|
285
|
+
* the agnostic sections have to stay on top for every language.
|
|
286
|
+
*/
|
|
287
|
+
it("returns the agnostic set first, in a stable order", () => {
|
|
288
|
+
const agnosticIds: Array<string> = getServiceAlertTemplates(null).map(
|
|
289
|
+
(template: ServiceAlertTemplate) => {
|
|
290
|
+
return template.id;
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
for (const language of getLanguagesWithServiceAlertTemplates()) {
|
|
295
|
+
const ids: Array<string> = getServiceAlertTemplates(language).map(
|
|
296
|
+
(template: ServiceAlertTemplate) => {
|
|
297
|
+
return template.id;
|
|
298
|
+
},
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
expect(ids.slice(0, agnosticIds.length)).toEqual(agnosticIds);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("reports only languages that actually have templates", () => {
|
|
306
|
+
for (const language of getLanguagesWithServiceAlertTemplates()) {
|
|
307
|
+
expect(LANGUAGES_WITHOUT_TEMPLATES).not.toContain(language);
|
|
308
|
+
expect(TEMPLATE_COUNT_BY_LANGUAGE[language]).toBeGreaterThan(0);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe("the monitor step each template builds", () => {
|
|
314
|
+
it("produces a step that passes monitor-step validation", () => {
|
|
315
|
+
for (const template of ALL_TEMPLATES) {
|
|
316
|
+
const monitorSteps: MonitorSteps = new MonitorSteps();
|
|
317
|
+
|
|
318
|
+
monitorSteps.data = {
|
|
319
|
+
monitorStepsInstanceArray: [buildStep(template)],
|
|
320
|
+
defaultMonitorStatusId: ARGS.onlineMonitorStatusId,
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
expect(
|
|
324
|
+
MonitorSteps.getValidationError(monitorSteps, template.monitorType),
|
|
325
|
+
).toBeNull();
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("seeds the sub-config its monitor type is evaluated through", () => {
|
|
330
|
+
/*
|
|
331
|
+
* `getDefaultMonitorStep` only seeds the config matching the monitor
|
|
332
|
+
* type, and the metrics worker THROWS when `metricMonitor` is missing
|
|
333
|
+
* rather than falling back — so a Metrics template that forgets
|
|
334
|
+
* `setMetricMonitor` is a monitor that errors every cycle.
|
|
335
|
+
*/
|
|
336
|
+
for (const template of ALL_TEMPLATES) {
|
|
337
|
+
const step: MonitorStep = buildStep(template);
|
|
338
|
+
|
|
339
|
+
if (template.monitorType === MonitorType.Metrics) {
|
|
340
|
+
expect(step.data?.metricMonitor).toBeDefined();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (template.monitorType === MonitorType.Traces) {
|
|
344
|
+
expect(step.data?.traceMonitor).toBeDefined();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (template.monitorType === MonitorType.Exceptions) {
|
|
348
|
+
expect(step.data?.exceptionMonitor).toBeDefined();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("scopes every step to the service it was built for", () => {
|
|
354
|
+
for (const template of ALL_TEMPLATES) {
|
|
355
|
+
const step: MonitorStep = buildStep(template);
|
|
356
|
+
|
|
357
|
+
const config:
|
|
358
|
+
| MonitorStepMetricMonitor
|
|
359
|
+
| MonitorStepTraceMonitor
|
|
360
|
+
| MonitorStepExceptionMonitor
|
|
361
|
+
| undefined =
|
|
362
|
+
step.data?.metricMonitor ||
|
|
363
|
+
step.data?.traceMonitor ||
|
|
364
|
+
step.data?.exceptionMonitor;
|
|
365
|
+
|
|
366
|
+
const telemetryServiceIds: Array<ObjectID> =
|
|
367
|
+
config?.telemetryServiceIds || [];
|
|
368
|
+
|
|
369
|
+
expect(telemetryServiceIds.length).toBe(1);
|
|
370
|
+
expect(telemetryServiceIds[0]!.toString()).toBe(ARGS.serviceId);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("builds an unhealthy and a healthy criteria instance for every template", () => {
|
|
375
|
+
/*
|
|
376
|
+
* Both, always. A step with no healthy criteria opens an incident,
|
|
377
|
+
* auto-resolves it, and then leaves the monitor in its offline status
|
|
378
|
+
* with nothing that can ever move it back.
|
|
379
|
+
*/
|
|
380
|
+
for (const template of ALL_TEMPLATES) {
|
|
381
|
+
const instances: Array<MonitorCriteriaInstance> = getCriteriaInstances(
|
|
382
|
+
buildStep(template),
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
expect(instances.length).toBe(2);
|
|
386
|
+
|
|
387
|
+
const [unhealthy, healthy]: Array<MonitorCriteriaInstance> = instances;
|
|
388
|
+
|
|
389
|
+
expect(unhealthy!.data?.monitorStatusId?.toString()).toBe(
|
|
390
|
+
ARGS.offlineMonitorStatusId.toString(),
|
|
391
|
+
);
|
|
392
|
+
expect(unhealthy!.data?.createIncidents).toBe(true);
|
|
393
|
+
expect(unhealthy!.data?.createAlerts).toBe(true);
|
|
394
|
+
expect(unhealthy!.data?.incidents?.length).toBe(1);
|
|
395
|
+
expect(unhealthy!.data?.alerts?.length).toBe(1);
|
|
396
|
+
|
|
397
|
+
expect(healthy!.data?.monitorStatusId?.toString()).toBe(
|
|
398
|
+
ARGS.onlineMonitorStatusId.toString(),
|
|
399
|
+
);
|
|
400
|
+
expect(healthy!.data?.createIncidents).toBe(false);
|
|
401
|
+
expect(healthy!.data?.createAlerts).toBe(false);
|
|
402
|
+
expect(healthy!.data?.incidents?.length).toBe(0);
|
|
403
|
+
expect(healthy!.data?.alerts?.length).toBe(0);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("names and describes every criteria instance", () => {
|
|
408
|
+
for (const template of ALL_TEMPLATES) {
|
|
409
|
+
for (const instance of getCriteriaInstances(buildStep(template))) {
|
|
410
|
+
expect((instance.data?.name || "").trim().length).toBeGreaterThan(0);
|
|
411
|
+
expect(
|
|
412
|
+
(instance.data?.description || "").trim().length,
|
|
413
|
+
).toBeGreaterThan(0);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("interpolates the monitor name into every incident title", () => {
|
|
419
|
+
for (const template of ALL_TEMPLATES) {
|
|
420
|
+
for (const instance of getCriteriaInstances(buildStep(template))) {
|
|
421
|
+
for (const incident of instance.data?.incidents || []) {
|
|
422
|
+
expect(incident.title).toContain(ARGS.monitorName);
|
|
423
|
+
expect((incident.description || "").trim().length).toBeGreaterThan(
|
|
424
|
+
0,
|
|
425
|
+
);
|
|
426
|
+
expect(incident.incidentSeverityId?.toString()).toBe(
|
|
427
|
+
ARGS.defaultIncidentSeverityId.toString(),
|
|
428
|
+
);
|
|
429
|
+
expect(incident.autoResolveIncident).toBe(true);
|
|
430
|
+
// Filled in by the create flow from what the user picks.
|
|
431
|
+
expect(incident.onCallPolicyIds).toEqual([]);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
for (const alert of instance.data?.alerts || []) {
|
|
435
|
+
expect(alert.title).toContain(ARGS.monitorName);
|
|
436
|
+
expect(alert.alertSeverityId?.toString()).toBe(
|
|
437
|
+
ARGS.defaultAlertSeverityId.toString(),
|
|
438
|
+
);
|
|
439
|
+
expect(alert.autoResolveAlert).toBe(true);
|
|
440
|
+
expect(alert.onCallPolicyIds).toEqual([]);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
describe("criteria the evaluator can actually evaluate", () => {
|
|
448
|
+
/*
|
|
449
|
+
* The highest-value test in the file. There is exactly one implemented
|
|
450
|
+
* CheckOn per telemetry monitor type; every other value type-checks, saves
|
|
451
|
+
* and is then ignored, producing a monitor that is green forever.
|
|
452
|
+
*/
|
|
453
|
+
it("uses the one checkOn its monitor type implements", () => {
|
|
454
|
+
for (const template of ALL_TEMPLATES) {
|
|
455
|
+
const expected: CheckOn | undefined =
|
|
456
|
+
CHECK_ON_BY_MONITOR_TYPE[template.monitorType];
|
|
457
|
+
|
|
458
|
+
expect(expected).toBeDefined();
|
|
459
|
+
|
|
460
|
+
const filters: Array<CriteriaFilter> = getFilters(buildStep(template));
|
|
461
|
+
|
|
462
|
+
expect(filters.length).toBeGreaterThan(0);
|
|
463
|
+
|
|
464
|
+
for (const filter of filters) {
|
|
465
|
+
expect(filter.checkOn).toBe(expected);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it("uses only comparators the numeric evaluator implements", () => {
|
|
471
|
+
for (const template of ALL_TEMPLATES) {
|
|
472
|
+
for (const filter of getFilters(buildStep(template))) {
|
|
473
|
+
expect(NUMERIC_FILTER_TYPES).toContain(filter.filterType);
|
|
474
|
+
expect(typeof filter.value).toBe("number");
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
/*
|
|
480
|
+
* `evaluateOverTime` belongs to the probe-monitor path and is not read on
|
|
481
|
+
* the telemetry path. Setting it would look like it configured something.
|
|
482
|
+
*/
|
|
483
|
+
it("does not set evaluateOverTime on any filter", () => {
|
|
484
|
+
for (const template of ALL_TEMPLATES) {
|
|
485
|
+
for (const filter of getFilters(buildStep(template))) {
|
|
486
|
+
expect(filter.evaluateOverTime).toBeFalsy();
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("partitions the range at the threshold, with no gap and no overlap", () => {
|
|
492
|
+
/*
|
|
493
|
+
* The unhealthy and healthy filters must be exact complements. A gap
|
|
494
|
+
* leaves the monitor stuck in whichever state it was last in; an
|
|
495
|
+
* overlap makes the outcome depend on evaluation order.
|
|
496
|
+
*/
|
|
497
|
+
const COMPLEMENTS: Record<string, FilterType> = {
|
|
498
|
+
[FilterType.GreaterThan]: FilterType.LessThanOrEqualTo,
|
|
499
|
+
[FilterType.GreaterThanOrEqualTo]: FilterType.LessThan,
|
|
500
|
+
[FilterType.LessThan]: FilterType.GreaterThanOrEqualTo,
|
|
501
|
+
[FilterType.LessThanOrEqualTo]: FilterType.GreaterThan,
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
for (const template of ALL_TEMPLATES) {
|
|
505
|
+
const instances: Array<MonitorCriteriaInstance> = getCriteriaInstances(
|
|
506
|
+
buildStep(template),
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
const unhealthy: CriteriaFilter = instances[0]!.data!.filters[0]!;
|
|
510
|
+
const healthy: CriteriaFilter = instances[1]!.data!.filters[0]!;
|
|
511
|
+
|
|
512
|
+
expect(healthy.filterType).toBe(
|
|
513
|
+
COMPLEMENTS[unhealthy.filterType as FilterType],
|
|
514
|
+
);
|
|
515
|
+
expect(healthy.value).toBe(unhealthy.value);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
/*
|
|
520
|
+
* A metricAlias that matches no query and no formula does not throw — the
|
|
521
|
+
* evaluator falls back to result slot 0 and thresholds whatever happens to
|
|
522
|
+
* be there. On the two ratio templates that means comparing raw bytes
|
|
523
|
+
* against 90, which fires immediately and never clears.
|
|
524
|
+
*/
|
|
525
|
+
it("names an alias that exists, for every metric template", () => {
|
|
526
|
+
for (const template of ALL_TEMPLATES) {
|
|
527
|
+
if (template.monitorType !== MonitorType.Metrics) {
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const step: MonitorStep = buildStep(template);
|
|
532
|
+
const metricMonitor: MonitorStepMetricMonitor | undefined =
|
|
533
|
+
step.data?.metricMonitor;
|
|
534
|
+
|
|
535
|
+
const knownAliases: Array<string> = [
|
|
536
|
+
...(metricMonitor?.metricViewConfig?.queryConfigs || []).map(
|
|
537
|
+
(queryConfig: MetricQueryConfigData) => {
|
|
538
|
+
return queryConfig.metricAliasData?.metricVariable || "";
|
|
539
|
+
},
|
|
540
|
+
),
|
|
541
|
+
...(metricMonitor?.metricViewConfig?.formulaConfigs || []).map(
|
|
542
|
+
(formulaConfig: MetricFormulaConfigData) => {
|
|
543
|
+
return formulaConfig.metricAliasData?.metricVariable || "";
|
|
544
|
+
},
|
|
545
|
+
),
|
|
546
|
+
];
|
|
547
|
+
|
|
548
|
+
for (const filter of getFilters(step)) {
|
|
549
|
+
const alias: string | undefined =
|
|
550
|
+
filter.metricMonitorOptions?.metricAlias;
|
|
551
|
+
|
|
552
|
+
expect(alias).toBeTruthy();
|
|
553
|
+
expect(knownAliases).toContain(alias);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it("gives every metric query a metric name and a distinct alias", () => {
|
|
559
|
+
for (const template of ALL_TEMPLATES) {
|
|
560
|
+
if (template.monitorType !== MonitorType.Metrics) {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const metricMonitor: MonitorStepMetricMonitor | undefined =
|
|
565
|
+
buildStep(template).data?.metricMonitor;
|
|
566
|
+
|
|
567
|
+
const queryConfigs: Array<MetricQueryConfigData> =
|
|
568
|
+
metricMonitor?.metricViewConfig?.queryConfigs || [];
|
|
569
|
+
|
|
570
|
+
expect(queryConfigs.length).toBeGreaterThan(0);
|
|
571
|
+
|
|
572
|
+
const aliases: Array<string> = [];
|
|
573
|
+
|
|
574
|
+
for (const queryConfig of queryConfigs) {
|
|
575
|
+
const metricName: unknown =
|
|
576
|
+
queryConfig.metricQueryData?.filterData?.["metricName"];
|
|
577
|
+
|
|
578
|
+
expect(typeof metricName).toBe("string");
|
|
579
|
+
expect((metricName as string).length).toBeGreaterThan(0);
|
|
580
|
+
|
|
581
|
+
const alias: string =
|
|
582
|
+
queryConfig.metricAliasData?.metricVariable || "";
|
|
583
|
+
|
|
584
|
+
expect(alias.length).toBeGreaterThan(0);
|
|
585
|
+
aliases.push(alias);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
for (const formulaConfig of metricMonitor?.metricViewConfig
|
|
589
|
+
?.formulaConfigs || []) {
|
|
590
|
+
aliases.push(formulaConfig.metricAliasData?.metricVariable || "");
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
expect(new Set<string>(aliases).size).toBe(aliases.length);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
describe("the two ratio templates", () => {
|
|
599
|
+
const RATIO_TEMPLATE_IDS: Array<string> = [
|
|
600
|
+
"service-java-heap-utilization",
|
|
601
|
+
"service-nodejs-heap-pressure",
|
|
602
|
+
];
|
|
603
|
+
|
|
604
|
+
it.each(RATIO_TEMPLATE_IDS)(
|
|
605
|
+
"%s thresholds the formula, not either of its two queries",
|
|
606
|
+
(templateId: string) => {
|
|
607
|
+
const template: ServiceAlertTemplate | undefined =
|
|
608
|
+
getServiceAlertTemplateById(templateId);
|
|
609
|
+
|
|
610
|
+
expect(template).toBeDefined();
|
|
611
|
+
|
|
612
|
+
const metricMonitor: MonitorStepMetricMonitor | undefined = buildStep(
|
|
613
|
+
template!,
|
|
614
|
+
).data?.metricMonitor;
|
|
615
|
+
|
|
616
|
+
const queryConfigs: Array<MetricQueryConfigData> =
|
|
617
|
+
metricMonitor?.metricViewConfig?.queryConfigs || [];
|
|
618
|
+
const formulaConfigs: Array<MetricFormulaConfigData> =
|
|
619
|
+
metricMonitor?.metricViewConfig?.formulaConfigs || [];
|
|
620
|
+
|
|
621
|
+
expect(queryConfigs.length).toBe(2);
|
|
622
|
+
expect(formulaConfigs.length).toBe(1);
|
|
623
|
+
|
|
624
|
+
const formulaAlias: string =
|
|
625
|
+
formulaConfigs[0]!.metricAliasData?.metricVariable || "";
|
|
626
|
+
const numeratorAlias: string =
|
|
627
|
+
queryConfigs[0]!.metricAliasData?.metricVariable || "";
|
|
628
|
+
const denominatorAlias: string =
|
|
629
|
+
queryConfigs[1]!.metricAliasData?.metricVariable || "";
|
|
630
|
+
|
|
631
|
+
expect(formulaConfigs[0]!.metricFormulaData.metricFormula).toBe(
|
|
632
|
+
`(${numeratorAlias} / ${denominatorAlias}) * 100`,
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
for (const filter of getFilters(buildStep(template!))) {
|
|
636
|
+
expect(filter.metricMonitorOptions?.metricAlias).toBe(formulaAlias);
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
it.each(RATIO_TEMPLATE_IDS)(
|
|
642
|
+
"%s thresholds a percentage, so it needs no per-deployment tuning",
|
|
643
|
+
(templateId: string) => {
|
|
644
|
+
const template: ServiceAlertTemplate =
|
|
645
|
+
getServiceAlertTemplateById(templateId)!;
|
|
646
|
+
|
|
647
|
+
for (const filter of getFilters(buildStep(template))) {
|
|
648
|
+
expect(filter.value).toBeGreaterThan(0);
|
|
649
|
+
expect(filter.value).toBeLessThanOrEqual(100);
|
|
650
|
+
expect(filter.metricMonitorOptions?.thresholdUnit).toBe("%");
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
describe("the metric names each template reads", () => {
|
|
657
|
+
/*
|
|
658
|
+
* Restated here so a rename has to be made twice, on purpose. A template
|
|
659
|
+
* pointed at a metric name nobody emits is registered, renders, creates a
|
|
660
|
+
* monitor, and is green forever — there is no failing state to notice.
|
|
661
|
+
*/
|
|
662
|
+
const EXPECTED_METRIC_NAMES: Record<string, Array<string>> = {
|
|
663
|
+
"service-latency-p95": ["http.server.request.duration"],
|
|
664
|
+
"service-latency-p99": ["http.server.request.duration"],
|
|
665
|
+
"service-process-cpu-saturation": ["process.cpu.utilization"],
|
|
666
|
+
"service-process-memory-high": ["process.memory.usage"],
|
|
667
|
+
"service-java-heap-utilization": ["jvm.memory.used", "jvm.memory.limit"],
|
|
668
|
+
"service-java-live-heap-after-gc": ["jvm.memory.used_after_last_gc"],
|
|
669
|
+
"service-java-gc-pause-p99": ["jvm.gc.duration"],
|
|
670
|
+
"service-java-cpu-saturation": ["jvm.cpu.recent_utilization"],
|
|
671
|
+
"service-java-thread-explosion": ["jvm.thread.count"],
|
|
672
|
+
"service-java-non-heap-growth": ["jvm.memory.used"],
|
|
673
|
+
"service-dotnet-threadpool-starvation": [
|
|
674
|
+
"dotnet.thread_pool.queue.length",
|
|
675
|
+
],
|
|
676
|
+
"service-dotnet-threadpool-growth": ["dotnet.thread_pool.thread.count"],
|
|
677
|
+
"service-dotnet-gen2-heap-growth": [
|
|
678
|
+
"dotnet.gc.last_collection.heap.size",
|
|
679
|
+
],
|
|
680
|
+
"service-dotnet-working-set": ["dotnet.process.memory.working_set"],
|
|
681
|
+
"service-nodejs-event-loop-saturated": ["nodejs.eventloop.utilization"],
|
|
682
|
+
"service-nodejs-event-loop-lag": ["nodejs.eventloop.delay.p99"],
|
|
683
|
+
"service-nodejs-heap-pressure": [
|
|
684
|
+
"v8js.memory.heap.used",
|
|
685
|
+
"v8js.memory.heap.limit",
|
|
686
|
+
],
|
|
687
|
+
"service-nodejs-gc-pause-p99": ["v8js.gc.duration"],
|
|
688
|
+
"service-python-request-concurrency": ["http.server.active_requests"],
|
|
689
|
+
"service-python-rss-memory": ["process.runtime.cpython.memory"],
|
|
690
|
+
"service-python-thread-growth": ["process.runtime.cpython.thread_count"],
|
|
691
|
+
"service-go-goroutine-leak": ["go.goroutine.count"],
|
|
692
|
+
"service-go-heap-memory": ["go.memory.used"],
|
|
693
|
+
"service-go-stack-memory": ["go.memory.used"],
|
|
694
|
+
"service-go-scheduler-latency-p99": ["go.schedule.duration"],
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
it("covers every metric template", () => {
|
|
698
|
+
const metricTemplateIds: Array<string> = ALL_TEMPLATES.filter(
|
|
699
|
+
(template: ServiceAlertTemplate) => {
|
|
700
|
+
return template.monitorType === MonitorType.Metrics;
|
|
701
|
+
},
|
|
702
|
+
).map((template: ServiceAlertTemplate) => {
|
|
703
|
+
return template.id;
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
expect(metricTemplateIds.sort()).toEqual(
|
|
707
|
+
Object.keys(EXPECTED_METRIC_NAMES).sort(),
|
|
708
|
+
);
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
it.each(Object.entries(EXPECTED_METRIC_NAMES))(
|
|
712
|
+
"%s reads %s",
|
|
713
|
+
(templateId: string, metricNames: Array<string>) => {
|
|
714
|
+
const template: ServiceAlertTemplate =
|
|
715
|
+
getServiceAlertTemplateById(templateId)!;
|
|
716
|
+
|
|
717
|
+
const queryConfigs: Array<MetricQueryConfigData> =
|
|
718
|
+
buildStep(template).data?.metricMonitor?.metricViewConfig
|
|
719
|
+
?.queryConfigs || [];
|
|
720
|
+
|
|
721
|
+
expect(
|
|
722
|
+
queryConfigs.map((queryConfig: MetricQueryConfigData) => {
|
|
723
|
+
return queryConfig.metricQueryData?.filterData?.["metricName"];
|
|
724
|
+
}),
|
|
725
|
+
).toEqual(metricNames);
|
|
726
|
+
},
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
/*
|
|
730
|
+
* Two Go templates read the same metric and are told apart only by an
|
|
731
|
+
* attribute filter: `go.memory.used` splits into `stack` and `other`
|
|
732
|
+
* series, and an unfiltered query averages the two — reporting about half
|
|
733
|
+
* the heap, which makes the threshold wrong by that factor.
|
|
734
|
+
*/
|
|
735
|
+
it("separates the two go.memory.used templates by attribute", () => {
|
|
736
|
+
const heap: ServiceAlertTemplate = getServiceAlertTemplateById(
|
|
737
|
+
"service-go-heap-memory",
|
|
738
|
+
)!;
|
|
739
|
+
const stack: ServiceAlertTemplate = getServiceAlertTemplateById(
|
|
740
|
+
"service-go-stack-memory",
|
|
741
|
+
)!;
|
|
742
|
+
|
|
743
|
+
const attributesOf: (template: ServiceAlertTemplate) => unknown = (
|
|
744
|
+
template: ServiceAlertTemplate,
|
|
745
|
+
): unknown => {
|
|
746
|
+
return buildStep(template).data?.metricMonitor?.metricViewConfig
|
|
747
|
+
?.queryConfigs?.[0]?.metricQueryData?.filterData?.["attributes"];
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
expect(attributesOf(heap)).toEqual({ "go.memory.type": "other" });
|
|
751
|
+
expect(attributesOf(stack)).toEqual({ "go.memory.type": "stack" });
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
it("separates the two jvm.memory.used templates by attribute", () => {
|
|
755
|
+
const nonHeap: ServiceAlertTemplate = getServiceAlertTemplateById(
|
|
756
|
+
"service-java-non-heap-growth",
|
|
757
|
+
)!;
|
|
758
|
+
|
|
759
|
+
expect(
|
|
760
|
+
buildStep(nonHeap).data?.metricMonitor?.metricViewConfig
|
|
761
|
+
?.queryConfigs?.[0]?.metricQueryData?.filterData?.["attributes"],
|
|
762
|
+
).toEqual({ "jvm.memory.type": "non_heap" });
|
|
763
|
+
});
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
describe("the trace and exception templates", () => {
|
|
767
|
+
it("counts only error-status spans on the two error templates", () => {
|
|
768
|
+
for (const templateId of [
|
|
769
|
+
"service-failed-operations",
|
|
770
|
+
"service-error-burst",
|
|
771
|
+
]) {
|
|
772
|
+
const traceMonitor: MonitorStepTraceMonitor | undefined = buildStep(
|
|
773
|
+
getServiceAlertTemplateById(templateId)!,
|
|
774
|
+
).data?.traceMonitor;
|
|
775
|
+
|
|
776
|
+
expect(traceMonitor?.spanStatuses?.length).toBe(1);
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
/*
|
|
781
|
+
* The liveness template counts ALL spans, not error spans — filtering to
|
|
782
|
+
* errors would make "no errors" indistinguishable from "no traffic", and
|
|
783
|
+
* the whole point of it is to catch the second.
|
|
784
|
+
*/
|
|
785
|
+
it("counts all spans on the liveness template, and inverts the comparison", () => {
|
|
786
|
+
const template: ServiceAlertTemplate = getServiceAlertTemplateById(
|
|
787
|
+
"service-traffic-stopped",
|
|
788
|
+
)!;
|
|
789
|
+
|
|
790
|
+
const step: MonitorStep = buildStep(template);
|
|
791
|
+
|
|
792
|
+
expect(step.data?.traceMonitor?.spanStatuses).toEqual([]);
|
|
793
|
+
|
|
794
|
+
const filters: Array<CriteriaFilter> = getFilters(step);
|
|
795
|
+
|
|
796
|
+
expect(filters[0]!.filterType).toBe(FilterType.LessThan);
|
|
797
|
+
expect(filters[1]!.filterType).toBe(FilterType.GreaterThanOrEqualTo);
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
/*
|
|
801
|
+
* Excluding resolved and archived exception groups is what lets
|
|
802
|
+
* acknowledging an exception close the incident it opened. Including them
|
|
803
|
+
* would make the alert unclearable short of the occurrences ageing out.
|
|
804
|
+
*/
|
|
805
|
+
it("ignores exceptions the team has already resolved or archived", () => {
|
|
806
|
+
const exceptionMonitor: MonitorStepExceptionMonitor | undefined =
|
|
807
|
+
buildStep(getServiceAlertTemplateById("service-unhandled-exceptions")!)
|
|
808
|
+
.data?.exceptionMonitor;
|
|
809
|
+
|
|
810
|
+
expect(exceptionMonitor?.includeResolved).toBe(false);
|
|
811
|
+
expect(exceptionMonitor?.includeArchived).toBe(false);
|
|
812
|
+
expect(exceptionMonitor?.exceptionTypes).toEqual([]);
|
|
813
|
+
expect(exceptionMonitor?.message).toBe("");
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
/*
|
|
817
|
+
* These two windows are load-bearing beyond their own semantics: they are
|
|
818
|
+
* what keeps these templates' coverage fingerprints distinct from the RUM
|
|
819
|
+
* catalog's structurally identical pair. See the comment on
|
|
820
|
+
* failedOperationsTemplate.
|
|
821
|
+
*/
|
|
822
|
+
it("evaluates the two agnostic error templates over ten minutes", () => {
|
|
823
|
+
expect(
|
|
824
|
+
buildStep(getServiceAlertTemplateById("service-failed-operations")!)
|
|
825
|
+
.data?.traceMonitor?.lastXSecondsOfSpans,
|
|
826
|
+
).toBe(600);
|
|
827
|
+
|
|
828
|
+
expect(
|
|
829
|
+
buildStep(getServiceAlertTemplateById("service-unhandled-exceptions")!)
|
|
830
|
+
.data?.exceptionMonitor?.lastXSecondsOfExceptions,
|
|
831
|
+
).toBe(600);
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
describe("thresholds", () => {
|
|
836
|
+
it("expresses ratio thresholds on the 0-1 scale the SDKs report", () => {
|
|
837
|
+
/*
|
|
838
|
+
* `process.cpu.utilization`, `jvm.cpu.recent_utilization` and
|
|
839
|
+
* `nodejs.eventloop.utilization` are all dimensionless [0, 1] ratios, so
|
|
840
|
+
* "85%" is the literal 0.85. A slip to 85 makes the template
|
|
841
|
+
* unreachable — the value can never exceed 1 — and nothing fails.
|
|
842
|
+
*/
|
|
843
|
+
for (const templateId of [
|
|
844
|
+
"service-process-cpu-saturation",
|
|
845
|
+
"service-java-cpu-saturation",
|
|
846
|
+
"service-nodejs-event-loop-saturated",
|
|
847
|
+
]) {
|
|
848
|
+
for (const filter of getFilters(
|
|
849
|
+
buildStep(getServiceAlertTemplateById(templateId)!),
|
|
850
|
+
)) {
|
|
851
|
+
expect(filter.value).toBeGreaterThan(0);
|
|
852
|
+
expect(filter.value).toBeLessThan(1);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it("states duration thresholds in milliseconds, and says so", () => {
|
|
858
|
+
/*
|
|
859
|
+
* Every duration metric here is reported in seconds by the semantic
|
|
860
|
+
* conventions, so the threshold only means what it reads as if the alias
|
|
861
|
+
* declares milliseconds — the worker converts each sample into the
|
|
862
|
+
* alias's unit before comparing.
|
|
863
|
+
*/
|
|
864
|
+
for (const templateId of [
|
|
865
|
+
"service-latency-p95",
|
|
866
|
+
"service-latency-p99",
|
|
867
|
+
"service-java-gc-pause-p99",
|
|
868
|
+
"service-nodejs-event-loop-lag",
|
|
869
|
+
"service-nodejs-gc-pause-p99",
|
|
870
|
+
"service-go-scheduler-latency-p99",
|
|
871
|
+
]) {
|
|
872
|
+
const template: ServiceAlertTemplate =
|
|
873
|
+
getServiceAlertTemplateById(templateId)!;
|
|
874
|
+
|
|
875
|
+
const queryConfigs: Array<MetricQueryConfigData> =
|
|
876
|
+
buildStep(template).data?.metricMonitor?.metricViewConfig
|
|
877
|
+
?.queryConfigs || [];
|
|
878
|
+
|
|
879
|
+
expect(queryConfigs[0]!.metricAliasData?.legendUnit).toBe("ms");
|
|
880
|
+
|
|
881
|
+
for (const filter of getFilters(buildStep(template))) {
|
|
882
|
+
expect(filter.metricMonitorOptions?.thresholdUnit).toBe("ms");
|
|
883
|
+
expect(filter.value).toBeGreaterThanOrEqual(50);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
it("gives the error-burst template a much higher bar than failed-operations", () => {
|
|
889
|
+
const anyFailure: CriteriaFilter = getFilters(
|
|
890
|
+
buildStep(getServiceAlertTemplateById("service-failed-operations")!),
|
|
891
|
+
)[0]!;
|
|
892
|
+
const burst: CriteriaFilter = getFilters(
|
|
893
|
+
buildStep(getServiceAlertTemplateById("service-error-burst")!),
|
|
894
|
+
)[0]!;
|
|
895
|
+
|
|
896
|
+
expect(Number(burst.value)).toBeGreaterThan(Number(anyFailure.value));
|
|
897
|
+
expect(
|
|
898
|
+
getServiceAlertTemplateById("service-failed-operations")!.severity,
|
|
899
|
+
).toBe("Warning");
|
|
900
|
+
expect(getServiceAlertTemplateById("service-error-burst")!.severity).toBe(
|
|
901
|
+
"Critical",
|
|
902
|
+
);
|
|
903
|
+
});
|
|
904
|
+
});
|
|
905
|
+
});
|