@oneuptime/common 12.0.1 → 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/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/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/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/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/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/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/Database/TruncateColumnValue.test.ts +179 -0
- package/Tests/Types/Events/Recurring.test.ts +157 -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/StatusPage/StatusPageReport.ts +11 -0
- package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -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/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/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/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/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/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/StatusPage/StatusPageReportPeriodType.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
- 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,633 @@
|
|
|
1
|
+
import { describe, expect, it } from "@jest/globals";
|
|
2
|
+
import TeamMembersByUser, {
|
|
3
|
+
ProjectUserRow,
|
|
4
|
+
} from "../../../UI/Utils/TeamMembersByUser";
|
|
5
|
+
import Team from "../../../Models/DatabaseModels/Team";
|
|
6
|
+
import TeamMember from "../../../Models/DatabaseModels/TeamMember";
|
|
7
|
+
import User from "../../../Models/DatabaseModels/User";
|
|
8
|
+
import Email from "../../../Types/Email";
|
|
9
|
+
import Name from "../../../Types/Name";
|
|
10
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* The Users table lists TeamMember, which is a (user, team) pair. Before this
|
|
14
|
+
* util a person on three teams rendered as three rows with the same name and
|
|
15
|
+
* avatar, which customers read as three different people. Everything below
|
|
16
|
+
* pins the collapse of those rows into one row per person, because every way
|
|
17
|
+
* it can go wrong is silent: a group key read off the wrong field merges
|
|
18
|
+
* nobody (duplicates come straight back), a shared key merges strangers, and a
|
|
19
|
+
* team that is dropped or double-counted just shows a slightly wrong Teams
|
|
20
|
+
* column that nobody notices until a customer asks who is on which team.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
type MembershipSpec = {
|
|
24
|
+
id?: string | undefined;
|
|
25
|
+
userId?: string | undefined;
|
|
26
|
+
userIdOnUserObject?: string | undefined;
|
|
27
|
+
userName?: string | undefined;
|
|
28
|
+
userEmail?: string | undefined;
|
|
29
|
+
teamId?: string | undefined;
|
|
30
|
+
teamName?: string | undefined;
|
|
31
|
+
hasAcceptedInvitation?: boolean | undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const buildMembership: (spec: MembershipSpec) => TeamMember = (
|
|
35
|
+
spec: MembershipSpec,
|
|
36
|
+
): TeamMember => {
|
|
37
|
+
const teamMember: TeamMember = new TeamMember();
|
|
38
|
+
|
|
39
|
+
if (spec.id) {
|
|
40
|
+
teamMember._id = spec.id;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (spec.userId) {
|
|
44
|
+
teamMember.userId = new ObjectID(spec.userId);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (spec.userId || spec.userIdOnUserObject || spec.userName) {
|
|
48
|
+
const user: User = new User();
|
|
49
|
+
|
|
50
|
+
const userId: string | undefined = spec.userIdOnUserObject || spec.userId;
|
|
51
|
+
|
|
52
|
+
if (userId) {
|
|
53
|
+
user._id = userId;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (spec.userName) {
|
|
57
|
+
user.name = new Name(spec.userName);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (spec.userEmail) {
|
|
61
|
+
user.email = new Email(spec.userEmail);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
teamMember.user = user;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (spec.teamId) {
|
|
68
|
+
teamMember.teamId = new ObjectID(spec.teamId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (spec.teamId || spec.teamName) {
|
|
72
|
+
const team: Team = new Team();
|
|
73
|
+
|
|
74
|
+
if (spec.teamId) {
|
|
75
|
+
team._id = spec.teamId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (spec.teamName) {
|
|
79
|
+
team.name = spec.teamName;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
teamMember.team = team;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
teamMember.hasAcceptedInvitation = spec.hasAcceptedInvitation ?? true;
|
|
86
|
+
|
|
87
|
+
return teamMember;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const teamNamesOf: (row: ProjectUserRow) => Array<string> = (
|
|
91
|
+
row: ProjectUserRow,
|
|
92
|
+
): Array<string> => {
|
|
93
|
+
return row.teamsForUser.map((team: Team) => {
|
|
94
|
+
return team.name?.toString() || "";
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
describe("TeamMembersByUser.getGroupKey", () => {
|
|
99
|
+
it("groups on the membership's userId", () => {
|
|
100
|
+
const key: string | null = TeamMembersByUser.getGroupKey(
|
|
101
|
+
buildMembership({ userId: "user-1", teamId: "team-1" }),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(key).toBe("user-1");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("falls back to the id on the joined user when userId was not selected", () => {
|
|
108
|
+
const teamMember: TeamMember = buildMembership({
|
|
109
|
+
userIdOnUserObject: "user-1",
|
|
110
|
+
teamId: "team-1",
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(teamMember.userId).toBeUndefined();
|
|
114
|
+
expect(TeamMembersByUser.getGroupKey(teamMember)).toBe("user-1");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("returns null when the membership carries no user at all", () => {
|
|
118
|
+
const teamMember: TeamMember = new TeamMember();
|
|
119
|
+
|
|
120
|
+
expect(TeamMembersByUser.getGroupKey(teamMember)).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("TeamMembersByUser.groupByUser", () => {
|
|
125
|
+
it("returns nothing for no memberships", () => {
|
|
126
|
+
expect(TeamMembersByUser.groupByUser([])).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns one row for a user on one team", () => {
|
|
130
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
131
|
+
buildMembership({
|
|
132
|
+
userId: "user-1",
|
|
133
|
+
userName: "Colton Hawkins",
|
|
134
|
+
teamId: "team-1",
|
|
135
|
+
teamName: "NTW_Online Operations",
|
|
136
|
+
}),
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
expect(rows).toHaveLength(1);
|
|
140
|
+
expect(teamNamesOf(rows[0]!)).toEqual(["NTW_Online Operations"]);
|
|
141
|
+
expect(rows[0]!.teamCountForUser).toBe(1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("collapses one user on several teams into a single row carrying every team", () => {
|
|
145
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
146
|
+
buildMembership({
|
|
147
|
+
userId: "user-1",
|
|
148
|
+
userName: "Thomas Stock",
|
|
149
|
+
teamId: "team-1",
|
|
150
|
+
teamName: "NTW_Online Operations",
|
|
151
|
+
}),
|
|
152
|
+
buildMembership({
|
|
153
|
+
userId: "user-1",
|
|
154
|
+
userName: "Thomas Stock",
|
|
155
|
+
teamId: "team-2",
|
|
156
|
+
teamName: "Owners",
|
|
157
|
+
}),
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
expect(rows).toHaveLength(1);
|
|
161
|
+
expect(teamNamesOf(rows[0]!)).toEqual(["NTW_Online Operations", "Owners"]);
|
|
162
|
+
expect(rows[0]!.teamCountForUser).toBe(2);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("keeps different users apart", () => {
|
|
166
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
167
|
+
buildMembership({
|
|
168
|
+
userId: "user-1",
|
|
169
|
+
userName: "Thomas Stock",
|
|
170
|
+
teamId: "team-1",
|
|
171
|
+
teamName: "NTW_Online Operations",
|
|
172
|
+
}),
|
|
173
|
+
buildMembership({
|
|
174
|
+
userId: "user-2",
|
|
175
|
+
userName: "Jason Apel",
|
|
176
|
+
teamId: "team-1",
|
|
177
|
+
teamName: "NTW_Online Operations",
|
|
178
|
+
}),
|
|
179
|
+
buildMembership({
|
|
180
|
+
userId: "user-1",
|
|
181
|
+
userName: "Thomas Stock",
|
|
182
|
+
teamId: "team-2",
|
|
183
|
+
teamName: "Owners",
|
|
184
|
+
}),
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
expect(rows).toHaveLength(2);
|
|
188
|
+
expect(TeamMembersByUser.getDisplayName(rows[0]!)).toBe("Thomas Stock");
|
|
189
|
+
expect(TeamMembersByUser.getDisplayName(rows[1]!)).toBe("Jason Apel");
|
|
190
|
+
expect(teamNamesOf(rows[0]!)).toEqual(["NTW_Online Operations", "Owners"]);
|
|
191
|
+
expect(teamNamesOf(rows[1]!)).toEqual(["NTW_Online Operations"]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("keeps the order the server returned, by first appearance of each user", () => {
|
|
195
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
196
|
+
buildMembership({ userId: "user-3", teamId: "team-1" }),
|
|
197
|
+
buildMembership({ userId: "user-1", teamId: "team-1" }),
|
|
198
|
+
buildMembership({ userId: "user-3", teamId: "team-2" }),
|
|
199
|
+
buildMembership({ userId: "user-2", teamId: "team-1" }),
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
expect(
|
|
203
|
+
rows.map((row: ProjectUserRow) => {
|
|
204
|
+
return row.userId?.toString();
|
|
205
|
+
}),
|
|
206
|
+
).toEqual(["user-3", "user-1", "user-2"]);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("sorts the teams on a row by name, whatever order the memberships arrived in", () => {
|
|
210
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
211
|
+
buildMembership({ userId: "user-1", teamId: "t3", teamName: "Zulu" }),
|
|
212
|
+
buildMembership({ userId: "user-1", teamId: "t1", teamName: "alpha" }),
|
|
213
|
+
buildMembership({ userId: "user-1", teamId: "t2", teamName: "Bravo" }),
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
expect(teamNamesOf(rows[0]!)).toEqual(["alpha", "Bravo", "Zulu"]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("lists a team once even if the same membership pair comes back twice", () => {
|
|
220
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
221
|
+
buildMembership({ userId: "user-1", teamId: "team-1", teamName: "Ops" }),
|
|
222
|
+
buildMembership({ userId: "user-1", teamId: "team-1", teamName: "Ops" }),
|
|
223
|
+
]);
|
|
224
|
+
|
|
225
|
+
expect(teamNamesOf(rows[0]!)).toEqual(["Ops"]);
|
|
226
|
+
expect(rows[0]!.teamCountForUser).toBe(1);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("de-dupes on the team's own id when the membership has no teamId", () => {
|
|
230
|
+
const first: TeamMember = buildMembership({
|
|
231
|
+
userId: "user-1",
|
|
232
|
+
teamId: "team-1",
|
|
233
|
+
teamName: "Ops",
|
|
234
|
+
});
|
|
235
|
+
const second: TeamMember = buildMembership({
|
|
236
|
+
userId: "user-1",
|
|
237
|
+
teamId: "team-1",
|
|
238
|
+
teamName: "Ops",
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// The select asked for the joined team but not the foreign key.
|
|
242
|
+
delete first.teamId;
|
|
243
|
+
delete second.teamId;
|
|
244
|
+
|
|
245
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
246
|
+
first,
|
|
247
|
+
second,
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
expect(teamNamesOf(rows[0]!)).toEqual(["Ops"]);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("keeps a row whose membership has no team, with an empty team list", () => {
|
|
254
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
255
|
+
buildMembership({ userId: "user-1", userName: "Karsten Huster" }),
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
expect(rows).toHaveLength(1);
|
|
259
|
+
expect(rows[0]!.teamsForUser).toEqual([]);
|
|
260
|
+
expect(rows[0]!.teamCountForUser).toBe(0);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("never merges memberships that carry no user, however many there are", () => {
|
|
264
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
265
|
+
new TeamMember(),
|
|
266
|
+
new TeamMember(),
|
|
267
|
+
]);
|
|
268
|
+
|
|
269
|
+
expect(rows).toHaveLength(2);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("counts a user as a member as soon as one of their invitations is accepted", () => {
|
|
273
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
274
|
+
buildMembership({
|
|
275
|
+
userId: "user-1",
|
|
276
|
+
teamId: "team-1",
|
|
277
|
+
hasAcceptedInvitation: false,
|
|
278
|
+
}),
|
|
279
|
+
buildMembership({
|
|
280
|
+
userId: "user-1",
|
|
281
|
+
teamId: "team-2",
|
|
282
|
+
hasAcceptedInvitation: true,
|
|
283
|
+
}),
|
|
284
|
+
]);
|
|
285
|
+
|
|
286
|
+
expect(rows[0]!.hasAcceptedInvitation).toBe(true);
|
|
287
|
+
expect(rows[0]!.pendingTeamCountForUser).toBe(1);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("leaves a user with no accepted invitation as an invitee", () => {
|
|
291
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
292
|
+
buildMembership({
|
|
293
|
+
userId: "user-1",
|
|
294
|
+
teamId: "team-1",
|
|
295
|
+
hasAcceptedInvitation: false,
|
|
296
|
+
}),
|
|
297
|
+
buildMembership({
|
|
298
|
+
userId: "user-1",
|
|
299
|
+
teamId: "team-2",
|
|
300
|
+
hasAcceptedInvitation: false,
|
|
301
|
+
}),
|
|
302
|
+
]);
|
|
303
|
+
|
|
304
|
+
expect(rows[0]!.hasAcceptedInvitation).toBe(false);
|
|
305
|
+
expect(rows[0]!.pendingTeamCountForUser).toBe(2);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("reports no pending invitations when every membership is accepted", () => {
|
|
309
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
310
|
+
buildMembership({ userId: "user-1", teamId: "team-1" }),
|
|
311
|
+
buildMembership({ userId: "user-1", teamId: "team-2" }),
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
expect(rows[0]!.hasAcceptedInvitation).toBe(true);
|
|
315
|
+
expect(rows[0]!.pendingTeamCountForUser).toBe(0);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
/*
|
|
319
|
+
* The row's id identifies the person, and the table matches a selected row
|
|
320
|
+
* to an on-screen row by that id alone. It fetches the visible page and the
|
|
321
|
+
* "select all" set with two different sorts (the bulk fetch appends `_id` as
|
|
322
|
+
* a tiebreaker), so a first-seen id would make the same person carry a
|
|
323
|
+
* different id in the two results and every multi-team user's checkbox would
|
|
324
|
+
* silently fail to tick. Hence: lowest membership id, independent of the
|
|
325
|
+
* order the server happened to return.
|
|
326
|
+
*/
|
|
327
|
+
it("gives a row the same id whatever order the memberships arrive in", () => {
|
|
328
|
+
const oneOrder: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
329
|
+
buildMembership({ id: "member-3", userId: "user-1", teamId: "team-3" }),
|
|
330
|
+
buildMembership({ id: "member-1", userId: "user-1", teamId: "team-1" }),
|
|
331
|
+
buildMembership({ id: "member-2", userId: "user-1", teamId: "team-2" }),
|
|
332
|
+
]);
|
|
333
|
+
|
|
334
|
+
const otherOrder: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
335
|
+
buildMembership({ id: "member-2", userId: "user-1", teamId: "team-2" }),
|
|
336
|
+
buildMembership({ id: "member-3", userId: "user-1", teamId: "team-3" }),
|
|
337
|
+
buildMembership({ id: "member-1", userId: "user-1", teamId: "team-1" }),
|
|
338
|
+
]);
|
|
339
|
+
|
|
340
|
+
expect(oneOrder[0]!._id).toBe("member-1");
|
|
341
|
+
expect(otherOrder[0]!._id).toBe(oneOrder[0]!._id);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("keeps the row id pointing at a real membership of that user", () => {
|
|
345
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
346
|
+
buildMembership({ id: "member-9", userId: "user-1", teamId: "team-1" }),
|
|
347
|
+
buildMembership({ id: "member-4", userId: "user-1", teamId: "team-2" }),
|
|
348
|
+
]);
|
|
349
|
+
|
|
350
|
+
expect(["member-9", "member-4"]).toContain(rows[0]!._id);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("leaves a single-membership row on its own id", () => {
|
|
354
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
355
|
+
buildMembership({ id: "member-7", userId: "user-1", teamId: "team-1" }),
|
|
356
|
+
]);
|
|
357
|
+
|
|
358
|
+
expect(rows[0]!._id).toBe("member-7");
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("gives different users different row ids", () => {
|
|
362
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
363
|
+
buildMembership({ id: "member-1", userId: "user-1", teamId: "team-1" }),
|
|
364
|
+
buildMembership({ id: "member-2", userId: "user-2", teamId: "team-1" }),
|
|
365
|
+
buildMembership({ id: "member-3", userId: "user-1", teamId: "team-2" }),
|
|
366
|
+
]);
|
|
367
|
+
|
|
368
|
+
expect(rows[0]!._id).not.toBe(rows[1]!._id);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("does not invent an id for a row whose memberships have none", () => {
|
|
372
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
373
|
+
buildMembership({ userId: "user-1", teamId: "team-1" }),
|
|
374
|
+
buildMembership({ userId: "user-1", teamId: "team-2" }),
|
|
375
|
+
]);
|
|
376
|
+
|
|
377
|
+
expect(rows[0]!._id).toBeUndefined();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("groups by user even when the same person's memberships are not adjacent", () => {
|
|
381
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
382
|
+
buildMembership({ userId: "user-1", teamId: "t1", teamName: "A" }),
|
|
383
|
+
buildMembership({ userId: "user-2", teamId: "t1", teamName: "A" }),
|
|
384
|
+
buildMembership({ userId: "user-3", teamId: "t1", teamName: "A" }),
|
|
385
|
+
buildMembership({ userId: "user-1", teamId: "t2", teamName: "B" }),
|
|
386
|
+
]);
|
|
387
|
+
|
|
388
|
+
expect(rows).toHaveLength(3);
|
|
389
|
+
expect(teamNamesOf(rows[0]!)).toEqual(["A", "B"]);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe("TeamMembersByUser.mergeAllTeamsIntoRows", () => {
|
|
394
|
+
it("replaces a filtered-down team list with the user's full one", () => {
|
|
395
|
+
/*
|
|
396
|
+
* What the table sees when it is filtered by one team: the membership for
|
|
397
|
+
* the other team never came back, so the row claims one team.
|
|
398
|
+
*/
|
|
399
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
400
|
+
buildMembership({
|
|
401
|
+
userId: "user-1",
|
|
402
|
+
teamId: "team-1",
|
|
403
|
+
teamName: "NTW_IT Operations",
|
|
404
|
+
}),
|
|
405
|
+
]);
|
|
406
|
+
|
|
407
|
+
expect(teamNamesOf(rows[0]!)).toEqual(["NTW_IT Operations"]);
|
|
408
|
+
|
|
409
|
+
TeamMembersByUser.mergeAllTeamsIntoRows(rows, [
|
|
410
|
+
buildMembership({
|
|
411
|
+
userId: "user-1",
|
|
412
|
+
teamId: "team-1",
|
|
413
|
+
teamName: "NTW_IT Operations",
|
|
414
|
+
}),
|
|
415
|
+
buildMembership({
|
|
416
|
+
userId: "user-1",
|
|
417
|
+
teamId: "team-2",
|
|
418
|
+
teamName: "Owners",
|
|
419
|
+
}),
|
|
420
|
+
]);
|
|
421
|
+
|
|
422
|
+
expect(teamNamesOf(rows[0]!)).toEqual(["NTW_IT Operations", "Owners"]);
|
|
423
|
+
expect(rows[0]!.teamCountForUser).toBe(2);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it("merges into the right row when several users are on the page", () => {
|
|
427
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
428
|
+
buildMembership({ userId: "user-1", teamId: "team-1", teamName: "Ops" }),
|
|
429
|
+
buildMembership({ userId: "user-2", teamId: "team-1", teamName: "Ops" }),
|
|
430
|
+
]);
|
|
431
|
+
|
|
432
|
+
TeamMembersByUser.mergeAllTeamsIntoRows(rows, [
|
|
433
|
+
buildMembership({ userId: "user-1", teamId: "team-1", teamName: "Ops" }),
|
|
434
|
+
buildMembership({
|
|
435
|
+
userId: "user-1",
|
|
436
|
+
teamId: "team-2",
|
|
437
|
+
teamName: "Owners",
|
|
438
|
+
}),
|
|
439
|
+
buildMembership({ userId: "user-2", teamId: "team-1", teamName: "Ops" }),
|
|
440
|
+
]);
|
|
441
|
+
|
|
442
|
+
expect(teamNamesOf(rows[0]!)).toEqual(["Ops", "Owners"]);
|
|
443
|
+
expect(teamNamesOf(rows[1]!)).toEqual(["Ops"]);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it("re-reads the member status from the unfiltered memberships", () => {
|
|
447
|
+
/*
|
|
448
|
+
* Filtering by a team the user has not accepted yet would otherwise leave
|
|
449
|
+
* a full member of the project labelled "Invitation Sent".
|
|
450
|
+
*/
|
|
451
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
452
|
+
buildMembership({
|
|
453
|
+
userId: "user-1",
|
|
454
|
+
teamId: "team-2",
|
|
455
|
+
hasAcceptedInvitation: false,
|
|
456
|
+
}),
|
|
457
|
+
]);
|
|
458
|
+
|
|
459
|
+
expect(rows[0]!.hasAcceptedInvitation).toBe(false);
|
|
460
|
+
|
|
461
|
+
TeamMembersByUser.mergeAllTeamsIntoRows(rows, [
|
|
462
|
+
buildMembership({
|
|
463
|
+
userId: "user-1",
|
|
464
|
+
teamId: "team-1",
|
|
465
|
+
hasAcceptedInvitation: true,
|
|
466
|
+
}),
|
|
467
|
+
buildMembership({
|
|
468
|
+
userId: "user-1",
|
|
469
|
+
teamId: "team-2",
|
|
470
|
+
hasAcceptedInvitation: false,
|
|
471
|
+
}),
|
|
472
|
+
]);
|
|
473
|
+
|
|
474
|
+
expect(rows[0]!.hasAcceptedInvitation).toBe(true);
|
|
475
|
+
expect(rows[0]!.pendingTeamCountForUser).toBe(1);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it("leaves a row alone when the merge source knows nothing about that user", () => {
|
|
479
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
480
|
+
buildMembership({ userId: "user-1", teamId: "team-1", teamName: "Ops" }),
|
|
481
|
+
]);
|
|
482
|
+
|
|
483
|
+
TeamMembersByUser.mergeAllTeamsIntoRows(rows, [
|
|
484
|
+
buildMembership({
|
|
485
|
+
userId: "user-9",
|
|
486
|
+
teamId: "team-2",
|
|
487
|
+
teamName: "Owners",
|
|
488
|
+
}),
|
|
489
|
+
]);
|
|
490
|
+
|
|
491
|
+
expect(teamNamesOf(rows[0]!)).toEqual(["Ops"]);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it("leaves the rows alone when there is nothing to merge", () => {
|
|
495
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
496
|
+
buildMembership({ userId: "user-1", teamId: "team-1", teamName: "Ops" }),
|
|
497
|
+
]);
|
|
498
|
+
|
|
499
|
+
TeamMembersByUser.mergeAllTeamsIntoRows(rows, []);
|
|
500
|
+
|
|
501
|
+
expect(teamNamesOf(rows[0]!)).toEqual(["Ops"]);
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
describe("TeamMembersByUser.sortRowsByUserName", () => {
|
|
506
|
+
it("sorts by name, ignoring case", () => {
|
|
507
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
508
|
+
buildMembership({ userId: "user-1", userName: "colton Hawkins" }),
|
|
509
|
+
buildMembership({ userId: "user-2", userName: "Aaron Falzon" }),
|
|
510
|
+
buildMembership({ userId: "user-3", userName: "Thomas Stock" }),
|
|
511
|
+
]);
|
|
512
|
+
|
|
513
|
+
expect(
|
|
514
|
+
TeamMembersByUser.sortRowsByUserName(rows).map(
|
|
515
|
+
(row: ProjectUserRow): string => {
|
|
516
|
+
return TeamMembersByUser.getDisplayName(row);
|
|
517
|
+
},
|
|
518
|
+
),
|
|
519
|
+
).toEqual(["Aaron Falzon", "colton Hawkins", "Thomas Stock"]);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("sorts a user with no name under their email", () => {
|
|
523
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
524
|
+
buildMembership({ userId: "user-1", userName: "Zoe Baker" }),
|
|
525
|
+
buildMembership({ userId: "user-2", userEmail: "aaron@company.com" }),
|
|
526
|
+
]);
|
|
527
|
+
|
|
528
|
+
expect(
|
|
529
|
+
TeamMembersByUser.sortRowsByUserName(rows).map(
|
|
530
|
+
(row: ProjectUserRow): string => {
|
|
531
|
+
return TeamMembersByUser.getDisplayName(row);
|
|
532
|
+
},
|
|
533
|
+
),
|
|
534
|
+
).toEqual(["aaron@company.com", "Zoe Baker"]);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it("puts rows with nothing to sort on last rather than at the top", () => {
|
|
538
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
539
|
+
new TeamMember(),
|
|
540
|
+
buildMembership({ userId: "user-1", userName: "Aaron Falzon" }),
|
|
541
|
+
]);
|
|
542
|
+
|
|
543
|
+
const sorted: Array<ProjectUserRow> =
|
|
544
|
+
TeamMembersByUser.sortRowsByUserName(rows);
|
|
545
|
+
|
|
546
|
+
expect(TeamMembersByUser.getDisplayName(sorted[0]!)).toBe("Aaron Falzon");
|
|
547
|
+
expect(TeamMembersByUser.getDisplayName(sorted[1]!)).toBe("");
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it("does not reorder the array it was given", () => {
|
|
551
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
552
|
+
buildMembership({ userId: "user-1", userName: "Zoe Baker" }),
|
|
553
|
+
buildMembership({ userId: "user-2", userName: "Aaron Falzon" }),
|
|
554
|
+
]);
|
|
555
|
+
|
|
556
|
+
TeamMembersByUser.sortRowsByUserName(rows);
|
|
557
|
+
|
|
558
|
+
expect(TeamMembersByUser.getDisplayName(rows[0]!)).toBe("Zoe Baker");
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
describe("the duplicate rows customers reported", () => {
|
|
563
|
+
it("renders the reported project as one row per person", () => {
|
|
564
|
+
/*
|
|
565
|
+
* The exact list from the bug report: Thomas Stock and Jason Apel each
|
|
566
|
+
* appeared twice because each belongs to two teams.
|
|
567
|
+
*/
|
|
568
|
+
const rows: Array<ProjectUserRow> = TeamMembersByUser.groupByUser([
|
|
569
|
+
buildMembership({
|
|
570
|
+
userId: "thomas",
|
|
571
|
+
userName: "Thomas Stock",
|
|
572
|
+
teamId: "online-ops",
|
|
573
|
+
teamName: "NTW_Online Operations",
|
|
574
|
+
}),
|
|
575
|
+
buildMembership({
|
|
576
|
+
userId: "thomas",
|
|
577
|
+
userName: "Thomas Stock",
|
|
578
|
+
teamId: "owners",
|
|
579
|
+
teamName: "Owners",
|
|
580
|
+
}),
|
|
581
|
+
buildMembership({
|
|
582
|
+
userId: "jason",
|
|
583
|
+
userName: "Jason Apel",
|
|
584
|
+
teamId: "owners",
|
|
585
|
+
teamName: "Owners",
|
|
586
|
+
}),
|
|
587
|
+
buildMembership({
|
|
588
|
+
userId: "colton",
|
|
589
|
+
userName: "Colton Hawkins",
|
|
590
|
+
teamId: "online-ops",
|
|
591
|
+
teamName: "NTW_Online Operations",
|
|
592
|
+
}),
|
|
593
|
+
buildMembership({
|
|
594
|
+
userId: "karsten",
|
|
595
|
+
userName: "Karsten Huster",
|
|
596
|
+
teamId: "it-ops",
|
|
597
|
+
teamName: "NTW_IT Operations",
|
|
598
|
+
}),
|
|
599
|
+
buildMembership({
|
|
600
|
+
userId: "jason",
|
|
601
|
+
userName: "Jason Apel",
|
|
602
|
+
teamId: "online-ops",
|
|
603
|
+
teamName: "NTW_Online Operations",
|
|
604
|
+
}),
|
|
605
|
+
]);
|
|
606
|
+
|
|
607
|
+
expect(
|
|
608
|
+
rows.map((row: ProjectUserRow) => {
|
|
609
|
+
return {
|
|
610
|
+
name: TeamMembersByUser.getDisplayName(row),
|
|
611
|
+
teams: teamNamesOf(row),
|
|
612
|
+
};
|
|
613
|
+
}),
|
|
614
|
+
).toEqual([
|
|
615
|
+
{
|
|
616
|
+
name: "Thomas Stock",
|
|
617
|
+
teams: ["NTW_Online Operations", "Owners"],
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
name: "Jason Apel",
|
|
621
|
+
teams: ["NTW_Online Operations", "Owners"],
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
name: "Colton Hawkins",
|
|
625
|
+
teams: ["NTW_Online Operations"],
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
name: "Karsten Huster",
|
|
629
|
+
teams: ["NTW_IT Operations"],
|
|
630
|
+
},
|
|
631
|
+
]);
|
|
632
|
+
});
|
|
633
|
+
});
|