@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,338 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
jest,
|
|
8
|
+
} from "@jest/globals";
|
|
9
|
+
import ModelAPI from "../../../UI/Utils/ModelAPI/ModelAPI";
|
|
10
|
+
import ModelListCache from "../../../UI/Utils/ModelListCache";
|
|
11
|
+
import ListResult from "../../../Types/BaseDatabase/ListResult";
|
|
12
|
+
import Query from "../../../Types/BaseDatabase/Query";
|
|
13
|
+
import Select from "../../../Types/BaseDatabase/Select";
|
|
14
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
15
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
16
|
+
import AlertState from "../../../Models/DatabaseModels/AlertState";
|
|
17
|
+
import IncidentState from "../../../Models/DatabaseModels/IncidentState";
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* ModelListCache exists so that reference-data lists (incident states, alert
|
|
21
|
+
* states, custom-field definitions) are fetched once per mount burst instead
|
|
22
|
+
* of once per consumer. Everything below pins the contract that makes that
|
|
23
|
+
* safe: concurrent callers share one request, the TTL bounds staleness,
|
|
24
|
+
* distinct keys (model / project / shape) never bleed into each other, and
|
|
25
|
+
* invalidation - including mid-flight - really forces the next read back to
|
|
26
|
+
* the network.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
30
|
+
"00000000-0000-4000-8000-000000000001",
|
|
31
|
+
);
|
|
32
|
+
const OTHER_PROJECT_ID: ObjectID = new ObjectID(
|
|
33
|
+
"00000000-0000-4000-8000-000000000002",
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const TTL_IN_MS: number = 60 * 1000;
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* Hand-rolled rather than jest.SpiedFunction<typeof ModelAPI.getList>: the
|
|
40
|
+
* static is generic, and the installed jest typings cannot describe a spy on
|
|
41
|
+
* a generic method without collapsing it to one instantiation (same pattern
|
|
42
|
+
* as ProjectUsersModelAPI.test.ts).
|
|
43
|
+
*/
|
|
44
|
+
interface Spy {
|
|
45
|
+
mockResolvedValue: (value: unknown) => Spy;
|
|
46
|
+
mockResolvedValueOnce: (value: unknown) => Spy;
|
|
47
|
+
mockRejectedValueOnce: (reason: unknown) => Spy;
|
|
48
|
+
mockImplementation: (fn: (...args: Array<unknown>) => unknown) => Spy;
|
|
49
|
+
mockImplementationOnce: (fn: (...args: Array<unknown>) => unknown) => Spy;
|
|
50
|
+
mock: { calls: Array<Array<unknown>> };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let getListSpy: Spy;
|
|
54
|
+
|
|
55
|
+
type Deferred = {
|
|
56
|
+
promise: Promise<unknown>;
|
|
57
|
+
resolve: (value: unknown) => void;
|
|
58
|
+
reject: (reason: unknown) => void;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const makeDeferred: () => Deferred = (): Deferred => {
|
|
62
|
+
let resolveFn: (value: unknown) => void = () => {
|
|
63
|
+
// replaced synchronously by the Promise executor below.
|
|
64
|
+
};
|
|
65
|
+
let rejectFn: (reason: unknown) => void = () => {
|
|
66
|
+
// replaced synchronously by the Promise executor below.
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const promise: Promise<unknown> = new Promise(
|
|
70
|
+
(
|
|
71
|
+
resolve: (value: unknown) => void,
|
|
72
|
+
reject: (reason: unknown) => void,
|
|
73
|
+
): void => {
|
|
74
|
+
resolveFn = resolve;
|
|
75
|
+
rejectFn = reject;
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return { promise, resolve: resolveFn, reject: rejectFn };
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const makeIncidentStateList: () => ListResult<IncidentState> =
|
|
83
|
+
(): ListResult<IncidentState> => {
|
|
84
|
+
const state: IncidentState = new IncidentState();
|
|
85
|
+
state.isResolvedState = false;
|
|
86
|
+
|
|
87
|
+
return { data: [state], count: 1, skip: 0, limit: 90 };
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const makeAlertStateList: () => ListResult<AlertState> =
|
|
91
|
+
(): ListResult<AlertState> => {
|
|
92
|
+
const state: AlertState = new AlertState();
|
|
93
|
+
state.isResolvedState = false;
|
|
94
|
+
|
|
95
|
+
return { data: [state], count: 1, skip: 0, limit: 90 };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const fetchIncidentStates: (
|
|
99
|
+
projectId?: ObjectID,
|
|
100
|
+
) => Promise<ListResult<IncidentState>> = (
|
|
101
|
+
projectId: ObjectID = PROJECT_ID,
|
|
102
|
+
): Promise<ListResult<IncidentState>> => {
|
|
103
|
+
return ModelListCache.getList<IncidentState>({
|
|
104
|
+
modelType: IncidentState,
|
|
105
|
+
query: { projectId: projectId } as Query<IncidentState>,
|
|
106
|
+
skip: 0,
|
|
107
|
+
limit: 90,
|
|
108
|
+
sort: { order: SortOrder.Ascending } as never,
|
|
109
|
+
select: { _id: true, isResolvedState: true } as Select<IncidentState>,
|
|
110
|
+
projectId: projectId,
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const fetchAlertStates: () => Promise<ListResult<AlertState>> = (): Promise<
|
|
115
|
+
ListResult<AlertState>
|
|
116
|
+
> => {
|
|
117
|
+
return ModelListCache.getList<AlertState>({
|
|
118
|
+
modelType: AlertState,
|
|
119
|
+
query: { projectId: PROJECT_ID } as Query<AlertState>,
|
|
120
|
+
skip: 0,
|
|
121
|
+
limit: 90,
|
|
122
|
+
sort: { order: SortOrder.Ascending } as never,
|
|
123
|
+
select: { _id: true, isResolvedState: true } as Select<AlertState>,
|
|
124
|
+
projectId: PROJECT_ID,
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const getListCallCount: () => number = (): number => {
|
|
129
|
+
return getListSpy.mock.calls.length;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
describe("ModelListCache", () => {
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
// Fake timers fake Date.now too, which is all the TTL reads.
|
|
135
|
+
jest.useFakeTimers();
|
|
136
|
+
ModelListCache.invalidateAll();
|
|
137
|
+
getListSpy = jest.spyOn(ModelAPI, "getList") as unknown as Spy;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
afterEach(() => {
|
|
141
|
+
ModelListCache.invalidateAll();
|
|
142
|
+
jest.restoreAllMocks();
|
|
143
|
+
jest.useRealTimers();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("shares one in-flight request between concurrent callers", async () => {
|
|
147
|
+
const deferred: Deferred = makeDeferred();
|
|
148
|
+
getListSpy.mockImplementation(() => {
|
|
149
|
+
return deferred.promise;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const first: Promise<ListResult<IncidentState>> = fetchIncidentStates();
|
|
153
|
+
const second: Promise<ListResult<IncidentState>> = fetchIncidentStates();
|
|
154
|
+
|
|
155
|
+
// Both callers are waiting, yet only one request went out.
|
|
156
|
+
expect(getListCallCount()).toBe(1);
|
|
157
|
+
|
|
158
|
+
deferred.resolve(makeIncidentStateList());
|
|
159
|
+
|
|
160
|
+
const firstResult: ListResult<IncidentState> = await first;
|
|
161
|
+
const secondResult: ListResult<IncidentState> = await second;
|
|
162
|
+
|
|
163
|
+
expect(secondResult).toBe(firstResult);
|
|
164
|
+
expect(getListCallCount()).toBe(1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("serves a repeat call from the cache within the TTL", async () => {
|
|
168
|
+
const list: ListResult<IncidentState> = makeIncidentStateList();
|
|
169
|
+
getListSpy.mockResolvedValue(list);
|
|
170
|
+
|
|
171
|
+
const firstResult: ListResult<IncidentState> = await fetchIncidentStates();
|
|
172
|
+
|
|
173
|
+
// Just under the TTL: still the cached response, no new request.
|
|
174
|
+
jest.advanceTimersByTime(TTL_IN_MS - 1);
|
|
175
|
+
|
|
176
|
+
const secondResult: ListResult<IncidentState> = await fetchIncidentStates();
|
|
177
|
+
|
|
178
|
+
expect(getListCallCount()).toBe(1);
|
|
179
|
+
expect(secondResult).toBe(firstResult);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("refetches once the TTL has passed", async () => {
|
|
183
|
+
getListSpy.mockResolvedValue(makeIncidentStateList());
|
|
184
|
+
|
|
185
|
+
await fetchIncidentStates();
|
|
186
|
+
|
|
187
|
+
jest.advanceTimersByTime(TTL_IN_MS);
|
|
188
|
+
|
|
189
|
+
await fetchIncidentStates();
|
|
190
|
+
|
|
191
|
+
expect(getListCallCount()).toBe(2);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("keeps different model types on different keys", async () => {
|
|
195
|
+
getListSpy
|
|
196
|
+
.mockResolvedValueOnce(makeIncidentStateList())
|
|
197
|
+
.mockResolvedValueOnce(makeAlertStateList());
|
|
198
|
+
|
|
199
|
+
await fetchIncidentStates();
|
|
200
|
+
await fetchAlertStates();
|
|
201
|
+
|
|
202
|
+
expect(getListCallCount()).toBe(2);
|
|
203
|
+
|
|
204
|
+
// Both are now cached independently.
|
|
205
|
+
await fetchIncidentStates();
|
|
206
|
+
await fetchAlertStates();
|
|
207
|
+
|
|
208
|
+
expect(getListCallCount()).toBe(2);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("keeps different projects on different keys", async () => {
|
|
212
|
+
getListSpy.mockResolvedValue(makeIncidentStateList());
|
|
213
|
+
|
|
214
|
+
await fetchIncidentStates(PROJECT_ID);
|
|
215
|
+
await fetchIncidentStates(OTHER_PROJECT_ID);
|
|
216
|
+
|
|
217
|
+
expect(getListCallCount()).toBe(2);
|
|
218
|
+
|
|
219
|
+
await fetchIncidentStates(PROJECT_ID);
|
|
220
|
+
|
|
221
|
+
expect(getListCallCount()).toBe(2);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("keeps different request shapes on different keys", async () => {
|
|
225
|
+
getListSpy.mockResolvedValue(makeIncidentStateList());
|
|
226
|
+
|
|
227
|
+
await fetchIncidentStates();
|
|
228
|
+
|
|
229
|
+
// Same model and project, but a different select: a distinct list.
|
|
230
|
+
await ModelListCache.getList<IncidentState>({
|
|
231
|
+
modelType: IncidentState,
|
|
232
|
+
query: { projectId: PROJECT_ID } as Query<IncidentState>,
|
|
233
|
+
skip: 0,
|
|
234
|
+
limit: 90,
|
|
235
|
+
sort: { order: SortOrder.Ascending } as never,
|
|
236
|
+
select: { _id: true, name: true } as Select<IncidentState>,
|
|
237
|
+
projectId: PROJECT_ID,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
expect(getListCallCount()).toBe(2);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("invalidate(modelType) clears that model and only that model", async () => {
|
|
244
|
+
getListSpy
|
|
245
|
+
.mockResolvedValueOnce(makeIncidentStateList())
|
|
246
|
+
.mockResolvedValueOnce(makeAlertStateList())
|
|
247
|
+
.mockResolvedValue(makeIncidentStateList());
|
|
248
|
+
|
|
249
|
+
await fetchIncidentStates();
|
|
250
|
+
await fetchAlertStates();
|
|
251
|
+
|
|
252
|
+
ModelListCache.invalidate(IncidentState);
|
|
253
|
+
|
|
254
|
+
await fetchIncidentStates();
|
|
255
|
+
expect(getListCallCount()).toBe(3);
|
|
256
|
+
|
|
257
|
+
// The alert-state entry survived the incident-state invalidation.
|
|
258
|
+
await fetchAlertStates();
|
|
259
|
+
expect(getListCallCount()).toBe(3);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("invalidateAll clears every entry", async () => {
|
|
263
|
+
getListSpy
|
|
264
|
+
.mockResolvedValueOnce(makeIncidentStateList())
|
|
265
|
+
.mockResolvedValueOnce(makeAlertStateList())
|
|
266
|
+
.mockResolvedValueOnce(makeIncidentStateList())
|
|
267
|
+
.mockResolvedValueOnce(makeAlertStateList());
|
|
268
|
+
|
|
269
|
+
await fetchIncidentStates();
|
|
270
|
+
await fetchAlertStates();
|
|
271
|
+
|
|
272
|
+
ModelListCache.invalidateAll();
|
|
273
|
+
|
|
274
|
+
await fetchIncidentStates();
|
|
275
|
+
await fetchAlertStates();
|
|
276
|
+
|
|
277
|
+
expect(getListCallCount()).toBe(4);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("invalidate() with no argument behaves like invalidateAll", async () => {
|
|
281
|
+
getListSpy.mockResolvedValue(makeIncidentStateList());
|
|
282
|
+
|
|
283
|
+
await fetchIncidentStates();
|
|
284
|
+
|
|
285
|
+
ModelListCache.invalidate();
|
|
286
|
+
|
|
287
|
+
await fetchIncidentStates();
|
|
288
|
+
|
|
289
|
+
expect(getListCallCount()).toBe(2);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("does not cache failures", async () => {
|
|
293
|
+
const deferred: Deferred = makeDeferred();
|
|
294
|
+
getListSpy
|
|
295
|
+
.mockImplementationOnce(() => {
|
|
296
|
+
return deferred.promise;
|
|
297
|
+
})
|
|
298
|
+
.mockResolvedValueOnce(makeIncidentStateList());
|
|
299
|
+
|
|
300
|
+
/*
|
|
301
|
+
* Rejected only after the cache has hooked onto the promise, so the
|
|
302
|
+
* rejection never counts as unhandled in the test environment.
|
|
303
|
+
*/
|
|
304
|
+
const failing: Promise<ListResult<IncidentState>> = fetchIncidentStates();
|
|
305
|
+
const rejection: Promise<void> =
|
|
306
|
+
expect(failing).rejects.toThrow("network down");
|
|
307
|
+
deferred.reject(new Error("network down"));
|
|
308
|
+
await rejection;
|
|
309
|
+
|
|
310
|
+
// The failure left nothing behind: the retry goes back to the network.
|
|
311
|
+
const result: ListResult<IncidentState> = await fetchIncidentStates();
|
|
312
|
+
|
|
313
|
+
expect(result.count).toBe(1);
|
|
314
|
+
expect(getListCallCount()).toBe(2);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("discards a response whose key was invalidated while it was in flight", async () => {
|
|
318
|
+
const deferred: Deferred = makeDeferred();
|
|
319
|
+
getListSpy
|
|
320
|
+
.mockImplementationOnce(() => {
|
|
321
|
+
return deferred.promise;
|
|
322
|
+
})
|
|
323
|
+
.mockResolvedValueOnce(makeIncidentStateList());
|
|
324
|
+
|
|
325
|
+
const inFlight: Promise<ListResult<IncidentState>> = fetchIncidentStates();
|
|
326
|
+
|
|
327
|
+
ModelListCache.invalidateAll();
|
|
328
|
+
|
|
329
|
+
// The stale response still reaches its original caller...
|
|
330
|
+
deferred.resolve(makeIncidentStateList());
|
|
331
|
+
await inFlight;
|
|
332
|
+
|
|
333
|
+
// ...but it must not have (re)populated the cache.
|
|
334
|
+
await fetchIncidentStates();
|
|
335
|
+
|
|
336
|
+
expect(getListCallCount()).toBe(2);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
jest,
|
|
7
|
+
test,
|
|
8
|
+
} from "@jest/globals";
|
|
9
|
+
import ModelAPI from "../../../UI/Utils/ModelAPI/ModelAPI";
|
|
10
|
+
import ModelListCache from "../../../UI/Utils/ModelListCache";
|
|
11
|
+
import ProjectUtil from "../../../UI/Utils/Project";
|
|
12
|
+
import ListResult from "../../../Types/BaseDatabase/ListResult";
|
|
13
|
+
import Query from "../../../Types/BaseDatabase/Query";
|
|
14
|
+
import Select from "../../../Types/BaseDatabase/Select";
|
|
15
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
16
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
17
|
+
import Project from "../../../Models/DatabaseModels/Project";
|
|
18
|
+
import IncidentState from "../../../Models/DatabaseModels/IncidentState";
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* ProjectUtil.setCurrentProject runs on EVERY dashboard boot — in the same
|
|
22
|
+
* React commit as the Header/Home mounts whose reference-list requests are
|
|
23
|
+
* already in flight. It used to call ModelListCache.invalidateAll()
|
|
24
|
+
* unconditionally, which discarded the cache's very first fill right after
|
|
25
|
+
* login (the mid-flight-invalidation contract in ModelListCache.test.ts is
|
|
26
|
+
* what turned that wipe into re-requests). The contract pinned here: the
|
|
27
|
+
* cache is invalidated only when the project actually CHANGES — a re-set of
|
|
28
|
+
* the same project preserves entries and in-flight fills — while
|
|
29
|
+
* clearCurrentProject stays unconditional.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const PROJECT_A_ID: string = "11111111-1111-4111-8111-111111111111";
|
|
33
|
+
const PROJECT_B_ID: string = "22222222-2222-4222-8222-222222222222";
|
|
34
|
+
|
|
35
|
+
type BuildProjectFunction = (id: string) => Project;
|
|
36
|
+
|
|
37
|
+
const buildProject: BuildProjectFunction = (id: string): Project => {
|
|
38
|
+
const project: Project = new Project();
|
|
39
|
+
project._id = id;
|
|
40
|
+
return project;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
* Hand-rolled rather than jest.SpiedFunction<typeof ModelAPI.getList>: the
|
|
45
|
+
* static is generic, and the installed jest typings cannot describe a spy on
|
|
46
|
+
* a generic method without collapsing it to one instantiation (same pattern
|
|
47
|
+
* as ModelListCache.test.ts).
|
|
48
|
+
*/
|
|
49
|
+
interface Spy {
|
|
50
|
+
mockResolvedValue: (value: unknown) => Spy;
|
|
51
|
+
mockImplementationOnce: (fn: (...args: Array<unknown>) => unknown) => Spy;
|
|
52
|
+
mock: { calls: Array<Array<unknown>> };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let getListSpy: Spy;
|
|
56
|
+
|
|
57
|
+
type Deferred = {
|
|
58
|
+
promise: Promise<unknown>;
|
|
59
|
+
resolve: (value: unknown) => void;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const makeDeferred: () => Deferred = (): Deferred => {
|
|
63
|
+
let resolveFn: (value: unknown) => void = () => {
|
|
64
|
+
// replaced synchronously by the Promise executor below.
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const promise: Promise<unknown> = new Promise(
|
|
68
|
+
(resolve: (value: unknown) => void): void => {
|
|
69
|
+
resolveFn = resolve;
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return { promise, resolve: resolveFn };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const makeIncidentStateList: () => ListResult<IncidentState> =
|
|
77
|
+
(): ListResult<IncidentState> => {
|
|
78
|
+
const state: IncidentState = new IncidentState();
|
|
79
|
+
state.isResolvedState = false;
|
|
80
|
+
|
|
81
|
+
return { data: [state], count: 1, skip: 0, limit: 90 };
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const fetchIncidentStates: () => Promise<
|
|
85
|
+
ListResult<IncidentState>
|
|
86
|
+
> = (): Promise<ListResult<IncidentState>> => {
|
|
87
|
+
const projectId: ObjectID = new ObjectID(PROJECT_A_ID);
|
|
88
|
+
|
|
89
|
+
return ModelListCache.getList<IncidentState>({
|
|
90
|
+
modelType: IncidentState,
|
|
91
|
+
query: { projectId: projectId } as Query<IncidentState>,
|
|
92
|
+
skip: 0,
|
|
93
|
+
limit: 90,
|
|
94
|
+
sort: { order: SortOrder.Ascending } as never,
|
|
95
|
+
select: { _id: true, isResolvedState: true } as Select<IncidentState>,
|
|
96
|
+
projectId: projectId,
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const getListCallCount: () => number = (): number => {
|
|
101
|
+
return getListSpy.mock.calls.length;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
describe("ProjectUtil <-> ModelListCache invalidation", () => {
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
window.localStorage.clear();
|
|
107
|
+
window.sessionStorage.clear();
|
|
108
|
+
// A project-less route, so the URL never shadows the stored project id.
|
|
109
|
+
window.history.replaceState(window.history.state, "", "/dashboard");
|
|
110
|
+
ModelListCache.invalidateAll();
|
|
111
|
+
getListSpy = jest.spyOn(ModelAPI, "getList") as unknown as Spy;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
afterEach(() => {
|
|
115
|
+
ModelListCache.invalidateAll();
|
|
116
|
+
jest.restoreAllMocks();
|
|
117
|
+
window.localStorage.clear();
|
|
118
|
+
window.sessionStorage.clear();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("re-setting the SAME project preserves cached entries", async () => {
|
|
122
|
+
getListSpy.mockResolvedValue(makeIncidentStateList());
|
|
123
|
+
|
|
124
|
+
ProjectUtil.setCurrentProject(buildProject(PROJECT_A_ID));
|
|
125
|
+
|
|
126
|
+
const firstResult: ListResult<IncidentState> = await fetchIncidentStates();
|
|
127
|
+
|
|
128
|
+
expect(getListCallCount()).toBe(1);
|
|
129
|
+
|
|
130
|
+
// The every-boot re-set of the project this tab is already on...
|
|
131
|
+
ProjectUtil.setCurrentProject(buildProject(PROJECT_A_ID));
|
|
132
|
+
|
|
133
|
+
// ...must not wipe the cache: the repeat read is served without a request.
|
|
134
|
+
const secondResult: ListResult<IncidentState> = await fetchIncidentStates();
|
|
135
|
+
|
|
136
|
+
expect(getListCallCount()).toBe(1);
|
|
137
|
+
expect(secondResult).toBe(firstResult);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("re-setting the SAME project keeps an in-flight fill (the boot-after-login commit)", async () => {
|
|
141
|
+
/*
|
|
142
|
+
* The reported shape exactly: Header/Home mount and start their
|
|
143
|
+
* reference-list requests, then the boot-time setCurrentProject runs in
|
|
144
|
+
* the same commit. The in-flight fill must survive it — before the fix,
|
|
145
|
+
* the unconditional invalidateAll made the cache discard the response
|
|
146
|
+
* on arrival and the next consumer re-requested it.
|
|
147
|
+
*/
|
|
148
|
+
const deferred: Deferred = makeDeferred();
|
|
149
|
+
getListSpy
|
|
150
|
+
.mockImplementationOnce(() => {
|
|
151
|
+
return deferred.promise;
|
|
152
|
+
})
|
|
153
|
+
.mockResolvedValue(makeIncidentStateList());
|
|
154
|
+
|
|
155
|
+
// The tab is already on project A (session storage carries it)...
|
|
156
|
+
ProjectUtil.setCurrentProject(buildProject(PROJECT_A_ID));
|
|
157
|
+
|
|
158
|
+
// ...a mount starts the list request...
|
|
159
|
+
const inFlight: Promise<ListResult<IncidentState>> = fetchIncidentStates();
|
|
160
|
+
|
|
161
|
+
// ...and boot re-sets the same project while the request is in flight.
|
|
162
|
+
ProjectUtil.setCurrentProject(buildProject(PROJECT_A_ID));
|
|
163
|
+
|
|
164
|
+
deferred.resolve(makeIncidentStateList());
|
|
165
|
+
await inFlight;
|
|
166
|
+
|
|
167
|
+
// The fill stuck: the next consumer is served from the cache.
|
|
168
|
+
await fetchIncidentStates();
|
|
169
|
+
|
|
170
|
+
expect(getListCallCount()).toBe(1);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("switching to a DIFFERENT project invalidates the cached lists", async () => {
|
|
174
|
+
getListSpy.mockResolvedValue(makeIncidentStateList());
|
|
175
|
+
|
|
176
|
+
ProjectUtil.setCurrentProject(buildProject(PROJECT_A_ID));
|
|
177
|
+
|
|
178
|
+
await fetchIncidentStates();
|
|
179
|
+
|
|
180
|
+
expect(getListCallCount()).toBe(1);
|
|
181
|
+
|
|
182
|
+
ProjectUtil.setCurrentProject(buildProject(PROJECT_B_ID));
|
|
183
|
+
|
|
184
|
+
// Same cache key as before — only invalidation can force this refetch.
|
|
185
|
+
await fetchIncidentStates();
|
|
186
|
+
|
|
187
|
+
expect(getListCallCount()).toBe(2);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("the very first set in a fresh tab still invalidates (no stored id to match)", async () => {
|
|
191
|
+
/*
|
|
192
|
+
* With nothing in session storage there is no evidence the cache belongs
|
|
193
|
+
* to this project, so the conservative wipe stands. Only the same-id
|
|
194
|
+
* re-set — the every-boot case — earns the skip.
|
|
195
|
+
*/
|
|
196
|
+
getListSpy.mockResolvedValue(makeIncidentStateList());
|
|
197
|
+
|
|
198
|
+
await fetchIncidentStates();
|
|
199
|
+
|
|
200
|
+
expect(getListCallCount()).toBe(1);
|
|
201
|
+
|
|
202
|
+
ProjectUtil.setCurrentProject(buildProject(PROJECT_A_ID));
|
|
203
|
+
|
|
204
|
+
await fetchIncidentStates();
|
|
205
|
+
|
|
206
|
+
expect(getListCallCount()).toBe(2);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("clearCurrentProject invalidates unconditionally", async () => {
|
|
210
|
+
getListSpy.mockResolvedValue(makeIncidentStateList());
|
|
211
|
+
|
|
212
|
+
ProjectUtil.setCurrentProject(buildProject(PROJECT_A_ID));
|
|
213
|
+
|
|
214
|
+
await fetchIncidentStates();
|
|
215
|
+
|
|
216
|
+
expect(getListCallCount()).toBe(1);
|
|
217
|
+
|
|
218
|
+
// Leaving the project: its reference lists must not outlive it.
|
|
219
|
+
ProjectUtil.clearCurrentProject();
|
|
220
|
+
|
|
221
|
+
await fetchIncidentStates();
|
|
222
|
+
|
|
223
|
+
expect(getListCallCount()).toBe(2);
|
|
224
|
+
});
|
|
225
|
+
});
|
package/Tests/Utils/API.test.ts
CHANGED
|
@@ -22,7 +22,6 @@ import axios, {
|
|
|
22
22
|
} from "axios";
|
|
23
23
|
|
|
24
24
|
const DEFAULT_HEADERS: Headers = {
|
|
25
|
-
"Access-Control-Allow-Origin": "*",
|
|
26
25
|
Accept: "application/json",
|
|
27
26
|
"Content-Type": "application/json;charset=UTF-8",
|
|
28
27
|
};
|
|
@@ -76,7 +75,6 @@ function createAxiosResponse<T = any, D = any>(
|
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
const mergedHeaders: Headers = {
|
|
79
|
-
"Access-Control-Allow-Origin": "*",
|
|
80
78
|
Accept: "application/json",
|
|
81
79
|
"Content-Type": "application/json", // replace default header
|
|
82
80
|
"X-PoweredBy": "coffee", // add new header
|
|
@@ -327,6 +325,22 @@ describe("getDefaultHeaders", () => {
|
|
|
327
325
|
test("should return default headers", () => {
|
|
328
326
|
expect(API.getDefaultHeaders()).toEqual(DEFAULT_HEADERS);
|
|
329
327
|
});
|
|
328
|
+
|
|
329
|
+
test("should not send the response-only Access-Control-Allow-Origin header on requests", () => {
|
|
330
|
+
/*
|
|
331
|
+
* "Access-Control-Allow-Origin" is a RESPONSE header. Sending it as a
|
|
332
|
+
* request header is not just useless - as a non-simple custom header it
|
|
333
|
+
* forces a CORS preflight on every cross-origin call (self-hosted setups
|
|
334
|
+
* where the API lives on another origin), doubling the request count.
|
|
335
|
+
*/
|
|
336
|
+
expect(API.getDefaultHeaders()).not.toHaveProperty(
|
|
337
|
+
"Access-Control-Allow-Origin",
|
|
338
|
+
);
|
|
339
|
+
// and merging caller headers must not resurrect it either.
|
|
340
|
+
expect(API["getHeaders"]({ "X-PoweredBy": "coffee" })).not.toHaveProperty(
|
|
341
|
+
"Access-Control-Allow-Origin",
|
|
342
|
+
);
|
|
343
|
+
});
|
|
330
344
|
});
|
|
331
345
|
|
|
332
346
|
describe("getHeaders", () => {
|