diff --git a/.gitignore b/.gitignore index f3e6985a7..9109da4a7 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ package-lock.json BackEndFlask/logs/all.log BackEndFlask/models/hidden.py BackEndFlask/logs/all.log +BackEndFlask/.env .vscode # Nginx configuration files diff --git a/BackEndFlask/.env b/BackEndFlask/.env index 70fad1b9e..1b5d551e9 100644 --- a/BackEndFlask/.env +++ b/BackEndFlask/.env @@ -4,3 +4,7 @@ DEMO_ADMIN_PASSWORD=demo_admin DEMO_TA_INSTRUCTOR_PASSWORD=demo_ta DEMO_STUDENT_PASSWORD=demo_student SECRET_KEY=Thisissupposedtobesecret! +MYSQL_HOST=localhost:3306 +MYSQL_USER=rubricapp_admin +MYSQL_PASSWORD=ThisReallyNeedsToBeASecret1! +MYSQL_DATABASE=rubricapp diff --git a/BackEndFlask/.gitignore b/BackEndFlask/.gitignore index 93272c465..b73b77005 100644 --- a/BackEndFlask/.gitignore +++ b/BackEndFlask/.gitignore @@ -17,4 +17,5 @@ Functions/test.py Models/hidden.py dump.rdb BackEndFlaskVenv -Test \ No newline at end of file +Test +.env \ No newline at end of file diff --git a/BackEndFlask/controller/Routes/Assessment_task_routes.py b/BackEndFlask/controller/Routes/Assessment_task_routes.py index 2b1104f05..814b6a313 100644 --- a/BackEndFlask/controller/Routes/Assessment_task_routes.py +++ b/BackEndFlask/controller/Routes/Assessment_task_routes.py @@ -8,8 +8,12 @@ from models.role import get_role from controller.Route_response import * from models.user_course import get_user_courses_by_user_id + from flask_jwt_extended import jwt_required -from controller.security.CustomDecorators import AuthCheck, bad_token_check +from controller.security.CustomDecorators import ( + AuthCheck, bad_token_check, + admin_check +) from models.assessment_task import ( get_assessment_tasks_by_course_id, @@ -173,6 +177,7 @@ def get_one_assessment_task(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def add_assessment_task(): try: new_assessment_task = create_assessment_task(request.json) @@ -192,6 +197,7 @@ def add_assessment_task(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def update_assessment_task(): try: if request.args and request.args.get("notification"): @@ -256,6 +262,7 @@ def update_assessment_task(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def copy_course_assessments(): try: source_course_id = request.args.get('source_course_id') diff --git a/BackEndFlask/controller/Routes/Bulk_upload_routes.py b/BackEndFlask/controller/Routes/Bulk_upload_routes.py index f40442854..1e2eb64fc 100644 --- a/BackEndFlask/controller/Routes/Bulk_upload_routes.py +++ b/BackEndFlask/controller/Routes/Bulk_upload_routes.py @@ -10,12 +10,17 @@ import shutil from controller.Route_response import create_bad_response, create_good_response from flask_jwt_extended import jwt_required -from controller.security.CustomDecorators import AuthCheck, bad_token_check + +from controller.security.CustomDecorators import ( + AuthCheck, bad_token_check, + admin_check +) @bp.route('/bulk_upload', methods = ['POST']) @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def upload_CSV(): try: file = request.files['csv_file'] diff --git a/BackEndFlask/controller/Routes/Checkin_routes.py b/BackEndFlask/controller/Routes/Checkin_routes.py index ecf4d4d0e..e5b5a2ada 100644 --- a/BackEndFlask/controller/Routes/Checkin_routes.py +++ b/BackEndFlask/controller/Routes/Checkin_routes.py @@ -67,7 +67,7 @@ def get_checked_in(): except Exception as e: return create_bad_response(f"An error occurred getting checked in user {e}", "checkin", 400) -@bp.route('/checkin_events', methods = ['GET']) +@bp.route('/checkin_events', methods = ['GET']) @jwt_required() @bad_token_check() @AuthCheck() diff --git a/BackEndFlask/controller/Routes/Completed_assessment_routes.py b/BackEndFlask/controller/Routes/Completed_assessment_routes.py index b2ada7505..eadf8c013 100644 --- a/BackEndFlask/controller/Routes/Completed_assessment_routes.py +++ b/BackEndFlask/controller/Routes/Completed_assessment_routes.py @@ -3,7 +3,11 @@ from controller.Route_response import * from flask_jwt_extended import jwt_required from models.assessment_task import get_assessment_task -from controller.security.CustomDecorators import AuthCheck, bad_token_check + +from controller.security.CustomDecorators import ( + AuthCheck, bad_token_check, + admin_check +) from models.completed_assessment import ( get_completed_assessments, @@ -105,6 +109,10 @@ def get_all_completed_assessments(): get_assessment_task(assessment_task_id) # Trigger an error if not exists. completed_assessments = get_completed_assessment_with_team_name(assessment_task_id) + + if not completed_assessments: + 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} @@ -173,6 +181,7 @@ def add_completed_assessment(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def update_completed_assessment(): try: completed_assessment_id = request.args.get("completed_assessment_id") diff --git a/BackEndFlask/controller/Routes/Course_routes.py b/BackEndFlask/controller/Routes/Course_routes.py index 34d7e1504..9c5d57a10 100644 --- a/BackEndFlask/controller/Routes/Course_routes.py +++ b/BackEndFlask/controller/Routes/Course_routes.py @@ -2,7 +2,11 @@ from controller import bp from controller.Route_response import * from flask_jwt_extended import jwt_required -from controller.security.CustomDecorators import AuthCheck, bad_token_check + +from controller.security.CustomDecorators import( + AuthCheck, bad_token_check, + admin_check +) from models.course import( get_course, @@ -76,6 +80,7 @@ def get_one_course(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def add_course(): try: new_course = create_course(request.json) @@ -98,6 +103,7 @@ def add_course(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def update_course(): try: course_id = request.args.get("course_id") diff --git a/BackEndFlask/controller/Routes/Csv_routes.py b/BackEndFlask/controller/Routes/Csv_routes.py index cad037ddc..6b6058822 100644 --- a/BackEndFlask/controller/Routes/Csv_routes.py +++ b/BackEndFlask/controller/Routes/Csv_routes.py @@ -8,16 +8,20 @@ from controller import bp from controller.Route_response import * from flask_jwt_extended import jwt_required -from controller.security.CustomDecorators import AuthCheck, bad_token_check from Functions.exportCsv import create_csv_strings from models.assessment_task import get_assessment_task from models.user import get_user +from controller.security.CustomDecorators import ( + AuthCheck, bad_token_check, + admin_check +) @bp.route('/csv_assessment_export', methods = ['GET']) @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def get_completed_assessment_csv() -> dict: """ Description: diff --git a/BackEndFlask/controller/Routes/Rating_routes.py b/BackEndFlask/controller/Routes/Rating_routes.py index aebe91c32..74b1bbc26 100644 --- a/BackEndFlask/controller/Routes/Rating_routes.py +++ b/BackEndFlask/controller/Routes/Rating_routes.py @@ -6,12 +6,17 @@ from models.completed_assessment import * from models.queries import get_individual_ratings from flask_jwt_extended import jwt_required -from controller.security.CustomDecorators import AuthCheck, bad_token_check + +from controller.security.CustomDecorators import ( + AuthCheck, bad_token_check, + admin_check +) @bp.route("/rating", methods=["GET"]) @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def get_student_individual_ratings(): """ Description: @@ -53,6 +58,7 @@ def get_student_individual_ratings(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def student_view_feedback(): """ Description: diff --git a/BackEndFlask/controller/Routes/Refresh_route.py b/BackEndFlask/controller/Routes/Refresh_route.py index ce31aad9b..ebfacb068 100644 --- a/BackEndFlask/controller/Routes/Refresh_route.py +++ b/BackEndFlask/controller/Routes/Refresh_route.py @@ -5,6 +5,7 @@ from controller.Route_response import * from flask_jwt_extended import jwt_required, create_access_token from controller.security.CustomDecorators import AuthCheck, bad_token_check +import datetime @bp.route('/refresh', methods=['POST']) @jwt_required(refresh=True) @@ -14,7 +15,7 @@ def refresh_token(): try: user_id = int(request.args.get('user_id')) user = user_schema.dump(get_user(user_id)) - jwt = create_access_token([user_id]) + jwt = create_access_token([user_id], fresh=datetime.timedelta(minutes=60), expires_delta=datetime.timedelta(minutes=60)) return create_good_response(user, 200, "user", jwt) except: return create_bad_response("Bad request: user_id must be provided", "user", 400) diff --git a/BackEndFlask/controller/Routes/Rubric_routes.py b/BackEndFlask/controller/Routes/Rubric_routes.py index 4c1c2a7ad..49e8fc332 100644 --- a/BackEndFlask/controller/Routes/Rubric_routes.py +++ b/BackEndFlask/controller/Routes/Rubric_routes.py @@ -6,11 +6,15 @@ from models.rubric import get_rubric, get_rubrics, create_rubric, delete_rubric_by_id from models.category import get_categories_per_rubric, get_categories, get_ratings_by_category from models.suggestions import get_suggestions_per_category -from controller.security.CustomDecorators import AuthCheck, bad_token_check from models.observable_characteristics import get_observable_characteristic_per_category from models.queries import get_rubrics_and_total_categories, get_rubrics_and_total_categories_for_user_id, get_categories_for_user_id from models.user import get_user +from controller.security.CustomDecorators import( + AuthCheck, bad_token_check, + admin_check +) + @bp.route('/rubric', methods = ['GET']) @jwt_required() @bad_token_check() @@ -103,6 +107,7 @@ def get_all_rubrics(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def add_rubric(): # expects to recieve a json object with all two fields. # one named 'rubric' holds all the fields for a rubric (except rubric_id) @@ -161,6 +166,7 @@ def get_all_categories(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def edit_rubric(): try: if request.args and request.args.get("rubric_id"): @@ -197,6 +203,7 @@ def edit_rubric(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def delete_rubric(): try: if request.args and request.args.get("rubric_id"): diff --git a/BackEndFlask/controller/Routes/Team_bulk_upload_routes.py b/BackEndFlask/controller/Routes/Team_bulk_upload_routes.py index efcf6eedd..47ce68eac 100644 --- a/BackEndFlask/controller/Routes/Team_bulk_upload_routes.py +++ b/BackEndFlask/controller/Routes/Team_bulk_upload_routes.py @@ -10,12 +10,17 @@ from Functions import customExceptions from controller.Route_response import * from flask_jwt_extended import jwt_required -from controller.security.CustomDecorators import AuthCheck, bad_token_check + +from controller.security.CustomDecorators import ( + AuthCheck, bad_token_check, + admin_check +) @bp.route('/team_bulk_upload', methods=['POST']) @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def upload_team_csv(): try: file = request.files['csv_file'] diff --git a/BackEndFlask/controller/Routes/Team_routes.py b/BackEndFlask/controller/Routes/Team_routes.py index f8d8f4d11..9e7de4e66 100644 --- a/BackEndFlask/controller/Routes/Team_routes.py +++ b/BackEndFlask/controller/Routes/Team_routes.py @@ -14,7 +14,12 @@ from models.assessment_task import get_assessment_tasks_by_team_id from models.completed_assessment import completed_assessment_team_or_user_exists from models.team_user import * -from controller.security.CustomDecorators import AuthCheck, bad_token_check + +from controller.security.CustomDecorators import( + AuthCheck, bad_token_check, + admin_check +) + from models.queries import ( get_team_by_course_id_and_user_id, get_all_nonfull_adhoc_teams @@ -110,6 +115,7 @@ def get_nonfull_adhoc_teams(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def add_team(): try: new_team = create_team(request.json) @@ -124,6 +130,7 @@ def add_team(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def update_team(): try: team_id = request.args.get("team_id") @@ -139,6 +146,7 @@ def update_team(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def update_team_user_by_edit(): try: data = request.get_json() diff --git a/BackEndFlask/controller/Routes/Upload_csv_routes.py b/BackEndFlask/controller/Routes/Upload_csv_routes.py index 34ecab54d..120a37bad 100644 --- a/BackEndFlask/controller/Routes/Upload_csv_routes.py +++ b/BackEndFlask/controller/Routes/Upload_csv_routes.py @@ -8,12 +8,17 @@ from controller.Route_response import * from flask_jwt_extended import jwt_required import Functions.studentImport as studentImport -from controller.security.CustomDecorators import AuthCheck, bad_token_check + +from controller.security.CustomDecorators import ( + AuthCheck, bad_token_check, + admin_check +) @bp.route('/studentbulkuploadcsv', methods = ['POST']) @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def student_bulk_upload_csv(): try: file = request.files['csv_file'] diff --git a/BackEndFlask/controller/Routes/User_routes.py b/BackEndFlask/controller/Routes/User_routes.py index daf14a598..77b475c6b 100644 --- a/BackEndFlask/controller/Routes/User_routes.py +++ b/BackEndFlask/controller/Routes/User_routes.py @@ -2,7 +2,11 @@ from controller import bp from controller.Route_response import * from flask_jwt_extended import jwt_required -from controller.security.CustomDecorators import AuthCheck, bad_token_check + +from controller.security.CustomDecorators import( + AuthCheck, bad_token_check, + admin_check +) from models.role import ( get_role @@ -186,6 +190,7 @@ def get_all_team_members(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def add_user(): try: if(request.args and request.args.get("team_id")): @@ -247,6 +252,7 @@ def add_user(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def update_user(): try: if(request.args and request.args.get("uid") and request.args.get("course_id")): @@ -305,6 +311,7 @@ def update_user(): @jwt_required() @bad_token_check() @AuthCheck() +@admin_check() def delete_user(): try: if request.args and request.args.get("uid"): diff --git a/BackEndFlask/controller/security/CustomDecorators.py b/BackEndFlask/controller/security/CustomDecorators.py index 845f3b367..6a7aee5f7 100644 --- a/BackEndFlask/controller/security/CustomDecorators.py +++ b/BackEndFlask/controller/security/CustomDecorators.py @@ -2,6 +2,8 @@ from functools import wraps from .utility import to_int from .blacklist import is_token_blacklisted +from typing import Callable +from models.queries import is_admin_by_user_id from flask_jwt_extended import decode_token from flask_jwt_extended.exceptions import ( NoAuthorizationError, @@ -58,4 +60,37 @@ def verify_token(refresh: bool): raise NoAuthorizationError("No Authorization") id = to_int(id, "user_id") if id == decoded_id : return - raise NoAuthorizationError("No Authorization") \ No newline at end of file + raise NoAuthorizationError("No Authorization") + +def admin_check(refresh: bool = False) -> Callable: + """ + Description: + This is a decorator that checks to make sure that the route was called by an admin permisions. + I think it is best to use the decorator as the last decorator since it hits the db. + """ + def wrapper(fn): + @wraps(fn) + def decorator(*args): + verify_admin(refresh) + return current_app.ensure_sync(fn)(*args) + return decorator + return wrapper + +def verify_admin(refresh: bool) -> None: + """ + Description: + Uses token user_id to check user permisions. + + Exceptions: + Raises NoAuthorizationError if at any instance it can not be reliably determined if + the individual that called the route has admin level permissions. + """ + try: + # Figuring out the user_id from token. + # Assumes authcheck() has already concluded token_user_id == user_id from parameters. + token = request.headers.get('Authorization').split()[1] + decoded_id = decode_token(token)['sub'] if refresh else decode_token(token)['sub'][0] + if is_admin_by_user_id(decoded_id) == False: + raise NoAuthorizationError("No Authorization") + except: + raise NoAuthorizationError("No Authorization") \ No newline at end of file diff --git a/BackEndFlask/controller/security/utility.py b/BackEndFlask/controller/security/utility.py index de6560ef5..f849ccaef 100644 --- a/BackEndFlask/controller/security/utility.py +++ b/BackEndFlask/controller/security/utility.py @@ -1,4 +1,5 @@ import traceback +import datetime from flask import request from core import app from jwt import ExpiredSignatureError @@ -19,7 +20,7 @@ # jwt expires in 15mins; refresh token expires in 30days def create_tokens(user_i_d: any) -> 'tuple[str, str]': with app.app_context(): - jwt = create_access_token(str(user_i_d)) + jwt = create_access_token(str(user_i_d), fresh=datetime.timedelta(minutes=60), expires_delta=datetime.timedelta(minutes=60)) refresh = request.args.get('refresh_token') if not refresh: refresh = create_refresh_token(str(user_i_d)) diff --git a/BackEndFlask/core/__init__.py b/BackEndFlask/core/__init__.py index 0b62c26c5..d70f6939a 100644 --- a/BackEndFlask/core/__init__.py +++ b/BackEndFlask/core/__init__.py @@ -2,11 +2,13 @@ from flask_marshmallow import Marshmallow from flask_sqlalchemy import SQLAlchemy from models.tests import testing +from dotenv import load_dotenv from flask import Flask from flask_cors import CORS +import subprocess +load_dotenv() import sys import os -import subprocess import re import redis @@ -78,13 +80,19 @@ def setup_cron_jobs(): # Initialize JWT jwt = JWTManager(app) +account_db_path = os.getcwd() + os.path.join(os.path.sep, "core") + os.path.join(os.path.sep, "account.db") + +MYSQL_HOST=os.getenv('MYSQL_HOST') + +MYSQL_USER=os.getenv('MYSQL_USER') + +MYSQL_PASSWORD=os.getenv('MYSQL_PASSWORD') + +MYSQL_DATABASE=os.getenv('MYSQL_DATABASE') + +db_uri = (f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{MYSQL_HOST}/{MYSQL_DATABASE}") -# Database configuration -account_db_path = os.path.join(os.getcwd(), "core", "account.db") -if os.path.exists(account_db_path): - app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///./account.db' -else: - app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///../instance/account.db' +app.config['SQLALCHEMY_DATABASE_URI'] = db_uri db = SQLAlchemy(app) ma = Marshmallow(app) diff --git a/BackEndFlask/dbcreate.py b/BackEndFlask/dbcreate.py index 8b7e47880..ebed9fc9e 100644 --- a/BackEndFlask/dbcreate.py +++ b/BackEndFlask/dbcreate.py @@ -35,9 +35,21 @@ except Exception as e: print(f"[dbcreate] an error ({e}) occured with db.create_all()") print("[dbcreate] exiting...") - os.abort() + raise e print("[dbcreate] successfully created new db") time.sleep(sleep_time) + if (get_roles().__len__() == 0): + print("[dbcreate] attempting to load existing roles...") + time.sleep(sleep_time) + load_existing_roles() + print("[dbcreate] successfully loaded existing roles") + time.sleep(sleep_time) + if(get_users().__len__()==0): + print("[dbcreate] attempting to load SuperAdminUser...") + time.sleep(sleep_time) + load_SuperAdminUser() + print("[dbcreate] successfully loaded SuperAdminUser") + time.sleep(sleep_time) if(get_rubrics().__len__()==0): print("[dbcreate] attempting to load existing rubrics...") time.sleep(sleep_time) @@ -61,18 +73,6 @@ time.sleep(sleep_time) load_existing_suggestions() print("[dbcreate] successfully loaded existing suggestions") - if(get_roles().__len__()==0): - print("[dbcreate] attempting to load existing roles...") - time.sleep(sleep_time) - load_existing_roles() - print("[dbcreate] successfully loaded existing roles") - time.sleep(sleep_time) - if(get_users().__len__()==0): - print("[dbcreate] attempting to load SuperAdminUser...") - time.sleep(sleep_time) - load_SuperAdminUser() - print("[dbcreate] successfully loaded SuperAdminUser") - time.sleep(sleep_time) if len(sys.argv) == 2 and sys.argv[1]=="demo": if(get_users().__len__()==1): print("[dbcreate] attempting to load demo Admin...") @@ -130,17 +130,17 @@ load_demo_admin_assessment_task() print("[dbcreate] successfully loaded demo AssessmentTask") time.sleep(sleep_time) - if(get_feedback().__len__()==0): - print("[dbcreate] attempting to load demo Feedback...") - time.sleep(sleep_time) - load_demo_feedback() - print("[dbcreate] successfully loaded demo Feedback") - time.sleep(sleep_time) if (get_completed_assessments().__len__() == 0): print("[dbcreate] attempting to load demo completed assessments...") time.sleep(sleep_time) load_demo_completed_assessment() print("[dbcreate] successfully loaded demo completed assessments") time.sleep(sleep_time) + if(get_feedback().__len__()==0): + print("[dbcreate] attempting to load demo Feedback...") + time.sleep(sleep_time) + load_demo_feedback() + print("[dbcreate] successfully loaded demo Feedback") + time.sleep(sleep_time) - print("[dbcreate] exiting...") \ No newline at end of file + print("[dbcreate] exiting...") diff --git a/BackEndFlask/env/.env.production b/BackEndFlask/env/.env.production index eb4bec572..d9c37a3eb 100644 --- a/BackEndFlask/env/.env.production +++ b/BackEndFlask/env/.env.production @@ -1,11 +1,16 @@ # Contains the variables for production enviroment which uses mysql # while the same the urls are meant to override incorrect connection paths meant to serve as examples -DONT_LOOK = 'Thisissupposedtobesecret!2' -WIN_LIN = 'mysql+pymysql://sbadmin_tester:sb_test1@skillbuilder-db.c1db7ief4oer.us-east-2.rds.amazonaws.com:3306/skillbuilder-db' -MAC = 'mysql+pymysql://sbadmin_tester:sb_test1@skillbuilder-db.c1db7ief4oer.us-east-2.rds.amazonaws.com:3306/skillbuilder-db' + +MYSQL_HOST = 'rubricapp-db.c1db7ief4oer.us-east-2.rds.amazonaws.com:3306' +MYSQL_PASSWORD = 'ThisReallyNeedsToBeASecret1!' +MYSQL_USER = 'rubricapp_admin' +MYSQL_DATABASE = 'rubricapp' +WIN_LIN = 'mysql+pymysql://rubricapp_admin:{MYSQL_PASSWORD}@rubricapp-db.c1db7ief4oer.us-east-2.rds.amazonaws.com:3306/rubricapp' +MAC = 'mysql+pymysql://rubricapp_admin:{MYSQL_PASSWORD}@rubricapp-db.c1db7ief4oer.us-east-2.rds.amazonaws.com:3306/rubricapp' + #-----------------------------------------------------------------------------# # Final urls should look like the following for when we move to the server # # WIN_LIN = 'mysql+pymysql://user:password@127.0.0.1:3306/name_of_data_base' # # copy the same for mac to ensure everything is properly overwritten # -#-----------------------------------------------------------------------------# \ No newline at end of file +#-----------------------------------------------------------------------------# diff --git a/BackEndFlask/models/loadExistingRubrics.py b/BackEndFlask/models/loadExistingRubrics.py index 686afc0fd..4ddbe904d 100644 --- a/BackEndFlask/models/loadExistingRubrics.py +++ b/BackEndFlask/models/loadExistingRubrics.py @@ -20,7 +20,9 @@ def load_existing_rubrics(): # (Latest update is September 16, 2022) Problem Solving ["Problem Solving", "Analyzing a complex problem or situation, developing a viable strategy to address it, and executing that strategy (when appropriate)."], # (Latest update is July 19, 2022) Teamwork - ["Teamwork", "Interacting with others and buliding on each other's individual strengths and skills, working toward a common goal."] + ["Teamwork", "Interacting with others and buliding on each other's individual strengths and skills, working toward a common goal."], + # (Latest update is November 21, 2024) Metacognition + ["Metacognition", "Being able to regulate one's thinking and learning through planning, monitoring, and evaluating one's efforts."] ] for rubric in rubrics: r = {} @@ -71,6 +73,11 @@ def load_existing_categories(): [7, "Contributing", "Considered the contributions, strengths and skills of all team members", consistently], [7, "Progressing", "Moved forward towards a common goal", consistently], [7, "Building Community", "Acted as a cohesive unit that supported and included all team members.", consistently], + # (Latest update is November 21, 2024) Metacognition Categories 1-4 + [8, "Planning", "Set learning goals and made plans for achieving them", completely], + [8, "Monitoring", "Paid attention to progress on learning and understanding", completely], + [8, "Evaluating", "Reviewed learning gains and/or performance and determined strengths and areas to improve", completely], + [8, "Realistic Self-assessment", "Produced a self-assessment based on previous and current behaviors and circumstances, using reasonable judgment in future planning", completely], ] for category in categories: c = {} @@ -91,60 +98,73 @@ def load_existing_observable_characteristics(): [1, "Identified the question that needed to be answered or the situation that needed to be addressed"], [1, "Identified any situational factors that may be important to addressing the question or situation"], [1, "Identified the general types of data or information needed to address the question"], + [1, "None"], # Evaluating Observable Characteristics 1-3 [2, "Indicated what information is likely to be most relevant"], [2, "Determined the reliability of the source of information"], [2, "Determined the quality and accuracy of the information itself"], + [2, "None"], # Analyzing Observable Characteristics 1-3 [3, "Discussed information and explored possible meanings"], [3, "Identified general trends or patterns in the data/information that could be used as evidence"], [3, "Processed and/or transformed data/information to put it in forms that could be used as evidence"], + [3, "None"], # Synthesizing Observable Characteristics 1-3 [4, "Identified the relationships between different pieces of information or concepts"], [4, "Compared or contrasted what could be determined from different pieces of information"], [4, "Combined multiple pieces of information or ideas in valid ways to generate a new insight in conclusion"], + [4, "None"], # Forming Arguments Structure Observable Characteristics 1-4 [5, "Stated the conclusion or the claim of the argument"], [5, "Listed the evidence used to support the argument"], [5, "Linked the claim/conclusion to the evidence with focused and organized reasoning"], [5, "Stated any qualifiers that limit the conditions for which the argument is true"], + [5, "None"], # Forming Arguments Validity Observable Characteristics 1-5 [6, "The most relevant evidence was used appropriately to support the claim"], [6, "Reasoning was logical and effectively connected the data to the claim"], [6, "The argument was aligned with disciplinary/community concepts or practices"], [6, "Considered alternative or counter claims"], [6, "Considered evidence that could be used to refute or challenge the claim"], + [6, "None"], # (Latest update is November, 2022) Formal Communication # Intent Observable Characteristics 1-3 [7, "Clearly stated what the audience should gain from the communication"], [7, "Used each part of the communication to convey or support the main message"], [7, "Concluded by summarizing what was to be learned"], + [7, "None"], # Audience Observable Characteristic 1-3 [8, "Communicated to the full range of the audience, including novices and those with expertise"], [8, "Aligned the communication with the interests and background of the particular audience"], [8, "Used vocabulary that aligned with the discipline and was understood by the audience"], + [8, "None"], # Organization Observable Characteristics 1-3 [9, "There was a clear story arc that moved the communication forward"], [9, "Organizational cues and transitions clearly indicated the structure of the communication"], [9, "Sequence of ideas flowed in an order that was easy to follow"], + [9, "None"], # Visual Representations Observable Characteristics 1-3 [10, "Each figure conveyed a clear message"], [10, "Details of the visual representation were easily interpreted by the audience"], [10, "The use of the visual enhanced understanding by the audience"], + [10, "None"], # Format and Style Observable Characteristics 1-3 [11, "Stylistic elements were aesthetically pleasing and did not distract from the message"], [11, "Stylistic elements were designed to make the communication accessbile to the audience (size, colors, contrasts, etc.)"], [11, "The level of formality of the communication aligns with the setting, context, and purpose"], + [11, "None"], # Mechanics Written Word Observable Characteristics 1-4 [12, "Writing contained correct spelling, word choice, punctuation, and capitalization"], [12, "All phrases and sentences were grammatically correct"], [12, "All paragraphs (or slides) were well constructed around a central idea"], [12, "All figures and tables were called out in the narrative, and sources were correctly cited"], + [12, "None"], # Delivery Oral Observable Characteristics 1-4 [13, "Spoke loudly and clearly with a tone that indicated confidence and interest in the subject"], [13, "Vocal tone and pacing helped maintain audience interest"], [13, "Gestures and visual cues further oriented the audience to focus on particular items or messages"], [13, "Body language directed the delivery toward the audience and indicated the speaker was open to engagement"], + [13, "None"], # (Latest update is December 29, 2021) Information Processing # Evaluating Observable Characteristics 1-5 [14, "Established what needs to be accomplished with this information"], @@ -152,101 +172,144 @@ def load_existing_observable_characteristics(): [14, "Indicated what information is relevant"], [14, "Indicated what information is NOT relevant"], [14, "Indicated why certain information is relevant or not"], + [14, "None"], # Interpreting Observable Characteristics 1-4 [15, "Labeled or assigned correct meaning to information (text, tables, symbols, diagrams)"], [15, "Extracted specific details from information"], [15, "Rephrased information in own words"], [15, "Identified patterns in information and derived meaning from them"], + [15, "None"], # Manipulating or Transforming Extent Observable Characteristics 1-3 [16, "Determined what information needs to be converted to accomplish the task"], [16, "Described the process used to generate the transformation"], [16, "Converted all relevant information into a different representation of format"], + [16, "None"], # Manipulating or Transforming Accuracy Observable Characteristics 1-3 [17, "Conveyed the correct or intended meaning of the information in the new representation or format."], [17, "All relevant features of the original information/data are presented in the new representation of format"], [17, "Performed the transformation without errors"], + [17, "None"], # (Latest update is July 5, 2022) Interpersonal Communication # Speaking Observable Characteristics 1-4 [18, "Spoke clear and loudly enough for all team members to hear"], [18, "Used a tone that invited other people to respond"], [18, "Used language that was suitable for the listeners and context"], [18, "Spoke for a reasonable length of time for the situation"], + [18, "None"], # Listening Observable Characteristics 1-4 [19, "Patiently listened without interrupting the speaker"], [19, "Referenced others' ideas to indicate listening and understanding"], [19, "Presented nonverbal cues to indicate attentiveness"], [19, "Avoided engaging in activities that diverted attention"], + [19, "None"], # Responding Observable Characteristics 1-4 [20, "Acknowledged other members for their ideas or contributions"], [20, "Rephrased or referred to what other group members have said"], [20, "Asked other group members to futher explain a concept"], [20, "Elaborated or extended on someone else's idea(s)"], + [20, "None"], # (Latest update is April 24, 2023) Management # Planning Observable Characteristics 1-4 [21, "Generated a summary of the starting and ending points"], [21, "Generated a sequence of steps or tasks to reach the desired goal"], [21, "Discussed a timeline or time frame for completing project tasks"], [21, "Decided on a strategy to share information, updates and progress with all team members"], + [21, "None"], # Organizing Observable Characteristics 1-3 [22, "Decided upon the necessary resources and tools"], [22, "Identified the availability of resources, tools or information"], [22, "Gathered necessary information and tools"], + [22, "None"], # Coordinating Observable Characteristics 1-4 [23, "Determined if tasks need to be delegated or completed by the team as a whole"], [23, "Tailored the tasks toward strengths and availability of team members"], [23, "Assigned specific tasks and responsibilities to team members"], [23, "Established effective communication strategies and productive interactions among team members"], + [23, "None"], # Overseeing Observable Characteristics 1-5 [24, "Reinforced responsibilities and refocused team members toward completing project tasks"], [24, "Communicated status, next steps, and reiterated general plan to accomplish goals"], [24, "Sought and valued input from team members and provided them with constructive feedback"], [24, "Kept track of remaining materials, team and person hours"], [24, "Updated or adapted the tasks or plans as needed"], + [24, "None"], # (Latest update is September 16, 2022) Problem Solving # Analyzing the Situation Observable Characteristics 1-3 [25, "Described the problem that needed to be solved or the decisions that needed to be made"], [25, "Listed complicating factors or constraints that may be important to consider when developing a solution"], [25, "Identified the potential consequences to stakeholders or surrounding"], + [25, "None"], # Identifying Observable Characteristics 1-4 [26, "Reviewed the organized the necessary information and resources"], [26, "Evaluated which available information and resources are critical to solving the problem"], [26, "Determined the limitations of the tools or information that was given or gathered"], [26, "Identified reliable sources that may provide additional needed information, tools, or resources"], + [26, "None"], # Strategizing Observable Characteristics 1-3 [27, "Identified potential starting and ending points for the strategy"], [27, "Determined general steps needed to get from starting point to ending point"], [27, "Sequenced or mapped actions in a logical progression"], + [27, "None"], # Validating Observable Characteristics 1-4 [28, "Reviewed strategy with respect to the identified scope"], [28, "Provided rationale as to how steps within the process were properly sequenced"], [28, "Identified ways the process or stragey could be futher improved or optimized"], [28, "Evaluated the practicality of the overall strategy"], + [28, "None"], # Executing Observable Characteristics 1-4 [29, "Used data and information correctly"], [29, "Made assumptions about the use of data and information that are justifiable"], [29, "Determined that each step is being done in the order and the manner that was planned."], [29, "Verified that each step in the process was providing the desired outcome."], + [29, "None"], # (Latest update is July 19, 2022) Teamwork # Interacting Observable Characteristics 1-3 [30, "All team members communicated ideas related to a common goal"], [30, "Team members responded to each other verbally or nonverbally"], [30, "Directed each other to tasks and information"], + [30, "None"], # Constributing Observable Characteristics 1-4 [31, "Acknowledged the value of the statements of other team members"], [31, "Invited other team members to participate in the conversation, particulary if they had not contributed in a while"], [31, "Expanded on statements of other team members"], [31, "Asked follow-up questions to clarify team members' thoughts"], + [31, "None"], # Progressing Observable Characteristics 1-4 [32, "Stayed on task, focused on the assignment with only brief interruptions"], [32, "Refocused team members to make more effective progress towards the goal"], [32, "Worked simultaneously as single unit on the common goal"], [32, "Checked time to monitor progress on task."], + [32, "None"], # Building Community Observable Characteristics 1-5 [33, "Created a sense of belonging to the team for all team members"], - [33, "Acted as a single unit that did not break up into smaller, gragmented units for the entire task"], + [33, "Acted as a single unit that did not break up into smaller, fragmented units for the entire task"], [33, "Openly and respectfully discussed questions and disagreements between team members"], [33, "Listened carefully to people, and gave weight and respect to their contributions"], [33, "Welcomed and valued the individual identity and experiences of each team member"], + [33, "None"], + # (Latest update is November 20) Metacognition + # Planning Observable Characteristics 1-3 + [34, "Decided on a goal for the task"], + [34, "Determined a strategy, including needed resources, to use in the learning effort"], + [34, "Estimated the time interval needed to reach the goal of the task"], + [34, "None"], + # Monitoring Observable Characteristics 1-4 + [35, "Checked understanding of things to be learned, noting which areas were challenging"], + [35, "Assessed if strategies were effective, and adjusted strategies as needed"], + [35, "Considered if additional resources or assistance would be helpful"], + [35, "Kept track of overall progress on completing the task"], + [35, "None"], + # Evaluating Observable Characteristics 1-3 + [36, "Compared outcomes to personal goals and expectations"], + [36, "Compared performance to external standard or feedback"], + [36, "Identified which strategies were successful and which could be improved"], + [36, "None"], + # Realistic Self-assessment Observable Characteristics 1-4 + [37, "Focused the reflection on the skill or effort that was targeted"], + [37, "Provided specific evidence from past or recent performances in the reflection"], + [37, "Identified how circumstances supported or limited the completion of the task"], + [37, "Made realistic plans (based on previous and current behaviors and circumstances) to improve future performance"], + [37, "None"], ] for observable in observable_characteristics: create_observable_characteristic(observable) @@ -259,24 +322,28 @@ def load_existing_suggestions(): [1, "Highlight or clearly state the question to be addressed or type of conclusion that must be reached."], [1, "List the factors (if any) that may limit the feasibility of some possible conclusions."], [1, "Write down the information you think is needed to address the situation."], + [1, "Nothing specific at this time"], # Evaluating Suggestions 1-5 [2, "Review provided material and circle, highlight, or otherwise indicate information that may be used as evidence in reaching a conclusion."], [2, "Write down the other information (prior knowledge) that might be useful to lead to/support a possible conclusion."], [2, "Set aside any information, patterns, or insights that seem less relevant to addressing the situation at hand."], [2, "Consider whether the information was obtained from a reliable source (textbook, literature, instructor, websites with credible authors)"], [2, "Determine the quality of the information and whether it is sufficient to answer the question."], + [2, "Nothing specific at this time"], # Analyzing Suggestions 1-5 [3, "Interpret and label key pieces of information in text, tables, graphs, diagrams."], [3, "State in your own words what information represents or means."], [3, "Identify general trends in information, and note any information that doesn't fit the pattern."], [3, "Check your understanding of information with others and discuss any differences in understanding."], [3, "State how each piece of information, pattern, or insight can be used to reach a conclusion or support your claim."], + [3, "Nothing specific at this time"], # Synthesizing Suggestions 1-5 [4, "Look for two or more pieces or types of information that can be connected and state how they can be related to each other."], [4, "Write out the aspects that are similar and different in various pieces of information."], [4, "Map out how the information and/or concepts can be combined to support an argument or reach a conclusion."], [4, "Write a statement that summarizes the integration of the information and conveys a new understanding."], [4, "List the ways in which synthesized information could be used as evidence."], + [4, "Nothing specific at this time"], # Forming Arguments Structure Suggestions 1-7 [5, "Review the original goal - what question were you trying to answer?"], [5, "Clearly state your answer to the question (your claim or conclusion)."], @@ -285,12 +352,14 @@ def load_existing_suggestions(): [5, "Explain how each piece of information links to and supports your answer."], [5, "Make sure your answer includes the claim, information and reasoning."], [5, "Make sure the claim or conclusion answers the question."], + [5, "Nothing specific at this time"], # Forming Arguments Validity Suggestions 1-5 [6, "Provide a clear statement that articulates why the evidence you chose leads to the claim or conclusion."], [6, "Check to make sure that your reasoning is consistent with what is accepted in the discipline or context."], [6, "Test your ideas with others, and ask them to judge the quality of the argument or indicate how the argument could be made more convincing."], [6, "Ask yourself (and others) if there is evidence or data that doesn't suport your conclusion or might contradict your claim."], [6, "Consider if there are alternative explanations for the data you are considering."], + [6, "Nothing specific at this time"], # (Latest update is November, 2022) Formal Communication # Intent Suggestions 1-5 [7, "Decide if your main purpose is to inform, to persuade, to argue, to summarize, to entertain, to inspire, etc."], @@ -298,6 +367,7 @@ def load_existing_suggestions(): [7, "Make sure the purpose of the communication is presented early to orient your audience to the focus of the communication."], [7, "Check that the focus of each segment is clearly linked to the main message or intent of the communication."], [7, "Summarize the main ideas to wrap up the presentation (refer back to the initial statement(s) of what was to be learned)."], + [7, "Nothing specific at this time"], # Audience Suggestions 1-6 [8, "Identify the range and level of expertise and interest your audience has for the topic and design your communication to have aspects that will engage all members of the audience."], [8, "Identify what the audience needs to know to understand the narrative."], @@ -305,6 +375,7 @@ def load_existing_suggestions(): [8, "Only use jargon when it is understood readily by most members of your audience, and it makes the communication more effective and succinct."], [8, "Check that the vocabulary, sentence structure, and tone used in your communication is aligned with the level of your audience."], [8, "Collect feedback from others on drafts to make sure the core message of the communication is easily understood."], + [8, "Nothing specific at this time"], # Organization Suggestions 1-6 [9, "Consider the 'story' that you want to tell. Ask yourself what's the main message you want the audience to leave with."], [9, "Identify the critical points for the story (do this before you prepare the actual communication) and map out the key points."], @@ -312,6 +383,7 @@ def load_existing_suggestions(): [9, "Repeat key ideas to ensure the audience can follow the main idea."], [9, "Make sure that you introduce prerequisite information early in the communication."], [9, "Try more than one order for the topics, to see if overall flow is improved."], + [9, "Nothing specific at this time"], # Visual Representations Suggestions 1-6 [10, "Plan what types of figures are needed to support the narrative - consider writing out a figure description before you construct it."], [10, "Avoid including unnecessary details that detract from the intended message."], @@ -319,6 +391,7 @@ def load_existing_suggestions(): [10, "Be sure labels, text, and small details can be easily read."], [10, "Provide a caption that helps interpret the key aspects of the visual."], [10, "Seek feedback on visuals to gauge initial reaction and ease of interpretation."], + [10, "Nothing specific at this time"], # Format Style Suggestions 1-6 [11, "Use titles (headers) and subtitles (subheaders) to orient the audience and help them follow the narrative."], [11, "Look at pages or slides as a whole for an easy-to-read layout, such as white space, headers, line spacing, etc."], @@ -326,6 +399,7 @@ def load_existing_suggestions(): [11, "Use colors to carefully highlight or call attention to key elements to enhance your narrative without distracting from your message."], [11, "Make sure that text, figures, and colors are readable and accessible for all."], [11, "Seek feedback to confirm that the language, tone, and style of your communication match the level of formality needed for your context and purpose."], + [11, "Nothing specific at this time"], # Mechanics Written Words Suggestions 1-7 [12, "Proofread your writing for spelling errors, punctuation, autocorrects, etc."], [12, "Review sentence structure for subject-verb agreement, consistent tense, run on sentences, and other structural problems."], @@ -334,12 +408,14 @@ def load_existing_suggestions(): [12, "Confirm that each figure, table, etc has been numbered consecutively and has been called out and discussed further in the narrative."], [12, "Confirm that all work that has been published elsewhere or ideas/data that were not generated by the author(s) has been properly cited using appropriate conventions."], [12, "Ask someone else to review and provide feedback on your work."], + [12, "Nothing specific at this time"], # Delivery Oral Suggestions 1-5 [13, "Practice for others or record your talk; i. be sure that your voice can be heard, and your word pronunciations are clear. ii. listen for “ums”, “like”, or other verbal tics/filler words that can detract from your message. iii. observe your natural body language, gestures, and stance in front of the audience to be sure that they express confidence and enhance your message."], [13, "Add variety to your speed or vocal tone to emphasize key points or transitions."], [13, "Try to communicate/engage as if telling a story or having a conversation with the audience."], [13, "Face the audience and do not look continuously at the screen or notes."], [13, "Make eye contact with multiple members of the audience."], + [13, "Nothing specific at this time"], # (Latest update is December 29, 2021) Information Processing # Evaluating Suggestions 1-6 [14, "Restate in your own words the task or question that you are trying to address with this information."], @@ -348,23 +424,27 @@ def load_existing_suggestions(): [14, "Write down/circle/highlight the information that is needed to complete the task."], [14, "Put a line through info that you believe is not needed for the task"], [14, "Describe in what ways a particular piece of information may (or may not) be useful (or required) in completing the task"], + [14, "Nothing specific at this time"], # Interpreting Suggestions 1-5 [15, "Add notes or subtitles to key pieces of information found in text, tables, graphs, diagrams to describe its meaning."], [15, "State in your own words what information represents or means."], [15, "Summarize the ideas or relationships the information might convey."], [15, "Determine general trends in information and note any information that doesn't fit the trend"], [15, "Check your understanding of information with others"], + [15, "Nothing specific at this time"], # Manipulating or Transforming Extent Suggestions 1-5 [16, "Identify how the new format of the information differs from the provided format."], [16, "Identify what information needs to be transformed and make notations to ensure that all relevant information has been included."], [16, "Review the new representation or format to be sure all relevant information has been included."], [16, "Consider what information was not included in the new representation or format and make sure it was not necessary."], [16, "Check with peers to see if there is agreement on the method of transformation and final result."], + [16, "Nothing specific at this time"], # Manipulating or Transforming Accuracy Suggestions 1-4 [17, "Write down the features that need to be included in the new form."], [17, "Be sure that you have carefully interpreted the original information and translated that to the new form."], [17, "Carefully check to ensure that the original information is correctly represented in the new form."], [17, "Verify the accuracy of the transformation with others."], + [17, "Nothing specific at this time"], # (Latest update is July 5, 2022) Interpersonal Communication # Speaking Suggestions 1-6 [18, "Direct your voice towards the listeners and ask if you can be heard."], @@ -373,6 +453,7 @@ def load_existing_suggestions(): [18, "Carefully choose your words to align with the nature of the topic and the audience."], [18, "Speak for a length of time that allows frequent back and forth conversation."], [18, "Provide a level of detail appropriate to convey your main idea."], + [18, "Nothing specific at this time"], # Listening Suggestions 1-7 [19, "Allow team members to finish their contribution."], [19, "Indicate if you can't hear someone's spoken words."], @@ -381,12 +462,14 @@ def load_existing_suggestions(): [19, "Face the team member that is speaking and make eye contact."], [19, "Use active-listening body language or facial expressions that indicate attentiveness."], [19, "Remove distractions and direct your attention to the speaker."], + [19, "Nothing specific at this time"], # Responding Suggestions 1-5 [20, "Let team members know when they make a productive contribution."], [20, "State what others have said in your own words and confirm understanding."], [20, "Ask a follow-up question or ask for clarification."], [20, "Reference what others have said when you build on their ideas."], [20, "Offer an altenative to what a team member said."], + [20, "Nothing specific at this time"], # (Latest update is April 24, 2023) Management # Planning Suggestions 1-6 [21, "Write down the general starting point and starting conditions."], @@ -395,10 +478,12 @@ def load_existing_suggestions(): [21, "Double check to make sure that steps are sequenced sensibly."], [21, "Identify time needed for particular steps or other time constraints."], [21, "Make a regular plan to update progress."], + [21, "Nothing specific at this time"], # Organizing Suggestions 1-3 [22, "List the tools, resources, or information that the group needs to obtain."], [22, "List the location of the tools, resources, or information at the group's disposal."], [22, "Strategize about how to obtain the additional/needed tools, resources, or information."], + [22, "Nothing specific at this time"], # Coordinating Suggestions 1-7 [23, "Review the number of people you have addressing each task, and be sure that it is right-sized to make progress."], [23, "Analyze each task for likelihood of success, and be sure you have it staffed appropriately."], @@ -407,6 +492,7 @@ def load_existing_suggestions(): [23, "Delegate tasks outside the team if necessary, especially if the task is too large to complete in the given time."], [23, "Establish a mechanism to share status and work products."], [23, "Set up meetings to discuss challenges and progress."], + [23, "Nothing specific at this time"], # Overseeing Suggestions 1-8 [24, "Check in regularly with each team member to review their progress on tasks."], [24, "Provide a list of steps towards accomplishing the goal that all can refer to and check off each step when completed."], @@ -416,6 +502,7 @@ def load_existing_suggestions(): [24, "Reassign team members to activities that need more attention or person hours as other steps are completed."], [24, "Evaluate whether team members should be reassigned to tasks that better align with their skill sets."], [24, "Check to see if the original plan for project completion is still feasible; make changes if necessary."], + [24, "Nothing specific at this time"], # (Latest update is September 16, 2022) Problem Solving # Analyzing The Situation Suggestions 1-6 [25, "Read closely, and write down short summaries as you read through the entire context of the problem"], @@ -424,18 +511,21 @@ def load_existing_suggestions(): [25, "Prioritize the complicating factors from most to least important"], [25, "List anything that will be significantly impacted by your decision (such as conditions, objects, or people)"], [25, "Deliberate on the consequences of generating a wrong strategy or solution"], + [25, "Nothing specific at this time"], # Identifying Suggestions 1-5 [26, "Highlight or annotate the provided information that may be needed to solve the problem."], [26, "List information or principles that you already know that can help you solve the problem."], [26, "Sort the given and gathered information/resources as 'useful' or 'not useful.'"], [26, "List the particular limitations of the provided information or tools."], [26, "Identify ways to access any additional reliable information, tools or resources that you might need."], + [26, "Nothing specific at this time"], # Strategizing Suggestions 1-5 [27, "Write down a reasonable place to start and add a reasonable end goal"], [27, "Align any two steps in the order or sequence that they must happen. Then, add a third step and so on."], [27, "Consider starting at the end goal and working backwards"], [27, "Sketch a flowchart indicating some general steps from start to finish."], [27, "Add links/actions, or processes that connect the steps"], + [27, "Nothing specific at this time"], # Validating Suggestions 1-7 [28, "Summarize the problem succinctly - does your strategy address each aspect of the problem?"], [28, "Identify any steps that must occur in a specific order and verify that they do."], @@ -444,6 +534,7 @@ def load_existing_suggestions(): [28, "Check to see if you have access to necessary resources, and if not, propose substitutes."], [28, "Check that your strategy is practical and functional, with respect to time, cost, safety, personnel, regulations, etc."], [28, "Take time to continuously assess your strategy throughout the process."], + [28, "Nothing specific at this time"], # Executing Suggestions 1-7 [29, "Use authentic values and reasonable estimates for information needed to solve the problem"], [29, "Make sure that the information you are using applies to the conditions of the problem."], @@ -452,6 +543,7 @@ def load_existing_suggestions(): [29, "List any barriers that you are encountering in executing the steps"], [29, "Identify ways to overcome barriers in implementation steps of the strategy"], [29, "Check the outcome of each step of the strategy for effectiveness."], + [29, "Nothing specific at this time"], # (Latest update is July 19, 2022) Teamwork # Interacting Suggestions 1-6 [30, "Speak up and share your ideas/insights with team members."], @@ -460,6 +552,7 @@ def load_existing_suggestions(): [30, "Explicitly react (nod, speak out loud, write a note, etc.) to contributions from other team members to indicate that you are engaged."], [30, "Restate the prompt to make sure everyone is at the same place on the task."], [30, "Have all members of the team consider the same task at the same time rather than working independently"], + [30, "Nothing specific at this time"], # Contributing Suggestions 1-6 [31, "Acknowledge or point out particularly effective contributions."], [31, "Initiate discussions of agreement or disagreement with statements made by team members."], @@ -467,6 +560,7 @@ def load_existing_suggestions(): [31, "Regularly ask members of the team to share their ideas or explain their reasoning."], [31, "Add information or reasoning to contributions from other team members."], [31, "Ask for clarification or rephrase statements of other team members to ensure understanding."], + [31, "Nothing specific at this time"], # Progressing Suggestions 1-7 [32, "Minimize distractions and focus on the assignment (close unrelated websites or messaging on phone or computer, turn off music, put away unrelated materials)."], [32, "Redirect team members to current task."], @@ -475,6 +569,7 @@ def load_existing_suggestions(): [32, "Compare progress on task to the time remaining on assignment."], [32, "Communicate to team members that you need to move on."], [32, "As a team, list tasks to be done and agree on order for these tasks."], + [32, "Nothing specific at this time"], # Building Community Suggestions 1-8 [33, "Address team members by name."], [33, "Use inclusive (collective) team cues that draw all team members together."], @@ -484,6 +579,46 @@ def load_existing_suggestions(): [33, "Encourage all team members to work together on the same tasks at the same time, as needed."], [33, "Celebrate team successes and persistence through roadblocks."], [33, "Invite other team members to provide alternative views and reasoning."], - ] + [33, "Nothing specific at this time"], + # (Latest update is November 20, 2024) Metacognition + # Planning Suggestions 1-7 + [34, "Describe three or four ways this learning task connects to other topics and components in the course."], + [34, "Identify 2-3 ways that this learning task will help you meet the intended learning goals."], + [34, "Skim the assignment to get a sense of what is involved and to see what resources you will need to support your work."], + [34, "List the things that need to be completed for this assignment or task to be considered successful."], + [34, "Make a detailed plan for how you will complete the assignment."], + [34, "Decide if it makes sense to break the overall assignment into working segments, and figure out how long each would take."], + [34, "Estimate the total amount of time that you will need to complete the task."], + [34, "Nothing specific at this time"], + # Monitoring Suggestions 1-5 + [35, "Read the reflection prompt and objectives before you start and decide what skills or processes you should monitor during the task."], + [35, "Review objectives frequently while completing a task to check your understanding of important concepts."], + [35, "Survey your environment and mindset for distractions that block you from enacting your strategies and making progress (devices, noise, people, physical needs)."], + [35, "Pay attention to where in a process or strategy you are getting stuck, and identify what resources or support would help you to get past that point."], + [35, "Periodically pause and determine the percentage of work that you finished and compare it to the total time available to complete the work."], + [35, "Nothing specific at this time"], + # Evaluating Suggestions 1-7 + [36, "Compare how you actually performed to your initial expectations. Identify which expectations were met and where you fell short."], + [36, "For areas where you met your initial expectations, list your top three effective strategies or activities."], + [36, "For areas where you did not meet your initial expectations, decide if your goals were realistic."], + [36, "If your initial expectations were not realistic, make a list of issues you didn’t account for in your goal setting."], + [36, "For areas where your expectations were realistic but not met, identify what factors made you fall short of your target."], + [36, "Compare how you performed to an external standard or external feedback on your performance. Identify which criteria were met and which require additional work."], + [36, "For areas where you met the criteria, list your top three effective strategies or activities.Describe three or four ways this learning task connects to other topics and components in the course."], + [36, "For areas where you did not meet the criteria, list at least two areas or strategies where you need further work."], + [36, "Determine if you planned your time well by comparing the number of hours you allocated to the number of hours you actually needed to meet your goals."], + [36, "Decide if you were motivated or engaged in this task, and describe how that impacted your efforts."], + [36, "If you weren’t very motivated for this task, generate some ideas for how you could better motivate yourself. Identify 2-3 ways that this learning task will help you meet the intended learning goals."], + [36, "Nothing specific at this time"], + # Realistic Self-assessment Suggestions 1-7 + [37, "Before you start the reflection, review the prompt for the reflection or your goals for the reflection and make sure you are focusing on the intended skill or process (e.g. don’t comment on teamwork if asked to reflect on critical thinking)."], + [37, "List the specific actions that you took in completing this task, including planning and monitoring actions in addition to the task itself.  Which are similar to past approaches?  Which are different?"], + [37, "Considering the actions listed above and your typical approaches; rank them in terms of most productive to least productive."], + [37, "Identify the unproductive behavior or work habit you should change to most positively impact your performance and determine a strategy for how to change it."], + [37, "Consider the contextual factors (physical surroundings, time constraints, life circumstances) that affected your performance; note how your strategies need to be altered to account for them to improve future performance."], + [37, "Summarize 2-3 specific strategies you’ve identified to improve your performance on future tasks."], + [37, "Ask someone who knows you or your work well to review your self-assessment. Ask them if you accurately summarized your past efforts and if they think your future strategies are realistic for you."], + [37, "Nothing specific at this time"], + ] for suggestion in suggestions: create_suggestion(suggestion) \ No newline at end of file diff --git a/BackEndFlask/models/queries.py b/BackEndFlask/models/queries.py index d53092461..3429077ac 100644 --- a/BackEndFlask/models/queries.py +++ b/BackEndFlask/models/queries.py @@ -1051,8 +1051,8 @@ def get_csv_categories(rubric_id: int, user_id: int, team_id: int, at_id: int, c if team_id is not None : ocs_sfis_query[i].filter(CompletedAssessment.team_id == team_id) # Executing the query - ocs = ocs_sfis_query[0].all() - sfis = ocs_sfis_query[1].all() + ocs = ocs_sfis_query[0].distinct(ObservableCharacteristic.observable_characteristics_id).all() + sfis = ocs_sfis_query[1].distinct(SuggestionsForImprovement.suggestion_id).all() return ocs,sfis @@ -1082,9 +1082,6 @@ def get_course_name_by_at_id(at_id:int) -> str : return course_name[0][0] - - - def get_completed_assessment_ratio(course_id: int, assessment_task_id: int) -> int: """ Description: @@ -1096,10 +1093,44 @@ def get_completed_assessment_ratio(course_id: int, assessment_task_id: int) -> i Return: int (Ratio of users who have completed an assessment task rounded to the nearest whole number) """ + ratio = 0 + all_usernames_for_completed_task = get_completed_assessment_with_user_name(assessment_task_id) - all_students_in_course = get_users_by_course_id_and_role_id(course_id, 5) - ratio = (len(all_usernames_for_completed_task) / len(all_students_in_course)) * 100 + + if all_usernames_for_completed_task: + all_students_in_course = get_users_by_course_id_and_role_id(course_id, 5) + + ratio = len(all_usernames_for_completed_task) / len(all_students_in_course) * 100 + else: + all_teams_in_course = get_team_members_in_course(course_id) + all_teams_for_completed_task = get_completed_assessment_with_team_name(assessment_task_id) + + ratio = len(all_teams_for_completed_task) / len(all_teams_in_course) * 100 ratio_rounded = round(ratio) - return ratio_rounded \ No newline at end of file + return ratio_rounded + +def is_admin_by_user_id(user_id: int) -> bool: + """ + Description: + Returns whether a certain user_id is a admin. + + Parameters: + user_id: int (User id) + + Returns: + (if the user_id is an admin) + + Exceptions: None other than what the db may raise. + """ + + is_admin = db.session.query( + User.is_admin + ).filter( + User.user_id == user_id + ).all() + + if is_admin[0][0]: + return True + return False \ No newline at end of file diff --git a/BackEndFlask/models/schemas.py b/BackEndFlask/models/schemas.py index 3fbc3570d..433602e6d 100644 --- a/BackEndFlask/models/schemas.py +++ b/BackEndFlask/models/schemas.py @@ -16,29 +16,29 @@ 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) - Completed_Assessment(completed_assessment_id, assessment_task_id, by_role, team_id, user_id, initial_time, last_update, rating_observable_characteristics_suggestions_data) + Checkin(checkin_id, assessment_task_id, team_number, user_id, time) + CompletedAssessment(completed_assessment_id, assessment_task_id, by_role, team_id, user_id, initial_time, last_update, rating_observable_characteristics_suggestions_data) + Feedback(feedback_id, user_id, completed_assessment_id, feedback_time, lag_time) Blacklist(id, token) """ 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_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + role_name = db.Column(db.Text, nullable=False) class User(db.Model): __tablename__ = "User" - __table_args__ = {'sqlite_autoincrement': True} - user_id = db.Column(db.Integer, primary_key=True) - first_name = db.Column(db.String(30), nullable=False) - last_name = db.Column(db.String(30), nullable=False) - email = db.Column(db.String(255), unique=True, nullable=False) - password = db.Column(db.String(80), nullable=False) + user_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + first_name = db.Column(db.Text, nullable=False) + last_name = db.Column(db.Text, nullable=False) + email = db.Column(db.String(254), unique=True, nullable=False) + password = db.Column(db.Text, nullable=False) 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) - reset_code = db.Column(db.String(6), nullable=True) + reset_code = db.Column(db.Text, nullable=True) is_admin = db.Column(db.Boolean, nullable=False) class Rubric(db.Model): @@ -46,14 +46,14 @@ class Rubric(db.Model): __table_args__ = {'sqlite_autoincrement': True} rubric_id = db.Column(db.Integer, primary_key=True) rubric_name = db.Column(db.String(100)) - rubric_description = db.Column(db.String(100), nullable=True) + rubric_description = db.Column(db.Text, nullable=True) owner = db.Column(db.Integer, ForeignKey(User.user_id), nullable=True) class Category(db.Model): __tablename__ = "Category" __table_args__ = {'sqlite_autoincrement': True} category_id = db.Column(db.Integer, primary_key=True) - category_name = db.Column(db.String(30), nullable=False) + category_name = db.Column(db.Text, nullable=False) description = db.Column(db.String(255), nullable=False) rating_json = db.Column(db.JSON, nullable=False) @@ -80,12 +80,11 @@ class SuggestionsForImprovement(db.Model): class Course(db.Model): __tablename__ = "Course" - __table_args__ = {'sqlite_autoincrement': True} - course_id = db.Column(db.Integer, primary_key=True) - course_number = db.Column(db.String(10), nullable=False) - course_name = db.Column(db.String(50), nullable=False) + course_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + course_number = db.Column(db.Text, nullable=False) + course_name = db.Column(db.Text, nullable=False) year = db.Column(db.Integer, nullable=False) - term = db.Column(db.String(50), nullable=False) + term = db.Column(db.Text, nullable=False) active = db.Column(db.Boolean, nullable=False) admin_id = db.Column(db.Integer, ForeignKey(User.user_id, ondelete='RESTRICT'), nullable=False) use_tas = db.Column(db.Boolean, nullable=False) @@ -102,9 +101,8 @@ class UserCourse(db.Model): 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) - team_name = db.Column(db.String(25), nullable=False) + team_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + team_name = db.Column(db.Text, nullable=False) course_id = db.Column(db.Integer, ForeignKey(Course.course_id), nullable=False) observer_id = db.Column(db.Integer, ForeignKey(User.user_id, ondelete='RESTRICT'), nullable=False) date_created = db.Column(db.Date, nullable=False) @@ -112,34 +110,31 @@ class Team(db.Model): # keeps track of default teams for a fixed team scenario class TeamUser(db.Model): __tablename__ = "TeamUser" - __table_args__ = {'sqlite_autoincrement': True} - team_user_id = db.Column(db.Integer, primary_key=True) + team_user_id = db.Column(db.Integer, primary_key=True, autoincrement=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} - assessment_task_id = db.Column(db.Integer, primary_key=True) - assessment_task_name = db.Column(db.String(100)) + assessment_task_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + assessment_task_name = db.Column(db.Text) course_id = db.Column(db.Integer, ForeignKey(Course.course_id)) rubric_id = db.Column(db.Integer, ForeignKey(Rubric.rubric_id)) # how to handle updates and deletes role_id = db.Column(db.Integer, ForeignKey(Role.role_id)) due_date = db.Column(db.DateTime, nullable=False) - time_zone = db.Column(db.String(3), nullable=False) + time_zone = db.Column(db.Text, nullable=False) 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) - create_team_password = db.Column(db.String(25), nullable=True) + comment = db.Column(db.Text, nullable=True) + create_team_password = db.Column(db.Text, 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) class Checkin(db.Model): # keeps students checking to take a specific AT __tablename__ = "Checkin" - __table_args__ = {'sqlite_autoincrement': True} - checkin_id = db.Column(db.Integer, primary_key=True) + checkin_id = db.Column(db.Integer, primary_key=True, autoincrement=True) assessment_task_id = db.Column(db.Integer, ForeignKey(AssessmentTask.assessment_task_id), nullable=False) # not a foreign key because in the scenario without fixed teams, there will not be default team entries # to reference. if they are default teams, team_number will equal the team_id of the corresponding team @@ -149,8 +144,7 @@ class Checkin(db.Model): # keeps students checking to take a specific AT class CompletedAssessment(db.Model): __tablename__ = "CompletedAssessment" - __table_args__ = {'sqlite_autoincrement': True} - completed_assessment_id = db.Column(db.Integer, primary_key=True) + completed_assessment_id = db.Column(db.Integer, primary_key=True, autoincrement=True) assessment_task_id = db.Column(db.Integer, ForeignKey(AssessmentTask.assessment_task_id)) completed_by = db.Column(db.Integer, ForeignKey(User.user_id), nullable=False) team_id = db.Column(db.Integer, ForeignKey(Team.team_id), nullable=True) @@ -162,8 +156,7 @@ class CompletedAssessment(db.Model): class Feedback(db.Model): __tablename__ = "Feedback" - __table_args__ = {'sqlite_autoincrement': True} - feedback_id = db.Column(db.Integer, primary_key=True) + feedback_id = db.Column(db.Integer, primary_key=True, autoincrement=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 diff --git a/BackEndFlask/models/user.py b/BackEndFlask/models/user.py index b9ac1e235..dc9cf4624 100644 --- a/BackEndFlask/models/user.py +++ b/BackEndFlask/models/user.py @@ -186,7 +186,7 @@ def create_user(user_data): lms_id=user_data["lms_id"], consent=user_data["consent"], owner_id=user_data["owner_id"], - is_admin="role_id" in user_data.keys() and user_data["role_id"]==3, + is_admin="role_id" in user_data.keys() and user_data["role_id"] in [1,2,3], has_set_password=has_set_password, reset_code=None ) @@ -242,8 +242,9 @@ def load_SuperAdminUser(): "password": str(os.environ.get('SUPER_ADMIN_PASSWORD')), "lms_id": 0, "consent": None, - "owner_id": 0, - "role_id": None + "owner_id": None, + "role_id": 2, + "is_admin": True }) # user_id = 2 diff --git a/BackEndFlask/models/utility.py b/BackEndFlask/models/utility.py index 942ea62b0..dd8c0fbfb 100644 --- a/BackEndFlask/models/utility.py +++ b/BackEndFlask/models/utility.py @@ -1,6 +1,6 @@ import sys import yagmail -import random, string +import string, secrets from models.logger import logger from controller.Routes.RouteExceptions import EmailFailureException @@ -58,7 +58,7 @@ def send_email(address: str, subject: str, content: str): def generate_random_password(length: int): letters = string.ascii_letters + string.digits - return ''.join(random.choice(letters) for i in range(length)) + return ''.join(secrets.choice(letters) for i in range(length)) def error_log(f): ''' diff --git a/BackEndFlask/requirements.txt b/BackEndFlask/requirements.txt index 653d710fd..423cf8d5b 100644 --- a/BackEndFlask/requirements.txt +++ b/BackEndFlask/requirements.txt @@ -15,4 +15,5 @@ Werkzeug >= 2.0.2 redis >= 4.5.5 python-dotenv >= 1.0.0 yagmail >= 0.15.293 -openpyxl >= 3.1.2 \ No newline at end of file +openpyxl >= 3.1.2 +cryptography >= 43.0.1 \ No newline at end of file diff --git a/BackEndFlask/run.py b/BackEndFlask/run.py index f1bba48e6..58643d83c 100644 --- a/BackEndFlask/run.py +++ b/BackEndFlask/run.py @@ -1,12 +1,4 @@ from core import app -# 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) - - #the app.run(host="0.0.0.0") line is currently commented out and if and only when we are seting up an EC2 instance app.run(host="0.0.0.0") - - # token: MFFt4RjpXNMh1c_T1AQj diff --git a/BackEndFlask/setupEnv.py b/BackEndFlask/setupEnv.py index 9b01a1b30..0effeb690 100755 --- a/BackEndFlask/setupEnv.py +++ b/BackEndFlask/setupEnv.py @@ -41,6 +41,8 @@ def cmd(command, parent_function): err(f"Error running command: {command} in function: {parent_function}") + err(f"Exception: {e}") + err(f"Return code: {e.returncode}") err(f"Output: {e.output}") diff --git a/Dockerfile.backend b/Dockerfile.backend index c43103ed1..386c23bc2 100644 --- a/Dockerfile.backend +++ b/Dockerfile.backend @@ -24,11 +24,8 @@ RUN pip install --no-cache-dir --upgrade pip \ # Copy the rest of the backend code COPY BackEndFlask/ /app/ -# Initialize the Backend environment -RUN python setupEnv.py -d - # Expose the Backend port EXPOSE 5000 # Start the Flask server -CMD ["python", "setupEnv.py", "-s"] +CMD ["python", "setupEnv.py", "-ds"] diff --git a/FrontEndReact/src/View/Admin/Add/AddCourse/AdminAddCourse.js b/FrontEndReact/src/View/Admin/Add/AddCourse/AdminAddCourse.js index 0f4ddb821..3be2ad788 100644 --- a/FrontEndReact/src/View/Admin/Add/AddCourse/AdminAddCourse.js +++ b/FrontEndReact/src/View/Admin/Add/AddCourse/AdminAddCourse.js @@ -5,7 +5,8 @@ import validator from "validator"; import ErrorMessage from "../../../Error/ErrorMessage.js"; import { genericResourcePOST, genericResourcePUT } from "../../../../utility.js"; import Cookies from "universal-cookie"; -import { Box, Button, FormControl, Typography, TextField, FormControlLabel, Checkbox, FormGroup, } from "@mui/material"; +import HelpOutlineIcon from "@mui/icons-material/HelpOutline"; +import { Box, Button, FormControl, Typography, Popover, TextField, Tooltip, IconButton, FormControlLabel, Checkbox, FormGroup, } from "@mui/material"; @@ -25,7 +26,8 @@ class AdminAddCourse extends Component { year: "", active: true, useTas: true, - useFixedTeams: true, + useFixedTeams: true, + anchorEl: null, errors: { courseName: "", @@ -34,8 +36,13 @@ class AdminAddCourse extends Component { year: "", }, }; + } + setAnchorEl = (element) => { + this.setState({ anchorEl: element }); + }; + componentDidMount() { var navbar = this.props.navbar; var state = navbar.state; @@ -56,6 +63,13 @@ class AdminAddCourse extends Component { }); } } + handleClick = (event) => { + this.setAnchorEl(event.currentTarget); + }; + + handleClose = () => { + this.setAnchorEl(null); + }; handleChange = (e) => { const { id, value } = e.target; @@ -144,9 +158,6 @@ class AdminAddCourse extends Component { if (term.trim() === "") newErrors["term"] = "Term cannot be empty"; - else if (term.trim() !== "Spring" && term.trim() !== "Fall" && term.trim() !== "Summer") - newErrors["term"] = "Term should be either Spring, Fall, or Summer"; - if (newErrors["courseName"] !== "" || newErrors["courseNumber"] !== "" ||newErrors["year"] !== "" ||newErrors["term"] !== "") { this.setState({ errors: newErrors @@ -208,6 +219,8 @@ class AdminAddCourse extends Component { var navbar = this.props.navbar; var state = navbar.state; var addCourse = state.addCourse; + const open = Boolean(this.state.anchorEl); + const id = open ? 'simple-popover' : undefined; return ( @@ -266,7 +279,7 @@ class AdminAddCourse extends Component { id="term" name="newTerm" variant="outlined" - label="Term" + label="Type your Term name here" fullWidth value={term} error={!!errors.term} @@ -344,8 +357,31 @@ class AdminAddCourse extends Component { } name="newFixedTeams" - label="Fixed Team" - /> + label="Fixed Teams" + /> +
+ + + + + +
+ + Active: Uncheck this box at the end of the term to move it to the Inactive Courses table.
+
Use TA's: + Will you use Teaching or Learning Assistants in this course to fill out rubrics?
+
Fixed teams: Do you assign students to the same team for the entire semester?
+
+ { - render(); - - await waitFor(() => { - expectElementWithAriaLabelToBeInDocument(ct); - }); - - clickElementWithAriaLabel(ac); - - await waitFor(() => { - expectElementWithAriaLabelToBeInDocument(act); - }); - - changeElementWithAriaLabelWithInput(cnami, "Object Oriented Programming"); - - changeElementWithAriaLabelWithInput(cnumi, "CS3423"); - - changeElementWithAriaLabelWithInput(cti, "A"); - - changeElementWithAriaLabelWithInput(cyi, "2025"); - - clickElementWithAriaLabel(aosacb); - - await waitFor(() => { - expectElementWithAriaLabelToBeInDocument(acf); - - expectElementWithAriaLabelToHaveErrorMessage(cti, "Term should be either Spring, Fall, or Summer"); - }); -}); - - -test("AdminAddCourse.test.js Test 9: HelperText error should show for the addCourseYear text field when input is less than 2023", async () => { +test("AdminAddCourse.test.js Test 8: HelperText error should show for the addCourseYear text field when input is less than 2023", async () => { render(); await waitFor(() => { @@ -287,7 +255,7 @@ test("AdminAddCourse.test.js Test 9: HelperText error should show for the addCou }); -test("AdminAddCourse.test.js Test 10: HelperText error should show for the addCourseYear text field when input is not a numeric value", async () => { +test("AdminAddCourse.test.js Test 9: HelperText error should show for the addCourseYear text field when input is not a numeric value", async () => { render(); await waitFor(() => { @@ -318,7 +286,7 @@ test("AdminAddCourse.test.js Test 10: HelperText error should show for the addCo }); -test("AdminAddCourse.test.js Test 11: Filling in valid input and clicking the Add Course button should redirect you to course view page, and should contain the new course you just added", async () => { +test("AdminAddCourse.test.js Test 10: Filling in valid input and clicking the Add Course button should redirect you to course view page, and should contain the new course you just added", async () => { render(); await waitFor(() => { @@ -354,7 +322,7 @@ test("AdminAddCourse.test.js Test 11: Filling in valid input and clicking the Ad }); }); -test("AdminAddCourse.test.js Test 12: HelperText errors should show for the addCourseYear and addCourseTerm text fields when the input year is not numeric and the term is not 'Spring', 'Fall', or 'Summer'", async () => { +test("AdminAddCourse.test.js Test 11: HelperText errors should show for the addCourseYear text field when the input year is not numeric", async () => { render(); await waitFor(() => { @@ -382,6 +350,5 @@ test("AdminAddCourse.test.js Test 12: HelperText errors should show for the addC expectElementWithAriaLabelToHaveErrorMessage(cyi, "Year must be a numeric value"); - expectElementWithAriaLabelToHaveErrorMessage(cti, "Term should be either Spring, Fall, or Summer"); }); }); \ No newline at end of file diff --git a/FrontEndReact/src/View/Admin/Add/AddUsers/AdminAddUser.js b/FrontEndReact/src/View/Admin/Add/AddUsers/AdminAddUser.js index eb7e009d4..fe4bab14c 100644 --- a/FrontEndReact/src/View/Admin/Add/AddUsers/AdminAddUser.js +++ b/FrontEndReact/src/View/Admin/Add/AddUsers/AdminAddUser.js @@ -188,7 +188,7 @@ class AdminAddUser extends Component { "first_name": firstName, "last_name": lastName, "email": email, - "lms_id": lmsId, + "lms_id": lmsId !== "" ? lmsId : null, "consent": null, "owner_id": cookies.get('user')['user_id'], "role_id": navbar.props.isSuperAdmin ? 3 : role diff --git a/README.md b/README.md index d46b018d8..acdfa0f07 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,8 @@ for analysis. - Python 3.12 and up. +- MySQL-Server. + - Homebrew 4.2.18 and up. - Redis 7.2.4 and up. @@ -117,6 +119,49 @@ NOTE: - WINDOWS DEVELOPERS ARE NO LONGER SUPPORTED. +## Setting up the MySQL Environment: ## + +- Run the following command to install MySQL-Server +on Linux: + + sudo apt install mysql-server + +- Run the following command to install MySQL-Server +on MacOS: + + brew install mysql + +- Run the following command to start MySQL-Server +on MacOS: + + brew services start mysql + +- Run the following command to start MySQL-Server +in a new terminal: + + sudo mysql -u root + +- Next use these commands to create an account +named skillbuilder and set the password to +"WasPogil1#" + + CREATE DATABASE account; + CREATE USER 'skillbuilder'@'localhost' IDENTIFIED BY 'WasPogil1#'; + GRANT ALL PRIVILEGES ON *.* TO 'skillbuilder'@'localhost'; + FLUSH PRIVILEGES; + exit; + +NOTE: + +- The password should be changed for deployment. + +- Once this is done, you can use: `setupEnv.py` as normal +to create the database. If for any reason you want to +access the database directly, run the following command: + + mysql -u skillbuilder -p + +and then type the password. ## Installing requirements ## diff --git a/compose.yml b/compose.yml index e33bf294c..548cf4c07 100644 --- a/compose.yml +++ b/compose.yml @@ -6,7 +6,10 @@ services: ports: - "127.0.0.1:5050:5000" depends_on: - - redis + redis: + condition: service_started + mysql: + condition: service_healthy volumes: # Mount the source files inside the container to allow for Flask hot reloading to work - "./BackEndFlask/Functions:/app/Functions:rw" @@ -18,6 +21,10 @@ services: environment: - REDIS_HOST=redis - FLASK_DEBUG=1 + - MYSQL_HOST=mysql + - MYSQL_USER=skillbuilder + - MYSQL_PASSWORD=WasPogil1# + - MYSQL_DATABASE=account redis: image: redis:7.2.4 @@ -44,9 +51,30 @@ services: networks: - app-network + mysql: + image: mysql:8.0 + restart: always + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: account + MYSQL_USER: skillbuilder + MYSQL_PASSWORD: WasPogil1# + ports: + - "5551:3306" + volumes: + - mysql-data:/var/lib/mysql + networks: + - app-network + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + networks: app-network: driver: bridge volumes: + mysql-data: frontend-cache: