Skip to content

Commit 99fc3ce

Browse files
author
dushimsam
committed
feat(UI): added manage group-users page
Signed-off-by: dushimsam <[email protected]>
1 parent 133c1af commit 99fc3ce

File tree

11 files changed

+532
-4
lines changed

11 files changed

+532
-4
lines changed

src/Routes.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ const UploadDelete = React.lazy(() => import("pages/Organize/Uploads/Delete"));
9191
// Admin Pages
9292
const GroupCreate = React.lazy(() => import("pages/Admin/Group/Create"));
9393
const DeleteUser = React.lazy(() => import("pages/Admin/Users/Delete"));
94+
const ManageGroup = React.lazy(() => import("pages/Admin/Group/Manage"));
9495
const AddLicense = React.lazy(() => import("pages/Admin/License/Create"));
9596
const SelectLicense = React.lazy(() =>
9697
import("pages/Admin/License/SelectLicense")
@@ -284,6 +285,11 @@ const Routes = () => {
284285
path={routes.admin.group.create}
285286
component={GroupCreate}
286287
/>
288+
<AdminLayout
289+
exact
290+
path={routes.admin.group.manageGroup}
291+
component={ManageGroup}
292+
/>
287293
<AdminLayout
288294
exact
289295
path={routes.admin.license.create}

src/api/groups.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,42 @@ export const createGroupApi = (name) => {
5050
addGroupName: false,
5151
});
5252
};
53+
54+
// Get all group members
55+
export const getAllGroupMembersApi = (groupId) => {
56+
const url = endpoints.admin.groups.getAllGroupMembers(groupId);
57+
return sendRequest({
58+
url,
59+
method: "GET",
60+
headers: {
61+
Authorization: getToken(),
62+
},
63+
});
64+
};
65+
66+
// Remove Group Member
67+
export const removeGroupMemberApi = (groupId, userId) => {
68+
const url = endpoints.admin.groups.removeGroupMember(groupId, userId);
69+
return sendRequest({
70+
url,
71+
method: "DELETE",
72+
headers: {
73+
Authorization: getToken(),
74+
},
75+
});
76+
};
77+
78+
// Change user permission
79+
export const changeUserPermissionApi = (groupId, userId, permission) => {
80+
const url = endpoints.admin.groups.changeUserPermission(groupId, userId);
81+
return sendRequest({
82+
url,
83+
method: "PUT",
84+
headers: {
85+
Authorization: getToken(),
86+
},
87+
body: {
88+
perm: permission,
89+
},
90+
});
91+
};

src/api/groups.test.js

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515

1616
import sendRequest from "api/sendRequest";
1717
import endpoints from "constants/endpoints";
18-
import { createGroupApi, getAllGroupsApi } from "api/groups";
18+
import {
19+
changeUserPermissionApi,
20+
createGroupApi,
21+
getAllGroupMembersApi,
22+
getAllGroupsApi, removeGroupMemberApi,
23+
} from "api/groups";
1924
import { getToken } from "shared/authHelper";
2025

2126
jest.mock("api/sendRequest");
@@ -56,4 +61,63 @@ describe("groups", () => {
5661
})
5762
);
5863
});
64+
65+
test("removeGroupMemberApi", () => {
66+
const groupId = 2;
67+
const userId = 1;
68+
const url = endpoints.admin.groups.removeGroupMember(groupId, userId);
69+
sendRequest.mockImplementation(() => true);
70+
71+
expect(removeGroupMemberApi(groupId, userId)).toBe(sendRequest({}));
72+
expect(sendRequest).toHaveBeenCalledWith(
73+
expect.objectContaining({
74+
url,
75+
method: "DELETE",
76+
headers: {
77+
Authorization: getToken(),
78+
},
79+
})
80+
);
81+
});
82+
83+
test("getAllGroupMembersApi", () => {
84+
const groupId = 1;
85+
const url = endpoints.admin.groups.getAllGroupMembers(groupId);
86+
sendRequest.mockImplementation(() => true);
87+
88+
expect(getAllGroupMembersApi(groupId)).toBe(sendRequest({}));
89+
expect(sendRequest).toHaveBeenCalledWith(
90+
expect.objectContaining({
91+
url,
92+
method: "GET",
93+
headers: {
94+
Authorization: getToken(),
95+
},
96+
})
97+
);
98+
});
99+
100+
test("changeUserPermissionApi", () => {
101+
const groupId = 1;
102+
const userId = 1;
103+
const permission = 2;
104+
const url = endpoints.admin.groups.changeUserPermission(groupId, userId);
105+
sendRequest.mockImplementation(() => true);
106+
107+
expect(changeUserPermissionApi(groupId, userId, permission)).toBe(
108+
sendRequest({})
109+
);
110+
expect(sendRequest).toHaveBeenCalledWith(
111+
expect.objectContaining({
112+
url,
113+
method: "PUT",
114+
headers: {
115+
Authorization: getToken(),
116+
},
117+
body: {
118+
perm: permission,
119+
},
120+
})
121+
);
122+
});
59123
});
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
Copyright (C) 2022 Samuel Dushimimana ([email protected])
3+
4+
SPDX-License-Identifier: GPL-2.0
5+
6+
This program is free software; you can redistribute it and/or
7+
modify it under the terms of the GNU General Public License
8+
version 2 as published by the Free Software Foundation.
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License along
15+
with this program; if not, write to the Free Software Foundation, Inc.,
16+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17+
*/
18+
19+
import React, { useEffect, useState } from "react";
20+
21+
import { InputContainer } from "components/Widgets";
22+
23+
// Required functions for calling APIs
24+
import { changeUserPermission, removeGroupMember } from "services/groups";
25+
26+
import PropTypes from "prop-types";
27+
28+
// Required constants
29+
import { userPermissions } from "constants/constants";
30+
31+
const ChangePermissionContainer = ({
32+
groupMembers,
33+
noneGroupMembers,
34+
setShowMessage,
35+
setMessage,
36+
currGroup,
37+
handleFetchGroupMembers,
38+
}) => {
39+
const [currUser, setCurrentUser] = useState(null);
40+
const [currNonMember, setCurrentNonMember] = useState(null);
41+
42+
useEffect(() => {
43+
if (groupMembers.length > 0) {
44+
setCurrentUser({
45+
user: groupMembers[0].id,
46+
perm: groupMembers[0].group_perm,
47+
});
48+
}
49+
}, [groupMembers]);
50+
51+
useEffect(() => {
52+
if (noneGroupMembers.length > 0) {
53+
setCurrentNonMember({
54+
user: noneGroupMembers[0].id,
55+
perm: -1,
56+
});
57+
}
58+
}, [noneGroupMembers]);
59+
60+
const handleChangeCurrUser = async (newUser, isMember = true) => {
61+
if (isMember) {
62+
let perm;
63+
groupMembers.forEach((item) => {
64+
if (item.id === parseInt(newUser, 10)) {
65+
perm = item.group_perm;
66+
}
67+
});
68+
setCurrentUser({ user: parseInt(newUser, 10), perm });
69+
} else {
70+
setCurrentNonMember({ user: parseInt(newUser, 10), perm: -1 });
71+
}
72+
};
73+
74+
const handleSetNewPermission = async (newPerm, isMember = true) => {
75+
try {
76+
let res;
77+
78+
if (parseInt(newPerm, 10) === -1) {
79+
res = await removeGroupMember(currGroup, currUser.user);
80+
} else {
81+
res = await changeUserPermission(
82+
currGroup,
83+
isMember ? currUser.user : currNonMember.user,
84+
parseInt(newPerm, 10)
85+
);
86+
}
87+
88+
setShowMessage(true);
89+
setMessage({
90+
type: "success",
91+
text: res.message,
92+
});
93+
94+
handleFetchGroupMembers(currGroup);
95+
} catch (e) {
96+
setMessage({
97+
type: "danger",
98+
text: e.message,
99+
});
100+
} finally {
101+
setTimeout(() => {
102+
setShowMessage(false);
103+
}, [3000]);
104+
}
105+
};
106+
return (
107+
<>
108+
<tr>
109+
<td>
110+
<InputContainer
111+
type="select"
112+
name="name"
113+
options={groupMembers}
114+
id="select-user-tag"
115+
value={currUser?.user}
116+
property="name"
117+
onChange={(e) => handleChangeCurrUser(e.target.value)}
118+
/>
119+
</td>
120+
<td>
121+
<InputContainer
122+
type="select"
123+
name="name"
124+
options={userPermissions}
125+
id="select-tag"
126+
value={currUser?.perm}
127+
property="name"
128+
onChange={(e) => handleSetNewPermission(e.target.value)}
129+
/>
130+
</td>
131+
</tr>
132+
133+
{noneGroupMembers.length > 0 ? (
134+
<tr>
135+
<td>
136+
<InputContainer
137+
type="select"
138+
name="name"
139+
options={noneGroupMembers}
140+
id="select-user-tag"
141+
value={currNonMember?.user}
142+
property="name"
143+
onChange={(e) => handleChangeCurrUser(e.target.value, false)}
144+
/>
145+
</td>
146+
<td>
147+
<InputContainer
148+
type="select"
149+
name="name"
150+
options={userPermissions}
151+
id="select-tag"
152+
value={-1}
153+
property="name"
154+
onChange={(e) => handleSetNewPermission(e.target.value, false)}
155+
/>
156+
</td>
157+
</tr>
158+
) : (
159+
<></>
160+
)}
161+
</>
162+
);
163+
};
164+
165+
ChangePermissionContainer.propTypes = {
166+
groupMembers: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.any)).isRequired,
167+
noneGroupMembers: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.any))
168+
.isRequired,
169+
setMessage: PropTypes.func,
170+
currGroup: PropTypes.number,
171+
handleFetchGroupMembers: PropTypes.func,
172+
setShowMessage: PropTypes.func,
173+
};
174+
175+
export default ChangePermissionContainer;

src/components/Header/index.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,9 @@ const Header = () => {
255255
</NavDropdown.Item>
256256
<NavDropdown.Item
257257
as={Link}
258-
to={routes.admin.group.delete}
258+
to={routes.admin.group.manageGroup}
259259
>
260-
Delete Group
260+
Manage Group Users
261261
</NavDropdown.Item>
262262
</div>
263263
</DropdownButton>

src/components/Widgets/Input/index.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const InputContainer = ({
2626
id,
2727
className,
2828
onChange,
29+
defaultValue = null,
2930
children,
3031
checked = false,
3132
placeholder = null,
@@ -70,6 +71,7 @@ const InputContainer = ({
7071
className ? `mr-2 form-control ${className}` : `mr-2 form-control`
7172
}
7273
value={value}
74+
defaultValue={defaultValue}
7375
onChange={onChange}
7476
multiple={multiple && multiple}
7577
size={multiple ? "15" : ""}
@@ -125,6 +127,7 @@ InputContainer.propTypes = {
125127
onChange: PropTypes.func,
126128
checked: PropTypes.bool,
127129
disabled: PropTypes.bool,
130+
defaultValue: PropTypes.string,
128131
children: PropTypes.node,
129132
options: PropTypes.arrayOf(
130133
PropTypes.shape({

src/constants/constants.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,23 @@ export const initialMantainanceFields = {
260260
rmvRepoOldFiles1: false,
261261
rmvRepoOldFiles2: false,
262262
};
263+
264+
// eslint-disable-next-line camelcase
265+
export const userPermissions = [
266+
{
267+
id: -1,
268+
name: "None",
269+
},
270+
{
271+
id: 0,
272+
name: "User",
273+
},
274+
{
275+
id: 1,
276+
name: "Admin",
277+
},
278+
{
279+
id: 2,
280+
name: "Advisor",
281+
},
282+
];

src/constants/endpoints.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ const endpoints = {
7171
groups: {
7272
create: () => `${apiUrl}/groups`,
7373
getAll: () => `${apiUrl}/groups`,
74+
getAllGroupMembers: (groupId) => `${apiUrl}/groups/${groupId}/members`,
75+
changeUserPermission: (groupId, userId) =>
76+
`${apiUrl}/groups/${groupId}/user/${userId}`,
77+
removeGroupMember: (groupId, userId) =>
78+
`${apiUrl}/groups/${groupId}/user/${userId}`,
7479
},
7580
},
7681
license: {

src/constants/routes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const routes = {
6767
group: {
6868
create: "/admin/group/create",
6969
delete: "/admin/group/delete",
70+
manageGroup: "/admin/group/manage",
7071
},
7172
users: {
7273
delete: "/admin/users/delete",

0 commit comments

Comments
 (0)