@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
|
@@ -1542,6 +1542,159 @@ export default class AnalyticsDatabaseService<
|
|
|
1542
1542
|
return { ...sort, _id: direction } as Sort<TBaseModel>;
|
|
1543
1543
|
}
|
|
1544
1544
|
|
|
1545
|
+
/**
|
|
1546
|
+
* Bounds the rows an `ORDER BY ... , _id` find has to sort, without
|
|
1547
|
+
* changing which rows it returns.
|
|
1548
|
+
*
|
|
1549
|
+
* ClickHouse can read in sorting-key order — and stop early — only when the
|
|
1550
|
+
* ORDER BY lines up with the table's physical key. The `_id` tiebreaker
|
|
1551
|
+
* appended by `toPaginationStableSort` is deliberately NOT a sorting-key
|
|
1552
|
+
* column, so it denies that plan: the engine reads every row matching the
|
|
1553
|
+
* WHERE, sorts the lot in memory, then discards all but one page. On the
|
|
1554
|
+
* Logs viewer that turned a 100-row page into a full-window scan and tripped
|
|
1555
|
+
* the 3 GiB `max_memory_usage` ceiling (Code 241, surfacing as
|
|
1556
|
+
* "Server Error").
|
|
1557
|
+
*
|
|
1558
|
+
* Weakening the tiebreaker would reintroduce the skip/repeat paging bug it
|
|
1559
|
+
* was added to fix, so bound the input instead. `min(k)` over the top
|
|
1560
|
+
* `skip + limit` values of the leading sort key `k` is a rank statistic of
|
|
1561
|
+
* k's MULTISET: it does not depend on which of several tied rows the inner
|
|
1562
|
+
* LIMIT happened to keep. Every row that could reach position `skip + limit`
|
|
1563
|
+
* satisfies `k >= min(k)`, and every row tied at the boundary value is
|
|
1564
|
+
* admitted — so the page is exactly the page the unbounded query returns.
|
|
1565
|
+
* That is what makes this a pure cost change: every gate below is a
|
|
1566
|
+
* performance gate, and getting one wrong can only make a query slower,
|
|
1567
|
+
* never lose or repeat a row.
|
|
1568
|
+
*
|
|
1569
|
+
* The inner pass reads one narrow key column in physical order and stops at
|
|
1570
|
+
* `skip + limit` rows, so it costs far less than the wide outer read it
|
|
1571
|
+
* saves.
|
|
1572
|
+
*
|
|
1573
|
+
* `min()` is nested over a subquery rather than taken as the N-th value via
|
|
1574
|
+
* `ORDER BY k DESC LIMIT 1 OFFSET n - 1` on purpose: on a final page with
|
|
1575
|
+
* fewer than `skip + limit` matching rows the latter returns no row, the
|
|
1576
|
+
* scalar is NULL, `k >= NULL` is NULL, and the page comes back empty.
|
|
1577
|
+
* `min()` over the available rows degrades to the smallest matching value.
|
|
1578
|
+
*/
|
|
1579
|
+
private toSortKeyBoundaryFilter(
|
|
1580
|
+
findBy: FindBy<TBaseModel>,
|
|
1581
|
+
sort: Sort<TBaseModel>,
|
|
1582
|
+
options: { hasGroupBy: boolean; databaseName: string },
|
|
1583
|
+
): Statement | null {
|
|
1584
|
+
/*
|
|
1585
|
+
* Under GROUP BY the predicate would restrict the aggregation INPUT
|
|
1586
|
+
* rather than the page, so it would change results. Mirrors the
|
|
1587
|
+
* tiebreaker's own exemption, leaving grouped finds byte-identical.
|
|
1588
|
+
*/
|
|
1589
|
+
if (options.hasGroupBy) {
|
|
1590
|
+
return null;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
/*
|
|
1594
|
+
* Without an `_id` column there is no off-key tiebreaker — the six
|
|
1595
|
+
* `includeBaseColumns: false` aggregate targets — so the ORDER BY is
|
|
1596
|
+
* already key-aligned and the bound would be pure overhead. Same
|
|
1597
|
+
* condition toPaginationStableSort bails on.
|
|
1598
|
+
*/
|
|
1599
|
+
const hasIdColumn: boolean = this.model.tableColumns.some(
|
|
1600
|
+
(column: AnalyticsTableColumn): boolean => {
|
|
1601
|
+
return column.key === "_id";
|
|
1602
|
+
},
|
|
1603
|
+
);
|
|
1604
|
+
|
|
1605
|
+
if (!hasIdColumn) {
|
|
1606
|
+
return null;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
const leadingSortKey: string | undefined = Object.keys(sort || {})[0];
|
|
1610
|
+
|
|
1611
|
+
if (!leadingSortKey) {
|
|
1612
|
+
return null;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
/*
|
|
1616
|
+
* Only a column in the physical sorting key can be read in order, so
|
|
1617
|
+
* only there can the off-key `_id` term have cost anything. Off-key
|
|
1618
|
+
* leads (e.g. Log by `severityText`) already plan a bounded top-N and
|
|
1619
|
+
* an extra pass would be pure waste.
|
|
1620
|
+
*/
|
|
1621
|
+
if (!this.model.sortKeys.includes(leadingSortKey)) {
|
|
1622
|
+
return null;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
/*
|
|
1626
|
+
* A non-required column is `Nullable(...)` in the DDL, and `k >= NULL`
|
|
1627
|
+
* is NULL — the predicate would DROP those rows rather than just
|
|
1628
|
+
* narrowing the scan. Fail safe to today's behavior.
|
|
1629
|
+
*/
|
|
1630
|
+
const leadingSortColumn: AnalyticsTableColumn | null =
|
|
1631
|
+
this.model.getTableColumn(leadingSortKey);
|
|
1632
|
+
|
|
1633
|
+
if (!leadingSortColumn || !leadingSortColumn.required) {
|
|
1634
|
+
return null;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
/*
|
|
1638
|
+
* `findOneById` funnels a point lookup through `findOneBy` carrying the
|
|
1639
|
+
* model's default sort. At most one row can match, so bounding it would
|
|
1640
|
+
* double a query that has no ordering problem to begin with.
|
|
1641
|
+
*/
|
|
1642
|
+
if ((findBy.query as Record<string, unknown> | undefined)?.["_id"]) {
|
|
1643
|
+
return null;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
/*
|
|
1647
|
+
* The predicate is valid only BECAUSE of LIMIT/OFFSET: it admits the
|
|
1648
|
+
* rows that can reach position `skip + limit` and no more. Guards the
|
|
1649
|
+
* `new PositiveNumber(undefined)` -> NaN path that exists upstream.
|
|
1650
|
+
*/
|
|
1651
|
+
const limit: number = Number(findBy.limit);
|
|
1652
|
+
const skip: number = Number(findBy.skip);
|
|
1653
|
+
|
|
1654
|
+
if (!Number.isFinite(limit) || !Number.isFinite(skip)) {
|
|
1655
|
+
return null;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
const boundaryRowCount: number = skip + limit;
|
|
1659
|
+
|
|
1660
|
+
if (boundaryRowCount <= 0) {
|
|
1661
|
+
return null;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
const isDescending: boolean =
|
|
1665
|
+
(sort as Record<string, SortOrder | undefined>)[leadingSortKey] !==
|
|
1666
|
+
SortOrder.Ascending;
|
|
1667
|
+
|
|
1668
|
+
/*
|
|
1669
|
+
* The bound must be computed over the same row set the outer query
|
|
1670
|
+
* filters on, so the WHERE is regenerated rather than shared: appending
|
|
1671
|
+
* one Statement into two places would bind its parameters twice.
|
|
1672
|
+
*/
|
|
1673
|
+
const boundaryWhereStatement: Statement =
|
|
1674
|
+
this.statementGenerator.toWhereStatement(findBy.query);
|
|
1675
|
+
|
|
1676
|
+
const boundaryStatement: Statement = SQL` AND ${leadingSortKey} `;
|
|
1677
|
+
|
|
1678
|
+
boundaryStatement.append(isDescending ? SQL`>=` : SQL`<=`);
|
|
1679
|
+
boundaryStatement.append(
|
|
1680
|
+
isDescending
|
|
1681
|
+
? SQL` (SELECT min(${leadingSortKey}) FROM (SELECT ${leadingSortKey} FROM ${options.databaseName}.${this.model.tableName} WHERE TRUE `
|
|
1682
|
+
: SQL` (SELECT max(${leadingSortKey}) FROM (SELECT ${leadingSortKey} FROM ${options.databaseName}.${this.model.tableName} WHERE TRUE `,
|
|
1683
|
+
);
|
|
1684
|
+
boundaryStatement.append(boundaryWhereStatement);
|
|
1685
|
+
boundaryStatement.append(this.getRetentionReadFilter());
|
|
1686
|
+
boundaryStatement.append(SQL` ORDER BY ${leadingSortKey} `);
|
|
1687
|
+
boundaryStatement.append(isDescending ? SQL`DESC` : SQL`ASC`);
|
|
1688
|
+
boundaryStatement.append(
|
|
1689
|
+
SQL` LIMIT ${{
|
|
1690
|
+
value: boundaryRowCount,
|
|
1691
|
+
type: TableColumnType.Number,
|
|
1692
|
+
}}))`,
|
|
1693
|
+
);
|
|
1694
|
+
|
|
1695
|
+
return boundaryStatement;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1545
1698
|
public toFindStatement(findBy: FindBy<TBaseModel>): {
|
|
1546
1699
|
statement: Statement;
|
|
1547
1700
|
columns: Array<string>;
|
|
@@ -1577,6 +1730,18 @@ export default class AnalyticsDatabaseService<
|
|
|
1577
1730
|
}),
|
|
1578
1731
|
);
|
|
1579
1732
|
|
|
1733
|
+
/*
|
|
1734
|
+
* Keeps the `_id` tiebreaker affordable. Result-preserving by
|
|
1735
|
+
* construction, so it is appended to the WHERE without touching the
|
|
1736
|
+
* ORDER BY above. Null whenever the bound cannot help or cannot be
|
|
1737
|
+
* proven safe.
|
|
1738
|
+
*/
|
|
1739
|
+
const sortKeyBoundaryStatement: Statement | null =
|
|
1740
|
+
this.toSortKeyBoundaryFilter(findBy, findBy.sort!, {
|
|
1741
|
+
hasGroupBy: Boolean(groupByStatement),
|
|
1742
|
+
databaseName: databaseName,
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1580
1745
|
const statement: Statement = SQL``;
|
|
1581
1746
|
|
|
1582
1747
|
statement.append(SQL`SELECT `.append(select.statement));
|
|
@@ -1586,6 +1751,10 @@ export default class AnalyticsDatabaseService<
|
|
|
1586
1751
|
.append(whereStatement)
|
|
1587
1752
|
.append(this.getRetentionReadFilter());
|
|
1588
1753
|
|
|
1754
|
+
if (sortKeyBoundaryStatement) {
|
|
1755
|
+
statement.append(sortKeyBoundaryStatement);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1589
1758
|
if (groupByStatement) {
|
|
1590
1759
|
statement.append(SQL` GROUP BY `).append(groupByStatement);
|
|
1591
1760
|
}
|
|
@@ -2886,6 +2886,107 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
|
|
|
2886
2886
|
await repository.manager.query(sql, params);
|
|
2887
2887
|
}
|
|
2888
2888
|
|
|
2889
|
+
/*
|
|
2890
|
+
* Add one to a numeric column and return what it became, in a SINGLE
|
|
2891
|
+
* statement.
|
|
2892
|
+
*
|
|
2893
|
+
* This exists for counters that GATE something, where the read and the
|
|
2894
|
+
* write cannot be allowed to come apart. The motivating case is the failed
|
|
2895
|
+
* attempt counter on a notification-channel verification code: read the
|
|
2896
|
+
* count, decide, then write it back, and N requests racing each other all
|
|
2897
|
+
* read the same pre-increment value and all decide they are under the
|
|
2898
|
+
* limit — which is precisely the shape an attacker running a brute-force
|
|
2899
|
+
* loop produces. `atomicAddToColumnsByIdWithoutHooks` closes the
|
|
2900
|
+
* lost-update half of that but cannot answer "and what is it now?", and
|
|
2901
|
+
* `getRepository().increment()` cannot either.
|
|
2902
|
+
*
|
|
2903
|
+
* RETURNING makes the increment and the observation the same operation, so
|
|
2904
|
+
* every concurrent caller gets a distinct value and the k-th attempt is
|
|
2905
|
+
* refused no matter how the requests interleave.
|
|
2906
|
+
*
|
|
2907
|
+
* COALESCE so a NULL counter starts from zero rather than staying NULL.
|
|
2908
|
+
*
|
|
2909
|
+
* The column and table identifiers come from entity metadata, never from
|
|
2910
|
+
* the caller, and the id is bound as a parameter. Hooks, the `version`
|
|
2911
|
+
* bump and access control are all skipped: use ONLY for trusted internal
|
|
2912
|
+
* counter writes.
|
|
2913
|
+
*/
|
|
2914
|
+
@CaptureSpan()
|
|
2915
|
+
public async atomicIncrementColumnValueByOneAndGetValue(data: {
|
|
2916
|
+
id: ObjectID;
|
|
2917
|
+
columnName: keyof TBaseModel;
|
|
2918
|
+
}): Promise<number> {
|
|
2919
|
+
if (!data.id) {
|
|
2920
|
+
throw new BadDataException("id is required");
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
const repository: Repository<TBaseModel> = this.getRepository();
|
|
2924
|
+
const metadata: EntityMetadata = repository.metadata;
|
|
2925
|
+
|
|
2926
|
+
const column: ColumnMetadata | undefined =
|
|
2927
|
+
metadata.findColumnWithPropertyName(data.columnName as string);
|
|
2928
|
+
|
|
2929
|
+
if (!column) {
|
|
2930
|
+
throw new BadDataException(
|
|
2931
|
+
`atomicIncrementColumnValueByOneAndGetValue: unknown column "${String(
|
|
2932
|
+
data.columnName,
|
|
2933
|
+
)}" on "${metadata.tableName}"`,
|
|
2934
|
+
);
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
const primaryColumnName: string =
|
|
2938
|
+
metadata.primaryColumns[0]?.databaseName || "_id";
|
|
2939
|
+
|
|
2940
|
+
const quoted: string = `"${column.databaseName}"`;
|
|
2941
|
+
|
|
2942
|
+
const sql: string = `UPDATE "${metadata.tableName}" SET ${quoted} = COALESCE(${quoted}, 0) + 1 WHERE "${primaryColumnName}" = $1 RETURNING ${quoted}`;
|
|
2943
|
+
|
|
2944
|
+
const result: unknown = await repository.manager.query(sql, [
|
|
2945
|
+
data.id.toString(),
|
|
2946
|
+
]);
|
|
2947
|
+
|
|
2948
|
+
/*
|
|
2949
|
+
* TypeORM's Postgres driver returns `[rows, affectedCount]` for an UPDATE
|
|
2950
|
+
* and a bare row array for everything else, so both shapes are unwrapped
|
|
2951
|
+
* rather than one being assumed.
|
|
2952
|
+
*/
|
|
2953
|
+
const rows: Array<unknown> = Array.isArray(result)
|
|
2954
|
+
? Array.isArray(result[0])
|
|
2955
|
+
? (result[0] as Array<unknown>)
|
|
2956
|
+
: (result as Array<unknown>)
|
|
2957
|
+
: [];
|
|
2958
|
+
|
|
2959
|
+
const row: unknown = rows[0];
|
|
2960
|
+
|
|
2961
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
2962
|
+
/*
|
|
2963
|
+
* Nothing came back, so the row does not exist — deleted underneath us,
|
|
2964
|
+
* or never there. Callers gate on the returned count, so this must be an
|
|
2965
|
+
* error and not a zero, which would read as a fresh allowance.
|
|
2966
|
+
*/
|
|
2967
|
+
throw new BadDataException(
|
|
2968
|
+
`Item with ID ${data.id.toString()} not found`,
|
|
2969
|
+
);
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
const value: unknown = (row as Record<string, unknown>)[
|
|
2973
|
+
column.databaseName
|
|
2974
|
+
];
|
|
2975
|
+
|
|
2976
|
+
const parsed: number =
|
|
2977
|
+
typeof value === "number" ? value : parseInt(String(value), 10);
|
|
2978
|
+
|
|
2979
|
+
if (!Number.isFinite(parsed)) {
|
|
2980
|
+
throw new BadDataException(
|
|
2981
|
+
`atomicIncrementColumnValueByOneAndGetValue: "${String(
|
|
2982
|
+
data.columnName,
|
|
2983
|
+
)}" did not return a number`,
|
|
2984
|
+
);
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
return parsed;
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2889
2990
|
@CaptureSpan()
|
|
2890
2991
|
protected async atomicIncrementColumnValueByOne(data: {
|
|
2891
2992
|
id: ObjectID;
|
|
@@ -282,6 +282,7 @@ export class LogAggregationService {
|
|
|
282
282
|
getQuerySettings({
|
|
283
283
|
maxExecutionTimeInSeconds: 45,
|
|
284
284
|
timeoutOverflowMode: "break",
|
|
285
|
+
boundScanMemory: true,
|
|
285
286
|
additionalSettings: { optimize_use_projections: 1 },
|
|
286
287
|
}),
|
|
287
288
|
);
|
|
@@ -392,6 +393,7 @@ export class LogAggregationService {
|
|
|
392
393
|
getQuerySettings({
|
|
393
394
|
maxExecutionTimeInSeconds: 45,
|
|
394
395
|
timeoutOverflowMode: "break",
|
|
396
|
+
boundScanMemory: true,
|
|
395
397
|
}),
|
|
396
398
|
);
|
|
397
399
|
|
|
@@ -634,6 +636,7 @@ export class LogAggregationService {
|
|
|
634
636
|
getQuerySettings({
|
|
635
637
|
maxExecutionTimeInSeconds: 45,
|
|
636
638
|
timeoutOverflowMode: "break",
|
|
639
|
+
boundScanMemory: true,
|
|
637
640
|
}),
|
|
638
641
|
);
|
|
639
642
|
|
|
@@ -714,6 +717,7 @@ export class LogAggregationService {
|
|
|
714
717
|
getQuerySettings({
|
|
715
718
|
maxExecutionTimeInSeconds: 45,
|
|
716
719
|
timeoutOverflowMode: "break",
|
|
720
|
+
boundScanMemory: true,
|
|
717
721
|
}),
|
|
718
722
|
);
|
|
719
723
|
|
|
@@ -783,6 +787,7 @@ export class LogAggregationService {
|
|
|
783
787
|
getQuerySettings({
|
|
784
788
|
maxExecutionTimeInSeconds: 45,
|
|
785
789
|
timeoutOverflowMode: "break",
|
|
790
|
+
boundScanMemory: true,
|
|
786
791
|
}),
|
|
787
792
|
);
|
|
788
793
|
|
|
@@ -26,6 +26,7 @@ import UserPush from "../../Models/DatabaseModels/UserPush";
|
|
|
26
26
|
import PushNotificationLog from "../../Models/DatabaseModels/PushNotificationLog";
|
|
27
27
|
import PushNotificationLogService from "./PushNotificationLogService";
|
|
28
28
|
import PushStatus from "../../Types/PushNotification/PushStatus";
|
|
29
|
+
import AndroidNotificationChannel from "../../Types/PushNotification/AndroidNotificationChannel";
|
|
29
30
|
|
|
30
31
|
/*
|
|
31
32
|
* The push services the browsers actually use. A Web Push subscription is a
|
|
@@ -67,6 +68,43 @@ export interface PushNotificationOptions {
|
|
|
67
68
|
teamId?: ObjectID | undefined;
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
/*
|
|
72
|
+
* What Expo needs to be told for a notification to ring through a silenced
|
|
73
|
+
* handset. The two platforms disagree about where the answer lives, so this is
|
|
74
|
+
* computed once and reused by both the direct-SDK and relay send paths.
|
|
75
|
+
*
|
|
76
|
+
* iOS reads the payload: a `sound` object with `critical: true` plus
|
|
77
|
+
* `interruptionLevel: "critical"` is what makes APNs ignore the ringer switch
|
|
78
|
+
* and Focus, and it is refused unless the app carries Apple's critical-alert
|
|
79
|
+
* entitlement.
|
|
80
|
+
*
|
|
81
|
+
* Android ignores all of that and reads the CHANNEL. Sound, importance and Do
|
|
82
|
+
* Not Disturb bypass are baked into the channel when the app creates it and
|
|
83
|
+
* cannot be raised by a payload, so the only lever the server has is which
|
|
84
|
+
* channel id it names.
|
|
85
|
+
*/
|
|
86
|
+
export type ExpoPushSound =
|
|
87
|
+
| string
|
|
88
|
+
| null
|
|
89
|
+
| {
|
|
90
|
+
critical?: boolean;
|
|
91
|
+
name?: string | null;
|
|
92
|
+
volume?: number;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type ExpoInterruptionLevel =
|
|
96
|
+
| "active"
|
|
97
|
+
| "critical"
|
|
98
|
+
| "passive"
|
|
99
|
+
| "time-sensitive";
|
|
100
|
+
|
|
101
|
+
export interface ExpoDeliveryOptions {
|
|
102
|
+
channelId: string;
|
|
103
|
+
sound: ExpoPushSound;
|
|
104
|
+
priority: "high";
|
|
105
|
+
interruptionLevel?: ExpoInterruptionLevel;
|
|
106
|
+
}
|
|
107
|
+
|
|
70
108
|
export default class PushNotificationService {
|
|
71
109
|
public static isWebPushInitialized = false;
|
|
72
110
|
private static expoClient: Expo = new Expo(
|
|
@@ -408,6 +446,57 @@ export default class PushNotificationService {
|
|
|
408
446
|
}
|
|
409
447
|
}
|
|
410
448
|
|
|
449
|
+
/*
|
|
450
|
+
* Volume is pinned rather than exposed as a setting. A critical alert exists
|
|
451
|
+
* to wake somebody, and a responder who has already opted this device in and
|
|
452
|
+
* granted the OS permission has not asked to be woken quietly.
|
|
453
|
+
*/
|
|
454
|
+
public static readonly CRITICAL_ALERT_VOLUME: number = 1;
|
|
455
|
+
|
|
456
|
+
public static getExpoDeliveryOptions(
|
|
457
|
+
message: PushNotificationMessage,
|
|
458
|
+
deviceType: PushDeviceType,
|
|
459
|
+
): ExpoDeliveryOptions {
|
|
460
|
+
const isAndroid: boolean = deviceType === PushDeviceType.Android;
|
|
461
|
+
const isCritical: boolean = Boolean(message.isCriticalAlert);
|
|
462
|
+
|
|
463
|
+
/*
|
|
464
|
+
* iOS has no channels, so "default" here is the payload's own sound rather
|
|
465
|
+
* than a channel id. Android without the critical flag stays on
|
|
466
|
+
* oncall_high, which is what every push has used until now.
|
|
467
|
+
*/
|
|
468
|
+
const channelId: string = isAndroid
|
|
469
|
+
? isCritical
|
|
470
|
+
? AndroidNotificationChannel.Critical
|
|
471
|
+
: AndroidNotificationChannel.High
|
|
472
|
+
: "default";
|
|
473
|
+
|
|
474
|
+
if (!isCritical) {
|
|
475
|
+
return {
|
|
476
|
+
channelId: channelId,
|
|
477
|
+
sound: "default",
|
|
478
|
+
priority: "high",
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return {
|
|
483
|
+
channelId: channelId,
|
|
484
|
+
/*
|
|
485
|
+
* Sent to Android too, and harmlessly ignored there. Expo forwards it to
|
|
486
|
+
* APNs where it is the whole mechanism, and to FCM where the channel has
|
|
487
|
+
* already decided the sound; keeping one shape avoids a per-platform
|
|
488
|
+
* branch that could silently drop the iOS half.
|
|
489
|
+
*/
|
|
490
|
+
sound: {
|
|
491
|
+
critical: true,
|
|
492
|
+
name: "default",
|
|
493
|
+
volume: PushNotificationService.CRITICAL_ALERT_VOLUME,
|
|
494
|
+
},
|
|
495
|
+
priority: "high",
|
|
496
|
+
interruptionLevel: "critical",
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
411
500
|
private static async sendExpoPushNotification(
|
|
412
501
|
expoPushToken: string,
|
|
413
502
|
message: PushNotificationMessage,
|
|
@@ -430,8 +519,10 @@ export default class PushNotificationService {
|
|
|
430
519
|
dataPayload["url"] = message.url || message.clickAction || "";
|
|
431
520
|
}
|
|
432
521
|
|
|
433
|
-
const
|
|
434
|
-
|
|
522
|
+
const delivery: ExpoDeliveryOptions = this.getExpoDeliveryOptions(
|
|
523
|
+
message,
|
|
524
|
+
deviceType,
|
|
525
|
+
);
|
|
435
526
|
|
|
436
527
|
// If EXPO_ACCESS_TOKEN is not set, relay through the push notification gateway
|
|
437
528
|
if (!ExpoAccessToken) {
|
|
@@ -439,7 +530,7 @@ export default class PushNotificationService {
|
|
|
439
530
|
expoPushToken,
|
|
440
531
|
message,
|
|
441
532
|
dataPayload,
|
|
442
|
-
|
|
533
|
+
delivery,
|
|
443
534
|
deviceType,
|
|
444
535
|
);
|
|
445
536
|
return;
|
|
@@ -452,9 +543,12 @@ export default class PushNotificationService {
|
|
|
452
543
|
title: message.title,
|
|
453
544
|
body: message.body,
|
|
454
545
|
data: dataPayload,
|
|
455
|
-
sound:
|
|
456
|
-
priority:
|
|
457
|
-
channelId: channelId,
|
|
546
|
+
sound: delivery.sound,
|
|
547
|
+
priority: delivery.priority,
|
|
548
|
+
channelId: delivery.channelId,
|
|
549
|
+
...(delivery.interruptionLevel
|
|
550
|
+
? { interruptionLevel: delivery.interruptionLevel }
|
|
551
|
+
: {}),
|
|
458
552
|
};
|
|
459
553
|
|
|
460
554
|
const tickets: ExpoPushTicket[] =
|
|
@@ -500,7 +594,7 @@ export default class PushNotificationService {
|
|
|
500
594
|
expoPushToken: string,
|
|
501
595
|
message: PushNotificationMessage,
|
|
502
596
|
dataPayload: { [key: string]: string },
|
|
503
|
-
|
|
597
|
+
delivery: ExpoDeliveryOptions,
|
|
504
598
|
deviceType: PushDeviceType,
|
|
505
599
|
): Promise<void> {
|
|
506
600
|
logger.info(
|
|
@@ -516,9 +610,19 @@ export default class PushNotificationService {
|
|
|
516
610
|
title: message.title || "",
|
|
517
611
|
body: message.body || "",
|
|
518
612
|
data: dataPayload,
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
613
|
+
/*
|
|
614
|
+
* The relay re-sends exactly what it is given, so a critical page
|
|
615
|
+
* that loses its sound object here arrives on the responder's
|
|
616
|
+
* phone as an ordinary silent notification. That is the failure
|
|
617
|
+
* this feature exists to prevent, so the whole delivery shape
|
|
618
|
+
* crosses the wire rather than the channel id alone.
|
|
619
|
+
*/
|
|
620
|
+
sound: delivery.sound,
|
|
621
|
+
priority: delivery.priority,
|
|
622
|
+
channelId: delivery.channelId,
|
|
623
|
+
...(delivery.interruptionLevel
|
|
624
|
+
? { interruptionLevel: delivery.interruptionLevel }
|
|
625
|
+
: {}),
|
|
522
626
|
},
|
|
523
627
|
});
|
|
524
628
|
|
|
@@ -552,9 +656,10 @@ export default class PushNotificationService {
|
|
|
552
656
|
title?: string;
|
|
553
657
|
body?: string;
|
|
554
658
|
data?: { [key: string]: string };
|
|
555
|
-
sound?:
|
|
659
|
+
sound?: ExpoPushSound;
|
|
556
660
|
priority?: string;
|
|
557
661
|
channelId?: string;
|
|
662
|
+
interruptionLevel?: ExpoInterruptionLevel;
|
|
558
663
|
}): Promise<void> {
|
|
559
664
|
if (!ExpoAccessToken) {
|
|
560
665
|
throw new Error(
|
|
@@ -562,14 +667,24 @@ export default class PushNotificationService {
|
|
|
562
667
|
);
|
|
563
668
|
}
|
|
564
669
|
|
|
670
|
+
/*
|
|
671
|
+
* `sound: null` is a caller asking for a silent notification and has to
|
|
672
|
+
* survive, so the fallback tests for undefined rather than falsiness.
|
|
673
|
+
*/
|
|
674
|
+
const sound: ExpoPushSound =
|
|
675
|
+
data.sound === undefined ? "default" : data.sound;
|
|
676
|
+
|
|
565
677
|
const expoPushMessage: ExpoPushMessage = {
|
|
566
678
|
to: data.to,
|
|
567
679
|
title: data.title || "",
|
|
568
680
|
body: data.body || "",
|
|
569
681
|
data: data.data || {},
|
|
570
|
-
sound:
|
|
682
|
+
sound: sound,
|
|
571
683
|
priority: (data.priority as "default" | "normal" | "high") || "high",
|
|
572
684
|
channelId: data.channelId || "default",
|
|
685
|
+
...(data.interruptionLevel
|
|
686
|
+
? { interruptionLevel: data.interruptionLevel }
|
|
687
|
+
: {}),
|
|
573
688
|
};
|
|
574
689
|
|
|
575
690
|
const tickets: ExpoPushTicket[] =
|
|
@@ -10,6 +10,7 @@ import UpdateBy from "../Types/Database/UpdateBy";
|
|
|
10
10
|
import Errors from "../Utils/Errors";
|
|
11
11
|
import logger, { LogAttributes } from "../Utils/Logger";
|
|
12
12
|
import ProductAnalytics from "../Utils/ProductAnalytics";
|
|
13
|
+
import UserRegistrationToken from "../Utils/UserRegistrationToken";
|
|
13
14
|
import AccessTokenService from "./AccessTokenService";
|
|
14
15
|
import BillingService from "./BillingService";
|
|
15
16
|
import DatabaseService from "./DatabaseService";
|
|
@@ -166,8 +167,23 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
166
167
|
? (createBy.miscDataProps["name"] as string).trim()
|
|
167
168
|
: undefined;
|
|
168
169
|
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
/*
|
|
171
|
+
* `password` comes back too, because whether this person has finished
|
|
172
|
+
* registering decides both which link the invitation carries and whether
|
|
173
|
+
* that link needs a registration token. UserService.findByEmail selects
|
|
174
|
+
* only the id, so it cannot answer that.
|
|
175
|
+
*/
|
|
176
|
+
let user: User | null = await UserService.findOneBy({
|
|
177
|
+
query: {
|
|
178
|
+
email: email,
|
|
179
|
+
},
|
|
180
|
+
select: {
|
|
181
|
+
_id: true,
|
|
182
|
+
password: true,
|
|
183
|
+
},
|
|
184
|
+
props: {
|
|
185
|
+
isRoot: true,
|
|
186
|
+
},
|
|
171
187
|
});
|
|
172
188
|
|
|
173
189
|
let isNewUser: boolean = false;
|
|
@@ -228,6 +244,39 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
228
244
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
229
245
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
230
246
|
|
|
247
|
+
/*
|
|
248
|
+
* "Still has to register", not "we just created the row". Someone
|
|
249
|
+
* invited to a second project before they ever signed up already has a
|
|
250
|
+
* user row and still has no password, and used to be sent the sign-in
|
|
251
|
+
* link — which they could not use, having no password to sign in with.
|
|
252
|
+
*/
|
|
253
|
+
const needsRegistration: boolean = isNewUser || !user.password;
|
|
254
|
+
|
|
255
|
+
/*
|
|
256
|
+
* The token is what lets the recipient claim this account, so it exists
|
|
257
|
+
* only on the link inside this email and only for someone who has not
|
|
258
|
+
* registered yet. An already-registered invitee gets the plain link;
|
|
259
|
+
* they sign in instead, and minting a claim token for an account that
|
|
260
|
+
* cannot be claimed would just be a spare key lying around.
|
|
261
|
+
*/
|
|
262
|
+
const registerLink: string = needsRegistration
|
|
263
|
+
? (
|
|
264
|
+
await UserRegistrationToken.generateRegistrationLink({
|
|
265
|
+
userId: user.id!,
|
|
266
|
+
email: email,
|
|
267
|
+
})
|
|
268
|
+
).toString()
|
|
269
|
+
: URL.fromString(
|
|
270
|
+
new URL(
|
|
271
|
+
httpProtocol,
|
|
272
|
+
host,
|
|
273
|
+
new Route(AccountsRoute.toString()),
|
|
274
|
+
).toString(),
|
|
275
|
+
)
|
|
276
|
+
.addRoute("/register")
|
|
277
|
+
.addQueryParam("email", email.toString(), true)
|
|
278
|
+
.toString();
|
|
279
|
+
|
|
231
280
|
MailService.sendMail(
|
|
232
281
|
{
|
|
233
282
|
toEmail: email,
|
|
@@ -240,17 +289,8 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
240
289
|
new Route(AccountsRoute.toString()),
|
|
241
290
|
).toString(),
|
|
242
291
|
).toString(),
|
|
243
|
-
registerLink:
|
|
244
|
-
|
|
245
|
-
httpProtocol,
|
|
246
|
-
host,
|
|
247
|
-
new Route(AccountsRoute.toString()),
|
|
248
|
-
).toString(),
|
|
249
|
-
)
|
|
250
|
-
.addRoute("/register")
|
|
251
|
-
.addQueryParam("email", email.toString(), true)
|
|
252
|
-
.toString(),
|
|
253
|
-
isNewUser: isNewUser.toString(),
|
|
292
|
+
registerLink: registerLink,
|
|
293
|
+
isNewUser: needsRegistration.toString(),
|
|
254
294
|
/*
|
|
255
295
|
* An auto-accepted member has nothing left to accept, so the
|
|
256
296
|
* template drops the "sign in to accept your invitation" framing
|