@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
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import DashboardDomain from "../../../Models/DatabaseModels/DashboardDomain";
|
|
2
|
+
import NetworkSite from "../../../Models/DatabaseModels/NetworkSite";
|
|
3
|
+
import OnCallDutyPolicySchedule from "../../../Models/DatabaseModels/OnCallDutyPolicySchedule";
|
|
4
|
+
import ScheduledMaintenanceTemplate from "../../../Models/DatabaseModels/ScheduledMaintenanceTemplate";
|
|
5
|
+
import StatusPage from "../../../Models/DatabaseModels/StatusPage";
|
|
6
|
+
import StatusPageDomain from "../../../Models/DatabaseModels/StatusPageDomain";
|
|
7
|
+
import WorkspaceNotificationSummary from "../../../Models/DatabaseModels/WorkspaceNotificationSummary";
|
|
8
|
+
import { AddHotQueryIndexesSecondPass1785148065137 } from "../../../Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass";
|
|
9
|
+
import SchemaMigrations from "../../../Server/Infrastructure/Postgres/SchemaMigrations/Index";
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { QueryRunner, getMetadataArgsStorage } from "typeorm";
|
|
12
|
+
import type { IndexMetadataArgs } from "typeorm/metadata-args/IndexMetadataArgs";
|
|
13
|
+
import { describe, expect, test } from "@jest/globals";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* The 8 columns below are the filter columns of hot recurring lookups that were
|
|
17
|
+
* full-table seq scans before AddHotQueryIndexesSecondPass1785148065137:
|
|
18
|
+
* - StatusPageDomain.fullDomain / DashboardDomain.fullDomain: resolved on
|
|
19
|
+
* EVERY public request to a custom-domain status page or dashboard (the
|
|
20
|
+
* Host header is looked up by fullDomain to find the page to serve),
|
|
21
|
+
* - StatusPage.sendNextReportBy: the EVERY_MINUTE status-page report cron
|
|
22
|
+
* sweeps for pages whose next report is due,
|
|
23
|
+
* - OnCallDutyPolicySchedule.rosterHandoffAt / rosterNextStartAt: the
|
|
24
|
+
* EVERY_MINUTE on-call roster crons sweep for schedules due a handoff or
|
|
25
|
+
* a roster start,
|
|
26
|
+
* - WorkspaceNotificationSummary.nextSendAt: the workspace notification
|
|
27
|
+
* digest cron sweeps for summaries due to send,
|
|
28
|
+
* - ScheduledMaintenanceTemplate.scheduleNextEventAt: the recurring
|
|
29
|
+
* scheduled-maintenance cron sweeps for templates due to spawn an event,
|
|
30
|
+
* - NetworkSite.lastRollupAt: the network-site rollup cron sweeps for sites
|
|
31
|
+
* due a metrics rollup.
|
|
32
|
+
*
|
|
33
|
+
* These tests pin the pieces that must all hold for the scans to stay gone —
|
|
34
|
+
* a regression in ANY one silently reintroduces them:
|
|
35
|
+
* 1. the @Index() decorator on each entity property (keeps future
|
|
36
|
+
* schema:generate runs from emitting a DROP INDEX as "drift"),
|
|
37
|
+
* 2. the migration's SQL contract: up() creates exactly these 8 indexes
|
|
38
|
+
* and nothing else (no ALTER TABLE drift statements from a regenerated
|
|
39
|
+
* migration), down() drops exactly the indexes up() created,
|
|
40
|
+
* 3. each index NAME equals TypeORM's deterministic derivation
|
|
41
|
+
* "IDX_" + sha1(tableName + "_" + columnName) truncated to 26 hex chars,
|
|
42
|
+
* so the hand-written migration creates the SAME index a schema:generate
|
|
43
|
+
* run would derive from the decorators (a name mismatch would make a
|
|
44
|
+
* future generated migration drop and recreate the index as drift),
|
|
45
|
+
* 4. the migration's registration in SchemaMigrations/Index.ts (an
|
|
46
|
+
* unregistered migration is dead code and never runs on boot).
|
|
47
|
+
*
|
|
48
|
+
* Pure metadata/mocks — no Postgres connection anywhere.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
type ModelClass = { new (): unknown };
|
|
52
|
+
|
|
53
|
+
// [entity class, table name (=== class name for all of these), indexed column]
|
|
54
|
+
const HOT_INDEX_PAIRS: Array<[ModelClass, string, string]> = [
|
|
55
|
+
[StatusPageDomain, "StatusPageDomain", "fullDomain"],
|
|
56
|
+
[DashboardDomain, "DashboardDomain", "fullDomain"],
|
|
57
|
+
[StatusPage, "StatusPage", "sendNextReportBy"],
|
|
58
|
+
[OnCallDutyPolicySchedule, "OnCallDutyPolicySchedule", "rosterHandoffAt"],
|
|
59
|
+
[OnCallDutyPolicySchedule, "OnCallDutyPolicySchedule", "rosterNextStartAt"],
|
|
60
|
+
[WorkspaceNotificationSummary, "WorkspaceNotificationSummary", "nextSendAt"],
|
|
61
|
+
[
|
|
62
|
+
ScheduledMaintenanceTemplate,
|
|
63
|
+
"ScheduledMaintenanceTemplate",
|
|
64
|
+
"scheduleNextEventAt",
|
|
65
|
+
],
|
|
66
|
+
[NetworkSite, "NetworkSite", "lastRollupAt"],
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* IndexMetadataArgs.columns is either an array of property names (property-
|
|
71
|
+
* level @Index()) or a function over the entity's properties map (class-level
|
|
72
|
+
* @Index((o) => [o.a, o.b])). Resolve both to plain property-name arrays; the
|
|
73
|
+
* function form is fed a proxy whose every property reads as its own name,
|
|
74
|
+
* which is exactly how TypeORM's propertiesMap behaves for flat columns.
|
|
75
|
+
*/
|
|
76
|
+
function resolveIndexColumns(
|
|
77
|
+
columns: IndexMetadataArgs["columns"],
|
|
78
|
+
): Array<string> {
|
|
79
|
+
if (!columns) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
if (Array.isArray(columns)) {
|
|
83
|
+
return columns.map(String);
|
|
84
|
+
}
|
|
85
|
+
const propertiesMap: Record<string, string> = new Proxy(
|
|
86
|
+
{} as Record<string, string>,
|
|
87
|
+
{
|
|
88
|
+
get: (_target: Record<string, string>, property: string | symbol) => {
|
|
89
|
+
return String(property);
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
const resolved: Array<unknown> | { [key: string]: number } =
|
|
94
|
+
columns(propertiesMap);
|
|
95
|
+
if (Array.isArray(resolved)) {
|
|
96
|
+
return resolved.map(String);
|
|
97
|
+
}
|
|
98
|
+
return Object.keys(resolved);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* TypeORM's DefaultNamingStrategy.indexName: "IDX_" + the first 26 hex chars
|
|
103
|
+
* of sha1(`${tableName}_${columnName}`) for a single-column index.
|
|
104
|
+
*/
|
|
105
|
+
function typeormIndexName(tableName: string, columnName: string): string {
|
|
106
|
+
const hash: string = createHash("sha1")
|
|
107
|
+
.update(`${tableName}_${columnName}`)
|
|
108
|
+
.digest("hex");
|
|
109
|
+
return `IDX_${hash.substring(0, 26)}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface CreateIndexStatement {
|
|
113
|
+
indexName: string;
|
|
114
|
+
tableName: string;
|
|
115
|
+
columnName: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const CREATE_INDEX_REGEX: RegExp =
|
|
119
|
+
/^CREATE INDEX "([^"]+)" ON "([^"]+)" \("([^"]+)"\)\s*$/;
|
|
120
|
+
const DROP_INDEX_REGEX: RegExp = /^DROP INDEX "public"\."([^"]+)"$/;
|
|
121
|
+
|
|
122
|
+
function makeQueryRunner(): { runner: QueryRunner; query: jest.Mock } {
|
|
123
|
+
const query: jest.Mock = jest.fn().mockResolvedValue(undefined);
|
|
124
|
+
return { runner: { query } as unknown as QueryRunner, query };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function executedSql(query: jest.Mock): Array<string> {
|
|
128
|
+
return query.mock.calls.map((call: Array<unknown>) => {
|
|
129
|
+
return String(call[0]);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
describe("AddHotQueryIndexesSecondPass: entity @Index() decorators", () => {
|
|
134
|
+
test.each(
|
|
135
|
+
HOT_INDEX_PAIRS.map(
|
|
136
|
+
([model, table, column]: [ModelClass, string, string]) => {
|
|
137
|
+
return [table, column, model] as [string, string, ModelClass];
|
|
138
|
+
},
|
|
139
|
+
),
|
|
140
|
+
)(
|
|
141
|
+
"%s.%s has a single-column index decorator",
|
|
142
|
+
(_table: string, column: string, model: ModelClass) => {
|
|
143
|
+
const singleColumnIndexes: Array<string> = getMetadataArgsStorage()
|
|
144
|
+
.indices.filter((index: IndexMetadataArgs) => {
|
|
145
|
+
return index.target === model;
|
|
146
|
+
})
|
|
147
|
+
.map((index: IndexMetadataArgs) => {
|
|
148
|
+
return resolveIndexColumns(index.columns);
|
|
149
|
+
})
|
|
150
|
+
.filter((columns: Array<string>) => {
|
|
151
|
+
return columns.length === 1;
|
|
152
|
+
})
|
|
153
|
+
.map((columns: Array<string>) => {
|
|
154
|
+
return columns[0] as string;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(singleColumnIndexes).toContain(column);
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("AddHotQueryIndexesSecondPass1785148065137 migration SQL contract", () => {
|
|
163
|
+
const migration: AddHotQueryIndexesSecondPass1785148065137 =
|
|
164
|
+
new AddHotQueryIndexesSecondPass1785148065137();
|
|
165
|
+
|
|
166
|
+
test("up() issues exactly 8 CREATE INDEX statements covering exactly the 8 hot columns — and nothing else", async () => {
|
|
167
|
+
const { runner, query } = makeQueryRunner();
|
|
168
|
+
await migration.up(runner);
|
|
169
|
+
|
|
170
|
+
const statements: Array<string> = executedSql(query);
|
|
171
|
+
expect(statements).toHaveLength(8);
|
|
172
|
+
|
|
173
|
+
/*
|
|
174
|
+
* A regenerated migration on a drifted schema would interleave ALTER
|
|
175
|
+
* TABLE / DROP statements with the index creates; none may sneak in.
|
|
176
|
+
*/
|
|
177
|
+
for (const sql of statements) {
|
|
178
|
+
expect(sql).not.toContain("ALTER TABLE");
|
|
179
|
+
expect(sql).not.toContain("DROP");
|
|
180
|
+
expect(sql).toMatch(CREATE_INDEX_REGEX);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const created: Array<CreateIndexStatement> = statements.map(
|
|
184
|
+
(sql: string) => {
|
|
185
|
+
const match: RegExpMatchArray = sql.match(
|
|
186
|
+
CREATE_INDEX_REGEX,
|
|
187
|
+
) as RegExpMatchArray;
|
|
188
|
+
return {
|
|
189
|
+
indexName: match[1] as string,
|
|
190
|
+
tableName: match[2] as string,
|
|
191
|
+
columnName: match[3] as string,
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const createdPairs: Array<string> = created
|
|
197
|
+
.map((statement: CreateIndexStatement) => {
|
|
198
|
+
return `"${statement.tableName}"."${statement.columnName}"`;
|
|
199
|
+
})
|
|
200
|
+
.sort();
|
|
201
|
+
const expectedPairs: Array<string> = HOT_INDEX_PAIRS.map(
|
|
202
|
+
([, table, column]: [ModelClass, string, string]) => {
|
|
203
|
+
return `"${table}"."${column}"`;
|
|
204
|
+
},
|
|
205
|
+
).sort();
|
|
206
|
+
|
|
207
|
+
expect(createdPairs).toEqual(expectedPairs);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("every index name matches TypeORM's deterministic sha1 derivation from (table, column)", async () => {
|
|
211
|
+
const { runner, query } = makeQueryRunner();
|
|
212
|
+
await migration.up(runner);
|
|
213
|
+
|
|
214
|
+
const statements: Array<string> = executedSql(query);
|
|
215
|
+
for (const sql of statements) {
|
|
216
|
+
const match: RegExpMatchArray = sql.match(
|
|
217
|
+
CREATE_INDEX_REGEX,
|
|
218
|
+
) as RegExpMatchArray;
|
|
219
|
+
const indexName: string = match[1] as string;
|
|
220
|
+
const tableName: string = match[2] as string;
|
|
221
|
+
const columnName: string = match[3] as string;
|
|
222
|
+
|
|
223
|
+
expect(indexName).toBe(typeormIndexName(tableName, columnName));
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("down() drops exactly the 8 indexes up() created", async () => {
|
|
228
|
+
const upRunner: { runner: QueryRunner; query: jest.Mock } =
|
|
229
|
+
makeQueryRunner();
|
|
230
|
+
await migration.up(upRunner.runner);
|
|
231
|
+
const createdNames: Array<string> = executedSql(upRunner.query).map(
|
|
232
|
+
(sql: string) => {
|
|
233
|
+
return (sql.match(CREATE_INDEX_REGEX) as RegExpMatchArray)[1] as string;
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const downRunner: { runner: QueryRunner; query: jest.Mock } =
|
|
238
|
+
makeQueryRunner();
|
|
239
|
+
await migration.down(downRunner.runner);
|
|
240
|
+
const statements: Array<string> = executedSql(downRunner.query);
|
|
241
|
+
expect(statements).toHaveLength(8);
|
|
242
|
+
|
|
243
|
+
const droppedNames: Array<string> = statements.map((sql: string) => {
|
|
244
|
+
expect(sql).toMatch(DROP_INDEX_REGEX);
|
|
245
|
+
return (sql.match(DROP_INDEX_REGEX) as RegExpMatchArray)[1] as string;
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect([...droppedNames].sort()).toEqual([...createdNames].sort());
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe("AddHotQueryIndexesSecondPass1785148065137 registration", () => {
|
|
253
|
+
test("is registered in SchemaMigrations/Index.ts so it actually runs on boot", () => {
|
|
254
|
+
expect(SchemaMigrations).toContain(
|
|
255
|
+
AddHotQueryIndexesSecondPass1785148065137,
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
@@ -206,7 +206,7 @@ describe("DatabaseService._updateBy — updateOneById with a model instance no l
|
|
|
206
206
|
isRoot: true,
|
|
207
207
|
},
|
|
208
208
|
}),
|
|
209
|
-
).resolves.
|
|
209
|
+
).resolves.toBe(1);
|
|
210
210
|
|
|
211
211
|
expect(findBySpy).toHaveBeenCalledTimes(1);
|
|
212
212
|
const findArgs: { select: Select<NetworkDeviceDiscoveryScan> } = (
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import NetworkDeviceDiscoveryScanService, {
|
|
2
|
+
Service as NetworkDeviceDiscoveryScanServiceClass,
|
|
3
|
+
} from "../../../Server/Services/NetworkDeviceDiscoveryScanService";
|
|
4
|
+
import DatabaseService from "../../../Server/Services/DatabaseService";
|
|
5
|
+
import NetworkDeviceDiscoveryScan from "../../../Models/DatabaseModels/NetworkDeviceDiscoveryScan";
|
|
6
|
+
import { describe, expect, test } from "@jest/globals";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* The /probe-ingest/probe/discovery-scan/list route hands the requesting
|
|
10
|
+
* probe its pending subnet scans and claims them (status "In Progress" +
|
|
11
|
+
* startedAt) via DatabaseService.updateColumnsByIdWithoutHooks — one raw
|
|
12
|
+
* parameterized UPDATE that skips ALL on-update hooks: workflow HTTP
|
|
13
|
+
* triggers, audit-log inserts, realtime events, service
|
|
14
|
+
* onBeforeUpdate/onUpdateSuccess. The probe synchronously waits on this
|
|
15
|
+
* route's response, and it sits behind the same probe-auth middleware whose
|
|
16
|
+
* hang got healthy probes flagged Disconnected — so the claim write must
|
|
17
|
+
* stay a single statement, not the full updateOneById pipeline (permission
|
|
18
|
+
* pre-fetch SELECT + row re-fetch + save() transaction).
|
|
19
|
+
*
|
|
20
|
+
* - App/FeatureSet/Telemetry/API/ProbeIngest/DiscoveryScan.ts
|
|
21
|
+
* NetworkDeviceDiscoveryScan.status/startedAt when a probe claims a
|
|
22
|
+
* Pending scan
|
|
23
|
+
*
|
|
24
|
+
* The conversion dropped NOTHING: the model declares no update workflow, no
|
|
25
|
+
* audit logging and no realtime events, and the service overrides neither
|
|
26
|
+
* update hook — so the old pipeline's hook stages were inert for this
|
|
27
|
+
* write. But the fast path skips hooks UNCONDITIONALLY: if someone later
|
|
28
|
+
* adds a decorator to NetworkDeviceDiscoveryScan or an update-hook override
|
|
29
|
+
* to its service, nothing at the call site fails — the new hook is just
|
|
30
|
+
* silently never fired for the claim write. This suite turns that silent
|
|
31
|
+
* drift into a loud test failure: if any assertion here starts failing, the
|
|
32
|
+
* hookless claim writes in DiscoveryScan.ts silently skip that new hook —
|
|
33
|
+
* revisit the call site before changing the assertion.
|
|
34
|
+
*
|
|
35
|
+
* Pure model-metadata + class-shape tests — no Postgres, no Redis.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
describe("discovery-scan claim hookless write safety preconditions", () => {
|
|
39
|
+
describe("NetworkDeviceDiscoveryScan model (claim write in DiscoveryScan.ts)", () => {
|
|
40
|
+
/*
|
|
41
|
+
* No @EnableWorkflow decorator AT ALL — the accessor is entirely unset,
|
|
42
|
+
* which resolves to "no update workflow". If this becomes defined,
|
|
43
|
+
* someone added @EnableWorkflow to NetworkDeviceDiscoveryScan and must
|
|
44
|
+
* decide whether the claim write should keep skipping it.
|
|
45
|
+
*/
|
|
46
|
+
test("has no @EnableWorkflow metadata at all", () => {
|
|
47
|
+
const scan: NetworkDeviceDiscoveryScan = new NetworkDeviceDiscoveryScan();
|
|
48
|
+
expect(scan.enableWorkflowOn).toBeFalsy();
|
|
49
|
+
expect(scan.enableWorkflowOn?.update).toBeFalsy();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("has no on-update audit log", () => {
|
|
53
|
+
const scan: NetworkDeviceDiscoveryScan = new NetworkDeviceDiscoveryScan();
|
|
54
|
+
expect(scan.enableAuditLogOn?.update).toBeFalsy();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("has no realtime events", () => {
|
|
58
|
+
const scan: NetworkDeviceDiscoveryScan = new NetworkDeviceDiscoveryScan();
|
|
59
|
+
expect(scan.enableRealtimeEventsOn).toBeFalsy();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("fast-path columns exist on the entity", () => {
|
|
64
|
+
/*
|
|
65
|
+
* updateColumnsByIdWithoutHooks validates column names against entity
|
|
66
|
+
* metadata at runtime and throws BadDataException on an unknown column.
|
|
67
|
+
* Pinning column existence here means a rename breaks this suite at CI
|
|
68
|
+
* time instead of leaving every claimed scan stuck in Pending at
|
|
69
|
+
* runtime.
|
|
70
|
+
*/
|
|
71
|
+
test("NetworkDeviceDiscoveryScan has every column the claim write stamps", () => {
|
|
72
|
+
const scan: NetworkDeviceDiscoveryScan = new NetworkDeviceDiscoveryScan();
|
|
73
|
+
const fastPathColumns: Array<string> = ["status", "startedAt"];
|
|
74
|
+
for (const column of fastPathColumns) {
|
|
75
|
+
expect(scan.isTableColumn(column)).toBe(true);
|
|
76
|
+
}
|
|
77
|
+
// Negative control: isTableColumn actually discriminates.
|
|
78
|
+
expect(scan.isTableColumn("notARealColumn")).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("service defines no update hooks of its own", () => {
|
|
83
|
+
/*
|
|
84
|
+
* DatabaseService's base onBeforeUpdate/onUpdateSuccess are no-op
|
|
85
|
+
* pass-throughs; a service only gets update behavior by OVERRIDING
|
|
86
|
+
* them. Own-property checks on the concrete service prototype pin that
|
|
87
|
+
* NetworkDeviceDiscoveryScanService does not — so the fast path skips
|
|
88
|
+
* nothing. If an override appears, these fail loudly and the claim
|
|
89
|
+
* write in DiscoveryScan.ts must be re-evaluated.
|
|
90
|
+
*/
|
|
91
|
+
const updateHooks: Array<string> = ["onBeforeUpdate", "onUpdateSuccess"];
|
|
92
|
+
|
|
93
|
+
test("NetworkDeviceDiscoveryScanService does not override onBeforeUpdate/onUpdateSuccess", () => {
|
|
94
|
+
for (const hook of updateHooks) {
|
|
95
|
+
expect(
|
|
96
|
+
Object.prototype.hasOwnProperty.call(
|
|
97
|
+
NetworkDeviceDiscoveryScanServiceClass.prototype,
|
|
98
|
+
hook,
|
|
99
|
+
),
|
|
100
|
+
).toBe(false);
|
|
101
|
+
}
|
|
102
|
+
// The default export is an instance of the class checked above.
|
|
103
|
+
expect(NetworkDeviceDiscoveryScanService).toBeInstanceOf(
|
|
104
|
+
NetworkDeviceDiscoveryScanServiceClass,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* Positive control: the hooks DO exist on the DatabaseService base
|
|
110
|
+
* prototype, so the own-property checks above cannot pass vacuously
|
|
111
|
+
* (e.g. after a rename of the hook methods themselves).
|
|
112
|
+
*/
|
|
113
|
+
test("the base DatabaseService prototype defines both hooks", () => {
|
|
114
|
+
for (const hook of updateHooks) {
|
|
115
|
+
expect(
|
|
116
|
+
Object.prototype.hasOwnProperty.call(DatabaseService.prototype, hook),
|
|
117
|
+
).toBe(true);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import MonitorProbeService from "../../../Server/Services/MonitorProbeService";
|
|
2
|
+
import MonitorService from "../../../Server/Services/MonitorService";
|
|
3
|
+
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
4
|
+
import MonitorProbe from "../../../Models/DatabaseModels/MonitorProbe";
|
|
5
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
6
|
+
import UpdateBy from "../../../Server/Types/Database/UpdateBy";
|
|
7
|
+
import { OnUpdate } from "../../../Server/Types/Database/Hooks";
|
|
8
|
+
import { describe, expect, test, afterEach } from "@jest/globals";
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* Four hot bookkeeping writes were moved off the full updateOneById/
|
|
12
|
+
* updateOneBy pipeline onto DatabaseService.updateColumnsByIdWithoutHooks
|
|
13
|
+
* (one raw parameterized UPDATE; skips ALL on-update hooks — workflow HTTP
|
|
14
|
+
* triggers, audit-log inserts, realtime events, service onUpdateSuccess):
|
|
15
|
+
*
|
|
16
|
+
* - Common/Server/Utils/Monitor/MonitorResource.ts
|
|
17
|
+
* MonitorProbe.lastMonitoringLog, on every probe check result
|
|
18
|
+
* - App/FeatureSet/Workers/Jobs/IncomingRequestMonitor/CheckHeartbeat.ts
|
|
19
|
+
* Monitor.incomingRequestMonitorHeartbeatCheckedAt
|
|
20
|
+
* - App/FeatureSet/Workers/Jobs/IncomingEmailMonitor/CheckOnlineStatus.ts
|
|
21
|
+
* Monitor.incomingEmailMonitorHeartbeatCheckedAt
|
|
22
|
+
* - App/FeatureSet/Workers/Jobs/TelemetryMonitor/MonitorTelemetryMonitor.ts
|
|
23
|
+
* Monitor.telemetryMonitorLastMonitorAt + telemetryMonitorNextMonitorAt
|
|
24
|
+
*
|
|
25
|
+
* Those conversions are behavior-preserving only under the model-decorator
|
|
26
|
+
* preconditions pinned below. The fast path skips hooks UNCONDITIONALLY, so
|
|
27
|
+
* if someone later adds a decorator (or a column rename breaks a fast-path
|
|
28
|
+
* write), nothing at the call sites fails — the new hook is just silently
|
|
29
|
+
* never fired for these writes. This suite turns that silent drift into a
|
|
30
|
+
* loud test failure: when one of these assertions starts failing, revisit
|
|
31
|
+
* the corresponding fast-path call site before changing the assertion.
|
|
32
|
+
*
|
|
33
|
+
* Pure model-metadata + mocked-service tests — no Postgres, no Redis.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
describe("heartbeat write fast-path safety preconditions", () => {
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
jest.restoreAllMocks();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("MonitorProbe (lastMonitoringLog write in MonitorResource.ts)", () => {
|
|
42
|
+
/*
|
|
43
|
+
* The MonitorProbe conversion dropped NOTHING: the model has no
|
|
44
|
+
* @EnableWorkflow, no @EnableAuditLog and no realtime events, so the
|
|
45
|
+
* old updateOneBy pipeline's hooks were inert for this row anyway.
|
|
46
|
+
* If any assertion here starts failing, a decorator was added to
|
|
47
|
+
* MonitorProbe and the fast-path write in MonitorResource.ts now
|
|
48
|
+
* silently skips it — revisit that call site.
|
|
49
|
+
*/
|
|
50
|
+
test("has no on-update workflow trigger", () => {
|
|
51
|
+
const probe: MonitorProbe = new MonitorProbe();
|
|
52
|
+
expect(probe.enableWorkflowOn?.update).toBeFalsy();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("has no on-update audit log", () => {
|
|
56
|
+
const probe: MonitorProbe = new MonitorProbe();
|
|
57
|
+
expect(probe.enableAuditLogOn?.update).toBeFalsy();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("has no realtime events", () => {
|
|
61
|
+
const probe: MonitorProbe = new MonitorProbe();
|
|
62
|
+
expect(probe.enableRealtimeEventsOn).toBeFalsy();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("Monitor (heartbeat/scheduler timestamp writes in worker jobs)", () => {
|
|
67
|
+
/*
|
|
68
|
+
* Monitor DOES have @EnableWorkflow({ update: true }) and
|
|
69
|
+
* @EnableAuditLog(). The heartbeat/scheduler conversions DELIBERATELY
|
|
70
|
+
* skip both for these machine-stamped bookkeeping columns — firing a
|
|
71
|
+
* workflow HTTP trigger plus an audit-log insert per monitor per 30s/60s
|
|
72
|
+
* tick is exactly the load the fast path removes (documented precedent:
|
|
73
|
+
* the lastMonitoringLog write in MonitorResource.ts). These assertions
|
|
74
|
+
* pin that tradeoff so it stays a documented decision: if Monitor's
|
|
75
|
+
* decorators change shape, re-evaluate the worker-job call sites rather
|
|
76
|
+
* than assuming the skip is still the intended behavior.
|
|
77
|
+
*/
|
|
78
|
+
test("still has on-update workflow enabled (the skip is deliberate)", () => {
|
|
79
|
+
const monitor: Monitor = new Monitor();
|
|
80
|
+
expect(monitor.enableWorkflowOn?.update).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("still has on-update audit log enabled (the skip is deliberate)", () => {
|
|
84
|
+
const monitor: Monitor = new Monitor();
|
|
85
|
+
expect(monitor.enableAuditLogOn?.update).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
* Nothing ELSE was dropped: Monitor has no realtime events, so
|
|
90
|
+
* workflow + audit are the only hooks the fast path bypasses. If this
|
|
91
|
+
* starts failing, realtime updates on Monitor rows would silently never
|
|
92
|
+
* fire for the heartbeat writes — revisit the worker-job call sites.
|
|
93
|
+
*/
|
|
94
|
+
test("has no realtime events (so only workflow+audit are skipped)", () => {
|
|
95
|
+
const monitor: Monitor = new Monitor();
|
|
96
|
+
expect(monitor.enableRealtimeEventsOn).toBeFalsy();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("fast-path columns exist on their entities", () => {
|
|
101
|
+
/*
|
|
102
|
+
* updateColumnsByIdWithoutHooks validates column names against entity
|
|
103
|
+
* metadata at runtime and throws BadDataException on an unknown column.
|
|
104
|
+
* Pinning column existence here means a rename breaks this suite at CI
|
|
105
|
+
* time instead of breaking every heartbeat write at runtime.
|
|
106
|
+
*/
|
|
107
|
+
test("Monitor has every column the worker jobs stamp", () => {
|
|
108
|
+
const monitor: Monitor = new Monitor();
|
|
109
|
+
const fastPathColumns: Array<string> = [
|
|
110
|
+
"incomingRequestMonitorHeartbeatCheckedAt",
|
|
111
|
+
"incomingEmailMonitorHeartbeatCheckedAt",
|
|
112
|
+
"telemetryMonitorLastMonitorAt",
|
|
113
|
+
"telemetryMonitorNextMonitorAt",
|
|
114
|
+
];
|
|
115
|
+
for (const column of fastPathColumns) {
|
|
116
|
+
expect(monitor.isTableColumn(column)).toBe(true);
|
|
117
|
+
}
|
|
118
|
+
// Negative control: isTableColumn actually discriminates.
|
|
119
|
+
expect(monitor.isTableColumn("notARealColumn")).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("MonitorProbe has the lastMonitoringLog column", () => {
|
|
123
|
+
const probe: MonitorProbe = new MonitorProbe();
|
|
124
|
+
expect(probe.isTableColumn("lastMonitoringLog")).toBe(true);
|
|
125
|
+
expect(probe.isTableColumn("notARealColumn")).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("MonitorProbeService.onUpdateSuccess early-exit", () => {
|
|
130
|
+
/*
|
|
131
|
+
* The old updateOneBy path for lastMonitoringLog also ran
|
|
132
|
+
* MonitorProbeService.onUpdateSuccess, but that hook early-exits unless
|
|
133
|
+
* updateBy.data.isEnabled is set — so skipping it for a
|
|
134
|
+
* lastMonitoringLog-only write changes nothing. If the early-exit
|
|
135
|
+
* condition ever widens (e.g. the hook starts reacting to other
|
|
136
|
+
* columns), the no-op test below fails and the MonitorResource.ts
|
|
137
|
+
* fast-path write must be revisited.
|
|
138
|
+
*/
|
|
139
|
+
test("is a no-op when data lacks isEnabled (the fast-path case)", async () => {
|
|
140
|
+
const findBySpy: jest.SpyInstance = jest
|
|
141
|
+
.spyOn(MonitorProbeService, "findBy")
|
|
142
|
+
.mockResolvedValue([]);
|
|
143
|
+
const refreshSpy: jest.SpyInstance = jest
|
|
144
|
+
.spyOn(MonitorService, "refreshMonitorProbeStatus")
|
|
145
|
+
.mockResolvedValue(undefined as never);
|
|
146
|
+
|
|
147
|
+
const onUpdate: OnUpdate<MonitorProbe> = {
|
|
148
|
+
updateBy: {
|
|
149
|
+
query: {},
|
|
150
|
+
// What the old pipeline would have passed for the converted write.
|
|
151
|
+
data: { lastMonitoringLog: {} },
|
|
152
|
+
props: { isRoot: true },
|
|
153
|
+
} as UpdateBy<MonitorProbe>,
|
|
154
|
+
carryForward: null,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const result: OnUpdate<MonitorProbe> = await (MonitorProbeService as any)[
|
|
158
|
+
"onUpdateSuccess"
|
|
159
|
+
](onUpdate, [ObjectID.generate()]);
|
|
160
|
+
|
|
161
|
+
expect(result).toBe(onUpdate);
|
|
162
|
+
expect(findBySpy).not.toHaveBeenCalled();
|
|
163
|
+
expect(refreshSpy).not.toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/*
|
|
167
|
+
* Positive control: proves the spies above would have observed the hook
|
|
168
|
+
* doing work, so the no-op test cannot pass vacuously.
|
|
169
|
+
*/
|
|
170
|
+
test("does refresh monitor status when isEnabled IS in data", async () => {
|
|
171
|
+
const monitorId: ObjectID = ObjectID.generate();
|
|
172
|
+
const probeRow: MonitorProbe = new MonitorProbe();
|
|
173
|
+
probeRow.monitorId = monitorId;
|
|
174
|
+
|
|
175
|
+
const findBySpy: jest.SpyInstance = jest
|
|
176
|
+
.spyOn(MonitorProbeService, "findBy")
|
|
177
|
+
.mockResolvedValue([probeRow]);
|
|
178
|
+
const refreshSpy: jest.SpyInstance = jest
|
|
179
|
+
.spyOn(MonitorService, "refreshMonitorProbeStatus")
|
|
180
|
+
.mockResolvedValue(undefined as never);
|
|
181
|
+
|
|
182
|
+
const onUpdate: OnUpdate<MonitorProbe> = {
|
|
183
|
+
updateBy: {
|
|
184
|
+
query: {},
|
|
185
|
+
data: { isEnabled: false },
|
|
186
|
+
props: { isRoot: true },
|
|
187
|
+
} as UpdateBy<MonitorProbe>,
|
|
188
|
+
carryForward: null,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
await (MonitorProbeService as any)["onUpdateSuccess"](onUpdate, [
|
|
192
|
+
ObjectID.generate(),
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
expect(findBySpy).toHaveBeenCalledTimes(1);
|
|
196
|
+
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
|
197
|
+
expect(refreshSpy).toHaveBeenCalledWith(monitorId);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|