@oneuptime/common 11.7.1 → 11.7.3
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/AnalyticsModels/KubernetesCostAllocation.ts +103 -0
- package/Models/DatabaseModels/DashboardDomain.ts +1 -0
- package/Models/DatabaseModels/Incident.ts +2 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
- package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
- package/Models/DatabaseModels/Monitor.ts +1 -0
- package/Models/DatabaseModels/MonitorStatusTimeline.ts +1 -0
- package/Models/DatabaseModels/NetworkSite.ts +1 -0
- package/Models/DatabaseModels/NetworkSiteStatusTimeline.ts +1 -0
- package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -0
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +2 -0
- package/Models/DatabaseModels/Probe.ts +13 -13
- package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -0
- package/Models/DatabaseModels/StatusPage.ts +1 -0
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
- package/Models/DatabaseModels/StatusPageDomain.ts +1 -0
- package/Models/DatabaseModels/TableView.ts +40 -0
- package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +1 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +11 -0
- package/Server/API/BaseAPI.ts +17 -1
- package/Server/API/CommonAPI.ts +32 -0
- package/Server/API/MicrosoftTeamsAPI.ts +99 -2
- package/Server/API/ProbeAPI.ts +2 -0
- package/Server/API/SlackAPI.ts +62 -0
- package/Server/API/StatusPageAPI.ts +157 -192
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.ts +89 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.ts +61 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.ts +280 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.ts +13 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Services/DatabaseService.ts +11 -3
- package/Server/Services/MonitorGroupService.ts +145 -0
- package/Server/Services/MonitorService.ts +231 -30
- package/Server/Services/ProbeService.ts +319 -16
- package/Server/Services/StatusPageService.ts +72 -42
- package/Server/Services/WorkspaceNotificationRuleService.ts +232 -2
- package/Server/Utils/Database/PostgresErrorTranslator.ts +131 -0
- package/Server/Utils/Monitor/MonitorResource.ts +88 -35
- package/Server/Utils/Monitor/MonitorStatusTimeline.ts +21 -1
- package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +158 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +706 -6
- package/Tests/Models/AnalyticsModels/KubernetesCostAllocation.test.ts +16 -0
- package/Tests/Models/DatabaseModels/ProbeColumnAccessControl.test.ts +103 -0
- package/Tests/Server/API/BaseAPIUpdateNoOp.test.ts +135 -0
- package/Tests/Server/API/CommonAPIAuthGuard.test.ts +317 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +228 -0
- package/Tests/Server/API/ProbeAPI.test.ts +6 -0
- package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +167 -0
- package/Tests/Server/Services/AddHotQueryIndexesMigration.test.ts +255 -0
- package/Tests/Server/Services/AddHotQueryIndexesSecondPassMigration.test.ts +258 -0
- package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +1 -1
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +121 -0
- package/Tests/Server/Services/HeartbeatWriteFastPathSafety.test.ts +200 -0
- package/Tests/Server/Services/HookFreeWriteSafetySecondPass.test.ts +181 -0
- package/Tests/Server/Services/MonitorGroupServiceBatchedStatuses.test.ts +513 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +492 -0
- package/Tests/Server/Services/MonitorServiceProbeSelection.test.ts +401 -0
- package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +453 -0
- package/Tests/Server/Services/ProbeAuthKeyCacheAndHeartbeat.test.ts +679 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +3 -3
- package/Tests/Server/Services/StatusPageDomainResolutionCache.test.ts +245 -0
- package/Tests/Server/Services/StatusPageOverviewTimelineWindow.test.ts +283 -0
- package/Tests/Server/Services/StatusPageServiceMcp.test.ts +17 -1
- package/Tests/Server/Services/StatusPageServiceMonitorGroupBatching.test.ts +331 -0
- package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +12 -12
- package/Tests/Server/Services/WorkspaceNotificationRuleChats.test.ts +1390 -0
- package/Tests/Server/Utils/AI/Insights/InsightFixRouting.test.ts +4 -4
- package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +2 -2
- package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +1 -3
- package/Tests/Server/Utils/AI/Insights/InsightStoreHardening.test.ts +1 -3
- package/Tests/Server/Utils/AI/Insights/InsightTriage.test.ts +1 -1
- package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +5 -5
- package/Tests/Server/Utils/Alert/AlertPrivacyFilter.test.ts +187 -0
- package/Tests/Server/Utils/AlertEpisode/AlertEpisodePrivacyFilter.test.ts +170 -0
- package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +125 -0
- package/Tests/Server/Utils/Incident/IncidentPrivacyFilter.test.ts +264 -0
- package/Tests/Server/Utils/IncidentEpisode/IncidentEpisodePrivacyFilter.test.ts +175 -0
- package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +181 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +2 -2
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +341 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +2175 -0
- package/Tests/Server/Utils/Workspace/SlackChannelsList.test.ts +585 -0
- package/Tests/Types/Billing/MeteredPlan.test.ts +46 -0
- package/Tests/Types/JSONFunctions.test.ts +103 -1
- package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +219 -1
- package/Tests/UI/Components/BulkUpdateForm.test.tsx +174 -0
- package/Tests/UI/Components/CardModelDetailEdit.test.tsx +315 -0
- package/Tests/UI/Components/ModelDetailSelect.test.tsx +229 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1258 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +679 -0
- package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +572 -0
- package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +1678 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1094 -0
- package/Tests/UI/Components/ModelTableExportFromColumns.test.ts +153 -0
- package/Tests/UI/Components/MonacoLoader.test.ts +67 -0
- package/Tests/UI/Components/MonacoRuntime.test.ts +121 -0
- package/Tests/UI/Components/TableBulkCsvExport.test.tsx +212 -3
- package/Tests/UI/Utils/Project.test.ts +809 -0
- package/Tests/UI/Utils/TableColumnsToCsv.test.ts +360 -0
- package/Tests/Utils/Alerts/AlertMetricType.test.ts +143 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +219 -0
- package/Tests/Utils/Incident/IncidentMetricType.test.ts +174 -0
- package/Tests/Utils/Monitor/MonitorProbeSelectionUtil.test.ts +145 -0
- package/Tests/Utils/StatusPage/ResourceUptime.test.ts +263 -0
- package/Tests/Utils/UserPreferences.test.ts +563 -0
- package/Types/JSONFunctions.ts +7 -2
- package/Types/Kubernetes/KubernetesCostIngest.ts +37 -1
- package/Types/Workspace/NotificationRules/BaseNotificationRule.ts +4 -0
- package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +25 -10
- package/Types/Workspace/WorkspaceMessagePayload.ts +1 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +63 -8
- package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
- package/UI/Components/CodeEditor/MonacoLoader.ts +36 -0
- package/UI/Components/Forms/ModelForm.tsx +41 -18
- package/UI/Components/MasterPage/MasterPage.tsx +65 -0
- package/UI/Components/ModelDetail/CardModelDetail.tsx +23 -1
- package/UI/Components/ModelDetail/ModelDetail.tsx +46 -1
- package/UI/Components/ModelTable/BaseModelTable.tsx +687 -34
- package/UI/Components/ModelTable/Column.ts +29 -0
- package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +420 -0
- package/UI/Components/ModelTable/ColumnPreference.ts +482 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +326 -0
- package/UI/Components/ModelTable/ExportFromColumns.ts +57 -0
- package/UI/Components/ModelTable/TableView.tsx +24 -2
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +150 -0
- package/UI/Components/Navbar/NavBar.tsx +66 -1
- package/UI/Components/SideMenu/SideMenu.tsx +24 -4
- package/UI/Components/Table/Table.tsx +66 -14
- package/UI/Components/Table/TableRow.tsx +180 -175
- package/UI/Components/Table/Types/Column.ts +20 -0
- package/UI/Utils/Project.ts +214 -18
- package/UI/Utils/TableColumnsToCsv.ts +86 -6
- package/UI/esbuild-config.js +34 -0
- package/Utils/Monitor/MonitorProbeSelectionUtil.ts +72 -0
- package/Utils/UserPreferences.ts +53 -0
- package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js +86 -0
- package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DashboardDomain.js +1 -0
- package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +2 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +1 -0
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSite.js +1 -0
- package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +2 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Probe.js +13 -13
- package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TableView.js +40 -0
- package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +1 -0
- package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +12 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +21 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +72 -2
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/ProbeAPI.js +2 -0
- package/build/dist/Server/API/ProbeAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +44 -0
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +98 -152
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js +36 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js +254 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +10 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/MonitorGroupService.js +118 -0
- package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +198 -24
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ProbeService.js +238 -9
- package/build/dist/Server/Services/ProbeService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +46 -39
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +178 -3
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +80 -0
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +67 -29
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +31 -14
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +117 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +549 -7
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +8 -3
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesCostIngest.js.map +1 -1
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +17 -7
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +37 -11
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +2 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +34 -0
- package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +1 -0
- package/build/dist/UI/Components/Forms/ModelForm.js +37 -17
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js +50 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +22 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/ModelDetail.js +31 -1
- package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +472 -27
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +189 -0
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js +258 -0
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +168 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/ExportFromColumns.js +30 -0
- package/build/dist/UI/Components/ModelTable/ExportFromColumns.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/TableView.js +13 -2
- package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +84 -0
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -0
- package/build/dist/UI/Components/Navbar/NavBar.js +39 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js +15 -5
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +46 -13
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +11 -6
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Utils/Project.js +161 -15
- package/build/dist/UI/Utils/Project.js.map +1 -1
- package/build/dist/UI/Utils/TableColumnsToCsv.js +67 -6
- package/build/dist/UI/Utils/TableColumnsToCsv.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js +52 -0
- package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js.map +1 -0
- package/build/dist/Utils/UserPreferences.js +33 -0
- package/build/dist/Utils/UserPreferences.js.map +1 -1
- package/package.json +4 -3
|
@@ -185,7 +185,7 @@ describe("InsightFixRouting.routeInsightFix", () => {
|
|
|
185
185
|
const create: jest.SpyInstance = jest
|
|
186
186
|
.spyOn(TelemetryExceptionService, "createCodeFixRunForException")
|
|
187
187
|
.mockResolvedValue({ id: fixRunId } as unknown as AIRun);
|
|
188
|
-
jest.spyOn(AIRunService, "updateOneById").mockResolvedValue(
|
|
188
|
+
jest.spyOn(AIRunService, "updateOneById").mockResolvedValue(1);
|
|
189
189
|
|
|
190
190
|
const explicitlyOn: InsightFixRoutingResult =
|
|
191
191
|
await InsightFixRouting.routeInsightFix({
|
|
@@ -303,7 +303,7 @@ describe("InsightFixRouting.routeInsightFix", () => {
|
|
|
303
303
|
.mockResolvedValue({ id: fixRunId } as unknown as AIRun);
|
|
304
304
|
const stamp: jest.SpyInstance = jest
|
|
305
305
|
.spyOn(AIRunService, "updateOneById")
|
|
306
|
-
.mockResolvedValue(
|
|
306
|
+
.mockResolvedValue(1);
|
|
307
307
|
|
|
308
308
|
const result: InsightFixRoutingResult =
|
|
309
309
|
await InsightFixRouting.routeInsightFix({
|
|
@@ -339,7 +339,7 @@ describe("InsightFixRouting.routeInsightFix", () => {
|
|
|
339
339
|
.mockResolvedValue({ id: fixRunId } as unknown as AIRun);
|
|
340
340
|
const stamp: jest.SpyInstance = jest
|
|
341
341
|
.spyOn(AIRunService, "updateOneById")
|
|
342
|
-
.mockResolvedValue(
|
|
342
|
+
.mockResolvedValue(1);
|
|
343
343
|
|
|
344
344
|
const result: InsightFixRoutingResult =
|
|
345
345
|
await InsightFixRouting.routeInsightFix({
|
|
@@ -382,7 +382,7 @@ describe("InsightFixRouting.routeInsightFix", () => {
|
|
|
382
382
|
const createFromFindings: jest.SpyInstance = jest
|
|
383
383
|
.spyOn(FixPerformanceTaskTrigger, "createPerformanceFixTaskFromFindings")
|
|
384
384
|
.mockResolvedValue({ id: fixRunId } as unknown as AIRun);
|
|
385
|
-
jest.spyOn(AIRunService, "updateOneById").mockResolvedValue(
|
|
385
|
+
jest.spyOn(AIRunService, "updateOneById").mockResolvedValue(1);
|
|
386
386
|
|
|
387
387
|
await InsightFixRouting.routeInsightFix({
|
|
388
388
|
insight: makeLatencyInsight({ traceId: undefined }),
|
|
@@ -289,7 +289,7 @@ describe("InsightScanner — routing newly created insights", () => {
|
|
|
289
289
|
jest.spyOn(AIInsightService, "findBy").mockResolvedValue([]);
|
|
290
290
|
updateOneById = jest
|
|
291
291
|
.spyOn(AIInsightService, "updateOneById")
|
|
292
|
-
.mockResolvedValue(
|
|
292
|
+
.mockResolvedValue(1);
|
|
293
293
|
routeInsightFix = jest
|
|
294
294
|
.spyOn(InsightFixRouting, "routeInsightFix")
|
|
295
295
|
.mockResolvedValue({});
|
|
@@ -477,7 +477,7 @@ describe("InsightScanner — the self-heal sweep for insights stranded in Detect
|
|
|
477
477
|
});
|
|
478
478
|
updateOneById = jest
|
|
479
479
|
.spyOn(AIInsightService, "updateOneById")
|
|
480
|
-
.mockResolvedValue(
|
|
480
|
+
.mockResolvedValue(1);
|
|
481
481
|
routeInsightFix = jest
|
|
482
482
|
.spyOn(InsightFixRouting, "routeInsightFix")
|
|
483
483
|
.mockResolvedValue({});
|
|
@@ -74,9 +74,7 @@ function mockCreate(): jest.SpyInstance {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function mockUpdateOneById(): jest.SpyInstance {
|
|
77
|
-
return jest
|
|
78
|
-
.spyOn(AIInsightService, "updateOneById")
|
|
79
|
-
.mockResolvedValue(undefined);
|
|
77
|
+
return jest.spyOn(AIInsightService, "updateOneById").mockResolvedValue(1);
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
describe("InsightStore.upsertCandidates — dedupe matrix", () => {
|
|
@@ -83,9 +83,7 @@ function mockFindOneBy(existing: AIInsight | null): jest.SpyInstance {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
function mockUpdateOneById(): jest.SpyInstance {
|
|
86
|
-
return jest
|
|
87
|
-
.spyOn(AIInsightService, "updateOneById")
|
|
88
|
-
.mockResolvedValue(undefined);
|
|
86
|
+
return jest.spyOn(AIInsightService, "updateOneById").mockResolvedValue(1);
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
describe("InsightStore — column-length clamps", () => {
|
|
@@ -160,7 +160,7 @@ describe("InsightTriage.enqueueInsightTriage", () => {
|
|
|
160
160
|
.mockResolvedValue(triageRunId);
|
|
161
161
|
const persist: jest.SpyInstance = jest
|
|
162
162
|
.spyOn(AIInsightService, "updateOneById")
|
|
163
|
-
.mockResolvedValue(
|
|
163
|
+
.mockResolvedValue(1);
|
|
164
164
|
|
|
165
165
|
const result: InsightTriageResult =
|
|
166
166
|
await InsightTriage.enqueueInsightTriage({ insight: makeInsight() });
|
|
@@ -194,7 +194,7 @@ describe("InsightTriageRunner.executeTriage", () => {
|
|
|
194
194
|
.mockResolvedValue(makeInsight());
|
|
195
195
|
const persist: jest.SpyInstance = jest
|
|
196
196
|
.spyOn(AIInsightService, "updateOneById")
|
|
197
|
-
.mockResolvedValue(
|
|
197
|
+
.mockResolvedValue(1);
|
|
198
198
|
const incidentFeed: jest.SpyInstance = jest
|
|
199
199
|
.spyOn(IncidentFeedService, "createIncidentFeedItem")
|
|
200
200
|
.mockResolvedValue(undefined);
|
|
@@ -258,7 +258,7 @@ describe("InsightTriageRunner.executeTriage", () => {
|
|
|
258
258
|
.mockResolvedValue(makeInsight({ telemetryExceptionId }));
|
|
259
259
|
const persistInsight: jest.SpyInstance = jest
|
|
260
260
|
.spyOn(AIInsightService, "updateOneById")
|
|
261
|
-
.mockResolvedValue(
|
|
261
|
+
.mockResolvedValue(1);
|
|
262
262
|
const flipStatus: jest.SpyInstance = jest
|
|
263
263
|
.spyOn(AIInsightService, "updateOneBy")
|
|
264
264
|
.mockResolvedValue(undefined as never);
|
|
@@ -342,7 +342,7 @@ describe("InsightTriageRunner.executeTriage", () => {
|
|
|
342
342
|
);
|
|
343
343
|
const persistInsight: jest.SpyInstance = jest
|
|
344
344
|
.spyOn(AIInsightService, "updateOneById")
|
|
345
|
-
.mockResolvedValue(
|
|
345
|
+
.mockResolvedValue(1);
|
|
346
346
|
const flipStatus: jest.SpyInstance = jest
|
|
347
347
|
.spyOn(AIInsightService, "updateOneBy")
|
|
348
348
|
.mockResolvedValue(undefined as never);
|
|
@@ -389,7 +389,7 @@ describe("InsightTriageRunner.executeTriage", () => {
|
|
|
389
389
|
jest
|
|
390
390
|
.spyOn(AIInsightService, "findOneById")
|
|
391
391
|
.mockResolvedValue(makeInsight({ telemetryExceptionId }));
|
|
392
|
-
jest.spyOn(AIInsightService, "updateOneById").mockResolvedValue(
|
|
392
|
+
jest.spyOn(AIInsightService, "updateOneById").mockResolvedValue(1);
|
|
393
393
|
const exceptionWrites: jest.SpyInstance = jest
|
|
394
394
|
.spyOn(TelemetryExceptionService, "updateOneById")
|
|
395
395
|
.mockResolvedValue(undefined as never);
|
|
@@ -432,7 +432,7 @@ describe("InsightTriageRunner.executeTriage", () => {
|
|
|
432
432
|
jest
|
|
433
433
|
.spyOn(AIInsightService, "findOneById")
|
|
434
434
|
.mockResolvedValue(makeInsight({ telemetryExceptionId }));
|
|
435
|
-
jest.spyOn(AIInsightService, "updateOneById").mockResolvedValue(
|
|
435
|
+
jest.spyOn(AIInsightService, "updateOneById").mockResolvedValue(1);
|
|
436
436
|
const exceptionWrites: jest.SpyInstance = jest
|
|
437
437
|
.spyOn(TelemetryExceptionService, "updateOneById")
|
|
438
438
|
.mockResolvedValue(undefined as never);
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyAlertRelatedRecordPrivacyFilter,
|
|
3
|
+
applyAlertSelfPrivacyFilter,
|
|
4
|
+
getAlertRelatedRecordPrivacyRaw,
|
|
5
|
+
getAlertSelfPrivacyRaw,
|
|
6
|
+
shouldBypassAlertPrivacy,
|
|
7
|
+
} from "../../../../Server/Utils/Alert/AlertPrivacyFilter";
|
|
8
|
+
import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
9
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
10
|
+
import Permission, {
|
|
11
|
+
UserPermission,
|
|
12
|
+
UserTenantAccessPermission,
|
|
13
|
+
} from "../../../../Types/Permission";
|
|
14
|
+
import { describe, expect, test } from "@jest/globals";
|
|
15
|
+
import { FindOperator } from "typeorm";
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* AlertPrivacyFilter is the alert-side twin of IncidentPrivacyFilter: private
|
|
19
|
+
* alerts are visible to owners (directly or via team), and to project
|
|
20
|
+
* owners/admins/root. Each branch is pinned against the SQL actually emitted.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const userId: ObjectID = ObjectID.generate();
|
|
24
|
+
const tenantId: ObjectID = ObjectID.generate();
|
|
25
|
+
|
|
26
|
+
const propsWithProjectPermission: (
|
|
27
|
+
permission: Permission,
|
|
28
|
+
) => DatabaseCommonInteractionProps = (
|
|
29
|
+
permission: Permission,
|
|
30
|
+
): DatabaseCommonInteractionProps => {
|
|
31
|
+
const userPermission: UserPermission = {
|
|
32
|
+
_type: "UserPermission",
|
|
33
|
+
permission: permission,
|
|
34
|
+
labelIds: [],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const tenantPermission: UserTenantAccessPermission = {
|
|
38
|
+
_type: "UserTenantAccessPermission",
|
|
39
|
+
projectId: tenantId,
|
|
40
|
+
permissions: [userPermission],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
userId: userId,
|
|
45
|
+
tenantId: tenantId,
|
|
46
|
+
userTenantAccessPermission: {
|
|
47
|
+
[tenantId.toString()]: tenantPermission,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
53
|
+
type RawParts = { sql: string; params: Record<string, string> };
|
|
54
|
+
|
|
55
|
+
const rawParts: (clause: unknown) => RawParts = (clause: unknown): RawParts => {
|
|
56
|
+
const operator: any = clause as any;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
sql: String(operator._getSql("COLUMN")),
|
|
60
|
+
params: (operator._objectLiteralParameters || {}) as Record<string, string>,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
64
|
+
|
|
65
|
+
describe("shouldBypassAlertPrivacy", () => {
|
|
66
|
+
test("root, master-admin, project owner and project admin bypass", () => {
|
|
67
|
+
expect(shouldBypassAlertPrivacy({ isRoot: true })).toBe(true);
|
|
68
|
+
expect(shouldBypassAlertPrivacy({ isMasterAdmin: true })).toBe(true);
|
|
69
|
+
expect(
|
|
70
|
+
shouldBypassAlertPrivacy(
|
|
71
|
+
propsWithProjectPermission(Permission.ProjectOwner),
|
|
72
|
+
),
|
|
73
|
+
).toBe(true);
|
|
74
|
+
expect(
|
|
75
|
+
shouldBypassAlertPrivacy(
|
|
76
|
+
propsWithProjectPermission(Permission.ProjectAdmin),
|
|
77
|
+
),
|
|
78
|
+
).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("an ordinary member, or a permission under another tenant, does not bypass", () => {
|
|
82
|
+
expect(
|
|
83
|
+
shouldBypassAlertPrivacy(
|
|
84
|
+
propsWithProjectPermission(Permission.ProjectMember),
|
|
85
|
+
),
|
|
86
|
+
).toBe(false);
|
|
87
|
+
|
|
88
|
+
const wrongTenant: DatabaseCommonInteractionProps =
|
|
89
|
+
propsWithProjectPermission(Permission.ProjectOwner);
|
|
90
|
+
wrongTenant.tenantId = ObjectID.generate();
|
|
91
|
+
expect(shouldBypassAlertPrivacy(wrongTenant)).toBe(false);
|
|
92
|
+
|
|
93
|
+
expect(shouldBypassAlertPrivacy({})).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("getAlertSelfPrivacyRaw", () => {
|
|
98
|
+
test("bypassing callers get no clause", () => {
|
|
99
|
+
expect(getAlertSelfPrivacyRaw({ isRoot: true })).toBeUndefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("anonymous callers only see non-private alerts, no params bound", () => {
|
|
103
|
+
const { sql, params }: RawParts = rawParts(getAlertSelfPrivacyRaw({}));
|
|
104
|
+
|
|
105
|
+
expect(sql).toContain("COLUMN IS NULL OR COLUMN = FALSE");
|
|
106
|
+
expect(sql).not.toContain("AlertOwnerUser");
|
|
107
|
+
expect(Object.keys(params)).toHaveLength(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("a user also sees alerts they own directly or via a team, bound to their id", () => {
|
|
111
|
+
const { sql, params }: RawParts = rawParts(
|
|
112
|
+
getAlertSelfPrivacyRaw({ userId }),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(sql).toContain("AlertOwnerUser");
|
|
116
|
+
expect(sql).toContain("AlertOwnerTeam");
|
|
117
|
+
expect(sql).toContain("TeamMember");
|
|
118
|
+
expect(sql).toContain(`aou."deletedAt" IS NULL`);
|
|
119
|
+
expect(Object.values(params)).toEqual([userId.toString()]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("fresh parameter name per call", () => {
|
|
123
|
+
const first: RawParts = rawParts(getAlertSelfPrivacyRaw({ userId }));
|
|
124
|
+
const second: RawParts = rawParts(getAlertSelfPrivacyRaw({ userId }));
|
|
125
|
+
expect(Object.keys(first.params)).not.toEqual(Object.keys(second.params));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("getAlertRelatedRecordPrivacyRaw", () => {
|
|
130
|
+
test("anonymous callers are scoped to child rows of non-private alerts", () => {
|
|
131
|
+
const { sql, params }: RawParts = rawParts(
|
|
132
|
+
getAlertRelatedRecordPrivacyRaw({}),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
expect(sql).toContain(`FROM "Alert" a`);
|
|
136
|
+
expect(sql).toContain(`a."isPrivate" IS NULL OR a."isPrivate" = FALSE`);
|
|
137
|
+
expect(Object.keys(params)).toHaveLength(0);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("a user's child rows also include alerts they own", () => {
|
|
141
|
+
const { sql, params }: RawParts = rawParts(
|
|
142
|
+
getAlertRelatedRecordPrivacyRaw({ userId }),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
expect(sql).toContain("AlertOwnerUser");
|
|
146
|
+
expect(Object.values(params)).toEqual([userId.toString()]);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("applyAlertSelfPrivacyFilter / applyAlertRelatedRecordPrivacyFilter", () => {
|
|
151
|
+
test("bypassing callers get their query back untouched", () => {
|
|
152
|
+
const query: { isPrivate?: unknown } = {};
|
|
153
|
+
expect(applyAlertSelfPrivacyFilter(query, { isRoot: true })).toBe(query);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("self filter sets isPrivate and keeps other keys", () => {
|
|
157
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
158
|
+
const result: { projectId: ObjectID; isPrivate?: unknown } =
|
|
159
|
+
applyAlertSelfPrivacyFilter({ projectId }, { userId });
|
|
160
|
+
|
|
161
|
+
expect(result.projectId).toBe(projectId);
|
|
162
|
+
expect(result.isPrivate).toBeInstanceOf(FindOperator);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("self filter ANDs an existing isPrivate value", () => {
|
|
166
|
+
const result: { isPrivate?: unknown } = applyAlertSelfPrivacyFilter(
|
|
167
|
+
{ isPrivate: false },
|
|
168
|
+
{ userId },
|
|
169
|
+
);
|
|
170
|
+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
171
|
+
expect(String((result.isPrivate as any)._type)).toBe("and");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("related filter builds { alertId: clause } and ANDs an existing alertId", () => {
|
|
175
|
+
const created: { alertId?: unknown } = applyAlertRelatedRecordPrivacyFilter(
|
|
176
|
+
undefined as unknown as { alertId?: unknown },
|
|
177
|
+
{ userId },
|
|
178
|
+
);
|
|
179
|
+
expect(created.alertId).toBeInstanceOf(FindOperator);
|
|
180
|
+
|
|
181
|
+
const alertId: ObjectID = ObjectID.generate();
|
|
182
|
+
const combined: { alertId?: unknown } =
|
|
183
|
+
applyAlertRelatedRecordPrivacyFilter({ alertId }, { userId });
|
|
184
|
+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
185
|
+
expect(String((combined.alertId as any)._type)).toBe("and");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyAlertEpisodeRelatedRecordPrivacyFilter,
|
|
3
|
+
applyAlertEpisodeSelfPrivacyFilter,
|
|
4
|
+
getAlertEpisodeRelatedRecordPrivacyRaw,
|
|
5
|
+
getAlertEpisodeSelfPrivacyRaw,
|
|
6
|
+
shouldBypassAlertEpisodePrivacy,
|
|
7
|
+
} from "../../../../Server/Utils/AlertEpisode/AlertEpisodePrivacyFilter";
|
|
8
|
+
import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
9
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
10
|
+
import Permission, {
|
|
11
|
+
UserPermission,
|
|
12
|
+
UserTenantAccessPermission,
|
|
13
|
+
} from "../../../../Types/Permission";
|
|
14
|
+
import { describe, expect, test } from "@jest/globals";
|
|
15
|
+
import { FindOperator } from "typeorm";
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* Alert-episode privacy is the alert-side twin of incident-episode privacy.
|
|
19
|
+
* The emitted SQL is pinned so a wrong table/column or a dropped soft-delete
|
|
20
|
+
* guard is caught.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const userId: ObjectID = ObjectID.generate();
|
|
24
|
+
const tenantId: ObjectID = ObjectID.generate();
|
|
25
|
+
|
|
26
|
+
const propsWithProjectPermission: (
|
|
27
|
+
permission: Permission,
|
|
28
|
+
) => DatabaseCommonInteractionProps = (
|
|
29
|
+
permission: Permission,
|
|
30
|
+
): DatabaseCommonInteractionProps => {
|
|
31
|
+
const userPermission: UserPermission = {
|
|
32
|
+
_type: "UserPermission",
|
|
33
|
+
permission: permission,
|
|
34
|
+
labelIds: [],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const tenantPermission: UserTenantAccessPermission = {
|
|
38
|
+
_type: "UserTenantAccessPermission",
|
|
39
|
+
projectId: tenantId,
|
|
40
|
+
permissions: [userPermission],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
userId: userId,
|
|
45
|
+
tenantId: tenantId,
|
|
46
|
+
userTenantAccessPermission: {
|
|
47
|
+
[tenantId.toString()]: tenantPermission,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
53
|
+
type RawParts = { sql: string; params: Record<string, string> };
|
|
54
|
+
|
|
55
|
+
const rawParts: (clause: unknown) => RawParts = (clause: unknown): RawParts => {
|
|
56
|
+
const operator: any = clause as any;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
sql: String(operator._getSql("COLUMN")),
|
|
60
|
+
params: (operator._objectLiteralParameters || {}) as Record<string, string>,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
64
|
+
|
|
65
|
+
describe("shouldBypassAlertEpisodePrivacy", () => {
|
|
66
|
+
test("root, master-admin, owner and admin bypass; member does not", () => {
|
|
67
|
+
expect(shouldBypassAlertEpisodePrivacy({ isRoot: true })).toBe(true);
|
|
68
|
+
expect(shouldBypassAlertEpisodePrivacy({ isMasterAdmin: true })).toBe(true);
|
|
69
|
+
expect(
|
|
70
|
+
shouldBypassAlertEpisodePrivacy(
|
|
71
|
+
propsWithProjectPermission(Permission.ProjectAdmin),
|
|
72
|
+
),
|
|
73
|
+
).toBe(true);
|
|
74
|
+
expect(
|
|
75
|
+
shouldBypassAlertEpisodePrivacy(
|
|
76
|
+
propsWithProjectPermission(Permission.ProjectMember),
|
|
77
|
+
),
|
|
78
|
+
).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("getAlertEpisodeSelfPrivacyRaw", () => {
|
|
83
|
+
test("bypassing callers get no clause", () => {
|
|
84
|
+
expect(getAlertEpisodeSelfPrivacyRaw({ isRoot: true })).toBeUndefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("anonymous callers only see non-private episodes, no params bound", () => {
|
|
88
|
+
const { sql, params }: RawParts = rawParts(
|
|
89
|
+
getAlertEpisodeSelfPrivacyRaw({}),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(sql).toContain("COLUMN IS NULL OR COLUMN = FALSE");
|
|
93
|
+
expect(sql).not.toContain("AlertEpisodeOwnerUser");
|
|
94
|
+
expect(Object.keys(params)).toHaveLength(0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("a user also sees episodes they own, bound to their id", () => {
|
|
98
|
+
const { sql, params }: RawParts = rawParts(
|
|
99
|
+
getAlertEpisodeSelfPrivacyRaw({ userId }),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(sql).toContain("AlertEpisodeOwnerUser");
|
|
103
|
+
expect(sql).toContain("AlertEpisodeOwnerTeam");
|
|
104
|
+
expect(sql).toContain(`aeou."deletedAt" IS NULL`);
|
|
105
|
+
expect(Object.values(params)).toEqual([userId.toString()]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("fresh parameter name per call", () => {
|
|
109
|
+
const first: RawParts = rawParts(getAlertEpisodeSelfPrivacyRaw({ userId }));
|
|
110
|
+
const second: RawParts = rawParts(
|
|
111
|
+
getAlertEpisodeSelfPrivacyRaw({ userId }),
|
|
112
|
+
);
|
|
113
|
+
expect(Object.keys(first.params)).not.toEqual(Object.keys(second.params));
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("getAlertEpisodeRelatedRecordPrivacyRaw", () => {
|
|
118
|
+
test("anonymous callers are scoped to child rows of non-private episodes", () => {
|
|
119
|
+
const { sql }: RawParts = rawParts(
|
|
120
|
+
getAlertEpisodeRelatedRecordPrivacyRaw({}),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect(sql).toContain(`FROM "AlertEpisode" ae`);
|
|
124
|
+
expect(sql).toContain(`ae."isPrivate" IS NULL OR ae."isPrivate" = FALSE`);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("a user's child rows include episodes they own", () => {
|
|
128
|
+
const { sql, params }: RawParts = rawParts(
|
|
129
|
+
getAlertEpisodeRelatedRecordPrivacyRaw({ userId }),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(sql).toContain("AlertEpisodeOwnerUser");
|
|
133
|
+
expect(Object.values(params)).toEqual([userId.toString()]);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("apply* alert-episode filters", () => {
|
|
138
|
+
test("bypassing callers get their query back untouched", () => {
|
|
139
|
+
const query: { isPrivate?: unknown } = {};
|
|
140
|
+
expect(applyAlertEpisodeSelfPrivacyFilter(query, { isRoot: true })).toBe(
|
|
141
|
+
query,
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("self filter sets isPrivate; related filter builds { alertEpisodeId }", () => {
|
|
146
|
+
const self: { isPrivate?: unknown } = applyAlertEpisodeSelfPrivacyFilter(
|
|
147
|
+
{},
|
|
148
|
+
{ userId },
|
|
149
|
+
);
|
|
150
|
+
expect(self.isPrivate).toBeInstanceOf(FindOperator);
|
|
151
|
+
|
|
152
|
+
const related: { alertEpisodeId?: unknown } =
|
|
153
|
+
applyAlertEpisodeRelatedRecordPrivacyFilter(
|
|
154
|
+
undefined as unknown as { alertEpisodeId?: unknown },
|
|
155
|
+
{ userId },
|
|
156
|
+
);
|
|
157
|
+
expect(related.alertEpisodeId).toBeInstanceOf(FindOperator);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("an existing alertEpisodeId filter is ANDed, not replaced", () => {
|
|
161
|
+
const alertEpisodeId: ObjectID = ObjectID.generate();
|
|
162
|
+
const combined: { alertEpisodeId?: unknown } =
|
|
163
|
+
applyAlertEpisodeRelatedRecordPrivacyFilter(
|
|
164
|
+
{ alertEpisodeId },
|
|
165
|
+
{ userId },
|
|
166
|
+
);
|
|
167
|
+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
168
|
+
expect(String((combined.alertEpisodeId as any)._type)).toBe("and");
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import PostgresErrorTranslator from "../../../../Server/Utils/Database/PostgresErrorTranslator";
|
|
2
|
+
import BadDataException from "../../../../Types/Exception/BadDataException";
|
|
3
|
+
import Exception from "../../../../Types/Exception/Exception";
|
|
4
|
+
import { describe, expect, it } from "@jest/globals";
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* Contract under test: TypeORM's QueryFailedError is not an Exception, so it
|
|
8
|
+
* used to fall through StartServer's generic handler and reach the client as a
|
|
9
|
+
* bare 500 "Server Error" — which is exactly what deleting a project hit when
|
|
10
|
+
* another project's MonitorStatusTimeline rows still referenced its statuses.
|
|
11
|
+
*
|
|
12
|
+
* The translator turns the actionable Postgres failures into a
|
|
13
|
+
* BadDataException (400) and leaves everything else untouched.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type FakeQueryFailedError = {
|
|
17
|
+
message: string;
|
|
18
|
+
code?: string | undefined;
|
|
19
|
+
table?: string | undefined;
|
|
20
|
+
detail?: string | undefined;
|
|
21
|
+
driverError?: unknown;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const foreignKeyDeleteError: () => FakeQueryFailedError =
|
|
25
|
+
(): FakeQueryFailedError => {
|
|
26
|
+
return {
|
|
27
|
+
message:
|
|
28
|
+
'update or delete on table "MonitorStatus" violates foreign key constraint "FK_574feb4161c5216c2c7ee0faaf8" on table "MonitorStatusTimeline"',
|
|
29
|
+
code: "23503",
|
|
30
|
+
table: "MonitorStatusTimeline",
|
|
31
|
+
detail:
|
|
32
|
+
'Key (_id)=(cfc2f04f-79cb-4344-8c54-dafe5e3a290c) is still referenced from table "MonitorStatusTimeline".',
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe("PostgresErrorTranslator", () => {
|
|
37
|
+
describe("foreign key violation on delete", () => {
|
|
38
|
+
it("translates to a BadDataException naming the blocking table", () => {
|
|
39
|
+
const translated: unknown = PostgresErrorTranslator.translate(
|
|
40
|
+
foreignKeyDeleteError(),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
expect(translated).toBeInstanceOf(BadDataException);
|
|
44
|
+
expect((translated as Exception).message).toBe(
|
|
45
|
+
"This item cannot be deleted because Monitor Status Timeline records still reference it. Please delete those records first, and then try again.",
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("reads the pg fields off driverError when the wrapper has no code", () => {
|
|
50
|
+
const translated: unknown = PostgresErrorTranslator.translate({
|
|
51
|
+
message: "wrapped",
|
|
52
|
+
driverError: foreignKeyDeleteError(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(translated).toBeInstanceOf(BadDataException);
|
|
56
|
+
expect((translated as Exception).message).toContain(
|
|
57
|
+
"Monitor Status Timeline",
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("does not leak the offending row id into the message", () => {
|
|
62
|
+
const translated: unknown = PostgresErrorTranslator.translate(
|
|
63
|
+
foreignKeyDeleteError(),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect((translated as Exception).message).not.toContain(
|
|
67
|
+
"cfc2f04f-79cb-4344-8c54-dafe5e3a290c",
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("foreign key violation on insert or update", () => {
|
|
73
|
+
it("translates to a BadDataException naming the missing record", () => {
|
|
74
|
+
const translated: unknown = PostgresErrorTranslator.translate({
|
|
75
|
+
message: "insert violates foreign key constraint",
|
|
76
|
+
code: "23503",
|
|
77
|
+
table: "MonitorStatusTimeline",
|
|
78
|
+
detail:
|
|
79
|
+
'Key (monitorStatusId)=(cfc2f04f-79cb-4344-8c54-dafe5e3a290c) is not present in table "MonitorStatus".',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(translated).toBeInstanceOf(BadDataException);
|
|
83
|
+
expect((translated as Exception).message).toBe(
|
|
84
|
+
"This request references Monitor Status that does not exist. Please check the request and try again.",
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("errors it should not touch", () => {
|
|
90
|
+
it("passes through a non-foreign-key Postgres error unchanged", () => {
|
|
91
|
+
const uniqueViolation: FakeQueryFailedError = {
|
|
92
|
+
message: "duplicate key value violates unique constraint",
|
|
93
|
+
code: "23505",
|
|
94
|
+
table: "Project",
|
|
95
|
+
detail: "Key (slug)=(acme) already exists.",
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
expect(PostgresErrorTranslator.translate(uniqueViolation)).toBe(
|
|
99
|
+
uniqueViolation,
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("passes through an ordinary Exception unchanged", () => {
|
|
104
|
+
const exception: BadDataException = new BadDataException("Nope");
|
|
105
|
+
|
|
106
|
+
expect(PostgresErrorTranslator.translate(exception)).toBe(exception);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("passes through null and non-objects unchanged", () => {
|
|
110
|
+
expect(PostgresErrorTranslator.translate(null)).toBeNull();
|
|
111
|
+
expect(PostgresErrorTranslator.translate("boom")).toBe("boom");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("passes through a 23503 with an unrecognised detail unchanged", () => {
|
|
115
|
+
const odd: FakeQueryFailedError = {
|
|
116
|
+
message: "foreign key",
|
|
117
|
+
code: "23503",
|
|
118
|
+
table: "MonitorStatusTimeline",
|
|
119
|
+
detail: "something we have not seen before",
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
expect(PostgresErrorTranslator.translate(odd)).toBe(odd);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|