@oneuptime/common 12.0.0 → 12.0.2
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/Host.ts +12 -4
- package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
- package/Models/DatabaseModels/KubernetesResource.ts +8 -7
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +12 -2
- package/Models/DatabaseModels/PodmanResource.ts +6 -6
- package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
- package/Models/DatabaseModels/StatusPage.ts +95 -0
- package/Server/API/TeamMemberAPI.ts +85 -0
- package/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.ts +12 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Middleware/ProjectAuthorization.ts +66 -4
- package/Server/Services/BillingService.ts +600 -51
- package/Server/Services/CephClusterService.ts +47 -5
- package/Server/Services/CephResourceService.ts +43 -4
- package/Server/Services/CloudResourceService.ts +47 -5
- package/Server/Services/DatabaseService.ts +87 -6
- package/Server/Services/DockerHostService.ts +47 -5
- package/Server/Services/DockerResourceService.ts +62 -8
- package/Server/Services/DockerSwarmClusterService.ts +48 -5
- package/Server/Services/DockerSwarmResourceService.ts +53 -4
- package/Server/Services/HostService.ts +48 -5
- package/Server/Services/IoTDeviceService.ts +1 -17
- package/Server/Services/IoTFleetService.ts +47 -5
- package/Server/Services/KubernetesClusterService.ts +48 -5
- package/Server/Services/KubernetesContainerService.ts +50 -9
- package/Server/Services/KubernetesResourceService.ts +53 -7
- package/Server/Services/LabelService.ts +106 -1
- package/Server/Services/LlmProviderService.ts +69 -9
- package/Server/Services/MonitorService.ts +57 -0
- package/Server/Services/PodmanHostService.ts +47 -5
- package/Server/Services/PodmanResourceService.ts +62 -8
- package/Server/Services/ProjectService.ts +165 -35
- package/Server/Services/ProxmoxClusterService.ts +47 -5
- package/Server/Services/ProxmoxResourceService.ts +43 -4
- package/Server/Services/RumApplicationService.ts +48 -5
- package/Server/Services/RunnerService.ts +1 -7
- package/Server/Services/ServerlessFunctionService.ts +48 -5
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
- package/Server/Services/StatusPageService.ts +95 -36
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
- package/Server/Services/StatusPageSubscriberService.ts +3 -0
- package/Server/Types/Database/JSONColumnQuery.ts +621 -0
- package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
- package/Server/Types/Database/QueryHelper.ts +17 -32
- package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +12 -0
- package/Tests/App/Dashboard/RunnerInstallInstructions.test.tsx +119 -0
- package/Tests/App/Dashboard/RunnerStatus.test.tsx +274 -0
- package/Tests/App/Dashboard/UsersTableGroupsByPerson.test.tsx +448 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
- package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
- package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
- package/Tests/Server/Services/BillingService.test.ts +78 -3
- package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
- package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
- package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +510 -0
- package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
- package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
- package/Tests/Server/Services/LlmProviderUsableByProject.test.ts +293 -0
- package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
- package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +387 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
- package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +29 -13
- package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
- package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
- package/Tests/Server/Utils/AI/ToolArgsExtractors.test.ts +141 -0
- package/Tests/Server/Utils/AI/Toolbox/WidgetBuilder.test.ts +205 -0
- package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +897 -0
- package/Tests/Types/Events/Recurring.test.ts +157 -0
- package/Tests/Types/Runner/RunnerLiveStatus.test.ts +320 -0
- package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +285 -0
- package/Tests/UI/Components/Detail/EntityFields.test.tsx +166 -0
- package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
- package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
- package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
- package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
- package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
- package/Types/CustomField/CustomFieldDefinition.ts +24 -0
- package/Types/Date.ts +112 -0
- package/Types/Events/Recurring.ts +99 -1
- package/Types/Runbook/RunbookStep.ts +15 -0
- package/Types/Runner/RunnerLiveStatus.ts +114 -0
- package/Types/StatusPage/StatusPageReport.ts +11 -0
- package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
- package/UI/Components/Detail/Detail.tsx +155 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
- package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
- package/UI/Utils/TeamMembersByUser.ts +302 -0
- package/Utils/StatusPage/ReportPeriod.ts +236 -0
- package/Utils/Telemetry/HostIpAddresses.ts +98 -0
- package/build/dist/Models/DatabaseModels/Host.js +12 -4
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js +4 -4
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
- package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +16 -4
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
- package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +57 -2
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.js +11 -1
- package/build/dist/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +466 -36
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +42 -5
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CephResourceService.js +28 -3
- package/build/dist/Server/Services/CephResourceService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +42 -5
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +69 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +42 -5
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerResourceService.js +34 -4
- package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +43 -5
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
- package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +43 -5
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceService.js +1 -8
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetService.js +42 -5
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +43 -5
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
- package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
- package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +92 -0
- package/build/dist/Server/Services/LabelService.js.map +1 -1
- package/build/dist/Server/Services/LlmProviderService.js +61 -8
- package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +47 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +42 -5
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/PodmanResourceService.js +34 -4
- package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +135 -21
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +42 -5
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +43 -5
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/RunnerService.js +1 -6
- package/build/dist/Server/Services/RunnerService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +43 -5
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +69 -46
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +11 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
- package/build/dist/Types/Date.js +68 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +63 -0
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/Runbook/RunbookStep.js.map +1 -1
- package/build/dist/Types/Runner/RunnerLiveStatus.js +69 -0
- package/build/dist/Types/Runner/RunnerLiveStatus.js.map +1 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
- package/build/dist/UI/Components/Detail/Detail.js +97 -0
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
- package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
- package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +12 -1
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import React, { ReactElement } from "react";
|
|
4
|
+
import {
|
|
5
|
+
afterEach,
|
|
6
|
+
beforeEach,
|
|
7
|
+
describe,
|
|
8
|
+
expect,
|
|
9
|
+
jest,
|
|
10
|
+
test,
|
|
11
|
+
} from "@jest/globals";
|
|
12
|
+
import { MemoryRouter } from "react-router-dom";
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* The customer-facing bug this whole change exists for: Settings > Users
|
|
16
|
+
* listed TeamMember, a (user, team) join row, so one person on three teams
|
|
17
|
+
* filled three rows with the same name and avatar and read as three different
|
|
18
|
+
* people.
|
|
19
|
+
*
|
|
20
|
+
* The unit suites cover the grouping util and the API class. Neither of them
|
|
21
|
+
* touches the page, and the fix on the page is a single prop -
|
|
22
|
+
* modelAPI={ProjectUsersModelAPI} - whose loss brings every duplicate straight
|
|
23
|
+
* back with no test failing. That prop, the column that now has to render a
|
|
24
|
+
* list of teams rather than one, and the row's derived status are what this
|
|
25
|
+
* file pins.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
jest.mock("../../../UI/Utils/Permission", () => {
|
|
29
|
+
return {
|
|
30
|
+
__esModule: true,
|
|
31
|
+
default: {
|
|
32
|
+
getAllPermissions: () => {
|
|
33
|
+
return [];
|
|
34
|
+
},
|
|
35
|
+
getProjectPermissions: () => {
|
|
36
|
+
return [];
|
|
37
|
+
},
|
|
38
|
+
getGlobalPermissions: () => {
|
|
39
|
+
return [];
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
jest.mock("../../../UI/Utils/User", () => {
|
|
46
|
+
return {
|
|
47
|
+
__esModule: true,
|
|
48
|
+
default: {
|
|
49
|
+
isMasterAdmin: () => {
|
|
50
|
+
return true;
|
|
51
|
+
},
|
|
52
|
+
getUserId: () => {
|
|
53
|
+
return null;
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
jest.mock("../../../UI/Utils/Translation", () => {
|
|
60
|
+
return {
|
|
61
|
+
__esModule: true,
|
|
62
|
+
default: () => {
|
|
63
|
+
return {
|
|
64
|
+
translateString: (value: string | undefined) => {
|
|
65
|
+
return value;
|
|
66
|
+
},
|
|
67
|
+
translateValue: (value: unknown) => {
|
|
68
|
+
return value;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
* The page is rendered through a stand-in ModelTable that records the props it
|
|
77
|
+
* was given. Rendering the real one would drag in the facet bar, the URL
|
|
78
|
+
* state and the pager, none of which this file is about - what matters is
|
|
79
|
+
* which API the page hands the table, and what its columns do with a grouped
|
|
80
|
+
* row.
|
|
81
|
+
*/
|
|
82
|
+
type CapturedTableProps = {
|
|
83
|
+
modelAPI?: unknown;
|
|
84
|
+
columns: Array<{
|
|
85
|
+
title: string;
|
|
86
|
+
disableSort?: boolean | undefined;
|
|
87
|
+
getElement?: ((item: TeamMember) => ReactElement) | undefined;
|
|
88
|
+
getExportValue?: ((item: TeamMember) => string) | undefined;
|
|
89
|
+
}>;
|
|
90
|
+
deleteButtonText?: string | undefined;
|
|
91
|
+
singularName?: string | undefined;
|
|
92
|
+
pluralName?: string | undefined;
|
|
93
|
+
isDeleteable?: boolean | undefined;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
let capturedTableProps: CapturedTableProps | null = null;
|
|
97
|
+
|
|
98
|
+
jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
|
|
99
|
+
return {
|
|
100
|
+
__esModule: true,
|
|
101
|
+
default: (props: CapturedTableProps) => {
|
|
102
|
+
capturedTableProps = props;
|
|
103
|
+
return null;
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
import Users from "../../../../App/FeatureSet/Dashboard/src/Pages/Users/Index";
|
|
109
|
+
import ProjectUsersModelAPI from "../../../UI/Utils/ModelAPI/ProjectUsersModelAPI";
|
|
110
|
+
import TeamMembersByUser, {
|
|
111
|
+
ProjectUserRow,
|
|
112
|
+
} from "../../../UI/Utils/TeamMembersByUser";
|
|
113
|
+
import ModelAPI from "../../../UI/Utils/ModelAPI/ModelAPI";
|
|
114
|
+
import ProjectUtil from "../../../UI/Utils/Project";
|
|
115
|
+
import Project from "../../../Models/DatabaseModels/Project";
|
|
116
|
+
import Team from "../../../Models/DatabaseModels/Team";
|
|
117
|
+
import TeamMember from "../../../Models/DatabaseModels/TeamMember";
|
|
118
|
+
import User from "../../../Models/DatabaseModels/User";
|
|
119
|
+
import Route from "../../../Types/API/Route";
|
|
120
|
+
import Name from "../../../Types/Name";
|
|
121
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
122
|
+
|
|
123
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
124
|
+
"00000000-0000-4000-8000-000000000001",
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const currentProject: Project = new Project();
|
|
128
|
+
currentProject._id = PROJECT_ID.toString();
|
|
129
|
+
|
|
130
|
+
type MembershipSpec = {
|
|
131
|
+
id: string;
|
|
132
|
+
userId: string;
|
|
133
|
+
userName?: string | undefined;
|
|
134
|
+
teamId?: string | undefined;
|
|
135
|
+
teamName?: string | undefined;
|
|
136
|
+
hasAcceptedInvitation?: boolean | undefined;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const buildMembership: (spec: MembershipSpec) => TeamMember = (
|
|
140
|
+
spec: MembershipSpec,
|
|
141
|
+
): TeamMember => {
|
|
142
|
+
const teamMember: TeamMember = new TeamMember();
|
|
143
|
+
|
|
144
|
+
teamMember._id = spec.id;
|
|
145
|
+
teamMember.userId = new ObjectID(spec.userId);
|
|
146
|
+
teamMember.projectId = PROJECT_ID;
|
|
147
|
+
teamMember.hasAcceptedInvitation = spec.hasAcceptedInvitation ?? true;
|
|
148
|
+
|
|
149
|
+
const user: User = new User();
|
|
150
|
+
user._id = spec.userId;
|
|
151
|
+
|
|
152
|
+
if (spec.userName) {
|
|
153
|
+
user.name = new Name(spec.userName);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
teamMember.user = user;
|
|
157
|
+
|
|
158
|
+
if (spec.teamId) {
|
|
159
|
+
teamMember.teamId = new ObjectID(spec.teamId);
|
|
160
|
+
|
|
161
|
+
const team: Team = new Team();
|
|
162
|
+
team._id = spec.teamId;
|
|
163
|
+
team.name = spec.teamName || spec.teamId;
|
|
164
|
+
teamMember.team = team;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return teamMember;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/*
|
|
171
|
+
* A row exactly as ProjectUsersModelAPI hands it to the table, built by the
|
|
172
|
+
* real grouping code rather than hand-written, so a change in the shape of a
|
|
173
|
+
* grouped row cannot leave these assertions passing against a shape the page
|
|
174
|
+
* never actually receives.
|
|
175
|
+
*/
|
|
176
|
+
const buildGroupedRow: (
|
|
177
|
+
memberships: Array<MembershipSpec>,
|
|
178
|
+
) => ProjectUserRow = (memberships: Array<MembershipSpec>): ProjectUserRow => {
|
|
179
|
+
return TeamMembersByUser.groupByUser(memberships.map(buildMembership))[0]!;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
type ColumnOf = (title: string) => CapturedTableProps["columns"][0];
|
|
183
|
+
|
|
184
|
+
const columnTitled: ColumnOf = (
|
|
185
|
+
title: string,
|
|
186
|
+
): CapturedTableProps["columns"][0] => {
|
|
187
|
+
const column: CapturedTableProps["columns"][0] | undefined =
|
|
188
|
+
capturedTableProps?.columns.find(
|
|
189
|
+
(candidate: CapturedTableProps["columns"][0]) => {
|
|
190
|
+
return candidate.title === title;
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (!column) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`No "${title}" column. Columns: ${(capturedTableProps?.columns || [])
|
|
197
|
+
.map((candidate: CapturedTableProps["columns"][0]) => {
|
|
198
|
+
return candidate.title;
|
|
199
|
+
})
|
|
200
|
+
.join(", ")}`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return column;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
type RenderCellFunction = (title: string, row: ProjectUserRow) => void;
|
|
208
|
+
|
|
209
|
+
const renderCell: RenderCellFunction = (
|
|
210
|
+
title: string,
|
|
211
|
+
row: ProjectUserRow,
|
|
212
|
+
): void => {
|
|
213
|
+
render(<MemoryRouter>{columnTitled(title).getElement!(row)}</MemoryRouter>);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
describe("Settings > Users page", () => {
|
|
217
|
+
beforeEach(async () => {
|
|
218
|
+
capturedTableProps = null;
|
|
219
|
+
|
|
220
|
+
jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
|
|
221
|
+
// The page counts SCIM configs on mount to decide whether to warn.
|
|
222
|
+
jest.spyOn(ModelAPI, "count").mockResolvedValue(0);
|
|
223
|
+
jest.spyOn(ModelAPI, "getCommonHeaders").mockReturnValue({});
|
|
224
|
+
|
|
225
|
+
render(
|
|
226
|
+
<MemoryRouter>
|
|
227
|
+
<Users
|
|
228
|
+
pageRoute={new Route("/dashboard/users")}
|
|
229
|
+
currentProject={currentProject}
|
|
230
|
+
hasPaymentMethod={true}
|
|
231
|
+
/>
|
|
232
|
+
</MemoryRouter>,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
await waitFor(() => {
|
|
236
|
+
expect(capturedTableProps).not.toBeNull();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
afterEach(() => {
|
|
241
|
+
jest.restoreAllMocks();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("the fix itself", () => {
|
|
245
|
+
/*
|
|
246
|
+
* If this fails, every user on more than one team is back to occupying one
|
|
247
|
+
* row per team - which is the bug customers reported.
|
|
248
|
+
*/
|
|
249
|
+
test("lists people, not memberships, by using the grouping API", () => {
|
|
250
|
+
expect(capturedTableProps!.modelAPI).toBe(ProjectUsersModelAPI);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("names its rows after people", () => {
|
|
254
|
+
expect(capturedTableProps!.singularName).toBe("User");
|
|
255
|
+
expect(capturedTableProps!.pluralName).toBe("Users");
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe("the Teams column", () => {
|
|
260
|
+
test("shows every team the person belongs to, not just one", () => {
|
|
261
|
+
renderCell(
|
|
262
|
+
"Teams",
|
|
263
|
+
buildGroupedRow([
|
|
264
|
+
{
|
|
265
|
+
id: "m1",
|
|
266
|
+
userId: "u1",
|
|
267
|
+
userName: "Thomas Stock",
|
|
268
|
+
teamId: "t1",
|
|
269
|
+
teamName: "NTW_Online Operations",
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: "m2",
|
|
273
|
+
userId: "u1",
|
|
274
|
+
userName: "Thomas Stock",
|
|
275
|
+
teamId: "t2",
|
|
276
|
+
teamName: "Owners",
|
|
277
|
+
},
|
|
278
|
+
]),
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
expect(screen.getByText("NTW_Online Operations")).toBeInTheDocument();
|
|
282
|
+
expect(screen.getByText("Owners")).toBeInTheDocument();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("collapses a long team list behind a 'more' control rather than growing the row", () => {
|
|
286
|
+
renderCell(
|
|
287
|
+
"Teams",
|
|
288
|
+
buildGroupedRow([
|
|
289
|
+
{ id: "m1", userId: "u1", teamId: "t1", teamName: "Alpha" },
|
|
290
|
+
{ id: "m2", userId: "u1", teamId: "t2", teamName: "Bravo" },
|
|
291
|
+
{ id: "m3", userId: "u1", teamId: "t3", teamName: "Charlie" },
|
|
292
|
+
{ id: "m4", userId: "u1", teamId: "t4", teamName: "Delta" },
|
|
293
|
+
{ id: "m5", userId: "u1", teamId: "t5", teamName: "Echo" },
|
|
294
|
+
]),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
expect(screen.getByText("Alpha")).toBeInTheDocument();
|
|
298
|
+
expect(screen.queryByText("Echo")).not.toBeInTheDocument();
|
|
299
|
+
expect(screen.getByText("2 more teams")).toBeInTheDocument();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("says so when the person is on no team at all", () => {
|
|
303
|
+
renderCell(
|
|
304
|
+
"Teams",
|
|
305
|
+
buildGroupedRow([{ id: "m1", userId: "u1", userName: "Nobody" }]),
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
expect(screen.getByText("No team assigned")).toBeInTheDocument();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
/*
|
|
312
|
+
* The exporter is column-driven and would otherwise fall back to the
|
|
313
|
+
* single `team` on the membership the row was built from, so the CSV would
|
|
314
|
+
* quietly disagree with the cell beside it.
|
|
315
|
+
*/
|
|
316
|
+
test("exports every team, not the one the row was built from", () => {
|
|
317
|
+
const row: ProjectUserRow = buildGroupedRow([
|
|
318
|
+
{ id: "m1", userId: "u1", teamId: "t1", teamName: "Engineering" },
|
|
319
|
+
{ id: "m2", userId: "u1", teamId: "t2", teamName: "On-Call" },
|
|
320
|
+
{ id: "m3", userId: "u1", teamId: "t3", teamName: "Admins" },
|
|
321
|
+
]);
|
|
322
|
+
|
|
323
|
+
expect(columnTitled("Teams").getExportValue!(row)).toBe(
|
|
324
|
+
"Admins; Engineering; On-Call",
|
|
325
|
+
);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("exports an empty cell rather than crashing on a team-less row", () => {
|
|
329
|
+
const row: ProjectUserRow = buildGroupedRow([
|
|
330
|
+
{ id: "m1", userId: "u1", userName: "Nobody" },
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
expect(columnTitled("Teams").getExportValue!(row)).toBe("");
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe("the Status column", () => {
|
|
338
|
+
test("reads Member once any invitation has been accepted", () => {
|
|
339
|
+
renderCell(
|
|
340
|
+
"Status",
|
|
341
|
+
buildGroupedRow([
|
|
342
|
+
{
|
|
343
|
+
id: "m1",
|
|
344
|
+
userId: "u1",
|
|
345
|
+
teamId: "t1",
|
|
346
|
+
hasAcceptedInvitation: true,
|
|
347
|
+
},
|
|
348
|
+
]),
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
expect(screen.getByText("Member")).toBeInTheDocument();
|
|
352
|
+
expect(screen.queryByText(/Pending/)).not.toBeInTheDocument();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("reads Invitation Sent while nothing has been accepted", () => {
|
|
356
|
+
renderCell(
|
|
357
|
+
"Status",
|
|
358
|
+
buildGroupedRow([
|
|
359
|
+
{
|
|
360
|
+
id: "m1",
|
|
361
|
+
userId: "u1",
|
|
362
|
+
teamId: "t1",
|
|
363
|
+
hasAcceptedInvitation: false,
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: "m2",
|
|
367
|
+
userId: "u1",
|
|
368
|
+
teamId: "t2",
|
|
369
|
+
hasAcceptedInvitation: false,
|
|
370
|
+
},
|
|
371
|
+
]),
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
expect(screen.getByText("Invitation Sent")).toBeInTheDocument();
|
|
375
|
+
expect(screen.queryByText("Member")).not.toBeInTheDocument();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
/*
|
|
379
|
+
* Per-team invitation state used to be visible because each row WAS one
|
|
380
|
+
* team. On a grouped row it would disappear entirely without this.
|
|
381
|
+
*/
|
|
382
|
+
test("still surfaces invitations the person has not accepted yet", () => {
|
|
383
|
+
renderCell(
|
|
384
|
+
"Status",
|
|
385
|
+
buildGroupedRow([
|
|
386
|
+
{
|
|
387
|
+
id: "m1",
|
|
388
|
+
userId: "u1",
|
|
389
|
+
teamId: "t1",
|
|
390
|
+
hasAcceptedInvitation: true,
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
id: "m2",
|
|
394
|
+
userId: "u1",
|
|
395
|
+
teamId: "t2",
|
|
396
|
+
hasAcceptedInvitation: false,
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
id: "m3",
|
|
400
|
+
userId: "u1",
|
|
401
|
+
teamId: "t3",
|
|
402
|
+
hasAcceptedInvitation: false,
|
|
403
|
+
},
|
|
404
|
+
]),
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
expect(screen.getByText("Member")).toBeInTheDocument();
|
|
408
|
+
expect(screen.getByText("2 Invitations Pending")).toBeInTheDocument();
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("counts a single pending invitation in the singular", () => {
|
|
412
|
+
renderCell(
|
|
413
|
+
"Status",
|
|
414
|
+
buildGroupedRow([
|
|
415
|
+
{
|
|
416
|
+
id: "m1",
|
|
417
|
+
userId: "u1",
|
|
418
|
+
teamId: "t1",
|
|
419
|
+
hasAcceptedInvitation: true,
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
id: "m2",
|
|
423
|
+
userId: "u1",
|
|
424
|
+
teamId: "t2",
|
|
425
|
+
hasAcceptedInvitation: false,
|
|
426
|
+
},
|
|
427
|
+
]),
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
expect(screen.getByText("1 Invitation Pending")).toBeInTheDocument();
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
/*
|
|
434
|
+
* The server can only order by the stored per-membership column, which on
|
|
435
|
+
* a grouped row disagrees with the derived one on screen - so the control
|
|
436
|
+
* has to be off rather than visibly not sorting.
|
|
437
|
+
*/
|
|
438
|
+
test("offers no sort control, because the value is derived", () => {
|
|
439
|
+
expect(columnTitled("Status").disableSort).toBe(true);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
describe("removing someone", () => {
|
|
444
|
+
test("labels the action as removal from the project, not from a team", () => {
|
|
445
|
+
expect(capturedTableProps!.deleteButtonText).toBe("Remove from Project");
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
});
|