@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
|
@@ -1855,4 +1855,761 @@ describe("Status Page > Resources", () => {
|
|
|
1855
1855
|
);
|
|
1856
1856
|
});
|
|
1857
1857
|
});
|
|
1858
|
+
|
|
1859
|
+
/*
|
|
1860
|
+
* Clearing a group, in one decision rather than twenty seven.
|
|
1861
|
+
*
|
|
1862
|
+
* The reporter had a group holding twenty seven monitors and no way to say so:
|
|
1863
|
+
* every removal was its own overflow menu, its own confirmation and its own
|
|
1864
|
+
* reload of the group underneath them (issue #3419). The list carries a box
|
|
1865
|
+
* per row and a box at the top of it now, and the bulk bar turns whatever is
|
|
1866
|
+
* ticked into one confirmation and one pass of deletes.
|
|
1867
|
+
*
|
|
1868
|
+
* What is worth pinning down is not that a checkbox renders. It is that the
|
|
1869
|
+
* selection means what it says: that "select all" under a search means the
|
|
1870
|
+
* search's matches, that a selection cannot outlive the group it was made in
|
|
1871
|
+
* or the rows it was made of, and that a removal which fails halfway says
|
|
1872
|
+
* which of them did not go.
|
|
1873
|
+
*/
|
|
1874
|
+
describe("removing several resources at once", () => {
|
|
1875
|
+
type ResourceRowsFunction = () => Array<HTMLElement>;
|
|
1876
|
+
|
|
1877
|
+
const resourceRows: ResourceRowsFunction = (): Array<HTMLElement> => {
|
|
1878
|
+
return screen.queryAllByTestId("status-page-resource-row");
|
|
1879
|
+
};
|
|
1880
|
+
|
|
1881
|
+
type ResourceRowByNameFunction = (name: string) => HTMLElement;
|
|
1882
|
+
|
|
1883
|
+
const resourceRowByName: ResourceRowByNameFunction = (
|
|
1884
|
+
name: string,
|
|
1885
|
+
): HTMLElement => {
|
|
1886
|
+
const row: HTMLElement | undefined = resourceRows().find(
|
|
1887
|
+
(candidate: HTMLElement) => {
|
|
1888
|
+
return (candidate.textContent || "").includes(name);
|
|
1889
|
+
},
|
|
1890
|
+
);
|
|
1891
|
+
|
|
1892
|
+
if (!row) {
|
|
1893
|
+
throw new Error(`No resource row called ${name} is on screen`);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
return row;
|
|
1897
|
+
};
|
|
1898
|
+
|
|
1899
|
+
type SelectResourceFunction = (name: string) => Promise<void>;
|
|
1900
|
+
|
|
1901
|
+
const selectResource: SelectResourceFunction = async (
|
|
1902
|
+
name: string,
|
|
1903
|
+
): Promise<void> => {
|
|
1904
|
+
fireEvent.click(within(resourceRowByName(name)).getByRole("checkbox"));
|
|
1905
|
+
|
|
1906
|
+
await flushEffects();
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1909
|
+
type SelectAllResourcesFunction = () => Promise<void>;
|
|
1910
|
+
|
|
1911
|
+
const selectAllResources: SelectAllResourcesFunction =
|
|
1912
|
+
async (): Promise<void> => {
|
|
1913
|
+
fireEvent.click(
|
|
1914
|
+
screen.getByTestId("status-page-resource-list-select-all"),
|
|
1915
|
+
);
|
|
1916
|
+
|
|
1917
|
+
await flushEffects();
|
|
1918
|
+
};
|
|
1919
|
+
|
|
1920
|
+
type BulkBarFunction = () => HTMLElement | null;
|
|
1921
|
+
|
|
1922
|
+
const bulkBar: BulkBarFunction = (): HTMLElement | null => {
|
|
1923
|
+
return screen.queryByTestId("status-page-resource-panel-bulk-actions");
|
|
1924
|
+
};
|
|
1925
|
+
|
|
1926
|
+
type OpenBulkMenuFunction = () => Promise<void>;
|
|
1927
|
+
|
|
1928
|
+
const openBulkMenu: OpenBulkMenuFunction = async (): Promise<void> => {
|
|
1929
|
+
fireEvent.click(screen.getByText("Bulk Actions"));
|
|
1930
|
+
|
|
1931
|
+
await flushEffects();
|
|
1932
|
+
};
|
|
1933
|
+
|
|
1934
|
+
/* Through the menu, the confirmation, and out the other side. */
|
|
1935
|
+
type ConfirmBulkRemoveFunction = () => Promise<void>;
|
|
1936
|
+
|
|
1937
|
+
const confirmBulkRemove: ConfirmBulkRemoveFunction =
|
|
1938
|
+
async (): Promise<void> => {
|
|
1939
|
+
await openBulkMenu();
|
|
1940
|
+
|
|
1941
|
+
fireEvent.click(screen.getByText("Remove from status page"));
|
|
1942
|
+
await flushEffects();
|
|
1943
|
+
|
|
1944
|
+
fireEvent.click(screen.getByText("Remove from status page"));
|
|
1945
|
+
await flushEffects();
|
|
1946
|
+
};
|
|
1947
|
+
|
|
1948
|
+
type DeletedIdsFunction = () => Array<string>;
|
|
1949
|
+
|
|
1950
|
+
const deletedIds: DeletedIdsFunction = (): Array<string> => {
|
|
1951
|
+
return mockDeleteItem.mock.calls.map((call: Array<any>) => {
|
|
1952
|
+
return call[0].id.toString();
|
|
1953
|
+
});
|
|
1954
|
+
};
|
|
1955
|
+
|
|
1956
|
+
type SetUpGroupFunction = (
|
|
1957
|
+
resources?: Array<StatusPageResource> | undefined,
|
|
1958
|
+
) => Array<GetListCall>;
|
|
1959
|
+
|
|
1960
|
+
const setUpGroup: SetUpGroupFunction = (
|
|
1961
|
+
resources?: Array<StatusPageResource> | undefined,
|
|
1962
|
+
): Array<GetListCall> => {
|
|
1963
|
+
return setUpApi({
|
|
1964
|
+
groups: buildHierarchy(),
|
|
1965
|
+
resources: resources || [
|
|
1966
|
+
makeResource({ id: "a", monitorName: "API", order: 1 }),
|
|
1967
|
+
makeResource({ id: "b", monitorName: "Database", order: 2 }),
|
|
1968
|
+
makeResource({ id: "c", monitorName: "Cache", order: 3 }),
|
|
1969
|
+
],
|
|
1970
|
+
});
|
|
1971
|
+
};
|
|
1972
|
+
|
|
1973
|
+
describe("making a selection", () => {
|
|
1974
|
+
test("every row has a box, and the list is topped by one", async () => {
|
|
1975
|
+
setUpGroup();
|
|
1976
|
+
|
|
1977
|
+
renderPage();
|
|
1978
|
+
|
|
1979
|
+
await waitForExplorer();
|
|
1980
|
+
|
|
1981
|
+
expect(
|
|
1982
|
+
screen.queryAllByTestId("status-page-resource-row-select").length,
|
|
1983
|
+
).toBe(3);
|
|
1984
|
+
expect(
|
|
1985
|
+
screen.getByTestId("status-page-resource-list-select-all"),
|
|
1986
|
+
).toBeInTheDocument();
|
|
1987
|
+
});
|
|
1988
|
+
|
|
1989
|
+
test("nothing is offered until something is ticked", async () => {
|
|
1990
|
+
setUpGroup();
|
|
1991
|
+
|
|
1992
|
+
renderPage();
|
|
1993
|
+
|
|
1994
|
+
await waitForExplorer();
|
|
1995
|
+
|
|
1996
|
+
expect(bulkBar()).not.toBeInTheDocument();
|
|
1997
|
+
|
|
1998
|
+
await selectResource("Database");
|
|
1999
|
+
|
|
2000
|
+
expect(bulkBar()).toBeInTheDocument();
|
|
2001
|
+
});
|
|
2002
|
+
|
|
2003
|
+
test("the bar counts what is ticked, in the singular when there is one", async () => {
|
|
2004
|
+
setUpGroup();
|
|
2005
|
+
|
|
2006
|
+
renderPage();
|
|
2007
|
+
|
|
2008
|
+
await waitForExplorer();
|
|
2009
|
+
|
|
2010
|
+
await selectResource("Database");
|
|
2011
|
+
|
|
2012
|
+
expect(bulkBar()!.textContent).toContain("1 resource Selected");
|
|
2013
|
+
|
|
2014
|
+
await selectResource("Cache");
|
|
2015
|
+
|
|
2016
|
+
expect(bulkBar()!.textContent).toContain("2 resources Selected");
|
|
2017
|
+
});
|
|
2018
|
+
|
|
2019
|
+
test("un-ticking the last row puts the bar away again", async () => {
|
|
2020
|
+
setUpGroup();
|
|
2021
|
+
|
|
2022
|
+
renderPage();
|
|
2023
|
+
|
|
2024
|
+
await waitForExplorer();
|
|
2025
|
+
|
|
2026
|
+
await selectResource("Database");
|
|
2027
|
+
await selectResource("Database");
|
|
2028
|
+
|
|
2029
|
+
expect(bulkBar()).not.toBeInTheDocument();
|
|
2030
|
+
});
|
|
2031
|
+
|
|
2032
|
+
test("the box at the top selects the whole group, and clears it again", async () => {
|
|
2033
|
+
setUpGroup();
|
|
2034
|
+
|
|
2035
|
+
renderPage();
|
|
2036
|
+
|
|
2037
|
+
await waitForExplorer();
|
|
2038
|
+
|
|
2039
|
+
await selectAllResources();
|
|
2040
|
+
|
|
2041
|
+
expect(bulkBar()!.textContent).toContain("3 resources Selected");
|
|
2042
|
+
|
|
2043
|
+
resourceRows().forEach((row: HTMLElement) => {
|
|
2044
|
+
expect(row).toHaveAttribute("data-selected", "true");
|
|
2045
|
+
});
|
|
2046
|
+
|
|
2047
|
+
await selectAllResources();
|
|
2048
|
+
|
|
2049
|
+
expect(bulkBar()).not.toBeInTheDocument();
|
|
2050
|
+
});
|
|
2051
|
+
|
|
2052
|
+
/*
|
|
2053
|
+
* Half filled rather than empty. A box that can never reach "all
|
|
2054
|
+
* selected" and sits permanently empty reads as a selection that was
|
|
2055
|
+
* lost.
|
|
2056
|
+
*/
|
|
2057
|
+
test("the box at the top is half filled while only some rows are", async () => {
|
|
2058
|
+
setUpGroup();
|
|
2059
|
+
|
|
2060
|
+
renderPage();
|
|
2061
|
+
|
|
2062
|
+
await waitForExplorer();
|
|
2063
|
+
|
|
2064
|
+
await selectResource("API");
|
|
2065
|
+
|
|
2066
|
+
const selectAll: HTMLInputElement = screen.getByTestId(
|
|
2067
|
+
"status-page-resource-list-select-all",
|
|
2068
|
+
) as HTMLInputElement;
|
|
2069
|
+
|
|
2070
|
+
expect(selectAll.checked).toBe(false);
|
|
2071
|
+
expect(selectAll.indeterminate).toBe(true);
|
|
2072
|
+
|
|
2073
|
+
await selectResource("Database");
|
|
2074
|
+
await selectResource("Cache");
|
|
2075
|
+
|
|
2076
|
+
expect(
|
|
2077
|
+
(
|
|
2078
|
+
screen.getByTestId(
|
|
2079
|
+
"status-page-resource-list-select-all",
|
|
2080
|
+
) as HTMLInputElement
|
|
2081
|
+
).checked,
|
|
2082
|
+
).toBe(true);
|
|
2083
|
+
});
|
|
2084
|
+
|
|
2085
|
+
/*
|
|
2086
|
+
* The search is the whole reason a "select all" is worth having on a
|
|
2087
|
+
* group of twenty seven: pick out the ones that share a name, then clear
|
|
2088
|
+
* them. So it has to mean the matches and nothing else.
|
|
2089
|
+
*/
|
|
2090
|
+
test("select all under a search takes the matches and leaves the rest", async () => {
|
|
2091
|
+
setUpApi({
|
|
2092
|
+
groups: buildHierarchy(),
|
|
2093
|
+
resources: [
|
|
2094
|
+
makeResource({ id: "a", monitorName: "Prod API" }),
|
|
2095
|
+
makeResource({ id: "b", monitorName: "Prod Database" }),
|
|
2096
|
+
makeResource({ id: "c", monitorName: "Staging Cache" }),
|
|
2097
|
+
],
|
|
2098
|
+
});
|
|
2099
|
+
|
|
2100
|
+
renderPage();
|
|
2101
|
+
|
|
2102
|
+
await waitForExplorer();
|
|
2103
|
+
|
|
2104
|
+
fireEvent.change(
|
|
2105
|
+
screen.getByTestId("status-page-resource-panel-search"),
|
|
2106
|
+
{ target: { value: "prod" } },
|
|
2107
|
+
);
|
|
2108
|
+
await flushEffects();
|
|
2109
|
+
|
|
2110
|
+
expect(resourceRows().length).toBe(2);
|
|
2111
|
+
|
|
2112
|
+
await selectAllResources();
|
|
2113
|
+
|
|
2114
|
+
expect(bulkBar()!.textContent).toContain("2 resources Selected");
|
|
2115
|
+
});
|
|
2116
|
+
|
|
2117
|
+
/*
|
|
2118
|
+
* And a selection made before the search is still a selection: the rows
|
|
2119
|
+
* the filter hid are not rows the operator changed their mind about.
|
|
2120
|
+
*/
|
|
2121
|
+
test("a selection survives a search being typed and cleared", async () => {
|
|
2122
|
+
setUpApi({
|
|
2123
|
+
groups: buildHierarchy(),
|
|
2124
|
+
resources: [
|
|
2125
|
+
makeResource({ id: "a", monitorName: "Prod API" }),
|
|
2126
|
+
makeResource({ id: "b", monitorName: "Staging Cache" }),
|
|
2127
|
+
],
|
|
2128
|
+
});
|
|
2129
|
+
|
|
2130
|
+
renderPage();
|
|
2131
|
+
|
|
2132
|
+
await waitForExplorer();
|
|
2133
|
+
|
|
2134
|
+
await selectResource("Staging Cache");
|
|
2135
|
+
|
|
2136
|
+
fireEvent.change(
|
|
2137
|
+
screen.getByTestId("status-page-resource-panel-search"),
|
|
2138
|
+
{ target: { value: "prod" } },
|
|
2139
|
+
);
|
|
2140
|
+
await flushEffects();
|
|
2141
|
+
|
|
2142
|
+
/* Off screen, still counted. */
|
|
2143
|
+
expect(resourceRows().length).toBe(1);
|
|
2144
|
+
expect(bulkBar()!.textContent).toContain("1 resource Selected");
|
|
2145
|
+
|
|
2146
|
+
fireEvent.change(
|
|
2147
|
+
screen.getByTestId("status-page-resource-panel-search"),
|
|
2148
|
+
{ target: { value: "" } },
|
|
2149
|
+
);
|
|
2150
|
+
await flushEffects();
|
|
2151
|
+
|
|
2152
|
+
expect(resourceRowByName("Staging Cache")).toHaveAttribute(
|
|
2153
|
+
"data-selected",
|
|
2154
|
+
"true",
|
|
2155
|
+
);
|
|
2156
|
+
});
|
|
2157
|
+
|
|
2158
|
+
/*
|
|
2159
|
+
* A selection belongs to the group it was made in. Carrying it across
|
|
2160
|
+
* would aim a removal at rows the operator is no longer looking at.
|
|
2161
|
+
*/
|
|
2162
|
+
test("a selection does not follow the operator to the next group", async () => {
|
|
2163
|
+
setUpApi({
|
|
2164
|
+
groups: buildHierarchy(),
|
|
2165
|
+
resources: [
|
|
2166
|
+
makeResource({ id: "loose", monitorName: "Loose" }),
|
|
2167
|
+
makeResource({
|
|
2168
|
+
id: "market",
|
|
2169
|
+
groupId: MARKET_ID,
|
|
2170
|
+
monitorName: "Market Monitor",
|
|
2171
|
+
}),
|
|
2172
|
+
],
|
|
2173
|
+
});
|
|
2174
|
+
|
|
2175
|
+
renderPage();
|
|
2176
|
+
|
|
2177
|
+
await waitForExplorer();
|
|
2178
|
+
|
|
2179
|
+
await selectResource("Loose");
|
|
2180
|
+
|
|
2181
|
+
expect(bulkBar()).toBeInTheDocument();
|
|
2182
|
+
|
|
2183
|
+
await selectGroup("Market 1001");
|
|
2184
|
+
|
|
2185
|
+
expect(bulkBar()).not.toBeInTheDocument();
|
|
2186
|
+
});
|
|
2187
|
+
|
|
2188
|
+
/*
|
|
2189
|
+
* Ticking rows is only worth offering to somebody who can act on them,
|
|
2190
|
+
* and removing is the only thing this pane does with a selection.
|
|
2191
|
+
*/
|
|
2192
|
+
test("a viewer who may not remove anything is offered no boxes", async () => {
|
|
2193
|
+
mockIsMasterAdmin.mockReturnValue(false);
|
|
2194
|
+
|
|
2195
|
+
setUpGroup();
|
|
2196
|
+
|
|
2197
|
+
renderPage();
|
|
2198
|
+
|
|
2199
|
+
await waitForExplorer();
|
|
2200
|
+
|
|
2201
|
+
expect(resourceRows().length).toBe(3);
|
|
2202
|
+
expect(
|
|
2203
|
+
screen.queryAllByTestId("status-page-resource-row-select").length,
|
|
2204
|
+
).toBe(0);
|
|
2205
|
+
expect(
|
|
2206
|
+
screen.queryByTestId("status-page-resource-list-select-all"),
|
|
2207
|
+
).not.toBeInTheDocument();
|
|
2208
|
+
});
|
|
2209
|
+
});
|
|
2210
|
+
|
|
2211
|
+
describe("removing what was selected", () => {
|
|
2212
|
+
test("asks once, naming the number, and says the monitors survive", async () => {
|
|
2213
|
+
setUpGroup();
|
|
2214
|
+
|
|
2215
|
+
renderPage();
|
|
2216
|
+
|
|
2217
|
+
await waitForExplorer();
|
|
2218
|
+
|
|
2219
|
+
await selectResource("API");
|
|
2220
|
+
await selectResource("Cache");
|
|
2221
|
+
|
|
2222
|
+
await openBulkMenu();
|
|
2223
|
+
|
|
2224
|
+
fireEvent.click(screen.getByText("Remove from status page"));
|
|
2225
|
+
await flushEffects();
|
|
2226
|
+
|
|
2227
|
+
expect(screen.getByText("Remove 2 resources")).toBeInTheDocument();
|
|
2228
|
+
expect(
|
|
2229
|
+
screen.getByText(
|
|
2230
|
+
"Are you sure you want to remove 2 resources from this status page? The monitors themselves are not deleted.",
|
|
2231
|
+
),
|
|
2232
|
+
).toBeInTheDocument();
|
|
2233
|
+
|
|
2234
|
+
/* Nothing has been written yet - the question is still on screen. */
|
|
2235
|
+
expect(mockDeleteItem).not.toHaveBeenCalled();
|
|
2236
|
+
});
|
|
2237
|
+
|
|
2238
|
+
test("deletes exactly the rows that were ticked, and no others", async () => {
|
|
2239
|
+
setUpGroup();
|
|
2240
|
+
|
|
2241
|
+
renderPage();
|
|
2242
|
+
|
|
2243
|
+
await waitForExplorer();
|
|
2244
|
+
|
|
2245
|
+
await selectResource("API");
|
|
2246
|
+
await selectResource("Cache");
|
|
2247
|
+
|
|
2248
|
+
await confirmBulkRemove();
|
|
2249
|
+
|
|
2250
|
+
await waitFor(() => {
|
|
2251
|
+
expect(mockDeleteItem).toHaveBeenCalledTimes(2);
|
|
2252
|
+
});
|
|
2253
|
+
|
|
2254
|
+
expect(deletedIds().sort()).toEqual(["a", "c"]);
|
|
2255
|
+
});
|
|
2256
|
+
|
|
2257
|
+
test("clears a whole group from one press of the box at the top", async () => {
|
|
2258
|
+
setUpGroup();
|
|
2259
|
+
|
|
2260
|
+
renderPage();
|
|
2261
|
+
|
|
2262
|
+
await waitForExplorer();
|
|
2263
|
+
|
|
2264
|
+
await selectAllResources();
|
|
2265
|
+
|
|
2266
|
+
await confirmBulkRemove();
|
|
2267
|
+
|
|
2268
|
+
await waitFor(() => {
|
|
2269
|
+
expect(mockDeleteItem).toHaveBeenCalledTimes(3);
|
|
2270
|
+
});
|
|
2271
|
+
|
|
2272
|
+
expect(deletedIds().sort()).toEqual(["a", "b", "c"]);
|
|
2273
|
+
});
|
|
2274
|
+
|
|
2275
|
+
/*
|
|
2276
|
+
* The report of what happened is the last thing on screen, and clearing
|
|
2277
|
+
* the selection is what takes it away - so the group is only re-read once
|
|
2278
|
+
* the operator has dismissed it.
|
|
2279
|
+
*/
|
|
2280
|
+
test("re-reads the group and drops the selection once the report is closed", async () => {
|
|
2281
|
+
const calls: Array<GetListCall> = setUpGroup();
|
|
2282
|
+
|
|
2283
|
+
renderPage();
|
|
2284
|
+
|
|
2285
|
+
await waitForExplorer();
|
|
2286
|
+
|
|
2287
|
+
await selectResource("API");
|
|
2288
|
+
|
|
2289
|
+
const before: number = scopedResourceCalls(calls).length;
|
|
2290
|
+
|
|
2291
|
+
await confirmBulkRemove();
|
|
2292
|
+
|
|
2293
|
+
await waitFor(() => {
|
|
2294
|
+
expect(mockDeleteItem).toHaveBeenCalledTimes(1);
|
|
2295
|
+
});
|
|
2296
|
+
|
|
2297
|
+
/* Still on screen, still reporting, and the group not yet re-read. */
|
|
2298
|
+
expect(scopedResourceCalls(calls).length).toBe(before);
|
|
2299
|
+
expect(screen.getByText("Completed")).toBeInTheDocument();
|
|
2300
|
+
|
|
2301
|
+
fireEvent.click(screen.getByText("Close"));
|
|
2302
|
+
|
|
2303
|
+
await waitFor(() => {
|
|
2304
|
+
expect(scopedResourceCalls(calls).length).toBe(before + 1);
|
|
2305
|
+
});
|
|
2306
|
+
|
|
2307
|
+
await waitForSettled();
|
|
2308
|
+
|
|
2309
|
+
expect(bulkBar()).not.toBeInTheDocument();
|
|
2310
|
+
});
|
|
2311
|
+
|
|
2312
|
+
test("moves the group's count badge without re-reading the status page", async () => {
|
|
2313
|
+
const calls: Array<GetListCall> = setUpApi({
|
|
2314
|
+
groups: buildHierarchy(),
|
|
2315
|
+
resources: [
|
|
2316
|
+
makeResource({ id: "a", groupId: MARKET_ID, monitorName: "One" }),
|
|
2317
|
+
makeResource({ id: "b", groupId: MARKET_ID, monitorName: "Two" }),
|
|
2318
|
+
makeResource({
|
|
2319
|
+
id: "c",
|
|
2320
|
+
groupId: MARKET_ID,
|
|
2321
|
+
monitorName: "Three",
|
|
2322
|
+
}),
|
|
2323
|
+
],
|
|
2324
|
+
});
|
|
2325
|
+
|
|
2326
|
+
renderPage();
|
|
2327
|
+
|
|
2328
|
+
await waitForExplorer();
|
|
2329
|
+
|
|
2330
|
+
await selectGroup("Market 1001");
|
|
2331
|
+
|
|
2332
|
+
expect(
|
|
2333
|
+
within(navigatorRowByName("Market 1001")).getByTestId(
|
|
2334
|
+
"status-page-resource-navigator-count",
|
|
2335
|
+
).textContent,
|
|
2336
|
+
).toBe("3");
|
|
2337
|
+
|
|
2338
|
+
const unscopedCallsBefore: number =
|
|
2339
|
+
resourceCalls(calls).length - scopedResourceCalls(calls).length;
|
|
2340
|
+
|
|
2341
|
+
await selectResource("One");
|
|
2342
|
+
await selectResource("Two");
|
|
2343
|
+
|
|
2344
|
+
/* Whatever is asked for next, the group now holds one resource. */
|
|
2345
|
+
mockGetList.mockImplementation(async (callData: any): Promise<any> => {
|
|
2346
|
+
calls.push({
|
|
2347
|
+
modelType: callData.modelType,
|
|
2348
|
+
query: callData.query || {},
|
|
2349
|
+
});
|
|
2350
|
+
|
|
2351
|
+
if (callData.modelType === StatusPageGroup) {
|
|
2352
|
+
return {
|
|
2353
|
+
data: buildHierarchy(),
|
|
2354
|
+
count: 4,
|
|
2355
|
+
skip: 0,
|
|
2356
|
+
limit: 4,
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
return {
|
|
2361
|
+
data: [
|
|
2362
|
+
makeResource({
|
|
2363
|
+
id: "c",
|
|
2364
|
+
groupId: MARKET_ID,
|
|
2365
|
+
monitorName: "Three",
|
|
2366
|
+
}),
|
|
2367
|
+
],
|
|
2368
|
+
count: 1,
|
|
2369
|
+
skip: 0,
|
|
2370
|
+
limit: 1,
|
|
2371
|
+
};
|
|
2372
|
+
});
|
|
2373
|
+
|
|
2374
|
+
await confirmBulkRemove();
|
|
2375
|
+
|
|
2376
|
+
await waitFor(() => {
|
|
2377
|
+
expect(mockDeleteItem).toHaveBeenCalledTimes(2);
|
|
2378
|
+
});
|
|
2379
|
+
|
|
2380
|
+
fireEvent.click(screen.getByText("Close"));
|
|
2381
|
+
|
|
2382
|
+
await waitFor(() => {
|
|
2383
|
+
expect(
|
|
2384
|
+
within(navigatorRowByName("Market 1001")).getByTestId(
|
|
2385
|
+
"status-page-resource-navigator-count",
|
|
2386
|
+
).textContent,
|
|
2387
|
+
).toBe("1");
|
|
2388
|
+
});
|
|
2389
|
+
|
|
2390
|
+
expect(
|
|
2391
|
+
resourceCalls(calls).length - scopedResourceCalls(calls).length,
|
|
2392
|
+
).toBe(unscopedCallsBefore);
|
|
2393
|
+
});
|
|
2394
|
+
|
|
2395
|
+
/*
|
|
2396
|
+
* There is no bulk delete endpoint, so a removal is a pass of single
|
|
2397
|
+
* deletes and any one of them can fail. The ones that went stay gone, and
|
|
2398
|
+
* the operator is told which did not - by name, because "1 failed" over
|
|
2399
|
+
* twenty seven rows is not something anybody can act on.
|
|
2400
|
+
*/
|
|
2401
|
+
test("reports the ones that failed by name, and keeps the ones that went", async () => {
|
|
2402
|
+
setUpGroup();
|
|
2403
|
+
|
|
2404
|
+
renderPage();
|
|
2405
|
+
|
|
2406
|
+
await waitForExplorer();
|
|
2407
|
+
|
|
2408
|
+
mockDeleteItem.mockImplementation(async (data: any): Promise<any> => {
|
|
2409
|
+
if (data.id.toString() === "b") {
|
|
2410
|
+
throw new Error("Database is not yours to remove");
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
return {};
|
|
2414
|
+
});
|
|
2415
|
+
|
|
2416
|
+
await selectAllResources();
|
|
2417
|
+
|
|
2418
|
+
await confirmBulkRemove();
|
|
2419
|
+
|
|
2420
|
+
await waitFor(() => {
|
|
2421
|
+
expect(mockDeleteItem).toHaveBeenCalledTimes(3);
|
|
2422
|
+
});
|
|
2423
|
+
|
|
2424
|
+
expect(screen.getByText("Completed")).toBeInTheDocument();
|
|
2425
|
+
expect(screen.getByText("2 resources succeeded")).toBeInTheDocument();
|
|
2426
|
+
expect(screen.getByText("1 resource failed")).toBeInTheDocument();
|
|
2427
|
+
|
|
2428
|
+
/*
|
|
2429
|
+
* Scoped to the failure the report drew, not to the page: the row it
|
|
2430
|
+
* names is still in the list behind the modal, because a removal that
|
|
2431
|
+
* failed removed nothing.
|
|
2432
|
+
*/
|
|
2433
|
+
const failure: HTMLElement = screen.getByText(
|
|
2434
|
+
"Database is not yours to remove",
|
|
2435
|
+
).parentElement as HTMLElement;
|
|
2436
|
+
|
|
2437
|
+
expect(within(failure).getByText("Database")).toBeInTheDocument();
|
|
2438
|
+
|
|
2439
|
+
expect(resourceRowByName("Database")).toBeInTheDocument();
|
|
2440
|
+
});
|
|
2441
|
+
|
|
2442
|
+
test("a removal that fails outright still names what it was", async () => {
|
|
2443
|
+
setUpGroup();
|
|
2444
|
+
|
|
2445
|
+
renderPage();
|
|
2446
|
+
|
|
2447
|
+
await waitForExplorer();
|
|
2448
|
+
|
|
2449
|
+
mockDeleteItem.mockImplementation(async (): Promise<any> => {
|
|
2450
|
+
throw new Error("Status page is read only");
|
|
2451
|
+
});
|
|
2452
|
+
|
|
2453
|
+
await selectResource("API");
|
|
2454
|
+
|
|
2455
|
+
await confirmBulkRemove();
|
|
2456
|
+
|
|
2457
|
+
await waitFor(() => {
|
|
2458
|
+
expect(mockDeleteItem).toHaveBeenCalledTimes(1);
|
|
2459
|
+
});
|
|
2460
|
+
|
|
2461
|
+
expect(screen.getByText("1 resource failed")).toBeInTheDocument();
|
|
2462
|
+
expect(
|
|
2463
|
+
screen.getByText("Status page is read only"),
|
|
2464
|
+
).toBeInTheDocument();
|
|
2465
|
+
});
|
|
2466
|
+
|
|
2467
|
+
/*
|
|
2468
|
+
* The bar's own "Select all" is the whole group rather than the filtered
|
|
2469
|
+
* view - which is how it differs from the box at the top of the list, and
|
|
2470
|
+
* why it is offered at all once a search is on.
|
|
2471
|
+
*/
|
|
2472
|
+
test("the bar offers the rest of the group when the list is filtered", async () => {
|
|
2473
|
+
setUpApi({
|
|
2474
|
+
groups: buildHierarchy(),
|
|
2475
|
+
resources: [
|
|
2476
|
+
makeResource({ id: "a", monitorName: "Prod API" }),
|
|
2477
|
+
makeResource({ id: "b", monitorName: "Staging Cache" }),
|
|
2478
|
+
],
|
|
2479
|
+
});
|
|
2480
|
+
|
|
2481
|
+
renderPage();
|
|
2482
|
+
|
|
2483
|
+
await waitForExplorer();
|
|
2484
|
+
|
|
2485
|
+
fireEvent.change(
|
|
2486
|
+
screen.getByTestId("status-page-resource-panel-search"),
|
|
2487
|
+
{ target: { value: "prod" } },
|
|
2488
|
+
);
|
|
2489
|
+
await flushEffects();
|
|
2490
|
+
|
|
2491
|
+
await selectAllResources();
|
|
2492
|
+
|
|
2493
|
+
expect(bulkBar()!.textContent).toContain("1 resource Selected");
|
|
2494
|
+
|
|
2495
|
+
fireEvent.click(screen.getByText("Select All resources"));
|
|
2496
|
+
await flushEffects();
|
|
2497
|
+
|
|
2498
|
+
expect(bulkBar()!.textContent).toContain("2 resources Selected");
|
|
2499
|
+
});
|
|
2500
|
+
|
|
2501
|
+
/*
|
|
2502
|
+
* A group past the request's row cap holds resources this pane never
|
|
2503
|
+
* loaded, so "everything" is everything that arrived. Saying so is the
|
|
2504
|
+
* difference between a cleared group and a group that looks cleared.
|
|
2505
|
+
*/
|
|
2506
|
+
test("says so when the group holds more than the pane could load", async () => {
|
|
2507
|
+
setUpApi({ groups: buildHierarchy() });
|
|
2508
|
+
|
|
2509
|
+
/*
|
|
2510
|
+
* One row back, four thousand claimed. That is what a group past the
|
|
2511
|
+
* request's row cap looks like from here.
|
|
2512
|
+
*/
|
|
2513
|
+
mockGetList.mockImplementation(async (callData: any): Promise<any> => {
|
|
2514
|
+
if (callData.modelType === StatusPageGroup) {
|
|
2515
|
+
return {
|
|
2516
|
+
data: buildHierarchy(),
|
|
2517
|
+
count: 4,
|
|
2518
|
+
skip: 0,
|
|
2519
|
+
limit: 4,
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
return {
|
|
2524
|
+
data: [makeResource({ id: "a", monitorName: "API" })],
|
|
2525
|
+
count: 4000,
|
|
2526
|
+
skip: 0,
|
|
2527
|
+
limit: 1,
|
|
2528
|
+
};
|
|
2529
|
+
});
|
|
2530
|
+
|
|
2531
|
+
renderPage();
|
|
2532
|
+
|
|
2533
|
+
await waitForExplorer();
|
|
2534
|
+
|
|
2535
|
+
await selectAllResources();
|
|
2536
|
+
|
|
2537
|
+
expect(bulkBar()!.textContent).toContain(
|
|
2538
|
+
`Selected 1 of ${(4000).toLocaleString()} matching resources`,
|
|
2539
|
+
);
|
|
2540
|
+
});
|
|
2541
|
+
|
|
2542
|
+
test("the whole bar goes away when the selection is cleared by hand", async () => {
|
|
2543
|
+
setUpGroup();
|
|
2544
|
+
|
|
2545
|
+
renderPage();
|
|
2546
|
+
|
|
2547
|
+
await waitForExplorer();
|
|
2548
|
+
|
|
2549
|
+
await selectAllResources();
|
|
2550
|
+
|
|
2551
|
+
fireEvent.click(screen.getByText("Clear Selection"));
|
|
2552
|
+
await flushEffects();
|
|
2553
|
+
|
|
2554
|
+
expect(bulkBar()).not.toBeInTheDocument();
|
|
2555
|
+
resourceRows().forEach((row: HTMLElement) => {
|
|
2556
|
+
expect(row).toHaveAttribute("data-selected", "false");
|
|
2557
|
+
});
|
|
2558
|
+
});
|
|
2559
|
+
});
|
|
2560
|
+
|
|
2561
|
+
/*
|
|
2562
|
+
* A grid group is the same resources arranged differently, and clearing
|
|
2563
|
+
* twenty seven of them one at a time is no better there.
|
|
2564
|
+
*/
|
|
2565
|
+
describe("a grid group", () => {
|
|
2566
|
+
test("selects from its chips and removes them the same way", async () => {
|
|
2567
|
+
setUpApi({
|
|
2568
|
+
groups: buildHierarchy(),
|
|
2569
|
+
resources: [
|
|
2570
|
+
makeResource({ id: "loose", monitorName: "Loose" }),
|
|
2571
|
+
makeResource({
|
|
2572
|
+
id: "grid-a",
|
|
2573
|
+
groupId: GRID_ID,
|
|
2574
|
+
monitorName: "Auth US",
|
|
2575
|
+
rowAxisValue: "Auth",
|
|
2576
|
+
columnAxisValue: "US-East",
|
|
2577
|
+
}),
|
|
2578
|
+
makeResource({
|
|
2579
|
+
id: "grid-b",
|
|
2580
|
+
groupId: GRID_ID,
|
|
2581
|
+
monitorName: "API EU",
|
|
2582
|
+
rowAxisValue: "API",
|
|
2583
|
+
columnAxisValue: "EU-West",
|
|
2584
|
+
}),
|
|
2585
|
+
],
|
|
2586
|
+
});
|
|
2587
|
+
|
|
2588
|
+
renderPage();
|
|
2589
|
+
|
|
2590
|
+
await waitForExplorer();
|
|
2591
|
+
|
|
2592
|
+
await selectGroup("Grid Group");
|
|
2593
|
+
|
|
2594
|
+
expect(
|
|
2595
|
+
screen.getByTestId("status-page-resource-grid"),
|
|
2596
|
+
).toBeInTheDocument();
|
|
2597
|
+
|
|
2598
|
+
fireEvent.click(screen.getByLabelText("Select Auth US"));
|
|
2599
|
+
await flushEffects();
|
|
2600
|
+
fireEvent.click(screen.getByLabelText("Select API EU"));
|
|
2601
|
+
await flushEffects();
|
|
2602
|
+
|
|
2603
|
+
expect(bulkBar()!.textContent).toContain("2 resources Selected");
|
|
2604
|
+
|
|
2605
|
+
await confirmBulkRemove();
|
|
2606
|
+
|
|
2607
|
+
await waitFor(() => {
|
|
2608
|
+
expect(mockDeleteItem).toHaveBeenCalledTimes(2);
|
|
2609
|
+
});
|
|
2610
|
+
|
|
2611
|
+
expect(deletedIds().sort()).toEqual(["grid-a", "grid-b"]);
|
|
2612
|
+
});
|
|
2613
|
+
});
|
|
2614
|
+
});
|
|
1858
2615
|
});
|