@oneuptime/common 12.0.34 → 13.0.0
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/GlobalConfig.ts +12 -12
- package/Models/DatabaseModels/InventoryItem.ts +44 -1
- package/Models/DatabaseModels/NetworkDevice.ts +126 -0
- package/Server/EnvironmentConfig.ts +47 -16
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Infrastructure/Redis.ts +8 -8
- package/Server/Infrastructure/Status.ts +10 -4
- package/Server/Services/DatabaseService.ts +65 -13
- package/Server/Services/InventoryItemService.ts +31 -1
- package/Server/Services/MonitorTemplateService.ts +140 -19
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
- package/Server/Services/NetworkDeviceService.ts +69 -0
- package/Server/Services/UserNotificationRuleService.ts +76 -25
- package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
- package/Server/Types/Database/QueryUtil.ts +56 -0
- package/Server/Types/Markdown.ts +93 -1
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
- package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
- package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
- package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
- package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
- package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
- package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
- package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
- package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
- package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
- package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
- package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
- package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
- package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
- package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
- package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
- package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
- package/Tests/Models/InventoryItemStatus.test.ts +147 -0
- package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
- package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
- package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
- package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
- package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
- package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
- package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
- package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
- package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
- package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
- package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
- package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
- package/Tests/Server/Types/Markdown.test.ts +194 -1
- package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
- package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
- package/Tests/Server/Utils/EventLoop.test.ts +359 -0
- package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
- package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
- package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
- package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
- package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
- package/Tests/Types/JSONFunctions.test.ts +40 -0
- package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
- package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
- package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
- package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
- package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
- package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
- package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
- package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
- package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
- package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
- package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
- package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
- package/Types/Dashboard/DashboardVariable.ts +8 -0
- package/Types/JSON.ts +4 -0
- package/Types/JSONFunctions.ts +16 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
- package/Types/SerializableObjectDictionary.ts +4 -0
- package/Types/Telemetry/InventoryLiveness.ts +56 -0
- package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
- package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
- package/UI/esbuild-config.js +6 -0
- package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
- package/Utils/Dashboard/LabelVariable.ts +152 -0
- package/Utils/Dashboard/VariableUrlState.ts +27 -5
- package/Utils/MetricUnitUtil.ts +34 -0
- package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
- package/Utils/Monitor/MetricValueFormatter.ts +342 -0
- package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
- package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
- package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +44 -12
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Redis.js +8 -8
- package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
- package/build/dist/Server/Infrastructure/Status.js +10 -4
- package/build/dist/Server/Infrastructure/Status.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +58 -13
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +28 -1
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +75 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
- package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
- package/build/dist/Types/JSON.js +1 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +13 -0
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +4 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
- package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
- package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
- package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +26 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/test-setup.sh +9 -9
- package/tsconfig.json +51 -11
- package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
cleanup,
|
|
4
|
+
fireEvent,
|
|
5
|
+
render,
|
|
6
|
+
screen,
|
|
7
|
+
waitFor,
|
|
8
|
+
within,
|
|
9
|
+
} from "@testing-library/react";
|
|
10
|
+
import React from "react";
|
|
11
|
+
import NotificationSettings from "../../../../App/FeatureSet/Dashboard/src/Pages/UserSettings/NotificationSettings";
|
|
12
|
+
import UserNotificationSetting from "../../../Models/DatabaseModels/UserNotificationSetting";
|
|
13
|
+
import Route from "../../../Types/API/Route";
|
|
14
|
+
import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
|
|
15
|
+
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
16
|
+
import Query from "../../../Types/BaseDatabase/Query";
|
|
17
|
+
import NotificationSettingEventType from "../../../Types/NotificationSetting/NotificationSettingEventType";
|
|
18
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
19
|
+
import API from "../../../UI/Utils/API/API";
|
|
20
|
+
import ModelAPI from "../../../UI/Utils/ModelAPI/ModelAPI";
|
|
21
|
+
import ProjectUtil from "../../../UI/Utils/Project";
|
|
22
|
+
import User from "../../../UI/Utils/User";
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* User Settings > Notification Settings: the per-event, per-channel matrix,
|
|
26
|
+
* and nothing else.
|
|
27
|
+
*
|
|
28
|
+
* The two email-VOLUME controls that used to sit above the tabs here - the
|
|
29
|
+
* routine-email preset and the rollup switch - moved to User Settings > Email
|
|
30
|
+
* Preferences, because neither is a per-event choice and both were being read
|
|
31
|
+
* as settings for whichever tab happened to be open. They are covered by
|
|
32
|
+
* UserSettingsEmailPreferences.test.tsx. What this file still has to prove is
|
|
33
|
+
* that the move did not strand anybody: the matrix reads and writes the
|
|
34
|
+
* signed-in member's own rows, and the page says out loud where the volume
|
|
35
|
+
* controls went.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
39
|
+
"11111111-1111-4111-8111-111111111111",
|
|
40
|
+
);
|
|
41
|
+
const USER_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
|
|
42
|
+
|
|
43
|
+
describe("user settings > notification settings", () => {
|
|
44
|
+
let rows: Array<UserNotificationSetting>;
|
|
45
|
+
let getList: jest.SpyInstance;
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
rows = Object.values(NotificationSettingEventType).map(
|
|
49
|
+
(
|
|
50
|
+
eventType: NotificationSettingEventType,
|
|
51
|
+
index: number,
|
|
52
|
+
): UserNotificationSetting => {
|
|
53
|
+
const row: UserNotificationSetting = new UserNotificationSetting();
|
|
54
|
+
row._id = `33333333-3333-4333-8333-${String(index).padStart(12, "0")}`;
|
|
55
|
+
row.projectId = PROJECT_ID;
|
|
56
|
+
row.userId = USER_ID;
|
|
57
|
+
row.eventType = eventType;
|
|
58
|
+
row.alertByEmail = true;
|
|
59
|
+
row.alertBySMS = true;
|
|
60
|
+
return row;
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
|
|
64
|
+
jest.spyOn(User, "getUserId").mockReturnValue(USER_ID);
|
|
65
|
+
jest
|
|
66
|
+
.spyOn(ModelAPI, "getCommonHeaders")
|
|
67
|
+
.mockReturnValue({ tenantid: PROJECT_ID.toString() });
|
|
68
|
+
jest
|
|
69
|
+
.spyOn(API, "getFriendlyMessage")
|
|
70
|
+
.mockImplementation((err: unknown): string => {
|
|
71
|
+
return err instanceof Error || err instanceof HTTPErrorResponse
|
|
72
|
+
? err.message
|
|
73
|
+
: "Request failed";
|
|
74
|
+
});
|
|
75
|
+
getList = jest
|
|
76
|
+
.spyOn(ModelAPI, "getList")
|
|
77
|
+
.mockImplementation(
|
|
78
|
+
async (data: Parameters<typeof ModelAPI.getList>[0]): Promise<any> => {
|
|
79
|
+
const query: Query<UserNotificationSetting> = data.query;
|
|
80
|
+
const eventTypes: Array<string> = (query.eventType as Includes)
|
|
81
|
+
.values as Array<string>;
|
|
82
|
+
const settings: Array<UserNotificationSetting> = rows.filter(
|
|
83
|
+
(row: UserNotificationSetting): boolean => {
|
|
84
|
+
return (
|
|
85
|
+
eventTypes.includes(row.eventType!) &&
|
|
86
|
+
row.projectId?.toString() ===
|
|
87
|
+
(query.projectId as ObjectID).toString() &&
|
|
88
|
+
row.userId?.toString() === (query.userId as ObjectID).toString()
|
|
89
|
+
);
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
return {
|
|
93
|
+
data: settings,
|
|
94
|
+
count: settings.length,
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
afterEach(() => {
|
|
101
|
+
cleanup();
|
|
102
|
+
jest.restoreAllMocks();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
function renderPage(): void {
|
|
106
|
+
render(
|
|
107
|
+
<NotificationSettings
|
|
108
|
+
pageRoute={new Route("/user-settings/notification-settings")}
|
|
109
|
+
currentProject={null}
|
|
110
|
+
hasPaymentMethod={true}
|
|
111
|
+
/>,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function findEventRow(label: string): Promise<HTMLElement> {
|
|
116
|
+
return (await screen.findByText(label)).closest("tr")!;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/*
|
|
120
|
+
* THE POINTER, which is the whole cost of splitting the pages.
|
|
121
|
+
*
|
|
122
|
+
* Somebody drowning in OneUptime mail opens this page, because the menu and
|
|
123
|
+
* every owner-email footer call it the place your email choices live. The
|
|
124
|
+
* two controls that actually reduce the VOLUME are no longer on it. Without
|
|
125
|
+
* this line the matrix reads as the whole story, and the only remedy on
|
|
126
|
+
* offer is switching twenty-one events off by hand.
|
|
127
|
+
*/
|
|
128
|
+
describe("the pointer to Email Preferences", () => {
|
|
129
|
+
test("names what this page does not do, and links to the page that does", async () => {
|
|
130
|
+
renderPage();
|
|
131
|
+
await findEventRow("Incident created");
|
|
132
|
+
|
|
133
|
+
expect(
|
|
134
|
+
screen.getByText(
|
|
135
|
+
"These switches decide which notifications reach you, not how much email they add up to.",
|
|
136
|
+
),
|
|
137
|
+
).toBeVisible();
|
|
138
|
+
|
|
139
|
+
const link: HTMLElement = screen.getByRole("link", {
|
|
140
|
+
name: "Reduce routine emails or change email rollup in Email Preferences",
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(link).toHaveAttribute(
|
|
144
|
+
"href",
|
|
145
|
+
`/dashboard/${PROJECT_ID.toString()}/user-settings/email-preferences`,
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("the link carries the real project id, not the :projectId template", async () => {
|
|
150
|
+
renderPage();
|
|
151
|
+
await findEventRow("Incident created");
|
|
152
|
+
|
|
153
|
+
const href: string = screen
|
|
154
|
+
.getByRole("link", {
|
|
155
|
+
name: "Reduce routine emails or change email rollup in Email Preferences",
|
|
156
|
+
})
|
|
157
|
+
.getAttribute("href")!;
|
|
158
|
+
|
|
159
|
+
expect(href).not.toContain(":projectId");
|
|
160
|
+
expect(href).toContain(PROJECT_ID.toString());
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/*
|
|
164
|
+
* The volume controls left; nothing of them may be left behind. A stray
|
|
165
|
+
* "Reduce routine emails" button here would post to the bulk endpoint
|
|
166
|
+
* with no card explaining what it does.
|
|
167
|
+
*/
|
|
168
|
+
test("neither moved card is still rendered here", async () => {
|
|
169
|
+
renderPage();
|
|
170
|
+
await findEventRow("Incident created");
|
|
171
|
+
|
|
172
|
+
expect(
|
|
173
|
+
screen.queryByRole("button", { name: "Reduce routine emails" }),
|
|
174
|
+
).not.toBeInTheDocument();
|
|
175
|
+
expect(
|
|
176
|
+
screen.queryByText("Fewer routine emails"),
|
|
177
|
+
).not.toBeInTheDocument();
|
|
178
|
+
expect(screen.queryByText("Email Rollup")).not.toBeInTheDocument();
|
|
179
|
+
expect(
|
|
180
|
+
screen.queryByRole("switch", { name: /^Roll up notification emails:/ }),
|
|
181
|
+
).not.toBeInTheDocument();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe("the matrix", () => {
|
|
186
|
+
test("every event has one reachable preference and reads are scoped to the current member", async () => {
|
|
187
|
+
renderPage();
|
|
188
|
+
await findEventRow("Assigned to an incident");
|
|
189
|
+
for (const tabName of [
|
|
190
|
+
"Alerts",
|
|
191
|
+
"Monitoring",
|
|
192
|
+
"Status Pages",
|
|
193
|
+
"Scheduled Maintenance",
|
|
194
|
+
"On-Call",
|
|
195
|
+
]) {
|
|
196
|
+
fireEvent.click(screen.getByRole("tab", { name: tabName }));
|
|
197
|
+
await waitFor(() => {
|
|
198
|
+
expect(screen.getAllByRole("switch").length).toBeGreaterThan(1);
|
|
199
|
+
});
|
|
200
|
+
if (tabName === "Monitoring") {
|
|
201
|
+
await screen.findByText("AI agent status changed");
|
|
202
|
+
await screen.findByText("Added as AI agent owner");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const eventReads: Array<any> = getList.mock.calls.map(
|
|
206
|
+
(call: Array<any>): any => {
|
|
207
|
+
return call[0];
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
const queriedTypes: Array<string> = eventReads.flatMap(
|
|
211
|
+
(call: any): Array<string> => {
|
|
212
|
+
return call.query.eventType.values;
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
expect(queriedTypes.sort()).toEqual(
|
|
216
|
+
Object.values(NotificationSettingEventType).sort(),
|
|
217
|
+
);
|
|
218
|
+
for (const call of eventReads) {
|
|
219
|
+
expect(call.query.projectId).toEqual(PROJECT_ID);
|
|
220
|
+
expect(call.query.userId).toEqual(USER_ID);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
/*
|
|
225
|
+
* A single cell used to be blocked while the bulk preset ran, because
|
|
226
|
+
* both lived on this page. That interlock is gone with the preset, so the
|
|
227
|
+
* one thing left to prove is that an ordinary toggle still writes exactly
|
|
228
|
+
* one column of exactly one row.
|
|
229
|
+
*/
|
|
230
|
+
test("toggling a cell updates that row by id, one column only", async () => {
|
|
231
|
+
const updateById: jest.SpyInstance = jest
|
|
232
|
+
.spyOn(ModelAPI, "updateById")
|
|
233
|
+
.mockResolvedValue({} as any);
|
|
234
|
+
|
|
235
|
+
renderPage();
|
|
236
|
+
const row: HTMLElement = await findEventRow("Incident note posted");
|
|
237
|
+
const emailSwitch: HTMLElement = within(row).getByRole("switch", {
|
|
238
|
+
name: /^Email:/,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
expect(emailSwitch).toHaveAttribute("aria-checked", "true");
|
|
242
|
+
fireEvent.click(emailSwitch);
|
|
243
|
+
|
|
244
|
+
await waitFor(() => {
|
|
245
|
+
expect(updateById).toHaveBeenCalledTimes(1);
|
|
246
|
+
});
|
|
247
|
+
expect(updateById.mock.calls[0][0].data).toEqual({ alertByEmail: false });
|
|
248
|
+
expect(updateById.mock.calls[0][0].modelType).toBe(
|
|
249
|
+
UserNotificationSetting,
|
|
250
|
+
);
|
|
251
|
+
await waitFor(() => {
|
|
252
|
+
expect(
|
|
253
|
+
within(
|
|
254
|
+
screen.getByText("Incident note posted").closest("tr")!,
|
|
255
|
+
).getByRole("switch", { name: /^Email:/ }),
|
|
256
|
+
).toHaveAttribute("aria-checked", "false");
|
|
257
|
+
});
|
|
258
|
+
/* The other channel on the same row is untouched. */
|
|
259
|
+
expect(
|
|
260
|
+
within(
|
|
261
|
+
screen.getByText("Incident note posted").closest("tr")!,
|
|
262
|
+
).getByRole("switch", { name: /^SMS:/ }),
|
|
263
|
+
).toHaveAttribute("aria-checked", "true");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("a failed save puts the switch back and says why", async () => {
|
|
267
|
+
jest
|
|
268
|
+
.spyOn(ModelAPI, "updateById")
|
|
269
|
+
.mockRejectedValue(new Error("Preference could not be saved"));
|
|
270
|
+
|
|
271
|
+
renderPage();
|
|
272
|
+
const row: HTMLElement = await findEventRow("Incident note posted");
|
|
273
|
+
fireEvent.click(within(row).getByRole("switch", { name: /^Email:/ }));
|
|
274
|
+
|
|
275
|
+
expect(
|
|
276
|
+
await screen.findByText("Preference could not be saved"),
|
|
277
|
+
).toBeVisible();
|
|
278
|
+
await waitFor(() => {
|
|
279
|
+
expect(
|
|
280
|
+
within(
|
|
281
|
+
screen.getByText("Incident note posted").closest("tr")!,
|
|
282
|
+
).getByRole("switch", { name: /^Email:/ }),
|
|
283
|
+
).toHaveAttribute("aria-checked", "true");
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("switching projects re-reads every row against the new project", async () => {
|
|
288
|
+
const { rerender } = render(
|
|
289
|
+
<NotificationSettings
|
|
290
|
+
pageRoute={new Route("/user-settings/notification-settings")}
|
|
291
|
+
currentProject={null}
|
|
292
|
+
hasPaymentMethod={true}
|
|
293
|
+
/>,
|
|
294
|
+
);
|
|
295
|
+
await findEventRow("Incident created");
|
|
296
|
+
|
|
297
|
+
const otherProjectId: ObjectID = new ObjectID(
|
|
298
|
+
"44444444-4444-4444-8444-444444444444",
|
|
299
|
+
);
|
|
300
|
+
jest
|
|
301
|
+
.spyOn(ProjectUtil, "getCurrentProjectId")
|
|
302
|
+
.mockReturnValue(otherProjectId);
|
|
303
|
+
getList.mockClear();
|
|
304
|
+
rerender(
|
|
305
|
+
<NotificationSettings
|
|
306
|
+
pageRoute={new Route("/user-settings/notification-settings")}
|
|
307
|
+
currentProject={null}
|
|
308
|
+
hasPaymentMethod={true}
|
|
309
|
+
/>,
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const row: HTMLElement = await findEventRow("Incident created");
|
|
313
|
+
/* No rows exist for the other project, so every cell reads as off. */
|
|
314
|
+
expect(
|
|
315
|
+
within(row).getByRole("switch", { name: /^Email:/ }),
|
|
316
|
+
).toHaveAttribute("aria-checked", "false");
|
|
317
|
+
expect(getList.mock.calls.length).toBeGreaterThan(0);
|
|
318
|
+
for (const call of getList.mock.calls) {
|
|
319
|
+
expect(call[0].query.projectId).toEqual(otherProjectId);
|
|
320
|
+
expect(call[0].query.userId).toEqual(USER_ID);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
});
|
|
@@ -55,6 +55,7 @@ import DashboardVariable, {
|
|
|
55
55
|
import DashboardVariableInterpolation, {
|
|
56
56
|
ResolvedVariableValue,
|
|
57
57
|
} from "../../../Utils/Dashboard/VariableInterpolation";
|
|
58
|
+
import { VariableValueChange } from "../../../UI/Components/Dashboard/DashboardVariableControl";
|
|
58
59
|
import ObjectID from "../../../Types/ObjectID";
|
|
59
60
|
|
|
60
61
|
const DASHBOARD_ID: ObjectID = new ObjectID(
|
|
@@ -127,11 +128,19 @@ const ViewerHarness: React.FunctionComponent<HarnessProps> = (
|
|
|
127
128
|
<DashboardVariableSelector
|
|
128
129
|
variables={variables}
|
|
129
130
|
dashboardId={DASHBOARD_ID}
|
|
130
|
-
onVariableValueChange={(
|
|
131
|
+
onVariableValueChange={(
|
|
132
|
+
variableId: string,
|
|
133
|
+
change: VariableValueChange,
|
|
134
|
+
) => {
|
|
135
|
+
const value: string = change.selectedValue || "";
|
|
131
136
|
const updated: Array<DashboardVariable> = variables.map(
|
|
132
137
|
(v: DashboardVariable): DashboardVariable => {
|
|
133
138
|
if (v.id === variableId) {
|
|
134
|
-
return {
|
|
139
|
+
return {
|
|
140
|
+
...v,
|
|
141
|
+
selectedValue: change.selectedValue,
|
|
142
|
+
selectedValues: change.selectedValues,
|
|
143
|
+
};
|
|
135
144
|
}
|
|
136
145
|
return v;
|
|
137
146
|
},
|
|
@@ -436,8 +445,8 @@ describe("Public dashboard variable selector", () => {
|
|
|
436
445
|
test("each is labelled with its variable name", () => {
|
|
437
446
|
renderViewer([customListVariable({}), textVariable({})]);
|
|
438
447
|
|
|
439
|
-
expect(screen.getByText("region
|
|
440
|
-
expect(screen.getByText("service
|
|
448
|
+
expect(screen.getByText("region")).toBeInTheDocument();
|
|
449
|
+
expect(screen.getByText("service")).toBeInTheDocument();
|
|
441
450
|
});
|
|
442
451
|
|
|
443
452
|
test('picking "All" on one leaves the others on their defaults', async () => {
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type * as StatusPageConfig from "../../../../App/FeatureSet/StatusPage/src/Utils/Config";
|
|
2
|
+
import type { ComponentProps } from "../../../../App/FeatureSet/StatusPage/src/Pages/Accounts/SSO";
|
|
3
|
+
import type * as SharedConfig from "../../../UI/Config";
|
|
4
|
+
import type NavigationUtil from "../../../UI/Utils/Navigation";
|
|
5
|
+
import URL from "../../../Types/API/URL";
|
|
6
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
7
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
8
|
+
import React, { FunctionComponent, ReactElement } from "react";
|
|
9
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
10
|
+
|
|
11
|
+
interface ProviderListProps {
|
|
12
|
+
id: string;
|
|
13
|
+
overrideFetchApiUrl: URL;
|
|
14
|
+
onSelectChange: (list: Array<{ _id: string }>) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface LoginEnvironment {
|
|
18
|
+
pageUrl: string;
|
|
19
|
+
host: string;
|
|
20
|
+
protocol: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* Keep provider discovery local to this test while exercising the real SSO
|
|
25
|
+
* page's selection handler, URL construction, and redirect storage.
|
|
26
|
+
*/
|
|
27
|
+
jest.mock("Common/UI/Components/ModelList/ModelList", () => {
|
|
28
|
+
return {
|
|
29
|
+
__esModule: true,
|
|
30
|
+
default: (props: ProviderListProps): ReactElement => {
|
|
31
|
+
return (
|
|
32
|
+
<button
|
|
33
|
+
data-testid={props.id}
|
|
34
|
+
data-fetch-url={props.overrideFetchApiUrl.toString()}
|
|
35
|
+
onClick={() => {
|
|
36
|
+
props.onSelectChange([
|
|
37
|
+
{ _id: "22222222-2222-4222-8222-222222222222" },
|
|
38
|
+
]);
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{props.id}
|
|
42
|
+
</button>
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
jest.mock("react-i18next", () => {
|
|
49
|
+
return {
|
|
50
|
+
useTranslation: () => {
|
|
51
|
+
return {
|
|
52
|
+
t: (key: string): string => {
|
|
53
|
+
return key;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
* Isolated imports re-evaluate the build's environment-dependent constants.
|
|
62
|
+
* Share React with Testing Library so rendered cases use one hook dispatcher.
|
|
63
|
+
*/
|
|
64
|
+
jest.doMock("react", () => {
|
|
65
|
+
return React;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const originalLocation: Location = window.location;
|
|
69
|
+
const originalHost: string | undefined = process.env["HOST"];
|
|
70
|
+
const originalProtocol: string | undefined = process.env["HTTP_PROTOCOL"];
|
|
71
|
+
const statusPageId: string = "11111111-1111-4111-8111-111111111111";
|
|
72
|
+
const providerId: string = "22222222-2222-4222-8222-222222222222";
|
|
73
|
+
|
|
74
|
+
function configureEnvironment(data: LoginEnvironment): void {
|
|
75
|
+
process.env["HOST"] = data.host;
|
|
76
|
+
process.env["HTTP_PROTOCOL"] = data.protocol;
|
|
77
|
+
Object.defineProperty(window, "location", {
|
|
78
|
+
configurable: true,
|
|
79
|
+
value: new globalThis.URL(data.pageUrl),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
cleanup();
|
|
85
|
+
localStorage.clear();
|
|
86
|
+
jest.restoreAllMocks();
|
|
87
|
+
Object.defineProperty(window, "location", {
|
|
88
|
+
configurable: true,
|
|
89
|
+
value: originalLocation,
|
|
90
|
+
});
|
|
91
|
+
if (originalHost === undefined) {
|
|
92
|
+
delete process.env["HOST"];
|
|
93
|
+
} else {
|
|
94
|
+
process.env["HOST"] = originalHost;
|
|
95
|
+
}
|
|
96
|
+
if (originalProtocol === undefined) {
|
|
97
|
+
delete process.env["HTTP_PROTOCOL"];
|
|
98
|
+
} else {
|
|
99
|
+
process.env["HTTP_PROTOCOL"] = originalProtocol;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("Status Page OIDC login origin", () => {
|
|
104
|
+
test.each([
|
|
105
|
+
{
|
|
106
|
+
name: "custom status-page domain",
|
|
107
|
+
pageUrl: "https://status.example.com/sso",
|
|
108
|
+
host: "oneuptime.example.com",
|
|
109
|
+
protocol: "https",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "preview on the OneUptime domain",
|
|
113
|
+
pageUrl: `https://oneuptime.example.com/status-page/${statusPageId}/sso`,
|
|
114
|
+
host: "oneuptime.example.com",
|
|
115
|
+
protocol: "https",
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "HTTP development with a configured port",
|
|
119
|
+
pageUrl: "http://status.localhost:3002/sso",
|
|
120
|
+
host: "localhost:3000",
|
|
121
|
+
protocol: "http",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "configured HTTPS and port differing from the page",
|
|
125
|
+
pageUrl: "http://status.example.com/sso",
|
|
126
|
+
host: "oneuptime.example.com:8443",
|
|
127
|
+
protocol: "https",
|
|
128
|
+
},
|
|
129
|
+
])("uses the identity origin for $name", (data: LoginEnvironment) => {
|
|
130
|
+
configureEnvironment(data);
|
|
131
|
+
|
|
132
|
+
jest.isolateModules(() => {
|
|
133
|
+
const config: typeof StatusPageConfig = jest.requireActual(
|
|
134
|
+
"../../../../App/FeatureSet/StatusPage/src/Utils/Config",
|
|
135
|
+
) as typeof StatusPageConfig;
|
|
136
|
+
const shared: typeof SharedConfig = jest.requireActual(
|
|
137
|
+
"../../../UI/Config",
|
|
138
|
+
) as typeof SharedConfig;
|
|
139
|
+
const identityOrigin: string = `${data.protocol}://${data.host}`;
|
|
140
|
+
const pageOrigin: string = new globalThis.URL(data.pageUrl).origin;
|
|
141
|
+
|
|
142
|
+
expect(config.STATUS_PAGE_OIDC_API_URL.toString()).toBe(
|
|
143
|
+
`${identityOrigin}/identity/status-page-oidc`,
|
|
144
|
+
);
|
|
145
|
+
// Deriving the OIDC endpoint must not mutate the shared identity URL.
|
|
146
|
+
expect(shared.IDENTITY_URL.toString()).toBe(`${identityOrigin}/identity`);
|
|
147
|
+
expect(config.STATUS_PAGE_API_URL.toString()).toBe(
|
|
148
|
+
`${pageOrigin}/status-page-api`,
|
|
149
|
+
);
|
|
150
|
+
expect(config.STATUS_PAGE_SSO_API_URL.toString()).toBe(
|
|
151
|
+
`${pageOrigin}/status-page-sso-api`,
|
|
152
|
+
);
|
|
153
|
+
expect(config.STATUS_PAGE_IDENTITY_API_URL.toString()).toBe(
|
|
154
|
+
`${pageOrigin}/status-page-identity-api`,
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test.each([
|
|
160
|
+
{
|
|
161
|
+
name: "custom domain",
|
|
162
|
+
pageOrigin: "https://status.example.com",
|
|
163
|
+
loginPath: "/sso",
|
|
164
|
+
redirectPath: "/incidents/incident-id?tab=timeline#update",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: "preview",
|
|
168
|
+
pageOrigin: "https://oneuptime.example.com",
|
|
169
|
+
loginPath: `/status-page/${statusPageId}/sso`,
|
|
170
|
+
redirectPath: `/status-page/${statusPageId}/incidents/incident-id`,
|
|
171
|
+
},
|
|
172
|
+
])(
|
|
173
|
+
"selecting an OIDC provider from $name navigates to identity and preserves the return path",
|
|
174
|
+
(data: { pageOrigin: string; loginPath: string; redirectPath: string }) => {
|
|
175
|
+
configureEnvironment({
|
|
176
|
+
pageUrl: `${data.pageOrigin}${data.loginPath}?redirectUrl=${encodeURIComponent(data.redirectPath)}`,
|
|
177
|
+
host: "oneuptime.example.com",
|
|
178
|
+
protocol: "https",
|
|
179
|
+
});
|
|
180
|
+
localStorage.setItem("statusPageId", statusPageId);
|
|
181
|
+
localStorage.setItem(`isPrivateStatusPage-${statusPageId}`, "true");
|
|
182
|
+
|
|
183
|
+
jest.isolateModules(() => {
|
|
184
|
+
/*
|
|
185
|
+
* The page's default export is bound as `Sso` rather than `SSO` so the
|
|
186
|
+
* JSX tag stays PascalCase; the status page app renders this same
|
|
187
|
+
* component under the same name in App/FeatureSet/StatusPage/src/App.tsx.
|
|
188
|
+
*/
|
|
189
|
+
const Sso: FunctionComponent<ComponentProps> = (
|
|
190
|
+
jest.requireActual(
|
|
191
|
+
"../../../../App/FeatureSet/StatusPage/src/Pages/Accounts/SSO",
|
|
192
|
+
) as { default: FunctionComponent<ComponentProps> }
|
|
193
|
+
).default;
|
|
194
|
+
const Navigation: typeof NavigationUtil = (
|
|
195
|
+
jest.requireActual("../../../UI/Utils/Navigation") as {
|
|
196
|
+
default: typeof NavigationUtil;
|
|
197
|
+
}
|
|
198
|
+
).default;
|
|
199
|
+
const navigate: ReturnType<typeof jest.spyOn> = jest
|
|
200
|
+
.spyOn(Navigation, "navigate")
|
|
201
|
+
.mockImplementation(() => {});
|
|
202
|
+
|
|
203
|
+
render(
|
|
204
|
+
<Sso
|
|
205
|
+
statusPageName="Example Status"
|
|
206
|
+
logoFileId={new ObjectID(statusPageId)}
|
|
207
|
+
/>,
|
|
208
|
+
);
|
|
209
|
+
expect(
|
|
210
|
+
screen.getByTestId("oidc-list").getAttribute("data-fetch-url"),
|
|
211
|
+
).toBe(`${data.pageOrigin}/status-page-api/oidc/${statusPageId}`);
|
|
212
|
+
|
|
213
|
+
fireEvent.click(screen.getByTestId("oidc-list"));
|
|
214
|
+
|
|
215
|
+
expect(navigate).toHaveBeenCalledTimes(1);
|
|
216
|
+
expect(navigate.mock.calls[0]?.[0]?.toString()).toBe(
|
|
217
|
+
`https://oneuptime.example.com/identity/status-page-oidc/${statusPageId}/${providerId}`,
|
|
218
|
+
);
|
|
219
|
+
expect(localStorage.getItem("redirectUrl")).toBe(data.redirectPath);
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
});
|