From 810d32cbe30fba43432e1ac4b24772f8b5769896 Mon Sep 17 00:00:00 2001 From: Eric Ryu Date: Wed, 28 Jun 2023 10:10:46 -0400 Subject: [PATCH 1/6] add upload roles endpoint --- frontend/src/api/admin_api.js | 251 +++++++++++++++++++--------------- 1 file changed, 138 insertions(+), 113 deletions(-) diff --git a/frontend/src/api/admin_api.js b/frontend/src/api/admin_api.js index 696c45b0..9bdbe87e 100644 --- a/frontend/src/api/admin_api.js +++ b/frontend/src/api/admin_api.js @@ -1,136 +1,161 @@ -import axios from "axios"; +import axios from 'axios'; let all_courses = async () => { - let token = sessionStorage.getItem("token"); + let token = sessionStorage.getItem('token'); - let config = { - headers: { Authorization: `Bearer ${token}` }, - }; + let config = { + headers: { Authorization: `Bearer ${token}` } + }; - try { - return await axios.get(process.env.REACT_APP_API_URL + "/admin/course/all", config); - } catch (err) { - return err.response; - } -} + try { + return await axios.get(process.env.REACT_APP_API_URL + '/admin/course/all', config); + } catch (err) { + return err.response; + } +}; let impersonate = async (username) => { - let token = sessionStorage.getItem("token"); + let token = sessionStorage.getItem('token'); - const data = { - username: username, - } + const data = { + username: username + }; - let config = { - headers: { Authorization: `Bearer ${token}` } - }; - - try { - return await axios.post(process.env.REACT_APP_API_URL + "/admin/impersonate", data, config); - } catch (err) { - return err.response; - } -} + let config = { + headers: { Authorization: `Bearer ${token}` } + }; + try { + return await axios.post(process.env.REACT_APP_API_URL + '/admin/impersonate', data, config); + } catch (err) { + return err.response; + } +}; let add_course = async (data) => { - let token = sessionStorage.getItem("token"); - - let config = { - headers: { Authorization: `Bearer ${token}` }, - }; - - // let data = { - // course_code: course_code, - // course_session: course_session, - // gitlab_group_id: gitlab_group_id, - // default_token_count: default_token_count, - // token_length: token_length, - // hidden: hidden - // } - - try { - return await axios.post(process.env.REACT_APP_API_URL + "/admin/course/add", data, config); - } catch (err) { - return err.response; - } -} + let token = sessionStorage.getItem('token'); + + let config = { + headers: { Authorization: `Bearer ${token}` } + }; + + // let data = { + // course_code: course_code, + // course_session: course_session, + // gitlab_group_id: gitlab_group_id, + // default_token_count: default_token_count, + // token_length: token_length, + // hidden: hidden + // } + + try { + return await axios.post(process.env.REACT_APP_API_URL + '/admin/course/add', data, config); + } catch (err) { + return err.response; + } +}; let change_course = async (data) => { - let token = sessionStorage.getItem("token"); - - let config = { - headers: { Authorization: `Bearer ${token}` }, - }; - - // let data = { - // course_id: course_id, - // course_code: course_code, - // course_session: course_session, - // gitlab_group_id: gitlab_group_id, - // default_token_count: default_token_count, - // token_length: token_length, - // hidden: hidden - // } - - try { - return await axios.put(process.env.REACT_APP_API_URL + "/admin/course/change", data, config); - } catch (err) { - return err.response; - } -} + let token = sessionStorage.getItem('token'); + + let config = { + headers: { Authorization: `Bearer ${token}` } + }; + + // let data = { + // course_id: course_id, + // course_code: course_code, + // course_session: course_session, + // gitlab_group_id: gitlab_group_id, + // default_token_count: default_token_count, + // token_length: token_length, + // hidden: hidden + // } + + try { + return await axios.put( + process.env.REACT_APP_API_URL + '/admin/course/change', + data, + config + ); + } catch (err) { + return err.response; + } +}; let get_role = async (username) => { - let token = sessionStorage.getItem("token"); - - let config = { - headers: { Authorization: `Bearer ${token}` }, - } - - try { - return await axios.get(process.env.REACT_APP_API_URL + "/admin/role/get?username=" + username, config); - } catch (err) { - return err.response; - } -} + let token = sessionStorage.getItem('token'); + + let config = { + headers: { Authorization: `Bearer ${token}` } + }; + + try { + return await axios.get( + process.env.REACT_APP_API_URL + '/admin/role/get?username=' + username, + config + ); + } catch (err) { + return err.response; + } +}; let add_role = async (data) => { - let token = sessionStorage.getItem("token"); - - let config = { - headers: { Authorization: `Bearer ${token}` }, - } - - try { - return await axios.post(process.env.REACT_APP_API_URL + "/admin/role/add", data, config); - } catch (err) { - return err.response; - } -} + let token = sessionStorage.getItem('token'); + + let config = { + headers: { Authorization: `Bearer ${token}` } + }; + + try { + return await axios.post(process.env.REACT_APP_API_URL + '/admin/role/add', data, config); + } catch (err) { + return err.response; + } +}; + +let upload_role = async (formData) => { + let token = sessionStorage.getItem('token'); + + let config = { + headers: { Authorization: `Bearer ${token}` } + }; + + try { + return await axios.post( + `${process.env.REACT_APP_API_URL}/admin/role/upload`, + formData, + config + ); + } catch (err) { + return err.response; + } +}; let delete_role = async (data) => { - let token = sessionStorage.getItem("token"); + let token = sessionStorage.getItem('token'); - let config = { - headers: { Authorization: `Bearer ${token}` }, - data: data - } + let config = { + headers: { Authorization: `Bearer ${token}` }, + data: data + }; - try { - return await axios.delete(process.env.REACT_APP_API_URL + "/admin/role/delete", config); - } catch (err) { - return err.response; - } -} + try { + return await axios.delete(process.env.REACT_APP_API_URL + '/admin/role/delete', config); + } catch (err) { + return err.response; + } +}; let AdminApi = { - all_courses, - add_course, - change_course, - get_role, - add_role, - delete_role, - impersonate -} - -export default AdminApi; \ No newline at end of file + all_courses, + add_course, + change_course, + get_role, + add_role, + upload_role, + delete_role, + impersonate +}; + +export default AdminApi; From 888bc0d8fc9967e67aa6dc36aa4111adb11f2357 Mon Sep 17 00:00:00 2001 From: Eric Ryu Date: Wed, 28 Jun 2023 10:11:22 -0400 Subject: [PATCH 2/6] upload roles function should hit upload roles endpoint --- .../components/Page/Admin/AdminCoursePage.jsx | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Page/Admin/AdminCoursePage.jsx b/frontend/src/components/Page/Admin/AdminCoursePage.jsx index b099c134..f7fe7eb1 100644 --- a/frontend/src/components/Page/Admin/AdminCoursePage.jsx +++ b/frontend/src/components/Page/Admin/AdminCoursePage.jsx @@ -33,9 +33,27 @@ const AdminCoursePage = () => { }; const uploadRole = (data) => { - AdminApi.add_role({ ...data, course_id: course_id }).then((response) => { + // Create FormData object to send to endpoint + console.log(data); + const formData = new FormData(); + if (data['file'] !== undefined) { + formData.append('file', data['file']); + } + if (data['role'] !== undefined) { + formData.append('role', data['role']); + } + formData.append('course_id', course_id); + formData.append('update_user_info', false); + + AdminApi.upload_role(formData).then((response) => { console.log(response); - toast(response.data.message); + if (response.status === 200) + toast.success('The users in file have been assigned the specified role', { + theme: 'colored' + }); + else { + toast.error(response.data.message, { theme: 'colored' }); + } }); }; From 9c3e6008e4e34294ae01004a2331a170a09c5653 Mon Sep 17 00:00:00 2001 From: Eric Ryu Date: Wed, 28 Jun 2023 10:11:49 -0400 Subject: [PATCH 3/6] complete frontend design portion for uploading roles --- .../AdminCourseCRUD/AdminCourseCRUD.jsx | 152 +++++++++++++++++- 1 file changed, 148 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx b/frontend/src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx index a7332b30..938ff5f2 100644 --- a/frontend/src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx +++ b/frontend/src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx @@ -19,6 +19,9 @@ import CustomTextField from '../../FlexyMainComponents/forms/custom-elements/Cus import CustomFormLabel from '../../FlexyMainComponents/forms/custom-elements/CustomFormLabel'; import CustomSelect from '../../FlexyMainComponents/forms/custom-elements/CustomSelect'; +// Globally change maxWidth for each tab-list component +const MAX_WIDTH = 300; + const AdminCourseCRUD = (props) => { const { onSubmitFunctions } = props; const { AddRole, UploadRoles, DeleteRole } = onSubmitFunctions; @@ -33,7 +36,8 @@ const AdminCourseCRUD = (props) => { control: controlUploads, handleSubmit: handleSubmitUploads, register: registerUploads, - formState: formStateUploads + formState: formStateUploads, + watch: watchUploads } = useForm(); const { control: controlDelete, @@ -45,8 +49,18 @@ const AdminCourseCRUD = (props) => { // Determine if current user to be added is a new user const [isNewUser, setIsNewUser] = React.useState(true); + // Determine whether to delete all users from DB const [deleteAllUsers, setDeleteAllUsers] = React.useState(false); + // For updating filename on uploading file + const [filename, setFilename] = React.useState(''); + + // Watch for filename within 'file' property + const selectedFile = watchUploads('file', ''); + React.useEffect(() => { + if (selectedFile.length > 0) setFilename(selectedFile[0].name); + }, [selectedFile]); + React.useEffect(() => { if ( Object.keys(formStateAdd.errors).length > 0 || @@ -73,7 +87,7 @@ const AdminCourseCRUD = (props) => { flexDirection: 'column', alignItems: 'center' }} - maxWidth={300} + maxWidth={MAX_WIDTH} > { }; const UploadRolesComponent = () => { - return <>; + return ( + + + + + + Acceptable File Extensions: *.csv +
+ Refer to the "Notes" section for the format of file. +
+ + + + File Uploaded: {filename} + + + ( + <> + + Select Role for User * + + + + Instructor + + + TA + + + Student + + + + )} + name="role" + control={controlUploads} + defaultValue="" + rules={{ required: true }} + /> + +
+
+
+ + + Notes: + +
    +
  • + + The format of the csv file being uploaded should be as follows: +
    + + username,email +
    + user1,user1@example.com +
    + user2,user2@example.com +
    + ... +
    +
    + Note that the email field may be left blank and is optional. i.e. + the csv file format can be +
    + + username,email +
    + user1,user1@example.com +
    + user2, +
    + user3,user3@example.com +
    + ... +
    +
    +
  • +
  • + + There is no requirement on the name of the file. + +
  • +
+
+
+ ); }; const DeleteRoleComponent = () => { @@ -278,7 +422,7 @@ const AdminCourseCRUD = (props) => { flexDirection: 'column', alignItems: 'center' }} - maxWidth={300} + maxWidth={MAX_WIDTH} > Date: Wed, 28 Jun 2023 10:56:01 -0400 Subject: [PATCH 4/6] change to postForm of axios --- frontend/src/api/admin_api.js | 2 +- .../src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/api/admin_api.js b/frontend/src/api/admin_api.js index 9bdbe87e..5a002bbe 100644 --- a/frontend/src/api/admin_api.js +++ b/frontend/src/api/admin_api.js @@ -122,7 +122,7 @@ let upload_role = async (formData) => { }; try { - return await axios.post( + return await axios.postForm( `${process.env.REACT_APP_API_URL}/admin/role/upload`, formData, config diff --git a/frontend/src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx b/frontend/src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx index 938ff5f2..5308308f 100644 --- a/frontend/src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx +++ b/frontend/src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx @@ -323,7 +323,7 @@ const AdminCourseCRUD = (props) => { }) => ( <> - Select Role for User * + Select Role for Users in File * Date: Wed, 28 Jun 2023 11:10:50 -0400 Subject: [PATCH 5/6] fix sending file to endpoint --- frontend/src/api/admin_api.js | 2 +- .../components/General/AdminCourseCRUD/AdminCourseCRUD.jsx | 7 +++++++ frontend/src/components/Page/Admin/AdminCoursePage.jsx | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/src/api/admin_api.js b/frontend/src/api/admin_api.js index 5a002bbe..9bdbe87e 100644 --- a/frontend/src/api/admin_api.js +++ b/frontend/src/api/admin_api.js @@ -122,7 +122,7 @@ let upload_role = async (formData) => { }; try { - return await axios.postForm( + return await axios.post( `${process.env.REACT_APP_API_URL}/admin/role/upload`, formData, config diff --git a/frontend/src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx b/frontend/src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx index 5308308f..25590ecc 100644 --- a/frontend/src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx +++ b/frontend/src/components/General/AdminCourseCRUD/AdminCourseCRUD.jsx @@ -370,6 +370,13 @@ const AdminCourseCRUD = (props) => { Notes:
    +
  • + + Precondition: +
    + Users in the file need to already exist in the database. +
    +
  • The format of the csv file being uploaded should be as follows: diff --git a/frontend/src/components/Page/Admin/AdminCoursePage.jsx b/frontend/src/components/Page/Admin/AdminCoursePage.jsx index f7fe7eb1..f08c7009 100644 --- a/frontend/src/components/Page/Admin/AdminCoursePage.jsx +++ b/frontend/src/components/Page/Admin/AdminCoursePage.jsx @@ -36,8 +36,8 @@ const AdminCoursePage = () => { // Create FormData object to send to endpoint console.log(data); const formData = new FormData(); - if (data['file'] !== undefined) { - formData.append('file', data['file']); + if (data['file'][0] !== undefined) { + formData.append('file', data['file'][0]); } if (data['role'] !== undefined) { formData.append('role', data['role']); From 4185a306ac87944c619da864d15400c0d13bef81 Mon Sep 17 00:00:00 2001 From: Eric Ryu Date: Wed, 28 Jun 2023 11:12:35 -0400 Subject: [PATCH 6/6] remove unncessary console logs --- frontend/src/components/Page/Admin/AdminCoursePage.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/Page/Admin/AdminCoursePage.jsx b/frontend/src/components/Page/Admin/AdminCoursePage.jsx index f08c7009..211f505f 100644 --- a/frontend/src/components/Page/Admin/AdminCoursePage.jsx +++ b/frontend/src/components/Page/Admin/AdminCoursePage.jsx @@ -34,7 +34,6 @@ const AdminCoursePage = () => { const uploadRole = (data) => { // Create FormData object to send to endpoint - console.log(data); const formData = new FormData(); if (data['file'][0] !== undefined) { formData.append('file', data['file'][0]);