@oneuptime/common 12.0.23 → 12.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/DetectionRule.ts +55 -0
- package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +27 -7
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +67 -0
- package/Models/DatabaseModels/Project.ts +68 -0
- package/Models/DatabaseModels/User.ts +68 -0
- package/Server/API/TelemetryAPI.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.ts +28 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/GzipRequestBody.ts +311 -0
- package/Server/Middleware/MultipartFormData.ts +87 -3
- package/Server/Services/DetectionRuleService.ts +73 -6
- package/Server/Services/Index.ts +205 -0
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +59 -0
- package/Server/Services/NetworkDeviceService.ts +148 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
- package/Server/Services/OnCallDutyPolicyFeedService.ts +1 -1
- package/Server/Services/ProjectService.ts +29 -19
- package/Server/Services/SecurityEventService.ts +90 -13
- package/Server/Services/StatusPageResourceService.ts +223 -0
- package/Server/Services/TelemetryAttributeService.ts +17 -0
- package/Server/Services/UserService.ts +4 -6
- package/Server/Types/Markdown.ts +161 -67
- package/Server/Utils/AI/Toolbox/OnCallTools.ts +12 -4
- package/Server/Utils/AnalyticsDatabase/Statement.ts +13 -1
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +297 -6
- package/Server/Utils/Attribution.ts +4 -0
- package/Server/Utils/Marketing/MarketingEventUtil.ts +45 -7
- package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +55 -3
- package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +21 -0
- package/Server/Utils/StartServer.ts +12 -32
- package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +101 -4
- package/Server/Utils/Telemetry/LlmMetricSpend.ts +130 -0
- package/Server/Views/Partials/AnalyticsConsent.ejs +159 -102
- package/Server/Views/Partials/Head.ejs +2 -1
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +776 -38
- package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +359 -0
- package/Tests/App/Dashboard/CorrelateGraph.test.tsx +865 -0
- package/Tests/App/Dashboard/CorrelationGraph.test.ts +346 -0
- package/Tests/App/Dashboard/CriteriaFilterConditionDropdown.test.tsx +256 -0
- package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +738 -0
- package/Tests/App/Dashboard/CriteriaFilterMonitorTypeChange.test.ts +1122 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +283 -0
- package/Tests/App/Dashboard/ExternalStatusPageCriteriaFilter.test.ts +407 -0
- package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +5 -14
- package/Tests/App/Dashboard/MonitorCriteriaActionToggles.test.tsx +426 -0
- package/Tests/App/Dashboard/MonitorStepsCriteriaAlignmentWiring.test.tsx +352 -0
- package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +278 -0
- package/Tests/App/Dashboard/OnCallLayerShiftPreviewOverrides.test.ts +237 -0
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +2 -2
- package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +460 -0
- package/Tests/App/Dashboard/SecurityEventAttributeUtil.test.ts +171 -0
- package/Tests/App/Dashboard/SecurityEventCorrelation.test.ts +857 -0
- package/Tests/App/Dashboard/SecurityEventDetailObservables.test.tsx +194 -0
- package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +124 -0
- package/Tests/App/Dashboard/SecurityEventsMonitorStepForm.test.tsx +79 -19
- package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +382 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +216 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +590 -11
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +757 -0
- package/Tests/Models/DiscoveryScanNameColumn.test.ts +293 -0
- package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +564 -0
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +120 -4
- package/Tests/Server/Middleware/GzipRequestBody.test.ts +725 -0
- package/Tests/Server/Middleware/MultipartFormData.test.ts +298 -0
- package/Tests/Server/Services/AddDetectionRuleDistinctCountColumnsMigration.test.ts +311 -0
- package/Tests/Server/Services/DetectionRuleService.test.ts +134 -2
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +7 -1
- package/Tests/Server/Services/FeedRetentionConsistency.test.ts +131 -0
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +194 -0
- package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +800 -0
- package/Tests/Server/Services/OnCallScheduleAttachRosterRefresh.test.ts +244 -0
- package/Tests/Server/Services/SecurityEventService.test.ts +376 -0
- package/Tests/Server/Services/StatusPageResourceDuplicates.test.ts +703 -0
- package/Tests/Server/Services/TelemetryAttributeServiceSecurityEvent.test.ts +256 -0
- package/Tests/Server/Types/Workflow/Components/ComponentRegistryParity.test.ts +417 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWhereOperators.test.ts +677 -0
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +43 -0
- package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +15 -11
- package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +369 -8
- package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +35 -0
- package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +368 -4
- package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +245 -0
- package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +48 -5
- package/Tests/Types/Code/YamlSyntax.test.ts +396 -0
- package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +957 -0
- package/Tests/Types/Monitor/MonitorCriteriaActionValidation.test.ts +598 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +49 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +6 -6
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +4 -4
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +4 -3
- package/Tests/Types/Telemetry/LlmMetricConventions.test.ts +193 -0
- package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +0 -12
- package/Tests/UI/Components/CardModelDetailEdit.test.tsx +1 -17
- package/Tests/UI/Components/CodeEditor.test.tsx +474 -10
- package/Tests/UI/Components/Detail/YamlField.test.tsx +105 -0
- package/Tests/UI/Components/EntityDropdownLabelsBulkAdd.test.tsx +319 -0
- package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +231 -0
- package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +1 -0
- package/Tests/UI/Components/Forms/ValidationYAML.test.ts +234 -0
- package/Tests/UI/Components/ModelTable/AttributeColumns.test.tsx +573 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableAttributeColumns.test.tsx +824 -0
- package/Tests/UI/Components/ModelTable/ColumnCustomizationModalAddableColumns.test.tsx +707 -0
- package/Tests/UI/Components/SideOver.test.tsx +1 -1
- package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +294 -0
- package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +436 -0
- package/Tests/UI/Components/TableBulkCsvExport.test.tsx +6 -2
- package/Tests/UI/Components/Toggle.test.tsx +25 -0
- package/Tests/UI/Components/YamlEditor.test.tsx +847 -0
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +252 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +221 -0
- package/Tests/Utils/NetworkDiscovery/ScanNameUtil.test.ts +312 -0
- package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +468 -0
- package/Tests/Utils/Telemetry/LlmMetricQuery.test.ts +472 -0
- package/Types/Code/YamlSyntax.ts +396 -0
- package/Types/Exception/ExceptionCode.ts +1 -0
- package/Types/Exception/PayloadTooLargeException.ts +8 -0
- package/Types/Marketing/Attribution.ts +10 -0
- package/Types/Marketing/MarketingEvent.ts +4 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +116 -56
- package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +2 -1
- package/Types/SecurityEvent/DetectionFindingConstants.ts +11 -0
- package/Types/Telemetry/LlmMetricConventions.ts +124 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +3 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +10 -1
- package/UI/Components/CodeEditor/CodeEditor.tsx +241 -79
- package/UI/Components/CodeEditor/YamlEditor.tsx +369 -0
- package/UI/Components/Detail/Detail.tsx +10 -0
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +33 -0
- package/UI/Components/Forms/Fields/FormField.tsx +31 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
- package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +2 -0
- package/UI/Components/Forms/Validation.ts +46 -0
- package/UI/Components/Input/Input.tsx +3 -0
- package/UI/Components/ModelTable/AttributeColumns.tsx +321 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +263 -14
- package/UI/Components/ModelTable/Column.ts +8 -0
- package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +310 -0
- package/UI/Components/ModelTable/ColumnPreference.ts +13 -1
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +2 -2
- package/UI/Components/SideOver/SideOver.tsx +5 -5
- package/UI/Components/StatusPage/ResourceGrid.tsx +82 -2
- package/UI/Components/StatusPage/ResourceList.tsx +197 -38
- package/UI/Components/Toggle/Toggle.tsx +9 -1
- package/UI/Components/Types/FieldType.ts +1 -0
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +73 -1
- package/Utils/NetworkDiscovery/ScanNameUtil.ts +181 -0
- package/Utils/StatusPage/ResourceExplorer.ts +258 -0
- package/Utils/Telemetry/LlmMetricQuery.ts +204 -0
- package/build/dist/Models/DatabaseModels/DetectionRule.js +57 -0
- package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +27 -7
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +69 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +76 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +76 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +17 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js +21 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/GzipRequestBody.js +224 -0
- package/build/dist/Server/Middleware/GzipRequestBody.js.map +1 -0
- package/build/dist/Server/Middleware/MultipartFormData.js +71 -2
- package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
- package/build/dist/Server/Services/DetectionRuleService.js +49 -4
- package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +204 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +50 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +124 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +68 -13
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +25 -45
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/SecurityEventService.js +57 -5
- package/build/dist/Server/Services/SecurityEventService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageResourceService.js +140 -0
- package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryAttributeService.js +17 -0
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +2 -12
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +147 -61
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +12 -4
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +12 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +246 -9
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/Attribution.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +27 -7
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +42 -7
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +18 -0
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +12 -21
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +76 -5
- package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js +102 -0
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -0
- package/build/dist/Types/Code/YamlSyntax.js +212 -0
- package/build/dist/Types/Code/YamlSyntax.js.map +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
- package/build/dist/Types/Exception/PayloadTooLargeException.js +8 -0
- package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -0
- package/build/dist/Types/Marketing/Attribution.js +10 -0
- package/build/dist/Types/Marketing/Attribution.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +112 -51
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +2 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
- package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +9 -0
- package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
- package/build/dist/Types/Telemetry/LlmMetricConventions.js +98 -0
- package/build/dist/Types/Telemetry/LlmMetricConventions.js.map +1 -0
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +5 -2
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +206 -76
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js +201 -0
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -0
- package/build/dist/UI/Components/Detail/Detail.js +9 -0
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +16 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +14 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
- package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
- package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
- package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +2 -0
- package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +27 -0
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/AttributeColumns.js +159 -0
- package/build/dist/UI/Components/ModelTable/AttributeColumns.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +179 -17
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +133 -2
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js +7 -1
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +2 -2
- package/build/dist/UI/Components/SideOver/SideOver.js +5 -5
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +28 -2
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceList.js +87 -20
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
- package/build/dist/UI/Components/Toggle/Toggle.js +9 -1
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Types/FieldType.js +1 -0
- package/build/dist/UI/Components/Types/FieldType.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +37 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js +120 -0
- package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js.map +1 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js +171 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -1
- package/build/dist/Utils/Telemetry/LlmMetricQuery.js +119 -0
- package/build/dist/Utils/Telemetry/LlmMetricQuery.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +2 -1
- package/Tests/UI/Components/Toast.test.tsx +0 -197
- package/Tests/UI/Components/ToastStacking.test.tsx +0 -198
- package/UI/Components/Toast/Toast.tsx +0 -189
- package/UI/Components/Toast/ToastInit.tsx +0 -110
- package/build/dist/UI/Components/Toast/Toast.js +0 -123
- package/build/dist/UI/Components/Toast/Toast.js.map +0 -1
- package/build/dist/UI/Components/Toast/ToastInit.js +0 -53
- package/build/dist/UI/Components/Toast/ToastInit.js.map +0 -1
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
import StatusPageResourceService from "../../../Server/Services/StatusPageResourceService";
|
|
2
|
+
import CreateBy from "../../../Server/Types/Database/CreateBy";
|
|
3
|
+
import UpdateBy from "../../../Server/Types/Database/UpdateBy";
|
|
4
|
+
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
5
|
+
import MonitorGroup from "../../../Models/DatabaseModels/MonitorGroup";
|
|
6
|
+
import StatusPageResource from "../../../Models/DatabaseModels/StatusPageResource";
|
|
7
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
8
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
9
|
+
import PositiveNumber from "../../../Types/PositiveNumber";
|
|
10
|
+
import { afterEach, describe, expect, it, jest } from "@jest/globals";
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* Contract under test - a status page lists a monitor once.
|
|
14
|
+
*
|
|
15
|
+
* Nothing used to stop a second resource being created for a monitor that was
|
|
16
|
+
* already on the page. Selecting monitors by label re-selects every monitor
|
|
17
|
+
* carrying that label, so re-adding a label after one new monitor joined it
|
|
18
|
+
* wrote a duplicate resource for every monitor already there, and the public
|
|
19
|
+
* page listed each of them twice (issue #3420).
|
|
20
|
+
*
|
|
21
|
+
* The rule engine has always refused to add a monitor that is already on the
|
|
22
|
+
* page for exactly this reason. These cases pin the same promise onto the
|
|
23
|
+
* service, which is the one thing every other way of creating a resource -
|
|
24
|
+
* the resource form, the bulk add modal, the API - has to go through.
|
|
25
|
+
*
|
|
26
|
+
* Two properties matter and both are asserted from each write path:
|
|
27
|
+
*
|
|
28
|
+
* 1. the duplicate is judged per STATUS PAGE, not per group: a monitor in
|
|
29
|
+
* two groups is still a monitor a visitor sees twice;
|
|
30
|
+
*
|
|
31
|
+
* 2. the resource being edited is never a duplicate of itself, or saving
|
|
32
|
+
* the edit form without changing the monitor would start failing.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const STATUS_PAGE_ID: ObjectID = new ObjectID(
|
|
36
|
+
"11111111-1111-4111-8111-111111111111",
|
|
37
|
+
);
|
|
38
|
+
const OTHER_STATUS_PAGE_ID: ObjectID = new ObjectID(
|
|
39
|
+
"22222222-2222-4222-8222-222222222222",
|
|
40
|
+
);
|
|
41
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
42
|
+
"33333333-3333-4333-8333-333333333333",
|
|
43
|
+
);
|
|
44
|
+
const GROUP_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
45
|
+
const OTHER_GROUP_ID: ObjectID = new ObjectID(
|
|
46
|
+
"55555555-5555-4555-8555-555555555555",
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const MONITOR_ID: ObjectID = new ObjectID(
|
|
50
|
+
"66666666-6666-4666-8666-666666666666",
|
|
51
|
+
);
|
|
52
|
+
const OTHER_MONITOR_ID: ObjectID = new ObjectID(
|
|
53
|
+
"77777777-7777-4777-8777-777777777777",
|
|
54
|
+
);
|
|
55
|
+
const MONITOR_GROUP_ID: ObjectID = new ObjectID(
|
|
56
|
+
"88888888-8888-4888-8888-888888888888",
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const DUPLICATE_MONITOR_MESSAGE: string =
|
|
60
|
+
"This monitor is already added to this status page. A monitor can only be added once so it is not shown twice to your customers.";
|
|
61
|
+
const DUPLICATE_MONITOR_GROUP_MESSAGE: string =
|
|
62
|
+
"This monitor group is already added to this status page. A monitor group can only be added once so it is not shown twice to your customers.";
|
|
63
|
+
|
|
64
|
+
function resourceId(index: number): ObjectID {
|
|
65
|
+
const suffix: string = index.toString().padStart(12, "0");
|
|
66
|
+
return new ObjectID(`aaaaaaaa-aaaa-4aaa-8aaa-${suffix}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function makeResource(data: {
|
|
70
|
+
id: ObjectID;
|
|
71
|
+
statusPageId?: ObjectID | undefined;
|
|
72
|
+
statusPageGroupId?: ObjectID | undefined;
|
|
73
|
+
monitorId?: ObjectID | undefined;
|
|
74
|
+
monitorGroupId?: ObjectID | undefined;
|
|
75
|
+
}): StatusPageResource {
|
|
76
|
+
const resource: StatusPageResource = new StatusPageResource();
|
|
77
|
+
resource._id = data.id.toString();
|
|
78
|
+
resource.projectId = PROJECT_ID;
|
|
79
|
+
resource.statusPageId = data.statusPageId || STATUS_PAGE_ID;
|
|
80
|
+
|
|
81
|
+
if (data.statusPageGroupId) {
|
|
82
|
+
resource.statusPageGroupId = data.statusPageGroupId;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (data.monitorId) {
|
|
86
|
+
resource.monitorId = data.monitorId;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (data.monitorGroupId) {
|
|
90
|
+
resource.monitorGroupId = data.monitorGroupId;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return resource;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/*
|
|
97
|
+
* `QueryHelper.notEquals` is a TypeORM Raw operator: the id it excludes lives
|
|
98
|
+
* in the bound parameter bag rather than on the query object, so the fake
|
|
99
|
+
* reads it back out the same way QueryHelper's own tests do.
|
|
100
|
+
*/
|
|
101
|
+
function getExcludedId(value: unknown): string | null {
|
|
102
|
+
const parameters: Record<string, unknown> | undefined = (
|
|
103
|
+
value as { objectLiteralParameters?: Record<string, unknown> } | undefined
|
|
104
|
+
)?.objectLiteralParameters;
|
|
105
|
+
|
|
106
|
+
if (!parameters) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const values: Array<unknown> = Object.values(parameters);
|
|
111
|
+
|
|
112
|
+
return values.length > 0 ? String(values[0]) : null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getDirectId(value: unknown): string | null {
|
|
116
|
+
if (typeof value === "string") {
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (value instanceof ObjectID) {
|
|
121
|
+
return value.toString();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/*
|
|
128
|
+
* Stands in for the resources table. findOneBy answers the two reads the hooks
|
|
129
|
+
* make - "is this monitor already on the page" and "which resource is being
|
|
130
|
+
* updated" - and the order bookkeeping is stubbed so these cases only exercise
|
|
131
|
+
* the duplicate rules.
|
|
132
|
+
*/
|
|
133
|
+
function mockService(rows: Array<StatusPageResource>): {
|
|
134
|
+
findOneBy: jest.SpiedFunction<typeof StatusPageResourceService.findOneBy>;
|
|
135
|
+
countBy: jest.SpiedFunction<typeof StatusPageResourceService.countBy>;
|
|
136
|
+
updateOneBy: jest.SpiedFunction<typeof StatusPageResourceService.updateOneBy>;
|
|
137
|
+
} {
|
|
138
|
+
const findOneBy: any = jest
|
|
139
|
+
.spyOn(StatusPageResourceService, "findOneBy")
|
|
140
|
+
.mockImplementation(async (findBy: any) => {
|
|
141
|
+
const query: any = findBy.query || {};
|
|
142
|
+
const directId: string | null = getDirectId(query._id);
|
|
143
|
+
const excludedId: string | null = getExcludedId(query._id);
|
|
144
|
+
|
|
145
|
+
const match: StatusPageResource | undefined = rows.find(
|
|
146
|
+
(row: StatusPageResource) => {
|
|
147
|
+
if (directId && row._id !== directId) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (excludedId && row._id === excludedId) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (
|
|
156
|
+
query.statusPageId &&
|
|
157
|
+
row.statusPageId?.toString() !== query.statusPageId.toString()
|
|
158
|
+
) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
query.monitorId &&
|
|
164
|
+
row.monitorId?.toString() !== query.monitorId.toString()
|
|
165
|
+
) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (
|
|
170
|
+
query.monitorGroupId &&
|
|
171
|
+
row.monitorGroupId?.toString() !== query.monitorGroupId.toString()
|
|
172
|
+
) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return true;
|
|
177
|
+
},
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return match || null;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const countBy: any = jest
|
|
184
|
+
.spyOn(StatusPageResourceService, "countBy")
|
|
185
|
+
.mockResolvedValue(new PositiveNumber(rows.length) as never);
|
|
186
|
+
|
|
187
|
+
jest
|
|
188
|
+
.spyOn(StatusPageResourceService, "findBy")
|
|
189
|
+
.mockResolvedValue([] as never);
|
|
190
|
+
|
|
191
|
+
const updateOneBy: any = jest
|
|
192
|
+
.spyOn(StatusPageResourceService, "updateOneBy")
|
|
193
|
+
.mockResolvedValue(1 as never);
|
|
194
|
+
|
|
195
|
+
return { findOneBy, countBy, updateOneBy };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function createBy(data: {
|
|
199
|
+
statusPageId?: ObjectID | undefined;
|
|
200
|
+
omitStatusPageId?: boolean | undefined;
|
|
201
|
+
statusPageGroupId?: ObjectID | undefined;
|
|
202
|
+
monitorId?: ObjectID | undefined;
|
|
203
|
+
monitor?: ObjectID | undefined;
|
|
204
|
+
monitorGroupId?: ObjectID | undefined;
|
|
205
|
+
monitorGroup?: ObjectID | undefined;
|
|
206
|
+
}): CreateBy<StatusPageResource> {
|
|
207
|
+
const resource: StatusPageResource = new StatusPageResource();
|
|
208
|
+
resource.projectId = PROJECT_ID;
|
|
209
|
+
resource.displayName = "New Resource";
|
|
210
|
+
|
|
211
|
+
if (!data.omitStatusPageId) {
|
|
212
|
+
resource.statusPageId = data.statusPageId || STATUS_PAGE_ID;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (data.statusPageGroupId) {
|
|
216
|
+
resource.statusPageGroupId = data.statusPageGroupId;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (data.monitorId) {
|
|
220
|
+
resource.monitorId = data.monitorId;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (data.monitor) {
|
|
224
|
+
const monitor: Monitor = new Monitor();
|
|
225
|
+
monitor._id = data.monitor.toString();
|
|
226
|
+
resource.monitor = monitor;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (data.monitorGroupId) {
|
|
230
|
+
resource.monitorGroupId = data.monitorGroupId;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (data.monitorGroup) {
|
|
234
|
+
const monitorGroup: MonitorGroup = new MonitorGroup();
|
|
235
|
+
monitorGroup._id = data.monitorGroup.toString();
|
|
236
|
+
resource.monitorGroup = monitorGroup;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
data: resource,
|
|
241
|
+
props: { isRoot: true },
|
|
242
|
+
} as CreateBy<StatusPageResource>;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/*
|
|
246
|
+
* The update body is NOT a model. BaseAPI.updateItem builds it with
|
|
247
|
+
* JSONFunctions.deserialize, which revives ObjectID values but never nested
|
|
248
|
+
* models - so a relation the dashboard posts arrives as the plain
|
|
249
|
+
* `{ _id: "<uuid>" }` the browser sent, with no `id` on it. Building these
|
|
250
|
+
* fixtures as `new Monitor()` instead is what let an earlier version of the
|
|
251
|
+
* guard read nothing on every edit-form save and still pass its tests.
|
|
252
|
+
*/
|
|
253
|
+
function updateBy(data: {
|
|
254
|
+
id?: ObjectID | undefined;
|
|
255
|
+
monitorId?: ObjectID | undefined;
|
|
256
|
+
monitor?: ObjectID | undefined;
|
|
257
|
+
monitorGroupId?: ObjectID | undefined;
|
|
258
|
+
monitorGroup?: ObjectID | undefined;
|
|
259
|
+
displayName?: string | undefined;
|
|
260
|
+
}): UpdateBy<StatusPageResource> {
|
|
261
|
+
const updateData: Record<string, unknown> = {};
|
|
262
|
+
|
|
263
|
+
if (data.monitorId) {
|
|
264
|
+
updateData["monitorId"] = data.monitorId;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (data.monitor) {
|
|
268
|
+
updateData["monitor"] = { _id: data.monitor.toString() };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (data.monitorGroupId) {
|
|
272
|
+
updateData["monitorGroupId"] = data.monitorGroupId;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (data.monitorGroup) {
|
|
276
|
+
updateData["monitorGroup"] = { _id: data.monitorGroup.toString() };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (data.displayName) {
|
|
280
|
+
updateData["displayName"] = data.displayName;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
query: data.id ? { _id: data.id.toString() } : {},
|
|
285
|
+
data: updateData,
|
|
286
|
+
props: { isRoot: true },
|
|
287
|
+
} as unknown as UpdateBy<StatusPageResource>;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
type OnBeforeCreateFunction = (
|
|
291
|
+
createBy: CreateBy<StatusPageResource>,
|
|
292
|
+
) => Promise<unknown>;
|
|
293
|
+
|
|
294
|
+
const onBeforeCreate: OnBeforeCreateFunction = (
|
|
295
|
+
by: CreateBy<StatusPageResource>,
|
|
296
|
+
): Promise<unknown> => {
|
|
297
|
+
return (StatusPageResourceService as any).onBeforeCreate(by);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
type OnBeforeUpdateFunction = (
|
|
301
|
+
updateBy: UpdateBy<StatusPageResource>,
|
|
302
|
+
) => Promise<unknown>;
|
|
303
|
+
|
|
304
|
+
const onBeforeUpdate: OnBeforeUpdateFunction = (
|
|
305
|
+
by: UpdateBy<StatusPageResource>,
|
|
306
|
+
): Promise<unknown> => {
|
|
307
|
+
return (StatusPageResourceService as any).onBeforeUpdate(by);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
describe("StatusPageResourceService duplicate rules", () => {
|
|
311
|
+
afterEach(() => {
|
|
312
|
+
jest.restoreAllMocks();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe("on create", () => {
|
|
316
|
+
it("adds a monitor the status page does not list yet", async () => {
|
|
317
|
+
mockService([]);
|
|
318
|
+
|
|
319
|
+
await expect(
|
|
320
|
+
onBeforeCreate(createBy({ monitorId: MONITOR_ID })),
|
|
321
|
+
).resolves.toBeDefined();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("refuses a second resource for a monitor already on the page", async () => {
|
|
325
|
+
mockService([makeResource({ id: resourceId(1), monitorId: MONITOR_ID })]);
|
|
326
|
+
|
|
327
|
+
await expect(
|
|
328
|
+
onBeforeCreate(createBy({ monitorId: MONITOR_ID })),
|
|
329
|
+
).rejects.toThrow(new BadDataException(DUPLICATE_MONITOR_MESSAGE));
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
/*
|
|
333
|
+
* The reported bug: the monitors already in the group come back selected
|
|
334
|
+
* when the label is picked again, and adding used to duplicate every one.
|
|
335
|
+
*/
|
|
336
|
+
it("refuses re-adding a monitor to the same group it is already in", async () => {
|
|
337
|
+
mockService([
|
|
338
|
+
makeResource({
|
|
339
|
+
id: resourceId(1),
|
|
340
|
+
monitorId: MONITOR_ID,
|
|
341
|
+
statusPageGroupId: GROUP_ID,
|
|
342
|
+
}),
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
await expect(
|
|
346
|
+
onBeforeCreate(
|
|
347
|
+
createBy({ monitorId: MONITOR_ID, statusPageGroupId: GROUP_ID }),
|
|
348
|
+
),
|
|
349
|
+
).rejects.toThrow(new BadDataException(DUPLICATE_MONITOR_MESSAGE));
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("refuses a monitor that is on the page in a different group", async () => {
|
|
353
|
+
mockService([
|
|
354
|
+
makeResource({
|
|
355
|
+
id: resourceId(1),
|
|
356
|
+
monitorId: MONITOR_ID,
|
|
357
|
+
statusPageGroupId: GROUP_ID,
|
|
358
|
+
}),
|
|
359
|
+
]);
|
|
360
|
+
|
|
361
|
+
await expect(
|
|
362
|
+
onBeforeCreate(
|
|
363
|
+
createBy({
|
|
364
|
+
monitorId: MONITOR_ID,
|
|
365
|
+
statusPageGroupId: OTHER_GROUP_ID,
|
|
366
|
+
}),
|
|
367
|
+
),
|
|
368
|
+
).rejects.toThrow(new BadDataException(DUPLICATE_MONITOR_MESSAGE));
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("allows the same monitor on a different status page", async () => {
|
|
372
|
+
mockService([
|
|
373
|
+
makeResource({
|
|
374
|
+
id: resourceId(1),
|
|
375
|
+
statusPageId: OTHER_STATUS_PAGE_ID,
|
|
376
|
+
monitorId: MONITOR_ID,
|
|
377
|
+
}),
|
|
378
|
+
]);
|
|
379
|
+
|
|
380
|
+
await expect(
|
|
381
|
+
onBeforeCreate(createBy({ monitorId: MONITOR_ID })),
|
|
382
|
+
).resolves.toBeDefined();
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("allows a different monitor onto a page that already has one", async () => {
|
|
386
|
+
mockService([makeResource({ id: resourceId(1), monitorId: MONITOR_ID })]);
|
|
387
|
+
|
|
388
|
+
await expect(
|
|
389
|
+
onBeforeCreate(createBy({ monitorId: OTHER_MONITOR_ID })),
|
|
390
|
+
).resolves.toBeDefined();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
/*
|
|
394
|
+
* The dashboard's resource form posts the relation rather than the foreign
|
|
395
|
+
* key column, so the check has to read both or the single-add form keeps
|
|
396
|
+
* writing duplicates.
|
|
397
|
+
*/
|
|
398
|
+
it("resolves the monitor from the relation the resource form posts", async () => {
|
|
399
|
+
mockService([makeResource({ id: resourceId(1), monitorId: MONITOR_ID })]);
|
|
400
|
+
|
|
401
|
+
await expect(
|
|
402
|
+
onBeforeCreate(createBy({ monitor: MONITOR_ID })),
|
|
403
|
+
).rejects.toThrow(new BadDataException(DUPLICATE_MONITOR_MESSAGE));
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("refuses a second resource for a monitor group already on the page", async () => {
|
|
407
|
+
mockService([
|
|
408
|
+
makeResource({ id: resourceId(1), monitorGroupId: MONITOR_GROUP_ID }),
|
|
409
|
+
]);
|
|
410
|
+
|
|
411
|
+
await expect(
|
|
412
|
+
onBeforeCreate(createBy({ monitorGroupId: MONITOR_GROUP_ID })),
|
|
413
|
+
).rejects.toThrow(new BadDataException(DUPLICATE_MONITOR_GROUP_MESSAGE));
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it("resolves the monitor group from the relation the resource form posts", async () => {
|
|
417
|
+
mockService([
|
|
418
|
+
makeResource({ id: resourceId(1), monitorGroupId: MONITOR_GROUP_ID }),
|
|
419
|
+
]);
|
|
420
|
+
|
|
421
|
+
await expect(
|
|
422
|
+
onBeforeCreate(createBy({ monitorGroup: MONITOR_GROUP_ID })),
|
|
423
|
+
).rejects.toThrow(new BadDataException(DUPLICATE_MONITOR_GROUP_MESSAGE));
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it("does not treat a monitor group resource as a duplicate of a monitor", async () => {
|
|
427
|
+
mockService([
|
|
428
|
+
makeResource({ id: resourceId(1), monitorGroupId: MONITOR_GROUP_ID }),
|
|
429
|
+
]);
|
|
430
|
+
|
|
431
|
+
await expect(
|
|
432
|
+
onBeforeCreate(createBy({ monitorId: MONITOR_ID })),
|
|
433
|
+
).resolves.toBeDefined();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it("looks for a duplicate by status page and monitor only", async () => {
|
|
437
|
+
const mocks: ReturnType<typeof mockService> = mockService([]);
|
|
438
|
+
|
|
439
|
+
await onBeforeCreate(
|
|
440
|
+
createBy({ monitorId: MONITOR_ID, statusPageGroupId: GROUP_ID }),
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
const duplicateLookup: any = (mocks.findOneBy.mock.calls[0] as any)[0];
|
|
444
|
+
expect(duplicateLookup.query).toEqual({
|
|
445
|
+
statusPageId: STATUS_PAGE_ID,
|
|
446
|
+
monitorId: MONITOR_ID,
|
|
447
|
+
});
|
|
448
|
+
expect(duplicateLookup.props.isRoot).toBe(true);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("does not look for a duplicate when the resource names neither", async () => {
|
|
452
|
+
const mocks: ReturnType<typeof mockService> = mockService([]);
|
|
453
|
+
|
|
454
|
+
await onBeforeCreate(createBy({}));
|
|
455
|
+
|
|
456
|
+
expect(mocks.findOneBy).not.toHaveBeenCalled();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it("refuses before it renumbers the group", async () => {
|
|
460
|
+
const mocks: ReturnType<typeof mockService> = mockService([
|
|
461
|
+
makeResource({ id: resourceId(1), monitorId: MONITOR_ID }),
|
|
462
|
+
]);
|
|
463
|
+
|
|
464
|
+
await expect(
|
|
465
|
+
onBeforeCreate(createBy({ monitorId: MONITOR_ID })),
|
|
466
|
+
).rejects.toThrow(BadDataException);
|
|
467
|
+
|
|
468
|
+
expect(mocks.countBy).not.toHaveBeenCalled();
|
|
469
|
+
expect(mocks.updateOneBy).not.toHaveBeenCalled();
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("still requires a status page id", async () => {
|
|
473
|
+
const mocks: ReturnType<typeof mockService> = mockService([]);
|
|
474
|
+
|
|
475
|
+
await expect(
|
|
476
|
+
onBeforeCreate(
|
|
477
|
+
createBy({
|
|
478
|
+
omitStatusPageId: true,
|
|
479
|
+
monitorId: MONITOR_ID,
|
|
480
|
+
}),
|
|
481
|
+
),
|
|
482
|
+
).rejects.toThrow(
|
|
483
|
+
new BadDataException("Status Page Resource statusPageId is required"),
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
expect(mocks.findOneBy).not.toHaveBeenCalled();
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
describe("on update", () => {
|
|
491
|
+
it("refuses pointing a resource at a monitor the page already lists", async () => {
|
|
492
|
+
mockService([
|
|
493
|
+
makeResource({ id: resourceId(1), monitorId: OTHER_MONITOR_ID }),
|
|
494
|
+
makeResource({ id: resourceId(2), monitorId: MONITOR_ID }),
|
|
495
|
+
]);
|
|
496
|
+
|
|
497
|
+
await expect(
|
|
498
|
+
onBeforeUpdate(updateBy({ id: resourceId(1), monitorId: MONITOR_ID })),
|
|
499
|
+
).rejects.toThrow(new BadDataException(DUPLICATE_MONITOR_MESSAGE));
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it("lets a resource keep the monitor it already has", async () => {
|
|
503
|
+
mockService([makeResource({ id: resourceId(1), monitorId: MONITOR_ID })]);
|
|
504
|
+
|
|
505
|
+
await expect(
|
|
506
|
+
onBeforeUpdate(updateBy({ id: resourceId(1), monitorId: MONITOR_ID })),
|
|
507
|
+
).resolves.toBeDefined();
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
/*
|
|
511
|
+
* The edit form is a ModelForm: it posts the monitor on every save, even
|
|
512
|
+
* one that only renamed the row. Looking for a duplicate then would be
|
|
513
|
+
* both wasted work and, on a page that already carries a duplicate from
|
|
514
|
+
* before this rule existed, a refusal of a rename.
|
|
515
|
+
*/
|
|
516
|
+
it("does not even look for a duplicate when the monitor is unchanged", async () => {
|
|
517
|
+
const mocks: ReturnType<typeof mockService> = mockService([
|
|
518
|
+
makeResource({ id: resourceId(1), monitorId: MONITOR_ID }),
|
|
519
|
+
]);
|
|
520
|
+
|
|
521
|
+
await onBeforeUpdate(
|
|
522
|
+
updateBy({
|
|
523
|
+
id: resourceId(1),
|
|
524
|
+
monitorId: MONITOR_ID,
|
|
525
|
+
displayName: "Renamed",
|
|
526
|
+
}),
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
// One read: the resource being updated. No duplicate lookup after it.
|
|
530
|
+
expect(mocks.findOneBy).toHaveBeenCalledTimes(1);
|
|
531
|
+
expect(
|
|
532
|
+
(mocks.findOneBy.mock.calls[0] as any)[0].query.monitorId,
|
|
533
|
+
).toBeUndefined();
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it("still lets both rows of a pre-existing duplicate be renamed", async () => {
|
|
537
|
+
mockService([
|
|
538
|
+
makeResource({ id: resourceId(1), monitorId: MONITOR_ID }),
|
|
539
|
+
makeResource({ id: resourceId(2), monitorId: MONITOR_ID }),
|
|
540
|
+
]);
|
|
541
|
+
|
|
542
|
+
await expect(
|
|
543
|
+
onBeforeUpdate(
|
|
544
|
+
updateBy({
|
|
545
|
+
id: resourceId(1),
|
|
546
|
+
monitor: MONITOR_ID,
|
|
547
|
+
displayName: "Renamed",
|
|
548
|
+
}),
|
|
549
|
+
),
|
|
550
|
+
).resolves.toBeDefined();
|
|
551
|
+
|
|
552
|
+
await expect(
|
|
553
|
+
onBeforeUpdate(
|
|
554
|
+
updateBy({
|
|
555
|
+
id: resourceId(2),
|
|
556
|
+
monitor: MONITOR_ID,
|
|
557
|
+
displayName: "Renamed too",
|
|
558
|
+
}),
|
|
559
|
+
),
|
|
560
|
+
).resolves.toBeDefined();
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("looks for the duplicate by status page and the new monitor", async () => {
|
|
564
|
+
const mocks: ReturnType<typeof mockService> = mockService([
|
|
565
|
+
makeResource({ id: resourceId(1), monitorId: OTHER_MONITOR_ID }),
|
|
566
|
+
]);
|
|
567
|
+
|
|
568
|
+
await onBeforeUpdate(
|
|
569
|
+
updateBy({ id: resourceId(1), monitor: MONITOR_ID }),
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
const duplicateLookup: any = (mocks.findOneBy.mock.calls[1] as any)[0];
|
|
573
|
+
expect(duplicateLookup.query.statusPageId).toEqual(STATUS_PAGE_ID);
|
|
574
|
+
expect(duplicateLookup.query.monitorId?.toString()).toBe(
|
|
575
|
+
MONITOR_ID.toString(),
|
|
576
|
+
);
|
|
577
|
+
expect(getExcludedId(duplicateLookup.query._id)).toBe(
|
|
578
|
+
resourceId(1).toString(),
|
|
579
|
+
);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it("resolves the monitor from the relation the edit form posts", async () => {
|
|
583
|
+
mockService([
|
|
584
|
+
makeResource({ id: resourceId(1), monitorId: OTHER_MONITOR_ID }),
|
|
585
|
+
makeResource({ id: resourceId(2), monitorId: MONITOR_ID }),
|
|
586
|
+
]);
|
|
587
|
+
|
|
588
|
+
await expect(
|
|
589
|
+
onBeforeUpdate(updateBy({ id: resourceId(1), monitor: MONITOR_ID })),
|
|
590
|
+
).rejects.toThrow(new BadDataException(DUPLICATE_MONITOR_MESSAGE));
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it("refuses pointing a resource at a monitor group the page already lists", async () => {
|
|
594
|
+
mockService([
|
|
595
|
+
makeResource({ id: resourceId(1), monitorId: MONITOR_ID }),
|
|
596
|
+
makeResource({ id: resourceId(2), monitorGroupId: MONITOR_GROUP_ID }),
|
|
597
|
+
]);
|
|
598
|
+
|
|
599
|
+
await expect(
|
|
600
|
+
onBeforeUpdate(
|
|
601
|
+
updateBy({ id: resourceId(1), monitorGroupId: MONITOR_GROUP_ID }),
|
|
602
|
+
),
|
|
603
|
+
).rejects.toThrow(new BadDataException(DUPLICATE_MONITOR_GROUP_MESSAGE));
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it("costs nothing when the update does not touch the monitor", async () => {
|
|
607
|
+
const mocks: ReturnType<typeof mockService> = mockService([
|
|
608
|
+
makeResource({ id: resourceId(1), monitorId: MONITOR_ID }),
|
|
609
|
+
]);
|
|
610
|
+
|
|
611
|
+
await expect(
|
|
612
|
+
onBeforeUpdate(updateBy({ id: resourceId(1), displayName: "Renamed" })),
|
|
613
|
+
).resolves.toBeDefined();
|
|
614
|
+
|
|
615
|
+
expect(mocks.findOneBy).not.toHaveBeenCalled();
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it("skips the check when the update does not name a single resource", async () => {
|
|
619
|
+
const mocks: ReturnType<typeof mockService> = mockService([
|
|
620
|
+
makeResource({ id: resourceId(1), monitorId: MONITOR_ID }),
|
|
621
|
+
]);
|
|
622
|
+
|
|
623
|
+
await expect(
|
|
624
|
+
onBeforeUpdate(updateBy({ monitorId: MONITOR_ID })),
|
|
625
|
+
).resolves.toBeDefined();
|
|
626
|
+
|
|
627
|
+
expect(mocks.findOneBy).not.toHaveBeenCalled();
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it("does nothing when the resource being updated has gone", async () => {
|
|
631
|
+
mockService([]);
|
|
632
|
+
|
|
633
|
+
await expect(
|
|
634
|
+
onBeforeUpdate(updateBy({ id: resourceId(1), monitorId: MONITOR_ID })),
|
|
635
|
+
).resolves.toBeDefined();
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
describe("isResourceAlreadyOnStatusPage", () => {
|
|
640
|
+
it("reports a monitor that is on the page", async () => {
|
|
641
|
+
mockService([makeResource({ id: resourceId(1), monitorId: MONITOR_ID })]);
|
|
642
|
+
|
|
643
|
+
await expect(
|
|
644
|
+
StatusPageResourceService.isResourceAlreadyOnStatusPage({
|
|
645
|
+
statusPageId: STATUS_PAGE_ID,
|
|
646
|
+
monitorId: MONITOR_ID,
|
|
647
|
+
}),
|
|
648
|
+
).resolves.toBe(true);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
it("reports a monitor that is not on the page", async () => {
|
|
652
|
+
mockService([makeResource({ id: resourceId(1), monitorId: MONITOR_ID })]);
|
|
653
|
+
|
|
654
|
+
await expect(
|
|
655
|
+
StatusPageResourceService.isResourceAlreadyOnStatusPage({
|
|
656
|
+
statusPageId: STATUS_PAGE_ID,
|
|
657
|
+
monitorId: OTHER_MONITOR_ID,
|
|
658
|
+
}),
|
|
659
|
+
).resolves.toBe(false);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it("does not count the resource it was told to exclude", async () => {
|
|
663
|
+
mockService([makeResource({ id: resourceId(1), monitorId: MONITOR_ID })]);
|
|
664
|
+
|
|
665
|
+
await expect(
|
|
666
|
+
StatusPageResourceService.isResourceAlreadyOnStatusPage({
|
|
667
|
+
statusPageId: STATUS_PAGE_ID,
|
|
668
|
+
monitorId: MONITOR_ID,
|
|
669
|
+
excludeResourceId: resourceId(1),
|
|
670
|
+
}),
|
|
671
|
+
).resolves.toBe(false);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it("still reports a duplicate held by some other resource", async () => {
|
|
675
|
+
mockService([
|
|
676
|
+
makeResource({ id: resourceId(1), monitorId: MONITOR_ID }),
|
|
677
|
+
makeResource({ id: resourceId(2), monitorId: MONITOR_ID }),
|
|
678
|
+
]);
|
|
679
|
+
|
|
680
|
+
await expect(
|
|
681
|
+
StatusPageResourceService.isResourceAlreadyOnStatusPage({
|
|
682
|
+
statusPageId: STATUS_PAGE_ID,
|
|
683
|
+
monitorId: MONITOR_ID,
|
|
684
|
+
excludeResourceId: resourceId(1),
|
|
685
|
+
}),
|
|
686
|
+
).resolves.toBe(true);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it("does not read anything when no monitor or monitor group is named", async () => {
|
|
690
|
+
const mocks: ReturnType<typeof mockService> = mockService([
|
|
691
|
+
makeResource({ id: resourceId(1), monitorId: MONITOR_ID }),
|
|
692
|
+
]);
|
|
693
|
+
|
|
694
|
+
await expect(
|
|
695
|
+
StatusPageResourceService.isResourceAlreadyOnStatusPage({
|
|
696
|
+
statusPageId: STATUS_PAGE_ID,
|
|
697
|
+
}),
|
|
698
|
+
).resolves.toBe(false);
|
|
699
|
+
|
|
700
|
+
expect(mocks.findOneBy).not.toHaveBeenCalled();
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
});
|