From 1ea9a9b95161a5217694f6d643e65c437d7bcdff Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Fri, 1 Nov 2024 19:34:47 -0500 Subject: [PATCH 01/28] fix: SKIL-503 bug showing completed assessment tasks in "My Assessment Tasks". --- .../AssessmentTask/StudentViewAssessmentTask.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js index 15da5c64d..07fb70bdc 100644 --- a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js +++ b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js @@ -92,13 +92,23 @@ class StudentViewAssessmentTask extends Component { ) } else { + // SKIL-503 bugfix: + // For the assessments tasks, we only want to display the ones + // that have not been completed. This compares all the assessment + // tasks to the completed ones and filters all the ones that are also + // in the completed assessments. + let uncompletedAssessments = assessmentTasks.filter(task => + !completedAssessments.some(completed => + completed.assessment_task_id === task.assessment_task_id + ) + ); return(
Date: Sun, 3 Nov 2024 20:36:46 -0600 Subject: [PATCH 02/28] Added unlock button for viewing assesment tasks as an admin. --- .../ViewAssessmentTask/ViewAssessmentTasks.js | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js index 787ef80ec..d40517d10 100644 --- a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js +++ b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js @@ -8,7 +8,7 @@ import EditIcon from '@mui/icons-material/Edit'; import VisibilityIcon from '@mui/icons-material/Visibility'; import { formatDueDate, genericResourceGET, getHumanReadableDueDate } from '../../../../utility.js'; import Loading from '../../../Loading/Loading.js'; - +import LockOpenIcon from '@mui/icons-material/LockOpen'; class ViewAssessmentTasks extends Component { constructor(props) { @@ -312,6 +312,26 @@ class ViewAssessmentTasks extends Component { } } }, + { + name: "lock", + label: "Lock", + options: { + filter: false, + sort: false, + setCellHeaderProps: () => { return { align:"center", width:"70px", className:"button-column-alignment"}}, + setCellProps: () => { return { align:"center", width:"70px", className:"button-column-alignment"} }, + customBodyRender: (assessmentTaskId) => { + return( + <> + + + + + ) + } + } + + }, { name: "assessment_task_id", label: "To Do", @@ -324,7 +344,7 @@ class ViewAssessmentTasks extends Component { const assessmentTask = assessmentTasks.find(task => task.assessment_task_id === atId); const isTeamAssessment = assessmentTask && assessmentTask.unit_of_assessment; const teamsExist = this.props.teams && this.props.teams.length > 0; - + if (isTeamAssessment && (fixedTeams && !teamsExist)) { return ( @@ -429,4 +449,4 @@ class ViewAssessmentTasks extends Component { } } -export default ViewAssessmentTasks; \ No newline at end of file +export default ViewAssessmentTasks; From 7d574b537fb295645b2e86eeaddfa11aa0050c51 Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Wed, 6 Nov 2024 15:53:17 -0600 Subject: [PATCH 03/28] Started work on implementation for the API call for locking ATs. --- .../Routes/Assessment_task_routes.py | 25 +++++- BackEndFlask/models/assessment_task.py | 87 +++++++++++------- BackEndFlask/models/schemas.py | 21 ++--- .../ViewAssessmentTask/ViewAssessmentTasks.js | 88 +++++++++++-------- 4 files changed, 140 insertions(+), 81 deletions(-) diff --git a/BackEndFlask/controller/Routes/Assessment_task_routes.py b/BackEndFlask/controller/Routes/Assessment_task_routes.py index 2b1104f05..2f3e483f0 100644 --- a/BackEndFlask/controller/Routes/Assessment_task_routes.py +++ b/BackEndFlask/controller/Routes/Assessment_task_routes.py @@ -18,7 +18,8 @@ get_assessment_tasks, get_assessment_task, create_assessment_task, - replace_assessment_task + replace_assessment_task, + toggle_lock_status, ) from models.completed_assessment import ( @@ -187,7 +188,6 @@ def add_assessment_task(): ) - @bp.route('/assessment_task', methods = ['PUT']) @jwt_required() @bad_token_check() @@ -248,6 +248,22 @@ def update_assessment_task(): ) +# @bp.route('/assessment_task//toggle_lock_status', methods=['PUT']) +@bp.route('/assessment_task_toggle_lock', methods=['PUT']) +@jwt_required() +@bad_token_check() +@AuthCheck() +def toggle_lock_status_route(): + try: + lockStatus = request.args.get('lockStatus') + assessmentTaskId = request.args.get('assessmentTaskId') + msg = f"\n\n\tlockStatus = {lockStatus}\n\n\tassessmentTaskId = {assessmentTaskId}" + return create_bad_response(f"{msg}", "toggle_lock_status_route", 400) + except Exception as e: + return create_bad_response( + f"An error occurred copying course assessments {e}", "assessment_tasks", 400 + ) + # /assessment_task/ POST # copies over assessment_tasks from an existing course to another course @@ -308,9 +324,10 @@ class Meta: "comment", "number_of_teams", "max_team_size", - "notification_sent" + "notification_sent", + "locked", ) assessment_task_schema = AssessmentTaskSchema() -assessment_tasks_schema = AssessmentTaskSchema(many=True) \ No newline at end of file +assessment_tasks_schema = AssessmentTaskSchema(many=True) diff --git a/BackEndFlask/models/assessment_task.py b/BackEndFlask/models/assessment_task.py index 436e5b233..be11fc0f5 100644 --- a/BackEndFlask/models/assessment_task.py +++ b/BackEndFlask/models/assessment_task.py @@ -22,11 +22,11 @@ def __init__(self): def __str__(self): return self.message - + class InvalidMaxTeamSize(Exception): def __init__(self): self.message = "Number of people on a team must be greater than 0." - + def __str__(self): return self.message @@ -38,7 +38,7 @@ def validate_number_of_teams(number_of_teams): raise InvalidNumberOfTeams() except ValueError: raise InvalidNumberOfTeams() - + def validate_max_team_size(max_team_size): if max_team_size is not None: try: @@ -75,10 +75,10 @@ def get_assessment_tasks_by_team_id(team_id): @error_log def get_assessment_task(assessment_task_id): one_assessment_task = AssessmentTask.query.filter_by(assessment_task_id=assessment_task_id).first() - + if one_assessment_task is None: - raise InvalidAssessmentTaskID(assessment_task_id) - + raise InvalidAssessmentTaskID(assessment_task_id) + return one_assessment_task @error_log @@ -106,7 +106,8 @@ def create_assessment_task(assessment_task): comment=assessment_task["comment"], number_of_teams=assessment_task["number_of_teams"], max_team_size=assessment_task["max_team_size"], - notification_sent=None + notification_sent=None, + locked=assessment_task["locked"], ) db.session.add(new_assessment_task) @@ -128,7 +129,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "EST", - "unit_of_assessment": False + "unit_of_assessment": False, + "locked": True, }, { # Assessment Task 2 "assessment_task_name": "Formal Communication Assessment", @@ -142,7 +144,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": False, "time_zone": "EST", - "unit_of_assessment": False + "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 3 "assessment_task_name": "Information Processing Assessment", @@ -156,7 +159,8 @@ def load_demo_admin_assessment_task(): "show_ratings": False, "show_suggestions": True, "time_zone": "EST", - "unit_of_assessment": False + "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 4 "assessment_task_name": "Interpersonal Communication", @@ -170,7 +174,8 @@ def load_demo_admin_assessment_task(): "show_ratings": False, "show_suggestions": False, "time_zone": "EST", - "unit_of_assessment": False + "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 5 "assessment_task_name": "Management Assessment", @@ -184,7 +189,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "EST", - "unit_of_assessment": True + "unit_of_assessment": True, + "locked": False, }, { # Assessment Task 6 "assessment_task_name": "Problem Solving Assessment", @@ -198,7 +204,8 @@ def load_demo_admin_assessment_task(): "show_ratings": False, "show_suggestions": False, "time_zone": "EST", - "unit_of_assessment": False + "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 7 "assessment_task_name": "Teamwork Assessment", @@ -212,7 +219,8 @@ def load_demo_admin_assessment_task(): "show_ratings": False, "show_suggestions": True, "time_zone": "EST", - "unit_of_assessment": True + "unit_of_assessment": True, + "locked": False, }, { # Assessment Task 8 "assessment_task_name": "Critical Thinking Assessment 2", @@ -226,7 +234,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "CST", - "unit_of_assessment": True + "unit_of_assessment": True, + "locked": False, }, { # Assessment Task 9 "assessment_task_name": "AAAAAAAAAAAA", @@ -240,7 +249,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "EST", - "unit_of_assessment": True + "unit_of_assessment": True, + "locked": False, }, { # Assessment Task 10 "assessment_task_name": "CCCCCCCCCCCCC", @@ -254,7 +264,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "PST", - "unit_of_assessment": True + "unit_of_assessment": True, + "locked": False, }, { # Assessment Task 11 "assessment_task_name": "DDDDDDDDDDDDDD", @@ -268,7 +279,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "PST", - "unit_of_assessment": True + "unit_of_assessment": True, + "locked": False, }, { # Assessment Task 12 "assessment_task_name": "Student 1", @@ -282,7 +294,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "EST", - "unit_of_assessment": False + "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 13 "assessment_task_name": "Student 2 Individ", @@ -296,7 +309,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "PST", - "unit_of_assessment": False + "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 14 "assessment_task_name": "UI 1", @@ -310,7 +324,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "PST", - "unit_of_assessment": False + "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 15 "assessment_task_name": "UI 2", @@ -324,7 +339,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "PST", - "unit_of_assessment": False + "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 16 "assessment_task_name": "Calc 1", @@ -338,7 +354,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "PST", - "unit_of_assessment": False + "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 17 "assessment_task_name": "Calc 2", @@ -352,7 +369,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "PST", - "unit_of_assessment": False + "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 18 "assessment_task_name": "Phys 1", @@ -366,7 +384,8 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "MST", - "unit_of_assessment": False + "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 19 "assessment_task_name": "Phys 2", @@ -380,8 +399,9 @@ def load_demo_admin_assessment_task(): "show_ratings": True, "show_suggestions": True, "time_zone": "MST", - "unit_of_assessment": False - } + "unit_of_assessment": False, + "locked": False, + }, ] for assessment in list_of_assessment_tasks: @@ -398,7 +418,8 @@ def load_demo_admin_assessment_task(): "create_team_password": assessment["create_team_password"], "comment": assessment["comment"], "number_of_teams": assessment["number_of_teams"], - "max_team_size": assessment["max_team_size"] + "max_team_size": assessment["max_team_size"], + "locked": assessment["locked"], }) @error_log @@ -430,7 +451,6 @@ def replace_assessment_task(assessment_task, assessment_task_id): one_assessment_task.comment = assessment_task["comment"] one_assessment_task.number_of_teams = assessment_task["number_of_teams"] one_assessment_task.max_team_size = assessment_task["max_team_size"] - db.session.commit() @@ -444,4 +464,11 @@ def toggle_notification_sent_to_true(assessment_task_id, date): db.session.commit() - return one_assessment_task \ No newline at end of file + return one_assessment_task + +@error_log +def toggle_lock_status(assessment_task_id): + one_assessment_task = AssessmentTask.query.filter_by(assessment_task_id=assessment_task_id).first() + one_assessment_task.locked = not one_assessment_task.locked + db.session.commit() + return one_assessment_task diff --git a/BackEndFlask/models/schemas.py b/BackEndFlask/models/schemas.py index 3fbc3570d..ff23e8f11 100644 --- a/BackEndFlask/models/schemas.py +++ b/BackEndFlask/models/schemas.py @@ -20,11 +20,11 @@ Blacklist(id, token) """ -class Role(db.Model): +class Role(db.Model): __tablename__ = "Role" __table_args__ = {'sqlite_autoincrement': True} role_id = db.Column(db.Integer, primary_key=True) - role_name = db.Column(db.String(100), nullable=False) + role_name = db.Column(db.String(100), nullable=False) class User(db.Model): __tablename__ = "User" @@ -37,7 +37,7 @@ class User(db.Model): lms_id = db.Column(db.Integer, nullable=True) consent = db.Column(db.Boolean, nullable=True) owner_id = db.Column(db.Integer, ForeignKey(user_id), nullable=True) - has_set_password = db.Column(db.Boolean, nullable=False) + has_set_password = db.Column(db.Boolean, nullable=False) reset_code = db.Column(db.String(6), nullable=True) is_admin = db.Column(db.Boolean, nullable=False) @@ -57,13 +57,13 @@ class Category(db.Model): description = db.Column(db.String(255), nullable=False) rating_json = db.Column(db.JSON, nullable=False) -class RubricCategory(db.Model): +class RubricCategory(db.Model): __tablename__ = "RubricCategories" __table_args__ = {'sqlite_autoincrement': True} rubric_category_id = db.Column(db.Integer, primary_key=True) rubric_id = db.Column(db.Integer, ForeignKey(Rubric.rubric_id), nullable=False) category_id = db.Column(db.Integer, ForeignKey(Category.category_id), nullable=False) - + class ObservableCharacteristic(db.Model): __tablename__ = "ObservableCharacteristic" __table_args__ = {'sqlite_autoincrement': True} @@ -97,10 +97,10 @@ class UserCourse(db.Model): user_course_id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, ForeignKey(User.user_id), nullable=False) course_id = db.Column(db.Integer, ForeignKey(Course.course_id), nullable=False) - active = db.Column(db.Boolean) + active = db.Column(db.Boolean) role_id = db.Column(db.Integer, ForeignKey(Role.role_id), nullable=False) -class Team(db.Model): # keeps track of default teams for a fixed team scenario +class Team(db.Model): # keeps track of default teams for a fixed team scenario __tablename__ = "Team" __table_args__ = {'sqlite_autoincrement': True} team_id = db.Column(db.Integer, primary_key=True) @@ -116,7 +116,7 @@ class TeamUser(db.Model): team_user_id = db.Column(db.Integer, primary_key=True) team_id = db.Column(db.Integer, ForeignKey(Team.team_id), nullable=False) user_id = db.Column(db.Integer, ForeignKey(User.user_id), nullable=False) - + class AssessmentTask(db.Model): __tablename__ = "AssessmentTask" __table_args__ = {'sqlite_autoincrement' : True} @@ -130,11 +130,12 @@ class AssessmentTask(db.Model): show_suggestions = db.Column(db.Boolean, nullable=False) show_ratings = db.Column(db.Boolean, nullable=False) unit_of_assessment = db.Column(db.Boolean, nullable=False) # true if team, false if individuals - comment = db.Column(db.String(3000), nullable=True) + comment = db.Column(db.String(3000), nullable=True) create_team_password = db.Column(db.String(25), nullable=True) number_of_teams = db.Column(db.Integer, nullable=True) max_team_size = db.Column(db.Integer, nullable=True) notification_sent = db.Column(DateTime(timezone=True), nullable=True) + locked = db.Column(db.Boolean, nullable=False) class Checkin(db.Model): # keeps students checking to take a specific AT __tablename__ = "Checkin" @@ -166,4 +167,4 @@ class Feedback(db.Model): feedback_id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, ForeignKey(User.user_id), nullable=False) completed_assessment_id = db.Column(db.Integer, ForeignKey(CompletedAssessment.completed_assessment_id), nullable=False) - feedback_time = db.Column(DateTime(timezone=True), nullable=True) # time the student viewed their feedback \ No newline at end of file + feedback_time = db.Column(DateTime(timezone=True), nullable=True) # time the student viewed their feedback diff --git a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js index d40517d10..154efaa49 100644 --- a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js +++ b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js @@ -6,9 +6,10 @@ import { Button } from '@mui/material'; import { Tooltip } from '@mui/material'; import EditIcon from '@mui/icons-material/Edit'; import VisibilityIcon from '@mui/icons-material/Visibility'; -import { formatDueDate, genericResourceGET, getHumanReadableDueDate } from '../../../../utility.js'; +import { formatDueDate, genericResourceGET, genericResourcePUT, genericResourcePOST, getHumanReadableDueDate } from '../../../../utility.js'; import Loading from '../../../Loading/Loading.js'; import LockOpenIcon from '@mui/icons-material/LockOpen'; +import LockIcon from '@mui/icons-material/Lock'; class ViewAssessmentTasks extends Component { constructor(props) { @@ -21,7 +22,8 @@ class ViewAssessmentTasks extends Component { downloadedAssessment: null, exportButtonId: {}, completedAssessments: null, - assessmentTasks: null + assessmentTasks: null, + lockStatus: {}, } this.handleDownloadCsv = (atId, exportButtonId, assessmentTaskIdToAssessmentTaskName) => { @@ -42,6 +44,28 @@ class ViewAssessmentTasks extends Component { exportButtonId: newExportButtonJSON }); } + + // Function for toggling the lock icon. Also performs an API call + // to the DB. + this.toggleLockStatus = (assessmentTaskId) => { + this.setState((prevState) => { + const newLockStatus = { ...prevState.lockStatus }; + newLockStatus[assessmentTaskId] = !newLockStatus[assessmentTaskId]; + return { lockStatus: newLockStatus }; + }, () => { + // wait until after it updates... + const lockStatus = this.state.lockStatus[assessmentTaskId]; + const courseId = this.props.navbar.state.chosenCourse.course_id; + + genericResourcePUT( + `/assessment_task_toggle_lock?lockStatus=${lockStatus}&assessmentTaskId=${assessmentTaskId}`, + this, + JSON.stringify({ + locked: lockStatus, + }) + ); + }); + }; } componentDidUpdate () { @@ -78,7 +102,7 @@ class ViewAssessmentTasks extends Component { "assessmentTasks", this ); - + genericResourceGET( `/completed_assessment?course_id=${courseId}&only_course=true`, "completedAssessments", @@ -92,7 +116,7 @@ class ViewAssessmentTasks extends Component { return ; } const fixedTeams = this.props.navbar.state.chosenCourse["use_fixed_teams"]; - + var navbar = this.props.navbar; var adminViewAssessmentTask = navbar.adminViewAssessmentTask; @@ -100,6 +124,8 @@ class ViewAssessmentTasks extends Component { var rubricNames = adminViewAssessmentTask.rubricNames; var assessmentTasks = adminViewAssessmentTask.assessmentTasks; + const lockStatus = this.state.lockStatus; + let assessmentTasksToDueDates = {}; for(let index = 0; index < assessmentTasks.length; index++) { @@ -289,21 +315,29 @@ class ViewAssessmentTasks extends Component { customBodyRender: (assessmentTaskId) => { if (assessmentTaskId && assessmentTasks) { const selectedTask = assessmentTasks.find(task => task.assessment_task_id === assessmentTaskId); - + const isLocked = lockStatus[assessmentTaskId] ?? selectedTask?.locked; + if (selectedTask) { return ( - { - setCompleteAssessmentTaskTabWithID(selectedTask); - }} - aria-label='viewCompletedAssessmentIconButton' - > - - + <> + { + setCompleteAssessmentTaskTabWithID(selectedTask); + }} + aria-label='viewCompletedAssessmentIconButton' + > + + + + {/* Toggle lock/unlock button */} + this.toggleLockStatus(assessmentTaskId)}> + {isLocked ? : } + + ); } - } + } return( <> {"N/A"} @@ -312,26 +346,6 @@ class ViewAssessmentTasks extends Component { } } }, - { - name: "lock", - label: "Lock", - options: { - filter: false, - sort: false, - setCellHeaderProps: () => { return { align:"center", width:"70px", className:"button-column-alignment"}}, - setCellProps: () => { return { align:"center", width:"70px", className:"button-column-alignment"} }, - customBodyRender: (assessmentTaskId) => { - return( - <> - - - - - ) - } - } - - }, { name: "assessment_task_id", label: "To Do", @@ -361,7 +375,7 @@ class ViewAssessmentTasks extends Component { ); } - + return ( ) - } } } From 8594b3f7e38472780e0263494f219b7f07c27d84 Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Thu, 14 Nov 2024 10:08:23 -0600 Subject: [PATCH 06/28] removed broken lock code --- .../Routes/Assessment_task_routes.py | 4 +-- BackEndFlask/models/assessment_task.py | 21 ------------- BackEndFlask/models/schemas.py | 1 - .../ViewAssessmentTask/ViewAssessmentTasks.js | 30 ------------------- .../AssessmentTask/ViewAssessmentTasks.js | 7 ++--- 5 files changed, 3 insertions(+), 60 deletions(-) diff --git a/BackEndFlask/controller/Routes/Assessment_task_routes.py b/BackEndFlask/controller/Routes/Assessment_task_routes.py index 3d63e060a..16cf45857 100644 --- a/BackEndFlask/controller/Routes/Assessment_task_routes.py +++ b/BackEndFlask/controller/Routes/Assessment_task_routes.py @@ -98,8 +98,7 @@ def get_all_assessment_tasks(): user_course.course_id ) - for assessment_task in assessment_tasks: - all_assessment_tasks.append(assessment_task) + for assessment_task in assessment_tasks: all_assessment_tasks.append(assessment_task) return create_good_response( assessment_tasks_schema.dump(all_assessment_tasks), @@ -331,7 +330,6 @@ class Meta: "number_of_teams", "max_team_size", "notification_sent", - "locked", ) diff --git a/BackEndFlask/models/assessment_task.py b/BackEndFlask/models/assessment_task.py index be11fc0f5..317234e97 100644 --- a/BackEndFlask/models/assessment_task.py +++ b/BackEndFlask/models/assessment_task.py @@ -107,7 +107,6 @@ def create_assessment_task(assessment_task): number_of_teams=assessment_task["number_of_teams"], max_team_size=assessment_task["max_team_size"], notification_sent=None, - locked=assessment_task["locked"], ) db.session.add(new_assessment_task) @@ -130,7 +129,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "EST", "unit_of_assessment": False, - "locked": True, }, { # Assessment Task 2 "assessment_task_name": "Formal Communication Assessment", @@ -145,7 +143,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": False, "time_zone": "EST", "unit_of_assessment": False, - "locked": False, }, { # Assessment Task 3 "assessment_task_name": "Information Processing Assessment", @@ -160,7 +157,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "EST", "unit_of_assessment": False, - "locked": False, }, { # Assessment Task 4 "assessment_task_name": "Interpersonal Communication", @@ -175,7 +171,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": False, "time_zone": "EST", "unit_of_assessment": False, - "locked": False, }, { # Assessment Task 5 "assessment_task_name": "Management Assessment", @@ -190,7 +185,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "EST", "unit_of_assessment": True, - "locked": False, }, { # Assessment Task 6 "assessment_task_name": "Problem Solving Assessment", @@ -205,7 +199,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": False, "time_zone": "EST", "unit_of_assessment": False, - "locked": False, }, { # Assessment Task 7 "assessment_task_name": "Teamwork Assessment", @@ -220,7 +213,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "EST", "unit_of_assessment": True, - "locked": False, }, { # Assessment Task 8 "assessment_task_name": "Critical Thinking Assessment 2", @@ -235,7 +227,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "CST", "unit_of_assessment": True, - "locked": False, }, { # Assessment Task 9 "assessment_task_name": "AAAAAAAAAAAA", @@ -250,7 +241,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "EST", "unit_of_assessment": True, - "locked": False, }, { # Assessment Task 10 "assessment_task_name": "CCCCCCCCCCCCC", @@ -265,7 +255,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": True, - "locked": False, }, { # Assessment Task 11 "assessment_task_name": "DDDDDDDDDDDDDD", @@ -280,7 +269,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": True, - "locked": False, }, { # Assessment Task 12 "assessment_task_name": "Student 1", @@ -295,7 +283,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "EST", "unit_of_assessment": False, - "locked": False, }, { # Assessment Task 13 "assessment_task_name": "Student 2 Individ", @@ -310,7 +297,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": False, - "locked": False, }, { # Assessment Task 14 "assessment_task_name": "UI 1", @@ -325,7 +311,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": False, - "locked": False, }, { # Assessment Task 15 "assessment_task_name": "UI 2", @@ -340,7 +325,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": False, - "locked": False, }, { # Assessment Task 16 "assessment_task_name": "Calc 1", @@ -355,7 +339,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": False, - "locked": False, }, { # Assessment Task 17 "assessment_task_name": "Calc 2", @@ -370,7 +353,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": False, - "locked": False, }, { # Assessment Task 18 "assessment_task_name": "Phys 1", @@ -385,7 +367,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "MST", "unit_of_assessment": False, - "locked": False, }, { # Assessment Task 19 "assessment_task_name": "Phys 2", @@ -400,7 +381,6 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "MST", "unit_of_assessment": False, - "locked": False, }, ] @@ -419,7 +399,6 @@ def load_demo_admin_assessment_task(): "comment": assessment["comment"], "number_of_teams": assessment["number_of_teams"], "max_team_size": assessment["max_team_size"], - "locked": assessment["locked"], }) @error_log diff --git a/BackEndFlask/models/schemas.py b/BackEndFlask/models/schemas.py index ff23e8f11..d2e6185ca 100644 --- a/BackEndFlask/models/schemas.py +++ b/BackEndFlask/models/schemas.py @@ -135,7 +135,6 @@ class AssessmentTask(db.Model): number_of_teams = db.Column(db.Integer, nullable=True) max_team_size = db.Column(db.Integer, nullable=True) notification_sent = db.Column(DateTime(timezone=True), nullable=True) - locked = db.Column(db.Boolean, nullable=False) class Checkin(db.Model): # keeps students checking to take a specific AT __tablename__ = "Checkin" diff --git a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js index 154efaa49..1631a9407 100644 --- a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js +++ b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js @@ -8,8 +8,6 @@ import EditIcon from '@mui/icons-material/Edit'; import VisibilityIcon from '@mui/icons-material/Visibility'; import { formatDueDate, genericResourceGET, genericResourcePUT, genericResourcePOST, getHumanReadableDueDate } from '../../../../utility.js'; import Loading from '../../../Loading/Loading.js'; -import LockOpenIcon from '@mui/icons-material/LockOpen'; -import LockIcon from '@mui/icons-material/Lock'; class ViewAssessmentTasks extends Component { constructor(props) { @@ -44,28 +42,6 @@ class ViewAssessmentTasks extends Component { exportButtonId: newExportButtonJSON }); } - - // Function for toggling the lock icon. Also performs an API call - // to the DB. - this.toggleLockStatus = (assessmentTaskId) => { - this.setState((prevState) => { - const newLockStatus = { ...prevState.lockStatus }; - newLockStatus[assessmentTaskId] = !newLockStatus[assessmentTaskId]; - return { lockStatus: newLockStatus }; - }, () => { - // wait until after it updates... - const lockStatus = this.state.lockStatus[assessmentTaskId]; - const courseId = this.props.navbar.state.chosenCourse.course_id; - - genericResourcePUT( - `/assessment_task_toggle_lock?lockStatus=${lockStatus}&assessmentTaskId=${assessmentTaskId}`, - this, - JSON.stringify({ - locked: lockStatus, - }) - ); - }); - }; } componentDidUpdate () { @@ -315,7 +291,6 @@ class ViewAssessmentTasks extends Component { customBodyRender: (assessmentTaskId) => { if (assessmentTaskId && assessmentTasks) { const selectedTask = assessmentTasks.find(task => task.assessment_task_id === assessmentTaskId); - const isLocked = lockStatus[assessmentTaskId] ?? selectedTask?.locked; if (selectedTask) { return ( @@ -329,11 +304,6 @@ class ViewAssessmentTasks extends Component { > - - {/* Toggle lock/unlock button */} - this.toggleLockStatus(assessmentTaskId)}> - {isLocked ? : } - ); } diff --git a/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTasks.js b/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTasks.js index ce780d231..90f700ce0 100644 --- a/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTasks.js +++ b/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTasks.js @@ -126,9 +126,6 @@ class ViewAssessmentTasks extends Component { setCellHeaderProps: () => { return { align:"center", width:"140px", className:"button-column-alignment"}}, setCellProps: () => { return { align:"center", width:"140px", className:"button-column-alignment"} }, customBodyRender: (atId) => { - let atIsLocked = assessmentTasks.find((at) => at["assessment_task_id"] === atId)?.locked ?? false; - console.log('locked: ', atIsLocked); - return ( at["assessment_task_id"] === atId)["unit_of_assessment"])) || - this.isObjectFound(atId) === true) || atIsLocked + this.isObjectFound(atId) === true) : this.areAllATsComplete(atId) === true } @@ -183,7 +180,7 @@ class ViewAssessmentTasks extends Component { aria-label="startAssessmentTasksButton" > - {atIsLocked ? <>LOCKED : <>START} + START ) From fb2f1adb5fbf8aa39bce30b0d72bcb7cd065e5cd Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Thu, 14 Nov 2024 13:43:07 -0600 Subject: [PATCH 07/28] test comit --- BackEndFlask/run.py | 1 + 1 file changed, 1 insertion(+) diff --git a/BackEndFlask/run.py b/BackEndFlask/run.py index f1bba48e6..57654ad7f 100644 --- a/BackEndFlask/run.py +++ b/BackEndFlask/run.py @@ -2,6 +2,7 @@ # this variable is expected by the wsgi server # application = app + if __name__ == '__main__': #The app.run(debug = True) line is needed if we are working on our local machine # app.run(debug=True) From a0d4f102585bb3e48034a00bf937f5f38b24402f Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Thu, 14 Nov 2024 14:13:51 -0600 Subject: [PATCH 08/28] added lock buttons --- .../ViewAssessmentTask/ViewAssessmentTasks.js | 24 +- .../ViewCompleteIndividualAssessmentTasks.js | 579 +++++++++--------- .../ViewCompleteTeamAssessmentTasks.js | 571 +++++++++-------- 3 files changed, 600 insertions(+), 574 deletions(-) diff --git a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js index 1631a9407..10a9610eb 100644 --- a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js +++ b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js @@ -1,13 +1,16 @@ import React, { Component } from 'react'; import 'bootstrap/dist/css/bootstrap.css'; import CustomDataTable from '../../../Components/CustomDataTable.js'; -import { IconButton } from '@mui/material'; import { Button } from '@mui/material'; import { Tooltip } from '@mui/material'; import EditIcon from '@mui/icons-material/Edit'; import VisibilityIcon from '@mui/icons-material/Visibility'; import { formatDueDate, genericResourceGET, genericResourcePUT, genericResourcePOST, getHumanReadableDueDate } from '../../../../utility.js'; import Loading from '../../../Loading/Loading.js'; +import { IconButton } from '@mui/material'; +import LockIcon from '@mui/icons-material/Lock'; +import LockOpenIcon from '@mui/icons-material/LockOpen'; + class ViewAssessmentTasks extends Component { constructor(props) { @@ -316,6 +319,25 @@ class ViewAssessmentTasks extends Component { } } }, + { + name: "assessment_task_id", + label: "Lock", + options: { + filter: true, + setCellHeaderProps: () => { return { width:"50px"}}, + setCellProps: () => { return { width:"50px"} }, + customBodyRender: (rubricId) => { + return ( + <> + {alert("todo")}}> + + + + ) + } + } + }, { name: "assessment_task_id", label: "To Do", diff --git a/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteIndividualAssessmentTasks.js b/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteIndividualAssessmentTasks.js index 2fb794b21..ab6bd7163 100644 --- a/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteIndividualAssessmentTasks.js +++ b/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteIndividualAssessmentTasks.js @@ -9,308 +9,315 @@ import CustomButton from "../../../Student/View/Components/CustomButton"; import { genericResourcePUT } from "../../../../utility"; import ResponsiveNotification from "../../../Components/SendNotification"; import CourseInfo from "../../../Components/CourseInfo"; - - +import LockIcon from '@mui/icons-material/Lock'; +import LockOpenIcon from '@mui/icons-material/LockOpen'; class ViewCompleteIndividualAssessmentTasks extends Component { - constructor(props) { - super(props); - - this.state = { - errorMessage: null, - isLoaded: null, - showDialog: false, - notes: '', - notificationSent: false, - - errors: { - notes:'' - } - }; - } - - handleChange = (e) => { - const { id, value } = e.target; + constructor(props) { + super(props); + + this.state = { + errorMessage: null, + isLoaded: null, + showDialog: false, + notes: '', + notificationSent: false, + + errors: { + notes:'' + } + }; + } - this.setState({ - [id]: value, - errors: { - ...this.state.errors, - [id]: value.trim() === '' ? `${id.charAt(0).toUpperCase() + id.slice(1)} cannot be empty` : '', - }, - }); - }; + handleChange = (e) => { + const { id, value } = e.target; - handleDialog = () => { - this.setState({ - showDialog: this.state.showDialog === false ? true : false, - }) - } + this.setState({ + [id]: value, + errors: { + ...this.state.errors, + [id]: value.trim() === '' ? `${id.charAt(0).toUpperCase() + id.slice(1)} cannot be empty` : '', + }, + }); + }; - handleSendNotification = () => { - var notes = this.state.notes; + handleDialog = () => { + this.setState({ + showDialog: this.state.showDialog === false ? true : false, + }) + } - var navbar = this.props.navbar; + handleSendNotification = () => { + var notes = this.state.notes; - var state = navbar.state; + var navbar = this.props.navbar; - var chosenAssessmentTask = state.chosenAssessmentTask; + var state = navbar.state; - var date = new Date(); + var chosenAssessmentTask = state.chosenAssessmentTask; - if (notes.trim() === '') { - this.setState({ - errors: { - notes: 'Notification Message cannot be empty', - }, - }); + var date = new Date(); - return; - } + if (notes.trim() === '') { + this.setState({ + errors: { + notes: 'Notification Message cannot be empty', + }, + }); - genericResourcePUT( - `/assessment_task?assessment_task_id=${chosenAssessmentTask["assessment_task_id"]}¬ification=${true}`, - this, JSON.stringify({ - "notification_date": date, - "notification_message": notes - }) - ); - - this.setState({ - showDialog: false, - notificationSent: date, - }); - }; - - render() { - var navbar = this.props.navbar; - - var completedAssessmentTasks = navbar.adminViewCompleteAssessmentTasks.completeAssessmentTasks; - - var userNames = navbar.adminViewCompleteAssessmentTasks.userNames; - - var state = navbar.state; - - var chosenAssessmentTask = state.chosenAssessmentTask; - - var notificationSent = state.notificationSent; - - var chosenCourse = state.chosenCourse; - - const columns = [ - { - name: "assessment_task_id", - label: "Assessment Task", - options: { - filter: true, - - customBodyRender: () => { - return ( -

- {chosenAssessmentTask ? chosenAssessmentTask["assessment_task_name"]: "N/A"} -

- ); - }, - }, - }, - { - name: "last_name", - label: "Student Name", - options: { - filter: true, - - customBodyRender: (last_name) => { - return ( -

- {last_name ? last_name : "N/A"} -

- ); - }, - }, - }, - { - name: "completed_by", - label: "Assessor", - options: { - filter: true, - - customBodyRender: (completed_by) => { - return ( -

- {userNames && completed_by ? userNames[completed_by] : "N/A"} -

- ); - }, - }, - }, - { - name: "initial_time", - label: "Initial Time", - options: { - filter: true, - - customBodyRender: (dueDate) => { - var date = new Date(dueDate); - var month = date.getMonth(); - var day = date.getDate(); - var hour = date.getHours(); - var minute = date.getMinutes(); - - const monthNames = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ]; - - var initialTimeString = `${monthNames[month]} ${day} at ${hour % 12}:${minute < 10 ? "0" + minute : minute}${hour < 12 ? "am" : "pm"}`; - - return ( -

- {dueDate && initialTimeString ? initialTimeString : "N/A"} -

- ); - }, - }, - }, - { - name: "last_update", - label: "Last Updated", - options: { - filter: true, - - customBodyRender: (lastUpdate) => { - var date = new Date(lastUpdate); - var month = date.getMonth(); - var day = date.getDate(); - var hour = date.getHours(); - var minute = date.getMinutes(); - - const monthNames = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ]; - - var lastUpdateString = `${monthNames[month]} ${day} at ${hour % 12}:${minute < 10 ? "0" + minute : minute}${hour < 12 ? "am" : "pm"}`; - - return( -

- {lastUpdate && lastUpdateString ? lastUpdateString : "N/A"} -

- ) - } - } - }, - { - name: "completed_assessment_id", - label: "See More Details", - options: { - filter: false, - sort: false, - setCellHeaderProps: () => { return { align:"center", className:"button-column-alignment"}}, - setCellProps: () => { return { align:"center", className:"button-column-alignment"} }, - customBodyRender: (completedAssessmentId) => { - if (completedAssessmentId) { - return ( - { - navbar.setViewCompleteAssessmentTaskTabWithAssessmentTask( - completedAssessmentTasks, - completedAssessmentId, - chosenAssessmentTask - ); - }} - aria-label="See more details" - > - - - ) - - } else { - return( -

{"N/A"}

- ) - } - } + return; } - } - ]; - - const options = { - onRowsDelete: false, - download: false, - print: false, - selectableRows: "none", - viewColumns: false, - selectableRowsHeader: false, - responsive: "vertical", - tableBodyMaxHeight: "21rem", + + genericResourcePUT( + `/assessment_task?assessment_task_id=${chosenAssessmentTask["assessment_task_id"]}¬ification=${true}`, + this, JSON.stringify({ + "notification_date": date, + "notification_message": notes + }) + ); + + this.setState({ + showDialog: false, + notificationSent: date, + }); }; - return ( - - - - - - - Completed Assesssment Tasks - - - - - - - - - - - - - - ); - } + render() { + var navbar = this.props.navbar; + + var completedAssessmentTasks = navbar.adminViewCompleteAssessmentTasks.completeAssessmentTasks; + + var userNames = navbar.adminViewCompleteAssessmentTasks.userNames; + + var state = navbar.state; + + var chosenAssessmentTask = state.chosenAssessmentTask; + + var notificationSent = state.notificationSent; + + var chosenCourse = state.chosenCourse; + + const columns = [ + { + name: "assessment_task_id", + label: "Assessment Task", + options: { + filter: true, + + customBodyRender: () => { + return ( +

+ {chosenAssessmentTask ? chosenAssessmentTask["assessment_task_name"]: "N/A"} +

+ ); + }, + }, + }, + { + name: "last_name", + label: "Student Name", + options: { + filter: true, + + customBodyRender: (last_name) => { + return ( +

+ {last_name ? last_name : "N/A"} +

+ ); + }, + }, + }, + { + name: "completed_by", + label: "Assessor", + options: { + filter: true, + + customBodyRender: (completed_by) => { + return ( +

+ {userNames && completed_by ? userNames[completed_by] : "N/A"} +

+ ); + }, + }, + }, + { + name: "initial_time", + label: "Initial Time", + options: { + filter: true, + + customBodyRender: (dueDate) => { + var date = new Date(dueDate); + var month = date.getMonth(); + var day = date.getDate(); + var hour = date.getHours(); + var minute = date.getMinutes(); + + const monthNames = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; + + var initialTimeString = `${monthNames[month]} ${day} at ${hour % 12}:${minute < 10 ? "0" + minute : minute}${hour < 12 ? "am" : "pm"}`; + + return ( +

+ {dueDate && initialTimeString ? initialTimeString : "N/A"} +

+ ); + }, + }, + }, + { + name: "last_update", + label: "Last Updated", + options: { + filter: true, + + customBodyRender: (lastUpdate) => { + var date = new Date(lastUpdate); + var month = date.getMonth(); + var day = date.getDate(); + var hour = date.getHours(); + var minute = date.getMinutes(); + + const monthNames = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; + + var lastUpdateString = `${monthNames[month]} ${day} at ${hour % 12}:${minute < 10 ? "0" + minute : minute}${hour < 12 ? "am" : "pm"}`; + + return( +

+ {lastUpdate && lastUpdateString ? lastUpdateString : "N/A"} +

+ ) + } + } + }, + { + name: "completed_assessment_id", + label: "See More Details", + options: { + filter: false, + sort: false, + setCellHeaderProps: () => { return { align:"center", className:"button-column-alignment"}}, + setCellProps: () => { return { align:"center", className:"button-column-alignment"} }, + customBodyRender: (completedAssessmentId) => { + if (completedAssessmentId) { + return ( + { + navbar.setViewCompleteAssessmentTaskTabWithAssessmentTask( + completedAssessmentTasks, + completedAssessmentId, + chosenAssessmentTask + ); + }} + aria-label="See more details" + > + + + ) + + } else { + return( +

{"N/A"}

+ ) + } + } + } + } + ]; + + const options = { + onRowsDelete: false, + download: false, + print: false, + selectableRows: "none", + viewColumns: false, + selectableRowsHeader: false, + responsive: "vertical", + tableBodyMaxHeight: "21rem", + }; + + return ( + + + + + + + Completed Assesssment Tasks + + + + + {alert('todo')}}> + + + + + + + + + + + + + + ); + } } export default ViewCompleteIndividualAssessmentTasks; diff --git a/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteTeamAssessmentTasks.js b/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteTeamAssessmentTasks.js index b37cb6928..91c25c923 100644 --- a/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteTeamAssessmentTasks.js +++ b/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteTeamAssessmentTasks.js @@ -10,307 +10,304 @@ import { genericResourcePUT } from "../../../../utility"; import ResponsiveNotification from "../../../Components/SendNotification"; import CourseInfo from "../../../Components/CourseInfo"; - - - class ViewCompleteTeamAssessmentTasks extends Component { - constructor(props) { - super(props); - - this.state = { - errorMessage: null, - isLoaded: null, - showDialog: false, - notes: '', - notificationSent: false, - - errors: { - notes:'' - } - }; - } - - handleChange = (e) => { - const { id, value } = e.target; + constructor(props) { + super(props); + + this.state = { + errorMessage: null, + isLoaded: null, + showDialog: false, + notes: '', + notificationSent: false, + + errors: { + notes:'' + } + }; + } - this.setState({ - [id]: value, - errors: { - ...this.state.errors, - [id]: value.trim() === '' ? `${id.charAt(0).toUpperCase() + id.slice(1)} cannot be empty` : '', - }, - }); - }; + handleChange = (e) => { + const { id, value } = e.target; - handleDialog = () => { - this.setState({ - showDialog: this.state.showDialog === false ? true : false, - }) - } + this.setState({ + [id]: value, + errors: { + ...this.state.errors, + [id]: value.trim() === '' ? `${id.charAt(0).toUpperCase() + id.slice(1)} cannot be empty` : '', + }, + }); + }; - handleSendNotification = () => { - var notes = this.state.notes; + handleDialog = () => { + this.setState({ + showDialog: this.state.showDialog === false ? true : false, + }) + } - var navbar = this.props.navbar; + handleSendNotification = () => { + var notes = this.state.notes; - var state = navbar.state; + var navbar = this.props.navbar; - var chosenAssessmentTask = state.chosenAssessmentTask; + var state = navbar.state; - var date = new Date(); + var chosenAssessmentTask = state.chosenAssessmentTask; - if (notes.trim() === '') { - this.setState({ - errors: { - notes: 'Notification Message cannot be empty', - }, - }); + var date = new Date(); - return; - } + if (notes.trim() === '') { + this.setState({ + errors: { + notes: 'Notification Message cannot be empty', + }, + }); - genericResourcePUT( - `/assessment_task?assessment_task_id=${chosenAssessmentTask["assessment_task_id"]}¬ification=${true}`, - this, JSON.stringify({ - "notification_date": date, - "notification_message": notes - }) - ); - - this.setState({ - showDialog: false, - notificationSent: date, - }); - }; - - render() { - var navbar = this.props.navbar; - - var completedAssessmentTasks = navbar.adminViewCompleteAssessmentTasks.completeAssessmentTasks; - - var userNames = navbar.adminViewCompleteAssessmentTasks.userNames; - - var state = navbar.state; - - var chosenAssessmentTask = state.chosenAssessmentTask; - - var notificationSent = state.notificationSent; - - var chosenCourse = state.chosenCourse; - - const columns = [ - { - name: "assessment_task_id", - label: "Assessment Task", - options: { - filter: true, - - customBodyRender: () => { - return ( -

- {chosenAssessmentTask ? chosenAssessmentTask["assessment_task_name"]: "N/A"} -

- ); - }, - }, - }, - { - name: "team_name", - label: "Team Name", - options: { - filter: true, - - customBodyRender: (team_name) => { - return ( -

- {team_name ? team_name : "N/A"} -

- ); - }, - }, - }, - { - name: "completed_by", - label: "Assessor", - options: { - filter: true, - - customBodyRender: (completed_by) => { - return ( -

- {userNames && completed_by ? userNames[completed_by] : "N/A"} -

- ); - }, - }, - }, - { - name: "initial_time", - label: "Initial Time", - options: { - filter: true, - - customBodyRender: (dueDate) => { - var date = new Date(dueDate); - var month = date.getMonth(); - var day = date.getDate(); - var hour = date.getHours(); - var minute = date.getMinutes(); - - const monthNames = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ]; - - var initialTimeString = `${monthNames[month]} ${day} at ${hour % 12}:${minute < 10 ? "0" + minute : minute}${hour < 12 ? "am" : "pm"}`; - - return ( -

- {dueDate && initialTimeString ? initialTimeString : "N/A"} -

- ); - }, - }, - }, - { - name: "last_update", - label: "Last Updated", - options: { - filter: true, - - customBodyRender: (lastUpdate) => { - var date = new Date(lastUpdate); - var month = date.getMonth(); - var day = date.getDate(); - var hour = date.getHours(); - var minute = date.getMinutes(); - - const monthNames = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ]; - - var lastUpdateString = `${monthNames[month]} ${day} at ${hour % 12}:${minute < 10 ? "0" + minute : minute}${hour < 12 ? "am" : "pm"}`; - - return( -

- {lastUpdate && lastUpdateString ? lastUpdateString : "N/A"} -

- ) - } - } - }, - { - name: "completed_assessment_id", - label: "See More Details", - options: { - filter: false, - sort: false, - setCellHeaderProps: () => { return { align:"center", className:"button-column-alignment"}}, - setCellProps: () => { return { align:"center", className:"button-column-alignment"} }, - customBodyRender: (completedAssessmentId) => { - if (completedAssessmentId) { - return ( - { - navbar.setViewCompleteAssessmentTaskTabWithAssessmentTask( - completedAssessmentTasks, - completedAssessmentId, - chosenAssessmentTask - ); - }} - aria-label="See more details" - > - - - ) - - } else { - return( -

{"N/A"}

- ) - } - } + return; } - } - ]; - - const options = { - onRowsDelete: false, - download: false, - print: false, - selectableRows: "none", - viewColumns: false, - selectableRowsHeader: false, - responsive: "vertical", - tableBodyMaxHeight: "21rem", + + genericResourcePUT( + `/assessment_task?assessment_task_id=${chosenAssessmentTask["assessment_task_id"]}¬ification=${true}`, + this, JSON.stringify({ + "notification_date": date, + "notification_message": notes + }) + ); + + this.setState({ + showDialog: false, + notificationSent: date, + }); }; - return ( - - - - - - - Completed Assesssment Tasks - - - - - - - - - - - - - - ); - } + render() { + var navbar = this.props.navbar; + + var completedAssessmentTasks = navbar.adminViewCompleteAssessmentTasks.completeAssessmentTasks; + + var userNames = navbar.adminViewCompleteAssessmentTasks.userNames; + + var state = navbar.state; + + var chosenAssessmentTask = state.chosenAssessmentTask; + + var notificationSent = state.notificationSent; + + var chosenCourse = state.chosenCourse; + + const columns = [ + { + name: "assessment_task_id", + label: "Assessment Task", + options: { + filter: true, + + customBodyRender: () => { + return ( +

+ {chosenAssessmentTask ? chosenAssessmentTask["assessment_task_name"]: "N/A"} +

+ ); + }, + }, + }, + { + name: "team_name", + label: "Team Name", + options: { + filter: true, + + customBodyRender: (team_name) => { + return ( +

+ {team_name ? team_name : "N/A"} +

+ ); + }, + }, + }, + { + name: "completed_by", + label: "Assessor", + options: { + filter: true, + + customBodyRender: (completed_by) => { + return ( +

+ {userNames && completed_by ? userNames[completed_by] : "N/A"} +

+ ); + }, + }, + }, + { + name: "initial_time", + label: "Initial Time", + options: { + filter: true, + + customBodyRender: (dueDate) => { + var date = new Date(dueDate); + var month = date.getMonth(); + var day = date.getDate(); + var hour = date.getHours(); + var minute = date.getMinutes(); + + const monthNames = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; + + var initialTimeString = `${monthNames[month]} ${day} at ${hour % 12}:${minute < 10 ? "0" + minute : minute}${hour < 12 ? "am" : "pm"}`; + + return ( +

+ {dueDate && initialTimeString ? initialTimeString : "N/A"} +

+ ); + }, + }, + }, + { + name: "last_update", + label: "Last Updated", + options: { + filter: true, + + customBodyRender: (lastUpdate) => { + var date = new Date(lastUpdate); + var month = date.getMonth(); + var day = date.getDate(); + var hour = date.getHours(); + var minute = date.getMinutes(); + + const monthNames = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; + + var lastUpdateString = `${monthNames[month]} ${day} at ${hour % 12}:${minute < 10 ? "0" + minute : minute}${hour < 12 ? "am" : "pm"}`; + + return( +

+ {lastUpdate && lastUpdateString ? lastUpdateString : "N/A"} +

+ ) + } + } + }, + { + name: "completed_assessment_id", + label: "See More Details", + options: { + filter: false, + sort: false, + setCellHeaderProps: () => { return { align:"center", className:"button-column-alignment"}}, + setCellProps: () => { return { align:"center", className:"button-column-alignment"} }, + customBodyRender: (completedAssessmentId) => { + if (completedAssessmentId) { + return ( + { + navbar.setViewCompleteAssessmentTaskTabWithAssessmentTask( + completedAssessmentTasks, + completedAssessmentId, + chosenAssessmentTask + ); + }} + aria-label="See more details" + > + + + ) + + } else { + return( +

{"N/A"}

+ ) + } + } + } + } + ]; + + const options = { + onRowsDelete: false, + download: false, + print: false, + selectableRows: "none", + viewColumns: false, + selectableRowsHeader: false, + responsive: "vertical", + tableBodyMaxHeight: "21rem", + }; + + return ( + + + + + + + Completed Assesssment Tasks + + + + + + + + + + + + + + ); + } } export default ViewCompleteTeamAssessmentTasks; From 4c0d913a5ab8ecae51072e8c4f26f97b902bbfea Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Thu, 14 Nov 2024 15:20:21 -0600 Subject: [PATCH 09/28] temporary redirect when viewing a CAT as a student --- FrontEndReact/src/View/Navbar/AppState.js | 6 +- .../StudentViewAssessmentTaskInstructions.js | 2 +- .../ViewAssessmentTaskInstructions.js | 341 +++++++++--------- .../ViewCompletedAssessmentTasks.js | 16 +- 4 files changed, 188 insertions(+), 177 deletions(-) diff --git a/FrontEndReact/src/View/Navbar/AppState.js b/FrontEndReact/src/View/Navbar/AppState.js index df9ce9095..6718f1b36 100644 --- a/FrontEndReact/src/View/Navbar/AppState.js +++ b/FrontEndReact/src/View/Navbar/AppState.js @@ -130,7 +130,8 @@ class AppState extends Component { } this.setAssessmentTaskInstructions = (assessmentTasks, assessmentTaskId, completedAssessments=null, { - readOnly = false + readOnly = false, + skipInstructions = false }={}) => { // wip var completedAssessment = null; @@ -138,13 +139,14 @@ class AppState extends Component { completedAssessment = completedAssessments.find(completedAssessment => completedAssessment.assessment_task_id === assessmentTaskId) ?? null; } const assessmentTask = assessmentTasks.find(assessmentTask => assessmentTask["assessment_task_id"] === assessmentTaskId); - + this.setState({ activeTab: "AssessmentTaskInstructions", chosenCompleteAssessmentTask: completedAssessments ? completedAssessment : null, chosenAssessmentTask: assessmentTask, unitOfAssessment: assessmentTask["unit_of_assessment"], chosenCompleteAssessmentTaskIsReadOnly: readOnly, + skipInstructions: skipInstructions }); } diff --git a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTaskInstructions.js b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTaskInstructions.js index 1878611ea..06f8059a2 100644 --- a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTaskInstructions.js +++ b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTaskInstructions.js @@ -47,7 +47,7 @@ class StudentViewAssessmentTaskInstructions extends Component { } else if (!isLoaded || !rubrics) { return( - ) + ) } else { return( diff --git a/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTaskInstructions.js b/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTaskInstructions.js index 92c622280..69029914d 100644 --- a/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTaskInstructions.js +++ b/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTaskInstructions.js @@ -2,188 +2,193 @@ import React, { Component } from "react"; import 'bootstrap/dist/css/bootstrap.css'; import Button from '@mui/material/Button'; import {genericResourcePOST} from '../../../../utility.js'; -import Cookies from 'universal-cookie'; - - +import Cookies from 'universal-cookie'; class ViewAssessmentTaskInstructions extends Component { - constructor(props) { - super(props); + constructor(props) { + super(props); - this.state = { - categories: this.props.rubrics["category_json"], - instructions: this.props.navbar.state.chosenAssessmentTask["comment"], - } - } - - handleContinueClick = async () => { - const navbar = this.props.navbar; - const state = navbar.state; - const cookies = new Cookies(); - - try { - const userId = cookies.get('user')?.user_id; - if (!userId) { - console.error('User ID not found in cookies'); - this.props.navbar.setNewTab("ViewStudentCompleteAssessmentTask"); - return; + this.state = { + categories: this.props.rubrics["category_json"], + instructions: this.props.navbar.state.chosenAssessmentTask["comment"], + skipInstructions: this.props.navbar.state.skipInstructions, } + } - const completedAssessmentId = state.chosenCompleteAssessmentTask?.completed_assessment_id; - if (!completedAssessmentId) { - console.error('Completed assessment ID not found'); - this.props.navbar.setNewTab("ViewStudentCompleteAssessmentTask"); - return; + handleContinueClick = async () => { + const navbar = this.props.navbar; + const state = navbar.state; + const cookies = new Cookies(); + + try { + const userId = cookies.get('user')?.user_id; + if (!userId) { + console.error('User ID not found in cookies'); + this.props.navbar.setNewTab("ViewStudentCompleteAssessmentTask"); + return; + } + + const completedAssessmentId = state.chosenCompleteAssessmentTask?.completed_assessment_id; + if (!completedAssessmentId) { + console.error('Completed assessment ID not found'); + this.props.navbar.setNewTab("ViewStudentCompleteAssessmentTask"); + return; + } + + await genericResourcePOST( + '/feedback', + this, + JSON.stringify({ + user_id: userId, + completed_assessment_id: completedAssessmentId + }) + ); + + } catch (error) { + console.error('Error recording feedback view:', error); } - await genericResourcePOST( - '/feedback', - this, - JSON.stringify({ - user_id: userId, - completed_assessment_id: completedAssessmentId - }) - ); - - } catch (error) { - console.error('Error recording feedback view:', error); + this.props.navbar.setNewTab("ViewStudentCompleteAssessmentTask"); } - this.props.navbar.setNewTab("ViewStudentCompleteAssessmentTask"); -} + render() { + const skipInstructions = this.state.skipInstructions; - render() { - var assessmentTaskName = this.props.navbar.state.chosenAssessmentTask.assessmentTaskName; - - var rubricName = this.props.rubrics["rubric_name"]; - - var rubricDescription = this.props.rubrics["rubric_description"]; - - var categoryList = Object.keys(this.state.categories).map((category, index) => { - - if(index !== Object.keys(this.state.categories).length-1) { - category += ", "; - } - - return category; - }); - - return ( - <> -

- {assessmentTaskName} -

-
-
-

- {"Rubric for " + rubricName} -

- -
- Rubric Description: {rubricDescription} -
- -
-
-
-

{ + + if(index !== Object.keys(this.state.categories).length-1) { + category += ", "; + } + + return category; + }); + + if (skipInstructions) { + this.handleContinueClick(); + } + + return ( + <> +

- Assessment Categories: {categoryList} -

-
-

- Instructions + aria-label="viewAssessmentTaskInstructionsTitle" + > + {assessmentTaskName}

- -
-
- -
-
-
-
- - ) - } +
+

+ {"Rubric for " + rubricName} +

+ +
+ Rubric Description: {rubricDescription} +
+ +
+
+
+

+ Assessment Categories: {categoryList} +

+
+

+ Instructions +

+ +
+
+ +
+
+
+
+ + ) + } } export default ViewAssessmentTaskInstructions; diff --git a/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js b/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js index 9728b156f..3cf87af45 100644 --- a/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js +++ b/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js @@ -4,8 +4,8 @@ import CustomDataTable from "../../../Components/CustomDataTable"; import { IconButton } from "@mui/material"; import VisibilityIcon from '@mui/icons-material/Visibility'; import { getHumanReadableDueDate } from "../../../../utility"; - - +import Cookies from 'universal-cookie'; +import { genericResourcePOST } from '../../../../utility.js'; class ViewCompletedAssessmentTasks extends Component { render() { @@ -94,7 +94,12 @@ class ViewCompletedAssessmentTasks extends Component {
{ - navbar.setAssessmentTaskInstructions(assessmentTasks, atId, completedAssessments, { readOnly: true }); + navbar.setAssessmentTaskInstructions( + assessmentTasks, + atId, + completedAssessments, + { readOnly: true, skipInstructions: true } + ); }} aria-label="completedAssessmentTasksViewIconButton" > @@ -102,9 +107,8 @@ class ViewCompletedAssessmentTasks extends Component {
) - } - } + } }, ]; @@ -129,4 +133,4 @@ class ViewCompletedAssessmentTasks extends Component { } } -export default ViewCompletedAssessmentTasks; \ No newline at end of file +export default ViewCompletedAssessmentTasks; From 214cab335cc0dd25a21d3a39aa6dcf1f1a4812fe Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Thu, 14 Nov 2024 15:23:16 -0600 Subject: [PATCH 10/28] added unit of assessment in student view ATs --- .../View/AssessmentTask/ViewAssessmentTasks.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTasks.js b/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTasks.js index 90f700ce0..3fe21b55d 100644 --- a/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTasks.js +++ b/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTasks.js @@ -83,6 +83,22 @@ class ViewAssessmentTasks extends Component { setCellProps: () => { return { width:"300px"} }, } }, + { + name: "unit_of_assessment", + label: "Unit of Assessment", + options: { + filter: true, + setCellHeaderProps: () => { return { width:"270px"}}, + setCellProps: () => { return { width:"270px"} }, + customBodyRender: (isTeam) => { + return ( +

+ {isTeam ? "Team" : "Individual"} +

+ ) + } + }, + }, { name: "due_date", label: "Due Date", From 094125aab8efb3d028e440a0030e7d18d527a004 Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Sun, 17 Nov 2024 18:32:10 -0600 Subject: [PATCH 11/28] added getting team users route --- BackEndFlask/controller/Routes/Team_routes.py | 18 ++- BackEndFlask/models/queries.py | 6 +- .../StudentViewAssessmentTask.js | 2 - .../src/View/Student/View/StudentViewTeams.js | 7 +- .../src/View/Student/View/ViewTeams.js | 130 +++++++++--------- 5 files changed, 91 insertions(+), 72 deletions(-) diff --git a/BackEndFlask/controller/Routes/Team_routes.py b/BackEndFlask/controller/Routes/Team_routes.py index 561fc918e..75bfce2fd 100644 --- a/BackEndFlask/controller/Routes/Team_routes.py +++ b/BackEndFlask/controller/Routes/Team_routes.py @@ -14,7 +14,8 @@ from controller.security.CustomDecorators import AuthCheck, bad_token_check from models.queries import ( get_team_by_course_id_and_user_id, - get_all_nonfull_adhoc_teams + get_all_nonfull_adhoc_teams, + get_students_by_team_id, ) @bp.route('/team', methods = ['GET']) @@ -165,6 +166,21 @@ def update_team_user_by_edit(): return create_bad_response(f"An error occurred updating a team: {e}", "teams", 400) +@bp.route('/get_all_team_users', methods=['GET']) +@jwt_required() +@bad_token_check() +@AuthCheck() +def get_all_team_users(): + try: + course_id = request.args.get("course_id") + team_id = request.args.get("team_id") + users = get_students_by_team_id(course_id, team_id) + users_json = [{"name": user[1]} for user in users] + return create_good_response(users_json, 200, "teams") + except Exception as e: + return create_bad_response(f"An error occurred getting team users: {e}", "teams", 400) + + class TeamSchema(ma.Schema): class Meta: fields = ( diff --git a/BackEndFlask/models/queries.py b/BackEndFlask/models/queries.py index e4908338a..0641b5d1c 100644 --- a/BackEndFlask/models/queries.py +++ b/BackEndFlask/models/queries.py @@ -272,6 +272,10 @@ def get_students_by_team_id(course_id: int, team_id: int): ).all() +@error_log +def get_teams_and_users_of_team(course_id: int, team_id: int, user_id: int): + pass + @error_log def get_active_students_not_in_a_team(course_id: int, team_id: int): """ @@ -1073,4 +1077,4 @@ def get_completed_assessment_ratio(course_id: int, assessment_task_id: int) -> i ratio_rounded = round(ratio) - return ratio_rounded \ No newline at end of file + return ratio_rounded diff --git a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js index 6dec29d49..07fb70bdc 100644 --- a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js +++ b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js @@ -103,8 +103,6 @@ class StudentViewAssessmentTask extends Component { ) ); - console.log("uncompletedAssessments: ", uncompletedAssessments); - return(
{ return { width:"230px" } }, - setCellProps: () => { return { width:"230px" } }, - } - }, - { - name: "observer_id", - label: navbar.state.chosenCourse["use_tas"] ? "TA Name" : "Instructor Name", - options: { - filter: true, - setCellHeaderProps: () => { return { width:"230px" } }, - setCellProps: () => { return { width:"230px" } }, - customBodyRender: (observerId) => { - return( -

{users[observerId]}

- ) - } - } - }, - { - name: "date_created", - label: "Date Created", - options: { - filter: true, - setCellHeaderProps: () => { return { width:"160px" } }, - setCellProps: () => { return { width:"160px" } }, - customBodyRender: (date_created) => { - let dateCreatedString = getHumanReadableDueDate(date_created); + const columns = [ + { + name: "team_name", + label: "Team Name", + options: { + filter: true, + setCellHeaderProps: () => { return { width:"230px" } }, + setCellProps: () => { return { width:"230px" } }, + } + }, + { + name: "observer_id", + label: navbar.state.chosenCourse["use_tas"] ? "TA Name" : "Instructor Name", + options: { + filter: true, + setCellHeaderProps: () => { return { width:"230px" } }, + setCellProps: () => { return { width:"230px" } }, + customBodyRender: (observerId) => { + return( +

{users[observerId]}

+ ) + } + } + }, + { + name: "date_created", + label: "Date Created", + options: { + filter: true, + setCellHeaderProps: () => { return { width:"160px" } }, + setCellProps: () => { return { width:"160px" } }, + customBodyRender: (date_created) => { + let dateCreatedString = getHumanReadableDueDate(date_created); - return( -

- {date_created ? dateCreatedString : "N/A"} -

- ) - } - } - }, - ]; + return( +

+ {date_created ? dateCreatedString : "N/A"} +

+ ) + } + } + }, + ]; - const options = { - onRowsDelete: false, - download: false, - print: false, - viewColumns: false, - selectableRows: "none", - selectableRowsHeader: false, - responsive: "vertical", - tableBodyMaxHeight: "21rem" - }; + const options = { + onRowsDelete: false, + download: false, + print: false, + viewColumns: false, + selectableRows: "none", + selectableRowsHeader: false, + responsive: "vertical", + tableBodyMaxHeight: "21rem" + }; - return ( - - ) - } + return ( + + ) + } } -export default ViewTeams; \ No newline at end of file +export default ViewTeams; From 021293abe4c600a1b181722af144350edd05fed7 Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Sun, 17 Nov 2024 19:25:12 -0600 Subject: [PATCH 12/28] added lock column to CAT db table --- .../Routes/Completed_assessment_routes.py | 5 +++-- BackEndFlask/models/completed_assessment.py | 15 +++++++++++++-- BackEndFlask/models/queries.py | 5 +++++ BackEndFlask/models/schemas.py | 1 + 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/BackEndFlask/controller/Routes/Completed_assessment_routes.py b/BackEndFlask/controller/Routes/Completed_assessment_routes.py index 1f664562f..161c98e52 100644 --- a/BackEndFlask/controller/Routes/Completed_assessment_routes.py +++ b/BackEndFlask/controller/Routes/Completed_assessment_routes.py @@ -182,9 +182,10 @@ class Meta: 'team_name', 'user_id', 'first_name', - 'last_name', + 'last_name', 'initial_time', 'done', + 'locked', 'last_update', 'rating_observable_characteristics_suggestions_data', 'course_id', @@ -194,4 +195,4 @@ class Meta: completed_assessment_schema = CompletedAssessmentSchema() -completed_assessment_schemas = CompletedAssessmentSchema(many=True) \ No newline at end of file +completed_assessment_schemas = CompletedAssessmentSchema(many=True) diff --git a/BackEndFlask/models/completed_assessment.py b/BackEndFlask/models/completed_assessment.py index 36644eeb5..282d60205 100644 --- a/BackEndFlask/models/completed_assessment.py +++ b/BackEndFlask/models/completed_assessment.py @@ -76,7 +76,8 @@ def create_completed_assessment(completed_assessment_data): initial_time=datetime.strptime(completed_assessment_data["initial_time"], '%Y-%m-%dT%H:%M:%S.%fZ'), last_update=None if completed_assessment_data["last_update"] is None else datetime.strptime(completed_assessment_data["last_update"], '%Y-%m-%dT%H:%M:%S.%fZ'), rating_observable_characteristics_suggestions_data=completed_assessment_data["rating_observable_characteristics_suggestions_data"], - done=completed_assessment_data["done"] + done=completed_assessment_data["done"], + locked=completed_assessment_data["locked"], ) db.session.add(completed_assessment_data) @@ -89,6 +90,7 @@ def load_demo_completed_assessment(): { # Completed Assessment id 1 "assessment_task_id": 1, "done": True, + "locked": False, "initial_time": "2024-01-28T21:08:36.376000", "last_update": "2024-02-01T21:01:33.458000", "rating_observable_characteristics_suggestions_data": { @@ -192,6 +194,7 @@ def load_demo_completed_assessment(): { # Completed Assessment id 2 "assessment_task_id": 2, "done": True, + "locked": False, "initial_time": "2024-01-28T21:08:55.755000", "last_update": "2024-02-01T21:02:45.652000", "rating_observable_characteristics_suggestions_data": { @@ -310,6 +313,7 @@ def load_demo_completed_assessment(): { # Completed Assessment id 3 "assessment_task_id": 5, "done": True, + "locked": False, "initial_time": "2024-01-28T21:09:24.685000", "last_update": "2024-02-01T21:03:25.208000", "rating_observable_characteristics_suggestions_data": { @@ -383,6 +387,7 @@ def load_demo_completed_assessment(): { # Completed Assessment id 4 "assessment_task_id": 8, "done": True, + "locked": False, "initial_time": "2024-01-28T21:22:03.218000", "last_update": "2024-02-01T21:04:16.909000", "rating_observable_characteristics_suggestions_data": { @@ -486,6 +491,7 @@ def load_demo_completed_assessment(): { # Completed Assessment id 5 "assessment_task_id": 9, "done": True, + "locked": False, "initial_time": "2024-01-28T21:26:21.901000", "last_update": "2024-02-01T21:05:39.666000", "rating_observable_characteristics_suggestions_data": { @@ -604,6 +610,7 @@ def load_demo_completed_assessment(): { # Completed Assessment id 6 "assessment_task_id": 10, "done": True, + "locked": False, "initial_time": "2024-01-30T15:11:00.760000", "last_update": "2024-02-01T21:06:49.714000", "rating_observable_characteristics_suggestions_data": { @@ -722,6 +729,7 @@ def load_demo_completed_assessment(): { # Completed Assessment id 7 "assessment_task_id": 11, "done": True, + "locked": False, "initial_time": "2024-01-30T15:12:56.525000", "last_update": "2024-02-05T16:26:42.377000", "rating_observable_characteristics_suggestions_data": { @@ -795,6 +803,7 @@ def load_demo_completed_assessment(): { # Completed Assessment id 8 "assessment_task_id": 12, "done": True, + "locked": False, "initial_time": "2024-02-05T17:04:36.368000", "last_update": "2024-02-05T17:04:38.112000", "rating_observable_characteristics_suggestions_data": { @@ -883,6 +892,7 @@ def load_demo_completed_assessment(): { # Completed Assessment id 9 "assessment_task_id": 13, "done": True, + "locked": False, "initial_time": "2024-02-05T17:07:57.768000", "last_update": "2024-02-05T17:08:00.783000", "rating_observable_characteristics_suggestions_data": { @@ -980,6 +990,7 @@ def load_demo_completed_assessment(): "last_update": comp_assessment["last_update"], "rating_observable_characteristics_suggestions_data": comp_assessment["rating_observable_characteristics_suggestions_data"], "done": comp_assessment["done"], + "locked": comp_assessment["locked"], }) def replace_completed_assessment(completed_assessment_data, completed_assessment_id): @@ -1003,4 +1014,4 @@ def replace_completed_assessment(completed_assessment_data, completed_assessment db.session.commit() - return one_completed_assessment \ No newline at end of file + return one_completed_assessment diff --git a/BackEndFlask/models/queries.py b/BackEndFlask/models/queries.py index 0641b5d1c..d70506367 100644 --- a/BackEndFlask/models/queries.py +++ b/BackEndFlask/models/queries.py @@ -786,6 +786,7 @@ def get_completed_assessment_with_team_name(assessment_task_id): CompletedAssessment.last_update, CompletedAssessment.rating_observable_characteristics_suggestions_data, CompletedAssessment.done, + CompletedAssessment.locked, Team.team_name ).join( Team, Team.team_id == CompletedAssessment.team_id @@ -815,6 +816,7 @@ def get_completed_assessment_with_user_name(assessment_task_id): CompletedAssessment.last_update, CompletedAssessment.rating_observable_characteristics_suggestions_data, CompletedAssessment.done, + CompletedAssessment.locked, User.first_name, User.last_name ).join( @@ -870,6 +872,7 @@ def get_completed_assessment_by_user_id(course_id, user_id): CompletedAssessment.last_update, CompletedAssessment.rating_observable_characteristics_suggestions_data, CompletedAssessment.done, + CompletedAssessment.locked, AssessmentTask.assessment_task_name, AssessmentTask.rubric_id, AssessmentTask.unit_of_assessment @@ -893,6 +896,7 @@ def get_completed_assessment_by_user_id(course_id, user_id): CompletedAssessment.last_update, CompletedAssessment.rating_observable_characteristics_suggestions_data, CompletedAssessment.done, + CompletedAssessment.locked, AssessmentTask.assessment_task_name, AssessmentTask.rubric_id, AssessmentTask.unit_of_assessment @@ -928,6 +932,7 @@ def get_completed_assessment_by_ta_user_id(course_id, user_id): CompletedAssessment.last_update, CompletedAssessment.rating_observable_characteristics_suggestions_data, CompletedAssessment.done, + CompletedAssessment.locked, AssessmentTask.assessment_task_name, AssessmentTask.rubric_id, AssessmentTask.unit_of_assessment diff --git a/BackEndFlask/models/schemas.py b/BackEndFlask/models/schemas.py index d2e6185ca..2f35cb8bb 100644 --- a/BackEndFlask/models/schemas.py +++ b/BackEndFlask/models/schemas.py @@ -159,6 +159,7 @@ class CompletedAssessment(db.Model): last_update = db.Column(db.DateTime(timezone=True), nullable=True) rating_observable_characteristics_suggestions_data = db.Column(db.JSON, nullable=True) done = db.Column(db.Boolean, nullable=False) + locked = db.Column(db.Boolean, nullable=False) class Feedback(db.Model): __tablename__ = "Feedback" From ba8bbf683fbfbdaa22935b73358cd2a799b6dda9 Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Wed, 20 Nov 2024 15:10:41 -0600 Subject: [PATCH 13/28] assessment tasks now have a lock DB column --- .../Routes/Assessment_task_routes.py | 1 + BackEndFlask/models/assessment_task.py | 21 +++++++++++++++++++ BackEndFlask/models/schemas.py | 3 ++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/BackEndFlask/controller/Routes/Assessment_task_routes.py b/BackEndFlask/controller/Routes/Assessment_task_routes.py index 16cf45857..ba4dc0b4c 100644 --- a/BackEndFlask/controller/Routes/Assessment_task_routes.py +++ b/BackEndFlask/controller/Routes/Assessment_task_routes.py @@ -330,6 +330,7 @@ class Meta: "number_of_teams", "max_team_size", "notification_sent", + "locked", ) diff --git a/BackEndFlask/models/assessment_task.py b/BackEndFlask/models/assessment_task.py index 317234e97..1d554fcb9 100644 --- a/BackEndFlask/models/assessment_task.py +++ b/BackEndFlask/models/assessment_task.py @@ -100,6 +100,7 @@ def create_assessment_task(assessment_task): due_date=datetime.strptime(assessment_task["due_date"], '%Y-%m-%dT%H:%M:%S.%fZ'), time_zone=assessment_task["time_zone"], show_suggestions=assessment_task["show_suggestions"], + locked=assessment_task["locked"], show_ratings=assessment_task["show_ratings"], unit_of_assessment=assessment_task["unit_of_assessment"], create_team_password=assessment_task["create_team_password"], @@ -129,6 +130,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "EST", "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 2 "assessment_task_name": "Formal Communication Assessment", @@ -143,6 +145,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": False, "time_zone": "EST", "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 3 "assessment_task_name": "Information Processing Assessment", @@ -157,6 +160,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "EST", "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 4 "assessment_task_name": "Interpersonal Communication", @@ -171,6 +175,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": False, "time_zone": "EST", "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 5 "assessment_task_name": "Management Assessment", @@ -185,6 +190,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "EST", "unit_of_assessment": True, + "locked": False, }, { # Assessment Task 6 "assessment_task_name": "Problem Solving Assessment", @@ -199,6 +205,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": False, "time_zone": "EST", "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 7 "assessment_task_name": "Teamwork Assessment", @@ -213,6 +220,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "EST", "unit_of_assessment": True, + "locked": False, }, { # Assessment Task 8 "assessment_task_name": "Critical Thinking Assessment 2", @@ -227,6 +235,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "CST", "unit_of_assessment": True, + "locked": False, }, { # Assessment Task 9 "assessment_task_name": "AAAAAAAAAAAA", @@ -241,6 +250,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "EST", "unit_of_assessment": True, + "locked": False, }, { # Assessment Task 10 "assessment_task_name": "CCCCCCCCCCCCC", @@ -255,6 +265,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": True, + "locked": False, }, { # Assessment Task 11 "assessment_task_name": "DDDDDDDDDDDDDD", @@ -269,6 +280,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": True, + "locked": False, }, { # Assessment Task 12 "assessment_task_name": "Student 1", @@ -283,6 +295,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "EST", "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 13 "assessment_task_name": "Student 2 Individ", @@ -297,6 +310,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 14 "assessment_task_name": "UI 1", @@ -311,6 +325,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 15 "assessment_task_name": "UI 2", @@ -325,6 +340,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 16 "assessment_task_name": "Calc 1", @@ -339,6 +355,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 17 "assessment_task_name": "Calc 2", @@ -353,6 +370,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "PST", "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 18 "assessment_task_name": "Phys 1", @@ -367,6 +385,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "MST", "unit_of_assessment": False, + "locked": False, }, { # Assessment Task 19 "assessment_task_name": "Phys 2", @@ -381,6 +400,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": True, "time_zone": "MST", "unit_of_assessment": False, + "locked": False, }, ] @@ -393,6 +413,7 @@ def load_demo_admin_assessment_task(): "rubric_id": assessment["rubric_id"], "role_id": assessment["role_id"], "show_suggestions": assessment["show_suggestions"], + "locked": assessment["locked"], "show_ratings": assessment["show_ratings"], "unit_of_assessment": assessment["unit_of_assessment"], "create_team_password": assessment["create_team_password"], diff --git a/BackEndFlask/models/schemas.py b/BackEndFlask/models/schemas.py index 2f35cb8bb..3c57f619f 100644 --- a/BackEndFlask/models/schemas.py +++ b/BackEndFlask/models/schemas.py @@ -15,7 +15,7 @@ UserCourse(user_course_id, user_id, course_id, role_id) Team(team_id, team_name, course_id, observer_id, date_created, active_until) TeamUser(team_user_id, team_id, user_id) - AssessmentTask(assessment_task_id, assessment_task_name, course_id, rubric_id, role_id, due_date, time_zone, show_suggestions, show_ratings, unit_of_assessment, comment, number_of_teams) + AssessmentTask(assessment_task_id, assessment_task_name, course_id, rubric_id, role_id, due_date, time_zone, show_suggestions, show_ratings, unit_of_assessment, comment, number_of_teams, locked) Completed_Assessment(completed_assessment_id, assessment_task_id, by_role, team_id, user_id, initial_time, last_update, rating_observable_characteristics_suggestions_data) Blacklist(id, token) """ @@ -135,6 +135,7 @@ class AssessmentTask(db.Model): number_of_teams = db.Column(db.Integer, nullable=True) max_team_size = db.Column(db.Integer, nullable=True) notification_sent = db.Column(DateTime(timezone=True), nullable=True) + locked = db.Column(db.Boolean, nullable=False) class Checkin(db.Model): # keeps students checking to take a specific AT __tablename__ = "Checkin" From f011ab7ad7f7fa4a78d33ea9f2196e72197df0a8 Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Wed, 20 Nov 2024 16:12:59 -0600 Subject: [PATCH 14/28] ATs that are past due are now shown in the completed ATs. --- BackEndFlask/models/assessment_task.py | 4 +-- .../StudentViewAssessmentTask.js | 32 ++++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/BackEndFlask/models/assessment_task.py b/BackEndFlask/models/assessment_task.py index 1d554fcb9..d14e7b3ac 100644 --- a/BackEndFlask/models/assessment_task.py +++ b/BackEndFlask/models/assessment_task.py @@ -121,7 +121,7 @@ def load_demo_admin_assessment_task(): "assessment_task_name": "Critical Thinking Assessment", "comment": "An example comment", "create_team_password": "at_cta", - "due_date": "2023-04-24T08:30:00", + "due_date": "2027-04-24T08:30:00", "number_of_teams": None, "max_team_size": None, "role_id": 4, @@ -151,7 +151,7 @@ def load_demo_admin_assessment_task(): "assessment_task_name": "Information Processing Assessment", "comment": None, "create_team_password": "at_ipa", - "due_date": "2023-02-14T08:00:00", + "due_date": "2027-02-14T08:00:00", "number_of_teams": None, "max_team_size": None, "role_id": 5, diff --git a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js index 07fb70bdc..b7a69bbe0 100644 --- a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js +++ b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js @@ -31,33 +31,33 @@ class StudentViewAssessmentTask extends Component { var userRole = this.props.role["role_id"]; if (userRole === 5) { // If the user is a student, this returns completed assessments for the student - + genericResourceGET( - `/assessment_task?course_id=${chosenCourseID}`, + `/assessment_task?course_id=${chosenCourseID}`, "assessmentTasks", this); genericResourceGET( - `/completed_assessment?course_id=${chosenCourseID}`, + `/completed_assessment?course_id=${chosenCourseID}`, "completedAssessments", this); } else { // If the user is a TA, this returns assessments completed by the TA genericResourceGET( - `/assessment_task?course_id=${chosenCourseID}&role_id=${userRole}`, + `/assessment_task?course_id=${chosenCourseID}&role_id=${userRole}`, "assessmentTasks", this); genericResourceGET( - `/completed_assessment?course_id=${chosenCourseID}&role_id=${userRole}`, + `/completed_assessment?course_id=${chosenCourseID}&role_id=${userRole}`, "completedAssessments", this); } genericResourceGET( - `/checkin?course_id=${chosenCourseID}`, + `/checkin?course_id=${chosenCourseID}`, "checkin", this); genericResourceGET( `/rubric?all=${true}`, "rubrics", this); genericResourceGET( - `/course?course_id=${chosenCourseID}`, + `/course?course_id=${chosenCourseID}`, "counts", this); } @@ -92,17 +92,25 @@ class StudentViewAssessmentTask extends Component { ) } else { - // SKIL-503 bugfix: - // For the assessments tasks, we only want to display the ones - // that have not been completed. This compares all the assessment - // tasks to the completed ones and filters all the ones that are also - // in the completed assessments. let uncompletedAssessments = assessmentTasks.filter(task => !completedAssessments.some(completed => completed.assessment_task_id === task.assessment_task_id ) ); + for (let i = 0; i < uncompletedAssessments.length; i++) { + console.log("AT: ", uncompletedAssessments[i]); + + const dueDate = new Date(uncompletedAssessments[i].due_date); + const currentDate = new Date(); + + if (dueDate < currentDate) { + completedAssessments.push(uncompletedAssessments[i]); + uncompletedAssessments.splice(i, 1); + i--; + } + } + return(
Date: Mon, 25 Nov 2024 15:19:34 -0600 Subject: [PATCH 15/28] dashboard now gets ATs and CATs. --- .../Routes/Completed_assessment_routes.py | 8 +- BackEndFlask/models/assessment_task.py | 4 +- BackEndFlask/models/completed_assessment.py | 4 +- .../src/View/Student/StudentDashboard.js | 183 +++++++------ .../StudentViewAssessmentTask.js | 16 +- .../AssessmentTask/ViewAssessmentTasks.js | 10 + .../StudentCompletedAssessmentTasks.js | 4 +- .../ViewCompletedAssessmentTasks.js | 240 +++++++++--------- 8 files changed, 253 insertions(+), 216 deletions(-) diff --git a/BackEndFlask/controller/Routes/Completed_assessment_routes.py b/BackEndFlask/controller/Routes/Completed_assessment_routes.py index 161c98e52..9c36792cd 100644 --- a/BackEndFlask/controller/Routes/Completed_assessment_routes.py +++ b/BackEndFlask/controller/Routes/Completed_assessment_routes.py @@ -92,7 +92,7 @@ def get_all_completed_assessments(): completed_assessments = get_completed_assessment_with_team_name(assessment_task_id) else: completed_assessments = get_completed_assessment_with_user_name(assessment_task_id) - + completed_count = get_completed_assessment_count(assessment_task_id) result = [ {**completed_assessment_schema.dump(assessment), 'completed_count': completed_count} @@ -102,7 +102,7 @@ def get_all_completed_assessments(): if request.args and request.args.get("assessment_task_id"): assessment_task_id = int(request.args.get("assessment_task_id")) - + get_assessment_task(assessment_task_id) # Trigger an error if not exists. completed_assessments = get_completed_assessment_with_team_name(assessment_task_id) completed_count = get_completed_assessment_count(assessment_task_id) @@ -111,7 +111,7 @@ def get_all_completed_assessments(): for assessment in completed_assessments ] return create_good_response(result, 200, "completed_assessments") - + if request.args and request.args.get("completed_assessment_task_id"): completed_assessment_task_id = int(request.args.get("completed_assessment_task_id")) one_completed_assessment = get_completed_assessment_with_team_name(completed_assessment_task_id) @@ -135,7 +135,7 @@ def add_completed_assessment(): team_id = int(assessment_data["team_id"]) assessment_task_id = int(request.args.get("assessment_task_id")) user_id = int(assessment_data["user_id"]) - + completed = completed_assessment_exists(team_id, assessment_task_id, user_id) if completed: diff --git a/BackEndFlask/models/assessment_task.py b/BackEndFlask/models/assessment_task.py index d14e7b3ac..b75807c42 100644 --- a/BackEndFlask/models/assessment_task.py +++ b/BackEndFlask/models/assessment_task.py @@ -166,7 +166,7 @@ def load_demo_admin_assessment_task(): "assessment_task_name": "Interpersonal Communication", "comment": None, "create_team_password": "at_ic", - "due_date": "2023-03-05T09:30:00", + "due_date": "2025-03-05T09:30:00", "number_of_teams": None, "max_team_size": None, "role_id": 5, @@ -175,7 +175,7 @@ def load_demo_admin_assessment_task(): "show_suggestions": False, "time_zone": "EST", "unit_of_assessment": False, - "locked": False, + "locked": True, }, { # Assessment Task 5 "assessment_task_name": "Management Assessment", diff --git a/BackEndFlask/models/completed_assessment.py b/BackEndFlask/models/completed_assessment.py index 282d60205..992aaea64 100644 --- a/BackEndFlask/models/completed_assessment.py +++ b/BackEndFlask/models/completed_assessment.py @@ -50,8 +50,8 @@ def get_completed_assessment_count(assessment_task_id): def completed_assessment_exists(team_id, assessment_task_id, user_id): if (user_id == -1): # Team assessment, otherwise individual assessment return CompletedAssessment.query.filter_by(team_id=team_id, assessment_task_id=assessment_task_id, user_id=user_id).first() - else: - return CompletedAssessment.query.filter_by(user_id=user_id, assessment_task_id=assessment_task_id).first() + else: + return CompletedAssessment.query.filter_by(user_id=user_id, assessment_task_id=assessment_task_id).first() @error_log diff --git a/FrontEndReact/src/View/Student/StudentDashboard.js b/FrontEndReact/src/View/Student/StudentDashboard.js index 1af574e8e..daa19def7 100644 --- a/FrontEndReact/src/View/Student/StudentDashboard.js +++ b/FrontEndReact/src/View/Student/StudentDashboard.js @@ -6,9 +6,10 @@ import StudentViewAssessmentTask from '../Student/View/AssessmentTask/StudentVie import { Box, Typography } from '@mui/material'; import { genericResourceGET } from '../../utility.js'; import StudentCompletedAssessmentTasks from './View/CompletedAssessmentTask/StudentCompletedAssessmentTasks.js'; +import Loading from '../Loading/Loading.js'; // StudentDashboard is used for both students and TAs. -// StudentDashboard component is a parent component that renders the StudentViewAssessmentTask, +// StudentDashboard component is a parent component that renders the StudentViewAssessmentTask, // StudentCompletedAssessmentTasks, and depending on the role, either the StudentViewTeams or // the TAViewTeams components. @@ -19,6 +20,8 @@ class StudentDashboard extends Component { this.state = { roles: null, + assessmentTasks: null, + completedAssessments: null, } } @@ -31,6 +34,22 @@ class StudentDashboard extends Component { genericResourceGET( `/role?course_id=${chosenCourse}`, 'roles', this); + + var userRole = state.chosenCourse.role_id; + genericResourceGET( + `/assessment_task?course_id=${chosenCourse}&role_id=${userRole}`, + "assessmentTasks", this); + + if (userRole === 5) { + genericResourceGET( + `/completed_assessment?course_id=${chosenCourse}`, + "completedAssessments", this + ); + } else { + genericResourceGET( + `/completed_assessment?course_id=${chosenCourse}&role_id=${userRole}`, + "completedAssessments", this); + } } render() { @@ -42,92 +61,94 @@ class StudentDashboard extends Component { navbar.studentViewTeams.users = null; var role = this.state.roles; + var assessmentTasks = this.state.assessmentTasks; + var completedAssessments = this.state.completedAssessments; + + if (!role || !assessmentTasks || !completedAssessments) { + return + } return ( <> - {role && - <> - - - - - My Assessment Tasks - - - - - - - + + + + + My Assessment Tasks + - - - - - - Completed Assessments - - - - - - {role["role_id"] === 5 && - - } - {role["role_id"] === 4 && - - } - + + + + + + + + + + + + Completed Assessments + - - - - - - My Teams - - - - - - {role["role_id"] === 5 && - - } - {role["role_id"] === 4 && - - } - + + + + {role["role_id"] === 5 && + + } + {role["role_id"] === 4 && + + } + + + + + + + + My Teams + - - } + + + + {role["role_id"] === 5 && + + } + {role["role_id"] === 4 && + + } + + ) } diff --git a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js index b7a69bbe0..a31f0252a 100644 --- a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js +++ b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js @@ -5,8 +5,6 @@ import ErrorMessage from '../../../Error/ErrorMessage.js'; import { genericResourceGET, parseRubricNames } from '../../../../utility.js'; import Loading from '../../../Loading/Loading.js'; - - class StudentViewAssessmentTask extends Component { constructor(props) { super(props); @@ -92,15 +90,17 @@ class StudentViewAssessmentTask extends Component { ) } else { + // All AT.id === CAT.id means it is completed, do not + // display it in the TODO ATs. let uncompletedAssessments = assessmentTasks.filter(task => !completedAssessments.some(completed => completed.assessment_task_id === task.assessment_task_id ) ); + // All ATs that are past due needs to be not shown + // in the TODO ATs. for (let i = 0; i < uncompletedAssessments.length; i++) { - console.log("AT: ", uncompletedAssessments[i]); - const dueDate = new Date(uncompletedAssessments[i].due_date); const currentDate = new Date(); @@ -111,6 +111,14 @@ class StudentViewAssessmentTask extends Component { } } + for (let i = 0; i < uncompletedAssessments.length; i++) { + if (uncompletedAssessments[i].locked) { + completedAssessments.push(uncompletedAssessments[i]); + uncompletedAssessments.splice(i, 1); + i--; + } + } + return(
+ // !complatedAssessmentTasks.some(completed => + // completed.assessment_task_id === task.assessment_task_id + // ) + // ); + + // assessmentTasks = assessmentTasks.filter((at) => at.locked); const columns = [ { diff --git a/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/StudentCompletedAssessmentTasks.js b/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/StudentCompletedAssessmentTasks.js index 176ec5c04..272bb67cb 100644 --- a/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/StudentCompletedAssessmentTasks.js +++ b/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/StudentCompletedAssessmentTasks.js @@ -41,7 +41,7 @@ class StudentCompletedAssessmentTasks extends Component { ); } else { // If the user is a TA, this returns assessments completed by the TA genericResourceGET( - `/completed_assessment?course_id=${chosenCourseID}&role_id=${userRole}`, + `/completed_assessment?course_id=${chosenCourseID}&role_id=${userRole}`, "completedAssessments", this); } } @@ -83,4 +83,4 @@ class StudentCompletedAssessmentTasks extends Component { } } -export default StudentCompletedAssessmentTasks; \ No newline at end of file +export default StudentCompletedAssessmentTasks; diff --git a/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js b/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js index 3cf87af45..a87529337 100644 --- a/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js +++ b/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js @@ -4,133 +4,131 @@ import CustomDataTable from "../../../Components/CustomDataTable"; import { IconButton } from "@mui/material"; import VisibilityIcon from '@mui/icons-material/Visibility'; import { getHumanReadableDueDate } from "../../../../utility"; -import Cookies from 'universal-cookie'; -import { genericResourcePOST } from '../../../../utility.js'; class ViewCompletedAssessmentTasks extends Component { - render() { - var navbar = this.props.navbar; + render() { + var navbar = this.props.navbar; - var completedAssessments = this.props.completedAssessments; - var assessmentTasks = this.props.assessmentTasks; + var completedAssessments = this.props.completedAssessments; + var assessmentTasks = this.props.assessmentTasks; - const columns = [ - { - name: "assessment_task_name", - label: "Task Name", - options: { - filter: true, - setCellHeaderProps: () => { return { width:"250px" } }, - setCellProps: () => { return { width:"250px" } }, - } - }, - { - name: "initial_time", - label: "Initial Time", - options: { - filter: true, - setCellHeaderProps: () => { return { width:"150px" } }, - setCellProps: () => { return { width:"150px" } }, - customBodyRender: (initial_time) => { - return( - <> - {initial_time ? getHumanReadableDueDate(initial_time) : "N/A"} - - ); - } - } - }, - { - name: "last_update", - label: "Last Update", - options: { - filter: true, - setCellHeaderProps: () => { return { width:"150px" } }, - setCellProps: () => { return { width:"150px" } }, - customBodyRender: (last_update) => { - return( - <> - {last_update ? getHumanReadableDueDate(last_update) : "N/A"} - - ); - } - } - }, - { - name: "assessment_task_id", - label: "Unit of Assessment", - options: { - filter: true, - setCellHeaderProps: () => { return { width:"170px" } }, - setCellProps: () => { return { width:"140px" } }, - customBodyRender: (atId) => { - const assessmentTask = assessmentTasks.find(at => at.assessment_task_id === atId); - return <>{assessmentTask.unit_of_assessment ? "Team" : "Individual"}; - } - } - }, - { - name: "completed_by_role_id", - label: "Completed By", - options: { - filter: true, - setCellHeaderProps: () => { return { width:"140px" } }, - setCellProps: () => { return { width:"140px" } }, - customBodyRender: (roleId) => { - return <>{roleId === 5 ? "Student" : "TA/Instructor"}; - } - } - }, - { - name: "assessment_task_id", - label: "View", - options: { - filter: false, - sort: false, - setCellHeaderProps: () => { return { align:"center", width:"100px", className:"button-column-alignment" } }, - setCellProps: () => { return { align:"center", width:"100px", className:"button-column-alignment" } }, - customBodyRender: (atId) => { - return ( -
- { - navbar.setAssessmentTaskInstructions( - assessmentTasks, - atId, - completedAssessments, - { readOnly: true, skipInstructions: true } - ); - }} - aria-label="completedAssessmentTasksViewIconButton" - > - - -
- ) - } - } - }, - ]; + const columns = [ + { + name: "assessment_task_name", + label: "Task Name", + options: { + filter: true, + setCellHeaderProps: () => { return { width:"250px" } }, + setCellProps: () => { return { width:"250px" } }, + } + }, + { + name: "initial_time", + label: "Initial Time", + options: { + filter: true, + setCellHeaderProps: () => { return { width:"150px" } }, + setCellProps: () => { return { width:"150px" } }, + customBodyRender: (initial_time) => { + return( + <> + {initial_time ? getHumanReadableDueDate(initial_time) : "N/A"} + + ); + } + } + }, + { + name: "last_update", + label: "Last Update", + options: { + filter: true, + setCellHeaderProps: () => { return { width:"150px" } }, + setCellProps: () => { return { width:"150px" } }, + customBodyRender: (last_update) => { + return( + <> + {last_update ? getHumanReadableDueDate(last_update) : "N/A"} + + ); + } + } + }, + { + name: "assessment_task_id", + label: "Unit of Assessment", + options: { + filter: true, + setCellHeaderProps: () => { return { width:"170px" } }, + setCellProps: () => { return { width:"140px" } }, + customBodyRender: (atId) => { + const assessmentTask = assessmentTasks.find(at => at.assessment_task_id === atId); + return <>{assessmentTask.unit_of_assessment ? "Team" : "Individual"}; + } + } + }, + { + name: "completed_by_role_id", + label: "Completed By", + options: { + filter: true, + setCellHeaderProps: () => { return { width:"140px" } }, + setCellProps: () => { return { width:"140px" } }, + customBodyRender: (roleId) => { + return <>{roleId === 5 ? "Student" : "TA/Instructor"}; + } + } + }, + { + name: "assessment_task_id", + label: "View", + options: { + filter: false, + sort: false, + setCellHeaderProps: () => { return { align:"center", width:"100px", className:"button-column-alignment" } }, + setCellProps: () => { return { align:"center", width:"100px", className:"button-column-alignment" } }, + customBodyRender: (atId) => { + return ( +
+ { + navbar.setAssessmentTaskInstructions( + assessmentTasks, + atId, + completedAssessments, + { readOnly: true, skipInstructions: true } + ); + }} + aria-label="completedAssessmentTasksViewIconButton" + > + + +
+ ) + } + } + }, + ]; - const options = { - onRowsDelete: false, - download: false, - print: false, - viewColumns: false, - selectableRows: "none", - selectableRowsHeader: false, - responsive: "vertical", - tableBodyMaxHeight: "21rem" - }; + const options = { + onRowsDelete: false, + download: false, + print: false, + viewColumns: false, + selectableRows: "none", + selectableRowsHeader: false, + responsive: "vertical", + tableBodyMaxHeight: "21rem" + }; - return ( - - ) - } + return ( + + ) + } } export default ViewCompletedAssessmentTasks; From 8e178df7f474b73bae2bf4bfd338b9b4f9612d38 Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Mon, 25 Nov 2024 16:29:40 -0600 Subject: [PATCH 16/28] ATs and CATs now use filtered results --- .../src/View/Student/StudentDashboard.js | 33 ++++++++++++++++-- .../StudentViewAssessmentTask.js | 34 +++---------------- .../StudentCompletedAssessmentTasks.js | 5 ++- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/FrontEndReact/src/View/Student/StudentDashboard.js b/FrontEndReact/src/View/Student/StudentDashboard.js index daa19def7..1fa058ca7 100644 --- a/FrontEndReact/src/View/Student/StudentDashboard.js +++ b/FrontEndReact/src/View/Student/StudentDashboard.js @@ -61,13 +61,36 @@ class StudentDashboard extends Component { navbar.studentViewTeams.users = null; var role = this.state.roles; - var assessmentTasks = this.state.assessmentTasks; - var completedAssessments = this.state.completedAssessments; + let assessmentTasks = this.state.assessmentTasks; + let completedAssessments = this.state.completedAssessments; + + // Wait for information to be retrieved from DB. if (!role || !assessmentTasks || !completedAssessments) { return } + // Remove ATs where the ID matches one of the IDs + // in the CATs (that AT is completed, no need to display it). + assessmentTasks = assessmentTasks.filter(task => + !completedAssessments.some(completed => + completed.assessment_task_id === task.assessment_task_id + ) + ); + + // Move the remaining (past due) ATs into the CATs + // as well as any ATs that have been manually locked + // by an admin. + const currentDate = new Date(); + for (let i = 0; i < assessmentTasks.length; ++i) { + const dueDate = new Date(assessmentTasks[i].due_date); + if (dueDate < currentDate || assessmentTasks[i].locked) { + completedAssessments.push(assessmentTasks[i]); + assessmentTasks.splice(i, 1); + --i; + } + } + return ( <> @@ -88,6 +111,8 @@ class StudentDashboard extends Component { @@ -111,12 +136,16 @@ class StudentDashboard extends Component { } {role["role_id"] === 4 && } diff --git a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js index a31f0252a..b134bb4a8 100644 --- a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js +++ b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTask.js @@ -74,6 +74,9 @@ class StudentViewAssessmentTask extends Component { var role = this.props.role; + const filteredATs = this.props.filteredAssessments; + const filteredCATs = this.props.filteredCompleteAssessments; // Currently unused, but may be in the future. + if (errorMessage) { return(
@@ -90,41 +93,12 @@ class StudentViewAssessmentTask extends Component { ) } else { - // All AT.id === CAT.id means it is completed, do not - // display it in the TODO ATs. - let uncompletedAssessments = assessmentTasks.filter(task => - !completedAssessments.some(completed => - completed.assessment_task_id === task.assessment_task_id - ) - ); - - // All ATs that are past due needs to be not shown - // in the TODO ATs. - for (let i = 0; i < uncompletedAssessments.length; i++) { - const dueDate = new Date(uncompletedAssessments[i].due_date); - const currentDate = new Date(); - - if (dueDate < currentDate) { - completedAssessments.push(uncompletedAssessments[i]); - uncompletedAssessments.splice(i, 1); - i--; - } - } - - for (let i = 0; i < uncompletedAssessments.length; i++) { - if (uncompletedAssessments[i].locked) { - completedAssessments.push(uncompletedAssessments[i]); - uncompletedAssessments.splice(i, 1); - i--; - } - } - return(
@@ -74,7 +77,7 @@ class StudentCompletedAssessmentTasks extends Component {
From cc3da729666dd8d7ac4424d44c6e55c8e7111f10 Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Mon, 25 Nov 2024 19:21:31 -0600 Subject: [PATCH 17/28] team members show up again --- BackEndFlask/controller/Routes/Team_routes.py | 72 ++++++++++++++++++- BackEndFlask/models/queries.py | 38 ++++++++-- .../src/View/Student/View/ViewTeams.js | 18 +++-- 3 files changed, 119 insertions(+), 9 deletions(-) diff --git a/BackEndFlask/controller/Routes/Team_routes.py b/BackEndFlask/controller/Routes/Team_routes.py index 75bfce2fd..30fe5ccb3 100644 --- a/BackEndFlask/controller/Routes/Team_routes.py +++ b/BackEndFlask/controller/Routes/Team_routes.py @@ -16,6 +16,7 @@ get_team_by_course_id_and_user_id, get_all_nonfull_adhoc_teams, get_students_by_team_id, + get_team_users, ) @bp.route('/team', methods = ['GET']) @@ -38,6 +39,7 @@ def get_all_teams(): except Exception as e: return create_bad_response(f"An error occurred retrieving all teams: {e}", "teams", 400) +# WIP @bp.route('/team_by_user', methods = ['GET']) @jwt_required() @bad_token_check() @@ -50,11 +52,79 @@ def get_all_teams_by_user(): teams = get_team_by_course_id_and_user_id(course_id, user_id) - return create_good_response(teams_schema.dump(teams), 200, "teams") + json = [] + + for i in range(0, len(teams)): + team_id = teams[i].team_id + team_name = teams[i].team_name + team_users = get_team_users(course_id, team_id, user_id) + users = [] + + # Get the names of each team member w/ the LName shortened. + for user in team_users: + # users.append((user[1]+' '+user[2][0]+'.')) + users.append(user[1]) + + data = { + 'team_id': teams[i].team_id, + 'team_name': teams[i].team_name, + 'observer_id': teams[i].observer_id, + 'course_id': teams[i].course_id, + 'date_created': teams[i].date_created, + 'active_until': teams[i].active_until, + 'team_users': users, + } + json.append(data) + + return create_good_response(json, 200, "teams") + # return create_good_response(teams_schema.dump(teams), 200, "teams") except Exception as e: return create_bad_response(f"An error occurred retrieving all teams: {e}", "teams", 400) +# @bp.route('/team_by_user', methods=['GET']) +# @jwt_required() +# @bad_token_check() +# @AuthCheck() +# def get_all_teams_by_user(): +# try: +# if request.args and request.args.get("course_id"): +# course_id = int(request.args.get("course_id")) +# user_id = int(request.args.get("user_id")) +# +# # Get the teams that the user is associated with +# teams = get_team_by_course_id_and_user_id(course_id, user_id) +# +# # Prepare a list to store teams and their users +# teams_with_users = [] +# +# for team in teams: +# # Access team data directly since 'team' is a Team object +# team_id = team.team_id +# team_name = team.team_name +# +# # Get the users of the current team +# team_users = get_team_users(course_id, team_id, user_id) +# +# # Add team information along with its users to the result +# teams_with_users.append({ +# "team": { +# "team_id": team_id, +# "team_name": team_name, +# "course_id": team.course_id, +# "observer_id": team.observer_id, +# "date_created": team.date_created, +# "active_until": team.active_until +# }, +# "users": team_users +# }) +# +# # Return the teams along with their users +# return create_good_response(teams_with_users, 200, "teams_with_users") +# +# except Exception as e: +# return create_bad_response(f"An error occurred retrieving all teams: {e}", "teams", 400) + @bp.route('/team_by_observer', methods = ['GET']) @jwt_required() @bad_token_check() diff --git a/BackEndFlask/models/queries.py b/BackEndFlask/models/queries.py index d70506367..fb6084ecf 100644 --- a/BackEndFlask/models/queries.py +++ b/BackEndFlask/models/queries.py @@ -187,6 +187,7 @@ def get_role_in_course(user_id: int, course_id: int): return role +# WIP @error_log def get_team_by_course_id_and_user_id(course_id, user_id): """ @@ -211,6 +212,39 @@ def get_team_by_course_id_and_user_id(course_id, user_id): return teams +# HERE +@error_log +def get_team_users(course_id: int, team_id: int, user_id: int): + """ + Description: + Gets all users associated with the given team in the given course. + + Parameters: + course_id: int (The id of the course) + team_id: int (The id of the team) + user_id: int (The id of the logged-in user) + """ + users_in_team = db.session.query( + User.user_id, + User.first_name, + User.last_name, + User.email, + Team.team_id, + Team.team_name + ).join( + TeamUser, TeamUser.team_id == Team.team_id + ).join( + User, User.user_id == TeamUser.user_id + ).filter( + and_( + Team.course_id == course_id, + Team.team_id == team_id + ) + ).all() + + # Return the users in the team + return users_in_team + @error_log def get_team_by_course_id_and_observer_id(course_id, observer_id): """ @@ -272,10 +306,6 @@ def get_students_by_team_id(course_id: int, team_id: int): ).all() -@error_log -def get_teams_and_users_of_team(course_id: int, team_id: int, user_id: int): - pass - @error_log def get_active_students_not_in_a_team(course_id: int, team_id: int): """ diff --git a/FrontEndReact/src/View/Student/View/ViewTeams.js b/FrontEndReact/src/View/Student/View/ViewTeams.js index 95cc34a09..9a84b665d 100644 --- a/FrontEndReact/src/View/Student/View/ViewTeams.js +++ b/FrontEndReact/src/View/Student/View/ViewTeams.js @@ -3,14 +3,10 @@ import 'bootstrap/dist/css/bootstrap.css'; import CustomDataTable from "../../Components/CustomDataTable"; import { getHumanReadableDueDate } from "../../../utility"; - - class ViewTeams extends Component{ render() { var teams = this.props.teams; - var users = this.props.users; - var navbar = this.props.navbar; const columns = [ @@ -37,6 +33,20 @@ class ViewTeams extends Component{ } } }, + { + name: "team_users", + label: "Members", + options: { + filter: true, + setCellHeaderProps: () => { return { width:"230px" } }, + setCellProps: () => { return { width:"230px" } }, + customBodyRender: (user) => { + return( + <>{user + " "} + ); + } + }, + }, { name: "date_created", label: "Date Created", From fea81f2e32f20f6dc498acc9dc7643c3cb8f2e72 Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Tue, 26 Nov 2024 17:37:25 -0600 Subject: [PATCH 18/28] added locking to all CATs --- .../Routes/Completed_assessment_routes.py | 50 +++++++++- BackEndFlask/models/completed_assessment.py | 38 +++++++- .../ViewAssessmentTask/ViewAssessmentTasks.js | 21 ---- .../ViewCompleteIndividualAssessmentTasks.js | 96 ++++++++++++++++++- .../AssessmentTask/ViewAssessmentTasks.js | 12 +-- 5 files changed, 184 insertions(+), 33 deletions(-) diff --git a/BackEndFlask/controller/Routes/Completed_assessment_routes.py b/BackEndFlask/controller/Routes/Completed_assessment_routes.py index 9c36792cd..ed4677c60 100644 --- a/BackEndFlask/controller/Routes/Completed_assessment_routes.py +++ b/BackEndFlask/controller/Routes/Completed_assessment_routes.py @@ -11,7 +11,10 @@ create_completed_assessment, replace_completed_assessment, completed_assessment_exists, - get_completed_assessment_count + get_completed_assessment_count, + toggle_lock_status, + make_complete_assessment_locked, + make_complete_assessment_unlocked, ) from models.queries import ( @@ -25,6 +28,51 @@ from models.assessment_task import get_assessment_tasks_by_course_id +@bp.route('/completed_assessment_toggle_lock', methods=['PUT']) +@jwt_required() +@bad_token_check() +@AuthCheck() +def toggle_complete_assessment_lock_status(): + try: + assessmentTaskId = request.args.get('assessment_task_id') + toggle_lock_status(assessmentTaskId) + return create_good_response(None, 201, "completed_assessments") + + except Exception as e: + return create_bad_response( + f"An error occurred toggling CAT lock: {e}", "completed_assessments", 400 + ) + +@bp.route('/completed_assessment_lock', methods=['PUT']) +@jwt_required() +@bad_token_check() +@AuthCheck() +def lock_complete_assessment(): + try: + assessmentTaskId = request.args.get('assessment_task_id') + make_complete_assessment_locked(assessmentTaskId) + return create_good_response(None, 201, "completed_assessments") + + except Exception as e: + return create_bad_response( + f"An error occurred locking a CAT: {e}", "completed_assessments", 400 + ) + +@bp.route('/completed_assessment_unlock', methods=['PUT']) +@jwt_required() +@bad_token_check() +@AuthCheck() +def unlock_complete_assessment(): + try: + assessmentTaskId = request.args.get('assessment_task_id') + make_complete_assessment_unlocked(assessmentTaskId) + return create_good_response(None, 201, "completed_assessments") + + except Exception as e: + return create_bad_response( + f"An error occurred unlocking a CAT: {e}", "completed_assessments", 400 + ) + @bp.route('/completed_assessment', methods=['GET']) @jwt_required() @bad_token_check() diff --git a/BackEndFlask/models/completed_assessment.py b/BackEndFlask/models/completed_assessment.py index 992aaea64..bb0b260d5 100644 --- a/BackEndFlask/models/completed_assessment.py +++ b/BackEndFlask/models/completed_assessment.py @@ -77,7 +77,7 @@ def create_completed_assessment(completed_assessment_data): last_update=None if completed_assessment_data["last_update"] is None else datetime.strptime(completed_assessment_data["last_update"], '%Y-%m-%dT%H:%M:%S.%fZ'), rating_observable_characteristics_suggestions_data=completed_assessment_data["rating_observable_characteristics_suggestions_data"], done=completed_assessment_data["done"], - locked=completed_assessment_data["locked"], + locked=False, ) db.session.add(completed_assessment_data) @@ -85,6 +85,42 @@ def create_completed_assessment(completed_assessment_data): return completed_assessment_data +@error_log +def toggle_lock_status(completed_assessment_id): + one_completed_assessment = CompletedAssessment.query.filter_by(completed_assessment_id=completed_assessment_id).first() + + if one_completed_assessment is None: + raise InvalidCRID(completed_assessment_id) + + one_completed_assessment.locked = not one_completed_assessment.locked + db.session.commit() + + return one_completed_assessment + +@error_log +def make_complete_assessment_locked(completed_assessment_id): + one_completed_assessment = CompletedAssessment.query.filter_by(completed_assessment_id=completed_assessment_id).first() + + if one_completed_assessment is None: + raise InvalidCRID(completed_assessment_id) + + one_completed_assessment.locked = True + db.session.commit() + + return one_completed_assessment + +@error_log +def make_complete_assessment_unlocked(completed_assessment_id): + one_completed_assessment = CompletedAssessment.query.filter_by(completed_assessment_id=completed_assessment_id).first() + + if one_completed_assessment is None: + raise InvalidCRID(completed_assessment_id) + + one_completed_assessment.locked = False + db.session.commit() + + return one_completed_assessment + def load_demo_completed_assessment(): list_of_completed_assessments = [ { # Completed Assessment id 1 diff --git a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js index 10a9610eb..371969a8b 100644 --- a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js +++ b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js @@ -8,8 +8,6 @@ import VisibilityIcon from '@mui/icons-material/Visibility'; import { formatDueDate, genericResourceGET, genericResourcePUT, genericResourcePOST, getHumanReadableDueDate } from '../../../../utility.js'; import Loading from '../../../Loading/Loading.js'; import { IconButton } from '@mui/material'; -import LockIcon from '@mui/icons-material/Lock'; -import LockOpenIcon from '@mui/icons-material/LockOpen'; class ViewAssessmentTasks extends Component { @@ -319,25 +317,6 @@ class ViewAssessmentTasks extends Component { } } }, - { - name: "assessment_task_id", - label: "Lock", - options: { - filter: true, - setCellHeaderProps: () => { return { width:"50px"}}, - setCellProps: () => { return { width:"50px"} }, - customBodyRender: (rubricId) => { - return ( - <> - {alert("todo")}}> - - - - ) - } - } - }, { name: "assessment_task_id", label: "To Do", diff --git a/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteIndividualAssessmentTasks.js b/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteIndividualAssessmentTasks.js index ab6bd7163..825bbfae7 100644 --- a/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteIndividualAssessmentTasks.js +++ b/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteIndividualAssessmentTasks.js @@ -12,7 +12,6 @@ import CourseInfo from "../../../Components/CourseInfo"; import LockIcon from '@mui/icons-material/Lock'; import LockOpenIcon from '@mui/icons-material/LockOpen'; - class ViewCompleteIndividualAssessmentTasks extends Component { constructor(props) { super(props); @@ -23,6 +22,7 @@ class ViewCompleteIndividualAssessmentTasks extends Component { showDialog: false, notes: '', notificationSent: false, + lockStatus: {}, errors: { notes:'' @@ -30,6 +30,68 @@ class ViewCompleteIndividualAssessmentTasks extends Component { }; } + componentDidMount() { + const completedAssessmentTasks = this.props.navbar.adminViewCompleteAssessmentTasks.completeAssessmentTasks; + const initialLockStatus = {}; + + completedAssessmentTasks.forEach((task) => { + initialLockStatus[task.assessment_task_id] = task.locked; + }); + + this.setState({ lockStatus: initialLockStatus }); + } + + handleLockToggle = (assessmentTaskId, task) => { + this.setState((prevState) => { + const newLockStatus = { ...prevState.lockStatus }; + newLockStatus[assessmentTaskId] = !newLockStatus[assessmentTaskId]; + return { lockStatus: newLockStatus }; + }, () => { + const lockStatus = this.state.lockStatus[assessmentTaskId]; + + genericResourcePUT( + `/completed_assessment_toggle_lock?assessment_task_id=${assessmentTaskId}&locked=${lockStatus}`, + this, + JSON.stringify({ locked: lockStatus }) + ); + }); + }; + + handleUnlockAllCats = (assessmentTaskIds) => { + assessmentTaskIds.forEach((assessmentTaskId) => { + this.setState((prevState) => { + const newLockStatus = { ...prevState.lockStatus }; + newLockStatus[assessmentTaskId] = false; + return { lockStatus: newLockStatus }; + }, () => { + const lockStatus = this.state.lockStatus[assessmentTaskId]; + genericResourcePUT( + `/completed_assessment_unlock?assessment_task_id=${assessmentTaskId}`, + this, + JSON.stringify({ locked: lockStatus }) + ); + }); + }); + }; + + handleLockAllCats = (assessmentTaskIds) => { + assessmentTaskIds.forEach((assessmentTaskId) => { + this.setState((prevState) => { + const newLockStatus = { ...prevState.lockStatus }; + newLockStatus[assessmentTaskId] = true; + return { lockStatus: newLockStatus }; + }, () => { + const lockStatus = this.state.lockStatus[assessmentTaskId]; + + genericResourcePUT( + `/completed_assessment_lock?assessment_task_id=${assessmentTaskId}`, + this, + JSON.stringify({ locked: lockStatus }) + ); + }); + }); + }; + handleChange = (e) => { const { id, value } = e.target; @@ -220,6 +282,26 @@ class ViewCompleteIndividualAssessmentTasks extends Component { } } }, + { + name: "assessment_task_id", + label: "Lock", + options: { + filter: true, + customBodyRender: (catId) => { + const task = completedAssessmentTasks.find((task) => task["assessment_task_id"] === catId); + const isLocked = this.state.lockStatus[catId] !== undefined ? this.state.lockStatus[catId] : (task ? task.locked : false); + + return ( + this.handleLockToggle(catId, task)} + > + {isLocked ? : } + + ); + }, + } + }, { name: "completed_assessment_id", label: "See More Details", @@ -292,10 +374,20 @@ class ViewCompleteIndividualAssessmentTasks extends Component { {alert('todo')}}> + onClick={() => { + this.handleLockAllCats(completedAssessmentTasks.map((task) => task.assessment_task_id)); + }}> + { + this.handleUnlockAllCats(completedAssessmentTasks.map((task) => task.assessment_task_id)); + }}> + + + - // !complatedAssessmentTasks.some(completed => - // completed.assessment_task_id === task.assessment_task_id - // ) - // ); - - // assessmentTasks = assessmentTasks.filter((at) => at.locked); - const columns = [ { name: "assessment_task_name", @@ -152,6 +144,10 @@ class ViewAssessmentTasks extends Component { setCellHeaderProps: () => { return { align:"center", width:"140px", className:"button-column-alignment"}}, setCellProps: () => { return { align:"center", width:"140px", className:"button-column-alignment"} }, customBodyRender: (atId) => { + let at = assessmentTasks.find((at) => at["assessment_task_id"] === atId); + + console.log(at); + return ( Date: Sun, 1 Dec 2024 21:35:37 -0600 Subject: [PATCH 19/28] added locking to reg. assessments. started on new readme --- .../Routes/Assessment_task_routes.py | 2 - .../ViewAssessmentTask/ViewAssessmentTasks.js | 50 +++++++ README.md | 15 ++- README2.md | 126 ++++++++++++++++++ 4 files changed, 186 insertions(+), 7 deletions(-) create mode 100644 README2.md diff --git a/BackEndFlask/controller/Routes/Assessment_task_routes.py b/BackEndFlask/controller/Routes/Assessment_task_routes.py index ba4dc0b4c..a47d023e3 100644 --- a/BackEndFlask/controller/Routes/Assessment_task_routes.py +++ b/BackEndFlask/controller/Routes/Assessment_task_routes.py @@ -253,8 +253,6 @@ def update_assessment_task(): @AuthCheck() def toggle_lock_status_route(): try: - # TODO: deprecate _lockStatus - _lockStatus = request.args.get('lockStatus') assessmentTaskId = request.args.get('assessmentTaskId') toggle_lock_status(assessmentTaskId) diff --git a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js index 371969a8b..557c959c1 100644 --- a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js +++ b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js @@ -8,6 +8,8 @@ import VisibilityIcon from '@mui/icons-material/Visibility'; import { formatDueDate, genericResourceGET, genericResourcePUT, genericResourcePOST, getHumanReadableDueDate } from '../../../../utility.js'; import Loading from '../../../Loading/Loading.js'; import { IconButton } from '@mui/material'; +import LockIcon from '@mui/icons-material/Lock'; +import LockOpenIcon from '@mui/icons-material/LockOpen'; class ViewAssessmentTasks extends Component { @@ -43,6 +45,22 @@ class ViewAssessmentTasks extends Component { exportButtonId: newExportButtonJSON }); } + + this.handleLockToggle = (assessmentTaskId, task) => { + this.setState((prevState) => { + const newLockStatus = { ...prevState.lockStatus }; + newLockStatus[assessmentTaskId] = !newLockStatus[assessmentTaskId]; + return { lockStatus: newLockStatus }; + }, () => { + const lockStatus = this.state.lockStatus[assessmentTaskId]; + + genericResourcePUT( + `/assessment_task_toggle_lock?assessmentTaskId=${assessmentTaskId}`, + this, + JSON.stringify({ locked: lockStatus }) + ); + }); + }; } componentDidUpdate () { @@ -85,6 +103,15 @@ class ViewAssessmentTasks extends Component { "completedAssessments", this ); + + const assessmentTasks = this.props.navbar.adminViewAssessmentTask.assessmentTasks; + const initialLockStatus = {}; + + assessmentTasks.forEach((task) => { + initialLockStatus[task.assessment_task_id] = task.locked; + }); + + this.setState({ lockStatus: initialLockStatus }); } render() { @@ -243,6 +270,29 @@ class ViewAssessmentTasks extends Component { } } }, + { + name: "assessment_task_id", + label: "Lock", + options: { + filter: false, + sort: false, + setCellHeaderProps: () => { return { align:"center", width:"70px", className:"button-column-alignment"}}, + setCellProps: () => { return { align:"center", width:"70px", className:"button-column-alignment"} }, + customBodyRender: (atId) => { + const task = assessmentTasks.find((task) => task["assessment_task_id"] === atId); + const isLocked = this.state.lockStatus[atId] !== undefined ? this.state.lockStatus[atId] : (task ? task.locked : false); + + return ( + this.handleLockToggle(atId, task)} + > + {isLocked ? : } + + ); + } + } + }, { name: "assessment_task_id", label: "Edit", diff --git a/README.md b/README.md index d46b018d8..a870811f7 100644 --- a/README.md +++ b/README.md @@ -41,13 +41,18 @@ for analysis. no output: lsof -i :3000,5050,6379 - + If output, there is a chance you still have processes running and you need to use the following command to kill them off: kill - + + or optionally, send with a forced kill (not recommended as it does + not allow the process to shut down gracefully). + + sudo kill -9 + There is a chance that your OS has an important process running on one of these ports that should not be terminated. In that case, change the port for conflicting processes in the @@ -72,7 +77,7 @@ for analysis. Run the following command to build the images: docker compose build - + NOTE: To rebuild with new changes applied and ignore cached build run the following: @@ -89,8 +94,8 @@ for analysis. docker compose up Step 6: - Open a browser with the link http://localhost:3000 to see the frontend. - + Open a browser with the link http://localhost:3000 to see the frontend and log in + with one of the demo students/TAs/admins. ## REQUIREMENTS: ## diff --git a/README2.md b/README2.md new file mode 100644 index 000000000..0947622be --- /dev/null +++ b/README2.md @@ -0,0 +1,126 @@ +# SkillBuilder + +A web application for evaluating students' professional +skills, such as teamwork and communication. The purpose +of the SkillBuilder application is to allow instructors +to assess teams of students in real-time using +research-based or custom rubrics. Instructors can email +students their results, as well as download the data +for analysis. + +# Setup + +The following shows how to get SkillBuilder running on your operating system. + +## Requirements + +The following technologies are required: +1. `Python >= 3.12` +2. `Redis` +3. `Docker/Docker Desktop` +4. `Node >= v21.6.1` + +Find your operating system below and follow the instructions +on installing them. + +### Linux + +#### Debian/Ubuntu (and its derivatives) + +1. Perform any system upgrades. + +``` +sudo apt update -y +sudo apt upgrade -y +``` + +2. Install `Python3`: +``` +sudo apt install python3 +python3 --version +``` + +Ensure that the version is `>= 3.12`. + +*Note*: Debian uses the last _stable_ release of Python (which is not 3.12), but +from testing, it seems to work just fine. + +3. Install `Redis`: + +Using the following link to install: + +https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/ + +*Note*: Ubuntu and Debian typically use `systemctl` as the init system, but if using +something different, the docs will not cover those. + +4. Install `Node`: + +``` +sudo apt install nodejs +node -v +``` + +5. Install Docker/Docker Desktop: + +Use the following link for the instuctions for Ubuntu: + +https://docs.docker.com/desktop/setup/install/linux/ubuntu/ + +Use the following link for the instuctions for Debian: + +https://docs.docker.com/desktop/setup/install/linux/debian/ + +### MacOS + +MacOS will require some kind of package manager (this document will +use `homebrew`). + +You can find `homebrew` here: https://brew.sh/ + +1. Install `Python3` + +You can find the downloads here: + +https://www.python.org/downloads/macos/ + +2. Install `Redis` + +``` +brew instal redis +``` + +3. Install `Node` + +Either download prebuilt binaries directly, or use a package manager: + +https://nodejs.org/en/download/package-manager + +4. Install Docker/Docker Desktop + +The following link will walk you through it: + +https://docs.docker.com/desktop/setup/install/mac-install/ + +### Windows + +Running this project on bare metal Windows is no longer supported. +You will need to get WSL (Windows Subsystem for Linux) or preferably WSL2. + +The following shows you how to set it up: + +https://learn.microsoft.com/en-us/windows/wsl/install + +Once this is install and set up, open Windows Terminal, Powershell, Command Prompt +(or whatever terminal emulator you use) and do: + +``` +wsl +``` + +If this is working correctly, follow the installation instructions in the *Linux* +section of this README to get all dependencies. + +## Running Rubricapp + + From 0655b1e51f7e5024c5d45508e30ee779ef426141 Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Mon, 2 Dec 2024 01:00:44 -0600 Subject: [PATCH 20/28] added temporary error message --- .../View/Student/View/AssessmentTask/ViewAssessmentTasks.js | 6 +++--- .../CompletedAssessmentTask/ViewCompletedAssessmentTasks.js | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTasks.js b/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTasks.js index 8af71fdef..ff24eae8d 100644 --- a/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTasks.js +++ b/FrontEndReact/src/View/Student/View/AssessmentTask/ViewAssessmentTasks.js @@ -145,8 +145,8 @@ class ViewAssessmentTasks extends Component { setCellProps: () => { return { align:"center", width:"140px", className:"button-column-alignment"} }, customBodyRender: (atId) => { let at = assessmentTasks.find((at) => at["assessment_task_id"] === atId); - - console.log(at); + // let filledByStudent = at.completed_by_role_id === 5; + let filledByStudent = true; return ( at["assessment_task_id"] === atId)["unit_of_assessment"])) || - this.isObjectFound(atId) === true) + this.isObjectFound(atId) === true || !filledByStudent) : this.areAllATsComplete(atId) === true } diff --git a/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js b/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js index a87529337..1f967b0c8 100644 --- a/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js +++ b/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js @@ -63,6 +63,9 @@ class ViewCompletedAssessmentTasks extends Component { setCellProps: () => { return { width:"140px" } }, customBodyRender: (atId) => { const assessmentTask = assessmentTasks.find(at => at.assessment_task_id === atId); + if (assessmentTask === undefined) { + return <>UNDEFINED + } return <>{assessmentTask.unit_of_assessment ? "Team" : "Individual"}; } } From 1a4335040b7af36000ed0d98cae766ccd43a6032 Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Fri, 6 Dec 2024 00:16:06 -0600 Subject: [PATCH 21/28] restored changes --- .../Routes/Assessment_task_routes.py | 25 +++++++- .../controller/Routes/Rating_routes.py | 2 +- BackEndFlask/models/assessment_task.py | 28 +++++++++ BackEndFlask/models/schemas.py | 1 + .../ViewAssessmentTask/ViewAssessmentTasks.js | 62 ++++++++++++++++--- .../ViewCompleteIndividualAssessmentTasks.js | 25 ++++---- .../src/View/Student/StudentDashboard.js | 19 ++++-- .../ViewCompletedAssessmentTasks.js | 24 +++---- 8 files changed, 144 insertions(+), 42 deletions(-) diff --git a/BackEndFlask/controller/Routes/Assessment_task_routes.py b/BackEndFlask/controller/Routes/Assessment_task_routes.py index 2eabf0244..9aedd5a59 100644 --- a/BackEndFlask/controller/Routes/Assessment_task_routes.py +++ b/BackEndFlask/controller/Routes/Assessment_task_routes.py @@ -24,6 +24,7 @@ create_assessment_task, replace_assessment_task, toggle_lock_status, + toggle_published_status, ) from models.completed_assessment import ( @@ -270,7 +271,28 @@ def toggle_lock_status_route(): ) except Exception as e: return create_bad_response( - f"An error occurred copying course assessments {e}", "assessment_tasks", 400 + f"An error occurred toggling the lock status for assessment {e}", "assessment_tasks", 400 + ) + + +@bp.route('/assessment_task_toggle_published', methods=['PUT']) +@jwt_required() +@bad_token_check() +@AuthCheck() +def toggle_published_status_route(): + try: + assessmentTaskId = request.args.get('assessmentTaskId') + + toggle_published_status(assessmentTaskId) + + return create_good_response( + assessment_task_schema.dump(get_assessment_task(assessmentTaskId)), + 201, + "assessment_tasks" + ) + except Exception as e: + return create_bad_response( + f"An error occurred toggling the published status for assessment {e}", "assessment_tasks", 400 ) @@ -336,6 +358,7 @@ class Meta: "max_team_size", "notification_sent", "locked", + "published", ) diff --git a/BackEndFlask/controller/Routes/Rating_routes.py b/BackEndFlask/controller/Routes/Rating_routes.py index 0b62faa22..8232a786a 100644 --- a/BackEndFlask/controller/Routes/Rating_routes.py +++ b/BackEndFlask/controller/Routes/Rating_routes.py @@ -95,4 +95,4 @@ class Meta: ) student_feedback_schema = StudentFeedbackSchema() -student_feedbacks_schema = StudentFeedbackSchema(many=True) \ No newline at end of file +student_feedbacks_schema = StudentFeedbackSchema(many=True) diff --git a/BackEndFlask/models/assessment_task.py b/BackEndFlask/models/assessment_task.py index 6745576b5..bf212f798 100644 --- a/BackEndFlask/models/assessment_task.py +++ b/BackEndFlask/models/assessment_task.py @@ -101,6 +101,7 @@ def create_assessment_task(assessment_task): time_zone=assessment_task["time_zone"], show_suggestions=assessment_task["show_suggestions"], locked=assessment_task["locked"], + published=assessment_task["published"], show_ratings=assessment_task["show_ratings"], unit_of_assessment=assessment_task["unit_of_assessment"], create_team_password=assessment_task["create_team_password"], @@ -131,6 +132,7 @@ def load_demo_admin_assessment_task(): "time_zone": "EST", "unit_of_assessment": False, "locked": False, + "published": True, }, { # Assessment Task 2 "assessment_task_name": "Formal Communication Assessment", @@ -146,6 +148,7 @@ def load_demo_admin_assessment_task(): "time_zone": "EST", "unit_of_assessment": False, "locked": False, + "published": True, }, { # Assessment Task 3 "assessment_task_name": "Information Processing Assessment", @@ -161,6 +164,7 @@ def load_demo_admin_assessment_task(): "time_zone": "EST", "unit_of_assessment": False, "locked": False, + "published": True, }, { # Assessment Task 4 "assessment_task_name": "Interpersonal Communication", @@ -176,6 +180,7 @@ def load_demo_admin_assessment_task(): "time_zone": "EST", "unit_of_assessment": False, "locked": True, + "published": True, }, { # Assessment Task 5 "assessment_task_name": "Management Assessment", @@ -191,6 +196,7 @@ def load_demo_admin_assessment_task(): "time_zone": "EST", "unit_of_assessment": True, "locked": False, + "published": True, }, { # Assessment Task 6 "assessment_task_name": "Problem Solving Assessment", @@ -206,6 +212,7 @@ def load_demo_admin_assessment_task(): "time_zone": "EST", "unit_of_assessment": False, "locked": False, + "published": True, }, { # Assessment Task 7 "assessment_task_name": "Teamwork Assessment", @@ -221,6 +228,7 @@ def load_demo_admin_assessment_task(): "time_zone": "EST", "unit_of_assessment": True, "locked": False, + "published": True, }, { # Assessment Task 8 "assessment_task_name": "Critical Thinking Assessment 2", @@ -236,6 +244,7 @@ def load_demo_admin_assessment_task(): "time_zone": "CST", "unit_of_assessment": True, "locked": False, + "published": True, }, { # Assessment Task 9 "assessment_task_name": "AAAAAAAAAAAA", @@ -251,6 +260,7 @@ def load_demo_admin_assessment_task(): "time_zone": "EST", "unit_of_assessment": True, "locked": False, + "published": True, }, { # Assessment Task 10 "assessment_task_name": "CCCCCCCCCCCCC", @@ -266,6 +276,7 @@ def load_demo_admin_assessment_task(): "time_zone": "PST", "unit_of_assessment": True, "locked": False, + "published": True, }, { # Assessment Task 11 "assessment_task_name": "DDDDDDDDDDDDDD", @@ -281,6 +292,7 @@ def load_demo_admin_assessment_task(): "time_zone": "PST", "unit_of_assessment": True, "locked": False, + "published": True, }, { # Assessment Task 12 "assessment_task_name": "Student 1", @@ -296,6 +308,7 @@ def load_demo_admin_assessment_task(): "time_zone": "EST", "unit_of_assessment": False, "locked": False, + "published": True, }, { # Assessment Task 13 "assessment_task_name": "Student 2 Individ", @@ -311,6 +324,7 @@ def load_demo_admin_assessment_task(): "time_zone": "PST", "unit_of_assessment": False, "locked": False, + "published": True, }, { # Assessment Task 14 "assessment_task_name": "UI 1", @@ -326,6 +340,7 @@ def load_demo_admin_assessment_task(): "time_zone": "PST", "unit_of_assessment": False, "locked": False, + "published": True, }, { # Assessment Task 15 "assessment_task_name": "UI 2", @@ -341,6 +356,7 @@ def load_demo_admin_assessment_task(): "time_zone": "PST", "unit_of_assessment": False, "locked": False, + "published": True, }, { # Assessment Task 16 "assessment_task_name": "Calc 1", @@ -356,6 +372,7 @@ def load_demo_admin_assessment_task(): "time_zone": "PST", "unit_of_assessment": False, "locked": False, + "published": True, }, { # Assessment Task 17 "assessment_task_name": "Calc 2", @@ -371,6 +388,7 @@ def load_demo_admin_assessment_task(): "time_zone": "PST", "unit_of_assessment": False, "locked": False, + "published": True, }, { # Assessment Task 18 "assessment_task_name": "Phys 1", @@ -386,6 +404,7 @@ def load_demo_admin_assessment_task(): "time_zone": "MST", "unit_of_assessment": False, "locked": False, + "published": True, }, { # Assessment Task 19 "assessment_task_name": "Phys 2", @@ -401,6 +420,7 @@ def load_demo_admin_assessment_task(): "time_zone": "MST", "unit_of_assessment": False, "locked": False, + "published": True, }, ] @@ -414,6 +434,7 @@ def load_demo_admin_assessment_task(): "role_id": assessment["role_id"], "show_suggestions": assessment["show_suggestions"], "locked": assessment["locked"], + "published": assessment["published"], "show_ratings": assessment["show_ratings"], "unit_of_assessment": assessment["unit_of_assessment"], "create_team_password": assessment["create_team_password"], @@ -480,3 +501,10 @@ def toggle_lock_status(assessment_task_id): one_assessment_task.locked = not one_assessment_task.locked db.session.commit() return one_assessment_task + +@error_log +def toggle_published_status(assessment_task_id): + one_assessment_task = AssessmentTask.query.filter_by(assessment_task_id=assessment_task_id).first() + one_assessment_task.published = not one_assessment_task.published + db.session.commit() + return one_assessment_task diff --git a/BackEndFlask/models/schemas.py b/BackEndFlask/models/schemas.py index d60cb9cf7..2d08858c2 100644 --- a/BackEndFlask/models/schemas.py +++ b/BackEndFlask/models/schemas.py @@ -132,6 +132,7 @@ class AssessmentTask(db.Model): max_team_size = db.Column(db.Integer, nullable=True) notification_sent = db.Column(DateTime(timezone=True), nullable=True) locked = db.Column(db.Boolean, nullable=False) + published = db.Column(db.Boolean, nullable=False) class Checkin(db.Model): # keeps students checking to take a specific AT __tablename__ = "Checkin" diff --git a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js index 69005fa7d..3c83c3bd0 100644 --- a/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js +++ b/FrontEndReact/src/View/Admin/View/ViewAssessmentTask/ViewAssessmentTasks.js @@ -10,7 +10,8 @@ import Loading from '../../../Loading/Loading.js'; import { IconButton } from '@mui/material'; import LockIcon from '@mui/icons-material/Lock'; import LockOpenIcon from '@mui/icons-material/LockOpen'; - +import PublishIcon from '@mui/icons-material/Publish'; +import UnpublishedIcon from '@mui/icons-material/Unpublished'; class ViewAssessmentTasks extends Component { constructor(props) { @@ -25,6 +26,7 @@ class ViewAssessmentTasks extends Component { completedAssessments: null, assessmentTasks: null, lockStatus: {}, + publishedStatus: {}, } this.handleDownloadCsv = (atId, exportButtonId, assessmentTaskIdToAssessmentTaskName) => { @@ -40,13 +42,14 @@ class ViewAssessmentTasks extends Component { var assessmentName = assessmentTaskIdToAssessmentTaskName[atId]; var newExportButtonJSON = this.state.exportButtonId; - + newExportButtonJSON[assessmentName] = exportButtonId; - + this.setState({ downloadedAssessment: assessmentName, exportButtonId: newExportButtonJSON - }); } + }); + } }); } @@ -65,6 +68,23 @@ class ViewAssessmentTasks extends Component { ); }); }; + + this.handlePublishToggle = (assessmentTaskId, task) => { + this.setState((prevState) => { + const newPublishedStatus = { ...prevState.publishedStatus }; + newPublishedStatus[assessmentTaskId] = !newPublishedStatus[assessmentTaskId]; + return { publishedStatus: newPublishedStatus }; + }, () => { + const publishedStatus = this.state.publishedStatus[assessmentTaskId]; + + genericResourcePUT( + `/assessment_task_toggle_published?assessmentTaskId=${assessmentTaskId}`, + this, + JSON.stringify({ published: publishedStatus }) + ); + }); + }; + } componentDidUpdate () { @@ -81,9 +101,9 @@ class ViewAssessmentTasks extends Component { link.click(); var assessmentName = this.state.downloadedAssessment; - + const exportAssessmentTask = document.getElementById(this.state.exportButtonId[assessmentName]) - + setTimeout(() => { if(exportAssessmentTask) { exportAssessmentTask.removeAttribute("disabled"); @@ -116,16 +136,17 @@ class ViewAssessmentTasks extends Component { const assessmentTasks = this.props.navbar.adminViewAssessmentTask.assessmentTasks; const initialLockStatus = {}; + const initialPublishedStatus = {}; assessmentTasks.forEach((task) => { initialLockStatus[task.assessment_task_id] = task.locked; + initialPublishedStatus[task.assessment_task_id] = task.published; }); - this.setState({ lockStatus: initialLockStatus }); + this.setState({ lockStatus: initialLockStatus, publishedStatus: initialPublishedStatus }); } render() { - if (this.state.assessmentTasks === null || this.state.completedAssessments === null) { return ; } @@ -138,8 +159,6 @@ class ViewAssessmentTasks extends Component { var rubricNames = adminViewAssessmentTask.rubricNames; var assessmentTasks = adminViewAssessmentTask.assessmentTasks; - const lockStatus = this.state.lockStatus; - let assessmentTasksToDueDates = {}; for(let index = 0; index < assessmentTasks.length; index++) { @@ -280,6 +299,29 @@ class ViewAssessmentTasks extends Component { } } }, + { + name: "assessment_task_id", + label: "Publish", + options: { + filter: false, + sort: false, + setCellHeaderProps: () => { return { align:"center", width:"70px", className:"button-column-alignment"}}, + setCellProps: () => { return { align:"center", width:"70px", className:"button-column-alignment"} }, + customBodyRender: (atId) => { + const task = assessmentTasks.find((task) => task["assessment_task_id"] === atId); + const isPublished = this.state.publishedStatus[atId] !== undefined ? this.state.publishedStatus[atId] : (task ? task.published : false); + + return ( + this.handlePublishToggle(atId, task)} + > + {isPublished ? : } + + ); + } + } + }, { name: "assessment_task_id", label: "Lock", diff --git a/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteIndividualAssessmentTasks.js b/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteIndividualAssessmentTasks.js index 627031196..cc3534d23 100644 --- a/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteIndividualAssessmentTasks.js +++ b/FrontEndReact/src/View/Admin/View/ViewCompleteAssessmentTasks/ViewCompleteIndividualAssessmentTasks.js @@ -17,18 +17,19 @@ class ViewCompleteIndividualAssessmentTasks extends Component { super(props); this.state = { - errorMessage: null, - isLoaded: null, - showDialog: false, - notes: '', - notificationSent: false, - isSingleMsg: false, - compATId: null, - - errors: { - notes:'' - } - }; + errorMessage: null, + isLoaded: null, + showDialog: false, + notes: '', + notificationSent: false, + isSingleMsg: false, + compATId: null, + lockStatus: {}, + + errors: { + notes:'' + } + }; } componentDidMount() { diff --git a/FrontEndReact/src/View/Student/StudentDashboard.js b/FrontEndReact/src/View/Student/StudentDashboard.js index 1fa058ca7..4d6d65287 100644 --- a/FrontEndReact/src/View/Student/StudentDashboard.js +++ b/FrontEndReact/src/View/Student/StudentDashboard.js @@ -38,17 +38,18 @@ class StudentDashboard extends Component { var userRole = state.chosenCourse.role_id; genericResourceGET( `/assessment_task?course_id=${chosenCourse}&role_id=${userRole}`, - "assessmentTasks", this); + "assessment_tasks", this, {dest: "assessmentTasks"}); if (userRole === 5) { genericResourceGET( `/completed_assessment?course_id=${chosenCourse}`, - "completedAssessments", this + "completed_assessments", this, {dest: "completedAssessments"} ); } else { genericResourceGET( `/completed_assessment?course_id=${chosenCourse}&role_id=${userRole}`, - "completedAssessments", this); + "completed_assessments", this, {dest: "completedAssessments"} + ); } } @@ -83,12 +84,18 @@ class StudentDashboard extends Component { // by an admin. const currentDate = new Date(); for (let i = 0; i < assessmentTasks.length; ++i) { - const dueDate = new Date(assessmentTasks[i].due_date); - if (dueDate < currentDate || assessmentTasks[i].locked) { - completedAssessments.push(assessmentTasks[i]); + if (!assessmentTasks[i].published) { assessmentTasks.splice(i, 1); --i; } + else { + const dueDate = new Date(assessmentTasks[i].due_date); + if (dueDate < currentDate || assessmentTasks[i].locked) { + completedAssessments.push(assessmentTasks[i]); + assessmentTasks.splice(i, 1); + --i; + } + } } return ( diff --git a/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js b/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js index 3ef47975b..3ea27ec44 100644 --- a/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js +++ b/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js @@ -103,18 +103,18 @@ class ViewCompletedAssessmentTasks extends Component { completedAssessments, { readOnly: true, skipInstructions: true } ); - var singluarCompletedAssessment = null; - if (completedAssessments) { - singluarCompletedAssessment = completedAssessments.find(completedAssessment => completedAssessment.assessment_task_id === atId) ?? null; - } - genericResourcePOST( - `/rating`, - this, - JSON.stringify({ - "user_id" : singluarCompletedAssessment.user_id, - "completed_assessment_id": singluarCompletedAssessment.completed_assessment_id, - }), - ); + // var singluarCompletedAssessment = null; + // if (completedAssessments) { + // singluarCompletedAssessment = completedAssessments.find(completedAssessment => completedAssessment.assessment_task_id === atId) ?? null; + // } + // genericResourcePOST( + // `/rating`, + // this, + // JSON.stringify({ + // "user_id" : singluarCompletedAssessment.user_id, + // "completed_assessment_id": singluarCompletedAssessment.completed_assessment_id, + // }), + // ); }} aria-label="completedAssessmentTasksViewIconButton" > From 856e82809f83daed9d5ae8bac03aff377218e5b8 Mon Sep 17 00:00:00 2001 From: malloc-nbytes Date: Fri, 6 Dec 2024 03:27:50 -0600 Subject: [PATCH 22/28] added dbinsert helper scripts --- BackEndFlask/dbinsert.py | 83 +++++++++++++++++++ BackEndFlask/models/utility.py | 2 +- Dockerfile.backend | 2 +- .../StudentViewAssessmentTaskInstructions.js | 2 +- .../ViewCompletedAssessmentTasks.js | 36 ++++---- dbinsert.sh | 19 +++++ 6 files changed, 123 insertions(+), 21 deletions(-) create mode 100644 BackEndFlask/dbinsert.py create mode 100755 dbinsert.sh diff --git a/BackEndFlask/dbinsert.py b/BackEndFlask/dbinsert.py new file mode 100644 index 000000000..71d034dee --- /dev/null +++ b/BackEndFlask/dbinsert.py @@ -0,0 +1,83 @@ +#!/usr/local/bin/python3 + +from core import app, db +from models.user import create_user +import argparse +import sys + + +def insert_student(fname, lname, email, password, lms_id): + print("Inserting new student:") + print(f" fname: {fname}") + print(f" lname: {lname}") + print(f" email: {email}") + print(f" password: {password}") + print(f" lms_id: {lms_id}") + + with app.app_context(): + create_user({ + "first_name": fname, + "last_name": lname, + "email": email, + "password": password, + "lms_id": lms_id, + "consent": None, + "owner_id": 2, + "role_id": 5 + }) + + +def insert_admin(fname, lname, email, password): + print("Inserting new admin:") + print(f" fname: {fname}") + print(f" lname: {lname}") + print(f" email: {email}") + print(f" password: {password}") + + with app.app_context(): + create_user({ + "first_name": fname, + "last_name": lname, + "email": email, + "password": password, + "lms_id": 1, + "consent": None, + "owner_id": 1, + "role_id": 3 + }) + + +def main(): + parser = argparse.ArgumentParser(description="Insert user information into the database.") + + parser.add_argument("--new-student", action="store_true", help="Indicates that a new student is being added") + parser.add_argument("--fname", type=str, help="First name of the user", required=False) + parser.add_argument("--lname", type=str, help="Last name of the user", required=False) + parser.add_argument("--email", type=str, help="Email of the user", required=False) + parser.add_argument("--password", type=str, help="Password of the user", required=False) + parser.add_argument("--lms", type=str, help="LMS ID of the student", required=False) + + parser.add_argument("--new-admin", action="store_true", help="Indicates that a new admin is being added") + + args = parser.parse_args() + + if not args.new_student and not args.new_admin: + print("Error: You must specify either --new-student or --new-admin.") + sys.exit(1) + + if args.new_student: + if not (args.fname and args.lname and args.email and args.password and args.lms): + print("Error: Missing required fields for student (fname, lname, email, password, lms).") + sys.exit(1) + insert_student(args.fname, args.lname, args.email, args.password, args.lms) + + if args.new_admin: + if not (args.fname and args.lname and args.email and args.password): + print("Error: Missing required fields for admin (fname, lname, email, password).") + sys.exit(1) + insert_admin(args.fname, args.lname, args.email, args.password) + + +if __name__ == "__main__": + main() + diff --git a/BackEndFlask/models/utility.py b/BackEndFlask/models/utility.py index 6f9a7d2f3..cbbda6e29 100644 --- a/BackEndFlask/models/utility.py +++ b/BackEndFlask/models/utility.py @@ -81,4 +81,4 @@ def wrapper(*args, **kwargs): logger.error(f"{e.__traceback__.tb_frame.f_code.co_filename} { e.__traceback__.tb_lineno} Error Type: {type(e).__name__} Message: {e}") raise e - return wrapper \ No newline at end of file + return wrapper diff --git a/Dockerfile.backend b/Dockerfile.backend index 386c23bc2..45d07c55f 100644 --- a/Dockerfile.backend +++ b/Dockerfile.backend @@ -15,7 +15,7 @@ RUN apt-get update WORKDIR /app # Copy only requirements and setup scripts first to leverage Docker cache -COPY BackEndFlask/requirements.txt BackEndFlask/setupEnv.py /app/ +COPY BackEndFlask/requirements.txt BackEndFlask/setupEnv.py BackEndFlask/dbinsert.py /app/ # Install Python dependencies RUN pip install --no-cache-dir --upgrade pip \ diff --git a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTaskInstructions.js b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTaskInstructions.js index 06f8059a2..ca2ed3ec0 100644 --- a/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTaskInstructions.js +++ b/FrontEndReact/src/View/Student/View/AssessmentTask/StudentViewAssessmentTaskInstructions.js @@ -23,7 +23,7 @@ class StudentViewAssessmentTaskInstructions extends Component { genericResourceGET( `/rubric?rubric_id=${state.chosenAssessmentTask["rubric_id"]}`, - "rubrics", this + "rubrics", this, {dest: "rubrics"} ) } diff --git a/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js b/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js index 3ea27ec44..2cfb5d013 100644 --- a/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js +++ b/FrontEndReact/src/View/Student/View/CompletedAssessmentTask/ViewCompletedAssessmentTasks.js @@ -97,24 +97,24 @@ class ViewCompletedAssessmentTasks extends Component {
{ - navbar.setAssessmentTaskInstructions( - assessmentTasks, - atId, - completedAssessments, - { readOnly: true, skipInstructions: true } - ); - // var singluarCompletedAssessment = null; - // if (completedAssessments) { - // singluarCompletedAssessment = completedAssessments.find(completedAssessment => completedAssessment.assessment_task_id === atId) ?? null; - // } - // genericResourcePOST( - // `/rating`, - // this, - // JSON.stringify({ - // "user_id" : singluarCompletedAssessment.user_id, - // "completed_assessment_id": singluarCompletedAssessment.completed_assessment_id, - // }), - // ); + navbar.setAssessmentTaskInstructions( + assessmentTasks, + atId, + completedAssessments, + { readOnly: true, skipInstructions: true } + ); + var singluarCompletedAssessment = null; + if (completedAssessments) { + singluarCompletedAssessment = completedAssessments.find(completedAssessment => completedAssessment.assessment_task_id === atId) ?? null; + } + genericResourcePOST( + `/rating`, + this, + JSON.stringify({ + "user_id" : singluarCompletedAssessment.user_id, + "completed_assessment_id": singluarCompletedAssessment.completed_assessment_id, + }), + ); }} aria-label="completedAssessmentTasksViewIconButton" > diff --git a/dbinsert.sh b/dbinsert.sh new file mode 100755 index 000000000..cb3df339d --- /dev/null +++ b/dbinsert.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +function help() { + echo "This script is inteded to be ran *while* the backend is running in Docker." + echo -e "It will insert a new user into the DB so you don't have to do it in the website.\n" + echo "dbinsert.sh