Skip to content

Commit 06336ea

Browse files
committed
enable modifying multiple groups
1 parent aaba705 commit 06336ea

File tree

7 files changed

+94
-39
lines changed

7 files changed

+94
-39
lines changed

src/api/functions/entraId.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
commChairsGroupId,
3+
commChairsTestingGroupId,
24
execCouncilGroupId,
35
execCouncilTestingGroupId,
46
genericConfig,
@@ -214,6 +216,8 @@ export async function modifyGroup(
214216
execCouncilTestingGroupId,
215217
officersGroupId,
216218
officersGroupTestingId,
219+
commChairsGroupId,
220+
commChairsTestingGroupId,
217221
];
218222
if (
219223
paidMemberRequiredGroups.includes(group) &&

src/common/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ export const officersGroupId = "ff49e948-4587-416b-8224-65147540d5fc";
4444
export const officersGroupTestingId = "0e6e9199-506f-4ede-9d1b-e73f6811c9e5";
4545
export const execCouncilGroupId = "ad81254b-4eeb-4c96-8191-3acdce9194b1";
4646
export const execCouncilTestingGroupId = "dbe18eb2-9675-46c4-b1ef-749a6db4fedd";
47+
export const commChairsTestingGroupId = "d714adb7-07bb-4d4d-a40a-b035bc2a35a3";
48+
export const commChairsGroupId = "105e7d32-7289-435e-a67a-552c7f215507";
49+
export const miscTestingGroupId = "ff25ec56-6a33-420d-bdb0-51d8a3920e46";
4750

4851
const genericConfig: GenericConfigType = {
4952
EventsDynamoTableName: "infra-core-api-events",

src/ui/config.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { execCouncilGroupId, execCouncilTestingGroupId } from '@common/config';
1+
import {
2+
commChairsGroupId,
3+
commChairsTestingGroupId,
4+
execCouncilGroupId,
5+
execCouncilTestingGroupId,
6+
miscTestingGroupId,
7+
} from '@common/config';
28

39
export const runEnvironments = ['dev', 'prod', 'local-dev'] as const;
410
// local dev should be used when you want to test against a local instance of the API
@@ -8,12 +14,16 @@ export type RunEnvironment = (typeof runEnvironments)[number];
814
export type ValidServices = (typeof services)[number];
915
export type ValidService = ValidServices;
1016

17+
export type KnownGroups = {
18+
Exec: string;
19+
CommChairs: string;
20+
StripeLinkCreators: string;
21+
};
22+
1123
export type ConfigType = {
1224
AadValidClientId: string;
1325
ServiceConfiguration: Record<ValidServices, ServiceConfiguration>;
14-
KnownGroupMappings: {
15-
Exec: string;
16-
};
26+
KnownGroupMappings: KnownGroups;
1727
};
1828

1929
export type ServiceConfiguration = {
@@ -58,6 +68,8 @@ const environmentConfig: EnvironmentConfigType = {
5868
},
5969
KnownGroupMappings: {
6070
Exec: execCouncilTestingGroupId,
71+
CommChairs: commChairsTestingGroupId,
72+
StripeLinkCreators: miscTestingGroupId,
6173
},
6274
},
6375
dev: {
@@ -87,6 +99,8 @@ const environmentConfig: EnvironmentConfigType = {
8799
},
88100
KnownGroupMappings: {
89101
Exec: execCouncilTestingGroupId,
102+
CommChairs: commChairsTestingGroupId,
103+
StripeLinkCreators: miscTestingGroupId,
90104
},
91105
},
92106
prod: {
@@ -116,6 +130,8 @@ const environmentConfig: EnvironmentConfigType = {
116130
},
117131
KnownGroupMappings: {
118132
Exec: execCouncilGroupId,
133+
CommChairs: commChairsGroupId,
134+
StripeLinkCreators: '675203eb-fbb9-4789-af2f-e87a3243f8e6',
119135
},
120136
},
121137
} as const;

src/ui/pages/iam/GroupMemberManagement.test.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,7 @@ describe('Exec Group Management Panel tests', () => {
3030
it('renders with no members', async () => {
3131
const fetchMembers = async () => [];
3232
const updateMembers = async () => ({ success: [] });
33-
3433
await renderComponent(fetchMembers, updateMembers);
35-
36-
expect(screen.getByText('Exec Council Group Management')).toBeInTheDocument();
3734
expect(screen.queryByText(/.*@.*/)).not.toBeInTheDocument();
3835
});
3936

src/ui/pages/iam/GroupMemberManagement.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ const GroupMemberManagement: React.FC<GroupMemberManagementProps> = ({
169169
</Table.Td>
170170
<Table.Td>
171171
<Badge color="blue" variant="light">
172-
Active
172+
Queued for addition
173173
</Badge>
174174
</Table.Td>
175175
<Table.Td>
@@ -189,10 +189,6 @@ const GroupMemberManagement: React.FC<GroupMemberManagementProps> = ({
189189

190190
return (
191191
<div>
192-
<Text fw={500} mb={4} size="lg">
193-
Exec Council Group Management
194-
</Text>
195-
196192
<Table verticalSpacing="sm">
197193
<Table.Thead>
198194
<Table.Tr>

src/ui/pages/iam/ManageIam.page.tsx

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
1-
import React from 'react';
2-
import { Title, SimpleGrid, Container } from '@mantine/core';
1+
import React, { useState } from 'react';
2+
import { Title, SimpleGrid, Container, Select, Group, Stack, Text } from '@mantine/core';
33
import { AuthGuard } from '@ui/components/AuthGuard';
44
import { useApi } from '@ui/util/api';
55
import { AppRoles } from '@common/roles';
66
import UserInvitePanel from './UserInvitePanel';
77
import GroupMemberManagement from './GroupMemberManagement';
8-
import {
9-
EntraActionResponse,
10-
GroupMemberGetResponse,
11-
GroupModificationPatchRequest,
12-
} from '@common/types/iam';
8+
import { EntraActionResponse, GroupMemberGetResponse } from '@common/types/iam';
139
import { transformCommaSeperatedName } from '@common/utils';
14-
import { getRunEnvironmentConfig } from '@ui/config';
10+
import { getRunEnvironmentConfig, KnownGroups } from '@ui/config';
11+
12+
const userGroupMappings: KnownGroups = {
13+
Exec: 'Executive Council',
14+
CommChairs: 'Committee Chairs',
15+
StripeLinkCreators: 'Stripe Link Creators',
16+
};
1517

1618
export const ManageIamPage = () => {
1719
const api = useApi('core');
18-
const groupId = getRunEnvironmentConfig().KnownGroupMappings.Exec;
20+
const groupMappings = getRunEnvironmentConfig().KnownGroupMappings;
21+
const groupOptions = Object.entries(groupMappings).map(([key, value]) => ({
22+
label: userGroupMappings[key as keyof KnownGroups] || key,
23+
value: `${key}_${value}`, // to ensure that the same group for multiple roles still renders
24+
}));
25+
26+
const [selectedGroup, setSelectedGroup] = useState(groupOptions[0]?.value || '');
1927

2028
const handleInviteSubmit = async (emailList: string[]) => {
2129
try {
2230
const response = await api.post('/api/v1/iam/inviteUsers', {
2331
emails: emailList,
2432
});
25-
return response.data;
33+
return response.data as EntraActionResponse;
2634
} catch (error: any) {
2735
console.error('Failed to invite users:', error);
2836
return {
@@ -35,32 +43,36 @@ export const ManageIamPage = () => {
3543
}
3644
};
3745

38-
const getExecMembers = async () => {
46+
const getGroupMembers = async (selectedGroup: string) => {
3947
try {
40-
const response = await api.get(`/api/v1/iam/groups/${groupId}`);
41-
const responseMapped = response.data
42-
.map((x: any) => ({
48+
const response = await api.get(`/api/v1/iam/groups/${selectedGroup.split('_')[1]}`);
49+
const data = response.data as GroupMemberGetResponse;
50+
const responseMapped = data
51+
.map((x) => ({
4352
...x,
4453
name: transformCommaSeperatedName(x.name),
4554
}))
46-
.sort((x: any, y: any) => (x.name > y.name ? 1 : x.name < y.name ? -1 : 0));
47-
return responseMapped as GroupMemberGetResponse;
48-
} catch (error: any) {
55+
.sort((a, b) => (a.name > b.name ? 1 : a.name < b.name ? -1 : 0));
56+
return responseMapped;
57+
} catch (error) {
4958
console.error('Failed to get users:', error);
5059
return [];
5160
}
5261
};
5362

54-
const updateExecMembers = async (toAdd: string[], toRemove: string[]) => {
55-
const allMembers = toAdd.concat(toRemove);
63+
const updateGroupMembers = async (toAdd: string[], toRemove: string[]) => {
64+
const allMembers = [...toAdd, ...toRemove];
5665
try {
57-
const response = await api.patch(`/api/v1/iam/groups/${groupId}`, {
66+
const response = await api.patch(`/api/v1/iam/groups/${selectedGroup.split('_')[1]}`, {
5867
remove: toRemove,
5968
add: toAdd,
60-
} as GroupModificationPatchRequest);
61-
return response.data as EntraActionResponse;
62-
} catch (error: any) {
63-
console.error('Failed to get users:', error);
69+
});
70+
return response.data;
71+
} catch (error) {
72+
if (!(error instanceof Error)) {
73+
throw error;
74+
}
75+
console.error('Failed to modify group members:', error);
6476
return {
6577
success: [],
6678
failure: allMembers.map((email) => ({
@@ -82,9 +94,26 @@ export const ManageIamPage = () => {
8294
resourceDef={{ service: 'core', validRoles: [AppRoles.IAM_ADMIN] }}
8395
isAppShell={false}
8496
>
85-
<GroupMemberManagement fetchMembers={getExecMembers} updateMembers={updateExecMembers} />
97+
<Stack>
98+
<Text fw={500} mb={4} size="lg">
99+
Group Management
100+
</Text>
101+
<Select
102+
label="Select Group"
103+
data={groupOptions}
104+
value={selectedGroup}
105+
clearable={false}
106+
onChange={(value) => value && setSelectedGroup(value)}
107+
placeholder="Choose a group to manage"
108+
/>
109+
<GroupMemberManagement
110+
fetchMembers={() => {
111+
return getGroupMembers(selectedGroup);
112+
}}
113+
updateMembers={updateGroupMembers}
114+
/>
115+
</Stack>
86116
</AuthGuard>
87-
{/* For future panels, make sure to add an auth guard if not every IAM role can see it. */}
88117
</SimpleGrid>
89118
</AuthGuard>
90119
);

src/ui/pages/profile/ManageProfile.page.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { UserProfileData, UserProfileDataBase } from '@common/types/msGraphApi';
66
import { ManageProfileComponent } from './ManageProfileComponent';
77
import { useNavigate, useSearchParams } from 'react-router-dom';
88
import { useAuth } from '@ui/components/AuthContext';
9+
import { transformCommaSeperatedName } from '@common/utils';
910

1011
export const ManageProfilePage: React.FC = () => {
1112
const graphApi = useApi('msGraphApi');
@@ -27,7 +28,16 @@ export const ManageProfilePage: React.FC = () => {
2728
enhanced.discordUsername = discordUsername[0].replace('@discord', '');
2829
enhanced.otherMails = enhanced.otherMails?.filter((x) => !x.endsWith('@discord'));
2930
}
30-
return enhanced;
31+
const normalizedName = transformCommaSeperatedName(enhanced.displayName || '');
32+
const extractedFirstName = enhanced.givenName || normalizedName.split(' ')[0];
33+
let extractedLastName = enhanced.surname || normalizedName.split(' ')[1];
34+
extractedLastName = extractedLastName.slice(1, extractedLastName.length);
35+
return {
36+
...enhanced,
37+
displayName: normalizedName,
38+
givenName: extractedFirstName,
39+
surname: extractedLastName,
40+
};
3141
};
3242

3343
const setProfile = async (data: UserProfileData) => {

0 commit comments

Comments
 (0)