From bfb7869d99b1c77ee67c914cb916ed54b3bd4c07 Mon Sep 17 00:00:00 2001 From: tkaixiang Date: Thu, 28 Mar 2024 21:23:02 +0800 Subject: [PATCH 1/4] Add folder support for autograder (not working) --- lib/cadet/assessments/assessments.ex | 21 +++++++-- lib/cadet/jobs/autograder/grading_job.ex | 15 +++++-- lib/cadet/jobs/autograder/lambda_worker.ex | 51 +++++++++++++++++----- lib/cadet/jobs/autograder/utilities.ex | 11 ++++- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/lib/cadet/assessments/assessments.ex b/lib/cadet/assessments/assessments.ex index e1327bf0a..69c4c06d7 100644 --- a/lib/cadet/assessments/assessments.ex +++ b/lib/cadet/assessments/assessments.ex @@ -1746,17 +1746,30 @@ defmodule Cadet.Assessments do _requesting_user = %CourseRegistration{id: grader_id} ) when is_ecto_id(submission_id) and is_ecto_id(question_id) do - answer = + + questions = + Question + |> where(question_id: ^question_id) + |> order_by(:display_order) + |> Repo.all() + + answers = Answer - |> where(submission_id: ^submission_id, question_id: ^question_id) + |> where(submission_id: ^submission_id) |> preload([:question, :submission]) - |> Repo.one() + |> order_by(:question_id) + |> Repo.all() + + # the answer we want to target + answer = + answers + |> Enum.find(fn answer -> answer.question_id == question_id end) with {:get, answer} when not is_nil(answer) <- {:get, answer}, {:status, true} <- {:status, answer.submission.student_id == grader_id or answer.submission.status == :submitted} do - GradingJob.grade_answer(answer, answer.question, true) + GradingJob.grade_answer(answer.question, answers, answer, questions , true) {:ok, nil} else {:get, nil} -> diff --git a/lib/cadet/jobs/autograder/grading_job.ex b/lib/cadet/jobs/autograder/grading_job.ex index 89afb69ba..1a2d123c4 100644 --- a/lib/cadet/jobs/autograder/grading_job.ex +++ b/lib/cadet/jobs/autograder/grading_job.ex @@ -121,10 +121,11 @@ defmodule Cadet.Autograder.GradingJob do end end - def grade_answer(answer = %Answer{}, question = %Question{type: type}, overwrite \\ false) do + def grade_answer(question = %Question{type: type}, answers = [%Answer{}], answer = %Answer{}, + questions = [%Question{}], overwrite \\ false) do case type do :programming -> - Utilities.dispatch_programming_answer(answer, question, overwrite) + Utilities.dispatch_programming_answer(question, answers, answer, questions, overwrite) :mcq -> grade_mcq_answer(answer, question) @@ -208,7 +209,7 @@ defmodule Cadet.Autograder.GradingJob do defp grade_submission_question_answer_lists( submission_id, - [question = %Question{} | question_tail], + questions = [question = %Question{} | question_tail], answers = [answer = %Answer{} | answer_tail], regrade, overwrite @@ -216,7 +217,13 @@ defmodule Cadet.Autograder.GradingJob do when is_boolean(regrade) and is_boolean(overwrite) and is_ecto_id(submission_id) do if question.id == answer.question_id do if regrade || answer.autograding_status in [:none, :failed] do - grade_answer(answer, question, overwrite) + IO.inspect(question, label: "question") + IO.inspect(answers, label: "answers") + IO.inspect(answer, label: "answer") + IO.inspect(questions, label: "questions") + IO.inspect(overwrite, label: "overwrite") + + grade_answer(question, answers, answer, questions, overwrite) end grade_submission_question_answer_lists( diff --git a/lib/cadet/jobs/autograder/lambda_worker.ex b/lib/cadet/jobs/autograder/lambda_worker.ex index a2a6d3f7a..762bc740e 100644 --- a/lib/cadet/jobs/autograder/lambda_worker.ex +++ b/lib/cadet/jobs/autograder/lambda_worker.ex @@ -12,12 +12,25 @@ defmodule Cadet.Autograder.LambdaWorker do alias Cadet.Autograder.ResultStoreWorker alias Cadet.Assessments.{Answer, Question} + @type entrypoint_file :: String.t() + @doc """ This Que callback transforms an input of %{question: %Question{}, answer: %Answer{}} into the correct shape to dispatch to lambda, waits for the response, parses it, and enqueues a storage job. """ - def perform(params = %{answer: answer = %Answer{}, question: %Question{}}) do + def perform(%{ + base_question: base_question = %Question{}, + questions: questions = [%Question{}], + answers: answers = [%Answer{}], + answer: answer = %Answer{} + }) do + + params = %{ + base_question: base_question, + questions: questions, + answers: answers + } lambda_params = build_request_params(params) if Enum.empty?(lambda_params.testcases) do @@ -74,28 +87,42 @@ defmodule Cadet.Autograder.LambdaWorker do ) end - def build_request_params(%{question: question = %Question{}, answer: answer = %Answer{}}) do - question_content = question.question - + # base_question is the actual question that this request grades on + def build_request_params(%{base_question: base_question = %Question{}, + questions: questions = [%Question{}], answers: answers = [%Answer{}]}) do {_, upcased_name_external} = - question.grading_library.external + base_question.grading_library.external |> Map.from_struct() |> Map.get_and_update( :name, &{&1, &1 |> String.upcase()} ) + + filesContent = Enum.zip(questions, answers) + |> Enum.reduce(%{}, fn {question, answer}, acc -> + question_content = question.question + file_name = Integer.to_string(question.display_order) <> ".js" + final_answer_content = + Map.get(question_content, "prepend", "") <> + Map.get(answer.answer, "code", "") <> + Map.get(question_content, "postpend", "") + + Map.put(acc, file_name, final_answer_content) + end) + + base_question_content = base_question.question + # test %{ - prependProgram: Map.get(question_content, "prepend", ""), - studentProgram: Map.get(answer.answer, "code"), - postpendProgram: Map.get(question_content, "postpend", ""), + files: filesContent, + entrypointFile: base_question.display_order <> ".js", # entrypointFile is the base question's question number testcases: - Map.get(question_content, "public", []) ++ - Map.get(question_content, "opaque", []) ++ Map.get(question_content, "secret", []), + Map.get(base_question_content, "public", []) ++ + Map.get(base_question_content, "opaque", []) ++ Map.get(base_question_content, "secret", []), library: %{ - chapter: question.grading_library.chapter, + chapter: base_question.grading_library.chapter, external: upcased_name_external, - globals: Enum.map(question.grading_library.globals, fn {k, v} -> [k, v] end) + globals: Enum.map(base_question.grading_library.globals, fn {k, v} -> [k, v] end) } } end diff --git a/lib/cadet/jobs/autograder/utilities.ex b/lib/cadet/jobs/autograder/utilities.ex index 32208e0cf..369ada562 100644 --- a/lib/cadet/jobs/autograder/utilities.ex +++ b/lib/cadet/jobs/autograder/utilities.ex @@ -11,7 +11,12 @@ defmodule Cadet.Autograder.Utilities do alias Cadet.Accounts.CourseRegistration alias Cadet.Assessments.{Answer, Assessment, Question, Submission} - def dispatch_programming_answer(answer = %Answer{}, question = %Question{}, overwrite \\ false) do + def dispatch_programming_answer( + base_question = %Question{}, + answers = [%Answer{}], + answer = %Answer{}, + questions = [%Question{}], + overwrite \\ false) do # This should never fail answer = answer @@ -19,8 +24,10 @@ defmodule Cadet.Autograder.Utilities do |> Repo.update!() Que.add(Cadet.Autograder.LambdaWorker, %{ - question: question, + base_question: base_question, answer: answer, + questions: questions, + answers: answers, overwrite: overwrite }) end From 312ff7e4edfe7249e4df7ee8327cb49e767a986c Mon Sep 17 00:00:00 2001 From: tkaixiang Date: Fri, 29 Mar 2024 18:39:42 +0800 Subject: [PATCH 2/4] Fix backend issues, now working :D --- lib/cadet/jobs/autograder/grading_job.ex | 10 ++-------- lib/cadet/jobs/autograder/lambda_worker.ex | 21 +++++++++++++-------- lib/cadet/jobs/autograder/utilities.ex | 4 ++-- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/cadet/jobs/autograder/grading_job.ex b/lib/cadet/jobs/autograder/grading_job.ex index 1a2d123c4..78942039a 100644 --- a/lib/cadet/jobs/autograder/grading_job.ex +++ b/lib/cadet/jobs/autograder/grading_job.ex @@ -121,8 +121,8 @@ defmodule Cadet.Autograder.GradingJob do end end - def grade_answer(question = %Question{type: type}, answers = [%Answer{}], answer = %Answer{}, - questions = [%Question{}], overwrite \\ false) do + def grade_answer(question = %Question{type: type}, answers, answer = %Answer{}, + questions, overwrite \\ false) do case type do :programming -> Utilities.dispatch_programming_answer(question, answers, answer, questions, overwrite) @@ -217,12 +217,6 @@ defmodule Cadet.Autograder.GradingJob do when is_boolean(regrade) and is_boolean(overwrite) and is_ecto_id(submission_id) do if question.id == answer.question_id do if regrade || answer.autograding_status in [:none, :failed] do - IO.inspect(question, label: "question") - IO.inspect(answers, label: "answers") - IO.inspect(answer, label: "answer") - IO.inspect(questions, label: "questions") - IO.inspect(overwrite, label: "overwrite") - grade_answer(question, answers, answer, questions, overwrite) end diff --git a/lib/cadet/jobs/autograder/lambda_worker.ex b/lib/cadet/jobs/autograder/lambda_worker.ex index 762bc740e..7c1309585 100644 --- a/lib/cadet/jobs/autograder/lambda_worker.ex +++ b/lib/cadet/jobs/autograder/lambda_worker.ex @@ -21,8 +21,8 @@ defmodule Cadet.Autograder.LambdaWorker do """ def perform(%{ base_question: base_question = %Question{}, - questions: questions = [%Question{}], - answers: answers = [%Answer{}], + questions: questions, + answers: answers, answer: answer = %Answer{} }) do @@ -55,7 +55,12 @@ defmodule Cadet.Autograder.LambdaWorker do end end - def on_failure(%{answer: answer = %Answer{}, question: %Question{}}, error) do + def on_failure(%{ + base_question: base_question = %Question{}, + questions: questions, + answers: answers, + answer: answer = %Answer{} + }, error) do error_message = "Failed to get autograder result. answer_id: #{answer.id}, error: #{inspect(error, pretty: true)}" @@ -89,7 +94,7 @@ defmodule Cadet.Autograder.LambdaWorker do # base_question is the actual question that this request grades on def build_request_params(%{base_question: base_question = %Question{}, - questions: questions = [%Question{}], answers: answers = [%Answer{}]}) do + questions: questions, answers: answers}) do {_, upcased_name_external} = base_question.grading_library.external |> Map.from_struct() @@ -104,9 +109,9 @@ defmodule Cadet.Autograder.LambdaWorker do question_content = question.question file_name = Integer.to_string(question.display_order) <> ".js" final_answer_content = - Map.get(question_content, "prepend", "") <> - Map.get(answer.answer, "code", "") <> - Map.get(question_content, "postpend", "") + (Map.get(question_content, "prepend") || "") <> + (Map.get(answer.answer, "code") || "") <> + (Map.get(question_content, "postpend") || "") Map.put(acc, file_name, final_answer_content) end) @@ -115,7 +120,7 @@ defmodule Cadet.Autograder.LambdaWorker do # test %{ files: filesContent, - entrypointFile: base_question.display_order <> ".js", # entrypointFile is the base question's question number + entrypointFile: Integer.to_string(base_question_content.display_order) <> ".js", # entrypointFile is the base question's question number testcases: Map.get(base_question_content, "public", []) ++ Map.get(base_question_content, "opaque", []) ++ Map.get(base_question_content, "secret", []), diff --git a/lib/cadet/jobs/autograder/utilities.ex b/lib/cadet/jobs/autograder/utilities.ex index 369ada562..7fc5cbbce 100644 --- a/lib/cadet/jobs/autograder/utilities.ex +++ b/lib/cadet/jobs/autograder/utilities.ex @@ -13,9 +13,9 @@ defmodule Cadet.Autograder.Utilities do def dispatch_programming_answer( base_question = %Question{}, - answers = [%Answer{}], + answers, answer = %Answer{}, - questions = [%Question{}], + questions, overwrite \\ false) do # This should never fail answer = From 81bc6180ff292112fca418a074c372c2964d8c58 Mon Sep 17 00:00:00 2001 From: tkaixiang Date: Mon, 15 Apr 2024 16:30:56 +0800 Subject: [PATCH 3/4] Merge changes --- lib/cadet/accounts/teams.ex | 92 +++++++++---------- lib/cadet/assessments/assessments.ex | 33 ++++--- lib/cadet/jobs/autograder/grading_job.ex | 13 ++- lib/cadet/jobs/autograder/lambda_worker.ex | 51 +++++----- lib/cadet/jobs/autograder/utilities.ex | 11 ++- lib/cadet_web/admin_views/admin_teams_view.ex | 12 +-- lib/cadet_web/views/team_view.ex | 6 +- 7 files changed, 116 insertions(+), 102 deletions(-) diff --git a/lib/cadet/accounts/teams.ex b/lib/cadet/accounts/teams.ex index 347a36e06..6ef3f2976 100644 --- a/lib/cadet/accounts/teams.ex +++ b/lib/cadet/accounts/teams.ex @@ -14,15 +14,15 @@ defmodule Cadet.Accounts.Teams do @doc """ Creates a new team and assigns an assessment and team members to it. - + ## Parameters - + * `attrs` - A map containing the attributes for assessment id and creating the team and its members. - + ## Returns - + Returns a tuple `{:ok, team}` on success; otherwise, an error tuple. - + """ def create_team(attrs) do assessment_id = attrs["assessment_id"] @@ -69,16 +69,16 @@ defmodule Cadet.Accounts.Teams do @doc """ Validates whether there are student(s) who are already assigned to another group. - + ## Parameters - + * `team_attrs` - A list of all the teams and their members. * `assessment_id` - Id of the target assessment. - + ## Returns - + Returns `true` on success; otherwise, `false`. - + """ defp student_already_assigned?(team_attrs, assessment_id) do Enum.all?(team_attrs, fn team -> @@ -93,15 +93,15 @@ defmodule Cadet.Accounts.Teams do @doc """ Checks there is no duplicated student during team creation. - + ## Parameters - + * `team_attrs` - IDs of the team members being created - + ## Returns - + Returns `true` if all students in the list are distinct; otherwise, returns `false`. - + """ defp all_students_distinct?(team_attrs) do all_ids = @@ -118,16 +118,16 @@ defmodule Cadet.Accounts.Teams do @doc """ Checks if all the teams satisfy the max team size constraint. - + ## Parameters - + * `teams` - IDs of the team members being created * `max_team_size` - max team size of the team - + ## Returns - + Returns `true` if all the teams have size less or equal to the max team size; otherwise, returns `false`. - + """ defp all_team_within_max_size?(teams, max_team_size) do Enum.all?(teams, fn team -> @@ -138,16 +138,16 @@ defmodule Cadet.Accounts.Teams do @doc """ Checks if one or more students are enrolled in the course. - + ## Parameters - + * `teams` - ID of the team being created * `course_id` - ID of the course - + ## Returns - + Returns `true` if all students in the list enroll in the course; otherwise, returns `false`. - + """ defp all_student_enrolled_in_course?(teams, course_id) do all_ids = @@ -168,17 +168,17 @@ defmodule Cadet.Accounts.Teams do @doc """ Checks if one or more students are already in another team for the same assessment. - + ## Parameters - + * `team_id` - ID of the team being updated (use -1 for team creation) * `student_ids` - List of student IDs * `assessment_id` - ID of the assessment - + ## Returns - + Returns `true` if any student in the list is already a member of another team for the same assessment; otherwise, returns `false`. - + """ defp student_already_in_team?(team_id, student_ids, assessment_id) do query = @@ -196,17 +196,17 @@ defmodule Cadet.Accounts.Teams do @doc """ Updates an existing team, the corresponding assessment, and its members. - + ## Parameters - + * `team` - The existing team to be updated * `new_assessment_id` - The ID of the updated assessment * `student_ids` - List of student ids for team members - + ## Returns - + Returns a tuple `{:ok, updated_team}` on success, containing the updated team details; otherwise, an error tuple. - + """ def update_team(team = %Team{}, new_assessment_id, student_ids) do old_assessment_id = team.assessment_id @@ -235,13 +235,13 @@ defmodule Cadet.Accounts.Teams do @doc """ Updates team members based on the new list of student IDs. - + ## Parameters - + * `team` - The team being updated * `student_ids` - List of student ids for team members * `team_id` - ID of the team - + """ defp update_team_members(team, student_ids, team_id) do current_student_ids = team.team_members |> Enum.map(& &1.student_id) @@ -269,11 +269,11 @@ defmodule Cadet.Accounts.Teams do @doc """ Deletes a team along with its associated submissions and answers. - + ## Parameters - + * `team` - The team to be deleted - + """ def delete_team(team = %Team{}) do if has_submitted_answer?(team.id) do @@ -306,15 +306,15 @@ defmodule Cadet.Accounts.Teams do @doc """ Check whether a team has subnitted submissions and answers. - + ## Parameters - + * `team_id` - The team id of the team to be checked - + ## Returns - + Returns `true` if any one of the submission has the status of "submitted", `false` otherwise - + """ defp has_submitted_answer?(team_id) do submission = diff --git a/lib/cadet/assessments/assessments.ex b/lib/cadet/assessments/assessments.ex index 1d0afaf93..4f1b5dc86 100644 --- a/lib/cadet/assessments/assessments.ex +++ b/lib/cadet/assessments/assessments.ex @@ -905,10 +905,10 @@ defmodule Cadet.Assessments do Public internal api to submit new answers for a question. Possible return values are: `{:ok, nil}` -> success `{:error, error}` -> failed. `error` is in the format of `{http_response_code, error message}` - + Note: In the event of `find_or_create_submission` failing due to a race condition, error will be: `{:bad_request, "Missing or invalid parameter(s)"}` - + """ def answer_question( question = %Question{}, @@ -1190,10 +1190,10 @@ defmodule Cadet.Assessments do @doc """ Unpublishes grading for a submission and send notification to student. Requires admin or staff who is group leader of student. - + Only manually graded assessments can be individually unpublished. We can only unpublish all submissions for auto-graded assessments. - + Returns `{:ok, nil}` on success, otherwise `{:error, {status, message}}`. """ def unpublish_grading(submission_id, cr = %CourseRegistration{}) @@ -1230,10 +1230,10 @@ defmodule Cadet.Assessments do @doc """ Publishes grading for a submission and send notification to student. Requires admin or staff who is group leader of student and all answers to be graded. - + Only manually graded assessments can be individually published. We can only publish all submissions for auto-graded assessments. - + Returns `{:ok, nil}` on success, otherwise `{:error, {status, message}}`. """ def publish_grading(submission_id, cr = %CourseRegistration{}) @@ -1277,7 +1277,7 @@ defmodule Cadet.Assessments do @doc """ Publishes grading for a submission and send notification to student. This function is used by the auto-grading system to publish grading. Bypasses Course Reg checks. - + Returns `{:ok, nil}` on success, otherwise `{:error, {status, message}}`. """ def publish_grading(submission_id) @@ -1318,7 +1318,7 @@ defmodule Cadet.Assessments do @doc """ Publishes grading for all graded submissions for an assessment and sends notifications to students. Requires admin. - + Returns `{:ok, nil}` on success, otherwise `{:error, {status, message}}`. """ def publish_all_graded(publisher = %CourseRegistration{}, assessment_id) do @@ -1379,7 +1379,7 @@ defmodule Cadet.Assessments do @doc """ Unpublishes grading for all submissions with grades published for an assessment and sends notifications to students. Requires admin role. - + Returns `{:ok, nil}` on success, otherwise `{:error, {status, message}}`. """ @@ -1577,7 +1577,7 @@ defmodule Cadet.Assessments do @doc """ Fetches top answers for the given question, based on the contest relative_score - + Used for contest leaderboard fetching """ def fetch_top_relative_score_answers(question_id, number_of_answers) do @@ -1608,7 +1608,7 @@ defmodule Cadet.Assessments do @doc """ Fetches top answers for the given question, based on the contest popular_score - + Used for contest leaderboard fetching """ def fetch_top_popular_score_answers(question_id, number_of_answers) do @@ -1818,12 +1818,12 @@ defmodule Cadet.Assessments do @doc """ Function returning submissions under a grader. This function returns only the fields that are exposed in the /grading endpoint. - + The input parameters are the user and query parameters. Query parameters are used to filter the submissions. - + The return value is `{:ok, %{"count": count, "data": submissions}}` - + # Parameters - `pageSize`: Integer. The number of submissions to return. Default is 10. - `offset`: Integer. The number of submissions to skip. Default is 0. @@ -1837,7 +1837,7 @@ defmodule Cadet.Assessments do - `username`: String. User username. - `type`: String. Assessment Config type. - `isManuallyGraded`: Boolean. Whether the assessment is manually graded. - + # Implementation Uses helper functions to build the filter query. Helper functions are separated by tables in the database. """ @@ -2297,7 +2297,6 @@ defmodule Cadet.Assessments do _requesting_user = %CourseRegistration{id: grader_id} ) when is_ecto_id(submission_id) and is_ecto_id(question_id) do - questions = Question |> where(question_id: ^question_id) @@ -2320,7 +2319,7 @@ defmodule Cadet.Assessments do {:status, true} <- {:status, answer.submission.student_id == grader_id or answer.submission.status == :submitted} do - GradingJob.grade_answer(answer.question, answers, answer, questions , true) + GradingJob.grade_answer(answer.question, answers, answer, questions, true) {:ok, nil} else {:get, nil} -> diff --git a/lib/cadet/jobs/autograder/grading_job.ex b/lib/cadet/jobs/autograder/grading_job.ex index ba8c74eec..8f06d674a 100644 --- a/lib/cadet/jobs/autograder/grading_job.ex +++ b/lib/cadet/jobs/autograder/grading_job.ex @@ -46,10 +46,10 @@ defmodule Cadet.Autograder.GradingJob do Exposed as public function in case future mix tasks are needed to regrade certain submissions. Manual grading can also be triggered from iex with this function. - + Takes in submission to be graded. Submission will be graded regardless of its assessment's close_by date or submission status. - + Every answer will be regraded regardless of its current autograding status. """ def force_grade_individual_submission(submission = %Submission{}, overwrite \\ false) do @@ -123,8 +123,13 @@ defmodule Cadet.Autograder.GradingJob do end end - def grade_answer(question = %Question{type: type}, answers, answer = %Answer{}, - questions, overwrite \\ false) do + def grade_answer( + question = %Question{type: type}, + answers, + answer = %Answer{}, + questions, + overwrite \\ false + ) do case type do :programming -> Utilities.dispatch_programming_answer(question, answers, answer, questions, overwrite) diff --git a/lib/cadet/jobs/autograder/lambda_worker.ex b/lib/cadet/jobs/autograder/lambda_worker.ex index 7c1309585..2a9e326e7 100644 --- a/lib/cadet/jobs/autograder/lambda_worker.ex +++ b/lib/cadet/jobs/autograder/lambda_worker.ex @@ -20,17 +20,17 @@ defmodule Cadet.Autograder.LambdaWorker do storage job. """ def perform(%{ - base_question: base_question = %Question{}, - questions: questions, - answers: answers, - answer: answer = %Answer{} - }) do - + base_question: base_question = %Question{}, + questions: questions, + answers: answers, + answer: answer = %Answer{} + }) do params = %{ base_question: base_question, questions: questions, answers: answers } + lambda_params = build_request_params(params) if Enum.empty?(lambda_params.testcases) do @@ -55,12 +55,15 @@ defmodule Cadet.Autograder.LambdaWorker do end end - def on_failure(%{ - base_question: base_question = %Question{}, - questions: questions, - answers: answers, - answer: answer = %Answer{} - }, error) do + def on_failure( + %{ + base_question: base_question = %Question{}, + questions: questions, + answers: answers, + answer: answer = %Answer{} + }, + error + ) do error_message = "Failed to get autograder result. answer_id: #{answer.id}, error: #{inspect(error, pretty: true)}" @@ -93,8 +96,11 @@ defmodule Cadet.Autograder.LambdaWorker do end # base_question is the actual question that this request grades on - def build_request_params(%{base_question: base_question = %Question{}, - questions: questions, answers: answers}) do + def build_request_params(%{ + base_question: base_question = %Question{}, + questions: questions, + answers: answers + }) do {_, upcased_name_external} = base_question.grading_library.external |> Map.from_struct() @@ -103,27 +109,30 @@ defmodule Cadet.Autograder.LambdaWorker do &{&1, &1 |> String.upcase()} ) - - filesContent = Enum.zip(questions, answers) + filesContent = + Enum.zip(questions, answers) |> Enum.reduce(%{}, fn {question, answer}, acc -> question_content = question.question file_name = Integer.to_string(question.display_order) <> ".js" + final_answer_content = - (Map.get(question_content, "prepend") || "") <> + (Map.get(question_content, "prepend") || "") <> (Map.get(answer.answer, "code") || "") <> (Map.get(question_content, "postpend") || "") Map.put(acc, file_name, final_answer_content) end) - base_question_content = base_question.question - # test + base_question_content = base_question.question + # test %{ files: filesContent, - entrypointFile: Integer.to_string(base_question_content.display_order) <> ".js", # entrypointFile is the base question's question number + # entrypointFile is the base question's question number + entrypointFile: Integer.to_string(base_question_content.display_order) <> ".js", testcases: Map.get(base_question_content, "public", []) ++ - Map.get(base_question_content, "opaque", []) ++ Map.get(base_question_content, "secret", []), + Map.get(base_question_content, "opaque", []) ++ + Map.get(base_question_content, "secret", []), library: %{ chapter: base_question.grading_library.chapter, external: upcased_name_external, diff --git a/lib/cadet/jobs/autograder/utilities.ex b/lib/cadet/jobs/autograder/utilities.ex index 7fc5cbbce..a7367f694 100644 --- a/lib/cadet/jobs/autograder/utilities.ex +++ b/lib/cadet/jobs/autograder/utilities.ex @@ -12,11 +12,12 @@ defmodule Cadet.Autograder.Utilities do alias Cadet.Assessments.{Answer, Assessment, Question, Submission} def dispatch_programming_answer( - base_question = %Question{}, - answers, - answer = %Answer{}, - questions, - overwrite \\ false) do + base_question = %Question{}, + answers, + answer = %Answer{}, + questions, + overwrite \\ false + ) do # This should never fail answer = answer diff --git a/lib/cadet_web/admin_views/admin_teams_view.ex b/lib/cadet_web/admin_views/admin_teams_view.ex index 1f6891093..87b82d67c 100644 --- a/lib/cadet_web/admin_views/admin_teams_view.ex +++ b/lib/cadet_web/admin_views/admin_teams_view.ex @@ -7,11 +7,11 @@ defmodule CadetWeb.AdminTeamsView do @doc """ Renders a list of team formation overviews in JSON format. - + ## Parameters - + * `teamFormationOverviews` - A list of team formation overviews to be rendered. - + """ def render("index.json", %{team_formation_overviews: team_formation_overviews}) do render_many(team_formation_overviews, CadetWeb.AdminTeamsView, "team_formation_overview.json", @@ -21,11 +21,11 @@ defmodule CadetWeb.AdminTeamsView do @doc """ Renders a single team formation overview in JSON format. - + ## Parameters - + * `team_formation_overview` - The team formation overview to be rendered. - + """ def render("team_formation_overview.json", %{team_formation_overview: team_formation_overview}) do %{ diff --git a/lib/cadet_web/views/team_view.ex b/lib/cadet_web/views/team_view.ex index c93f684c1..f3d1f3285 100644 --- a/lib/cadet_web/views/team_view.ex +++ b/lib/cadet_web/views/team_view.ex @@ -7,11 +7,11 @@ defmodule CadetWeb.TeamView do @doc """ Renders the JSON representation of team formation overview. - + ## Parameters - + * `teamFormationOverview` - A map containing team formation overview data. - + """ def render("index.json", %{teamFormationOverview: teamFormationOverview}) do %{ From 28324f0a6f455b49c33089e6d18c0fdb2ac1b17c Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:38:10 +0800 Subject: [PATCH 4/4] Fix format --- lib/cadet/accounts/teams.ex | 92 +++++++++---------- lib/cadet/assessments/assessments.ex | 30 +++--- lib/cadet/jobs/autograder/grading_job.ex | 4 +- lib/cadet_web/admin_views/admin_teams_view.ex | 12 +-- lib/cadet_web/views/team_view.ex | 6 +- 5 files changed, 72 insertions(+), 72 deletions(-) diff --git a/lib/cadet/accounts/teams.ex b/lib/cadet/accounts/teams.ex index 6ef3f2976..347a36e06 100644 --- a/lib/cadet/accounts/teams.ex +++ b/lib/cadet/accounts/teams.ex @@ -14,15 +14,15 @@ defmodule Cadet.Accounts.Teams do @doc """ Creates a new team and assigns an assessment and team members to it. - + ## Parameters - + * `attrs` - A map containing the attributes for assessment id and creating the team and its members. - + ## Returns - + Returns a tuple `{:ok, team}` on success; otherwise, an error tuple. - + """ def create_team(attrs) do assessment_id = attrs["assessment_id"] @@ -69,16 +69,16 @@ defmodule Cadet.Accounts.Teams do @doc """ Validates whether there are student(s) who are already assigned to another group. - + ## Parameters - + * `team_attrs` - A list of all the teams and their members. * `assessment_id` - Id of the target assessment. - + ## Returns - + Returns `true` on success; otherwise, `false`. - + """ defp student_already_assigned?(team_attrs, assessment_id) do Enum.all?(team_attrs, fn team -> @@ -93,15 +93,15 @@ defmodule Cadet.Accounts.Teams do @doc """ Checks there is no duplicated student during team creation. - + ## Parameters - + * `team_attrs` - IDs of the team members being created - + ## Returns - + Returns `true` if all students in the list are distinct; otherwise, returns `false`. - + """ defp all_students_distinct?(team_attrs) do all_ids = @@ -118,16 +118,16 @@ defmodule Cadet.Accounts.Teams do @doc """ Checks if all the teams satisfy the max team size constraint. - + ## Parameters - + * `teams` - IDs of the team members being created * `max_team_size` - max team size of the team - + ## Returns - + Returns `true` if all the teams have size less or equal to the max team size; otherwise, returns `false`. - + """ defp all_team_within_max_size?(teams, max_team_size) do Enum.all?(teams, fn team -> @@ -138,16 +138,16 @@ defmodule Cadet.Accounts.Teams do @doc """ Checks if one or more students are enrolled in the course. - + ## Parameters - + * `teams` - ID of the team being created * `course_id` - ID of the course - + ## Returns - + Returns `true` if all students in the list enroll in the course; otherwise, returns `false`. - + """ defp all_student_enrolled_in_course?(teams, course_id) do all_ids = @@ -168,17 +168,17 @@ defmodule Cadet.Accounts.Teams do @doc """ Checks if one or more students are already in another team for the same assessment. - + ## Parameters - + * `team_id` - ID of the team being updated (use -1 for team creation) * `student_ids` - List of student IDs * `assessment_id` - ID of the assessment - + ## Returns - + Returns `true` if any student in the list is already a member of another team for the same assessment; otherwise, returns `false`. - + """ defp student_already_in_team?(team_id, student_ids, assessment_id) do query = @@ -196,17 +196,17 @@ defmodule Cadet.Accounts.Teams do @doc """ Updates an existing team, the corresponding assessment, and its members. - + ## Parameters - + * `team` - The existing team to be updated * `new_assessment_id` - The ID of the updated assessment * `student_ids` - List of student ids for team members - + ## Returns - + Returns a tuple `{:ok, updated_team}` on success, containing the updated team details; otherwise, an error tuple. - + """ def update_team(team = %Team{}, new_assessment_id, student_ids) do old_assessment_id = team.assessment_id @@ -235,13 +235,13 @@ defmodule Cadet.Accounts.Teams do @doc """ Updates team members based on the new list of student IDs. - + ## Parameters - + * `team` - The team being updated * `student_ids` - List of student ids for team members * `team_id` - ID of the team - + """ defp update_team_members(team, student_ids, team_id) do current_student_ids = team.team_members |> Enum.map(& &1.student_id) @@ -269,11 +269,11 @@ defmodule Cadet.Accounts.Teams do @doc """ Deletes a team along with its associated submissions and answers. - + ## Parameters - + * `team` - The team to be deleted - + """ def delete_team(team = %Team{}) do if has_submitted_answer?(team.id) do @@ -306,15 +306,15 @@ defmodule Cadet.Accounts.Teams do @doc """ Check whether a team has subnitted submissions and answers. - + ## Parameters - + * `team_id` - The team id of the team to be checked - + ## Returns - + Returns `true` if any one of the submission has the status of "submitted", `false` otherwise - + """ defp has_submitted_answer?(team_id) do submission = diff --git a/lib/cadet/assessments/assessments.ex b/lib/cadet/assessments/assessments.ex index 4f1b5dc86..1b338f9a9 100644 --- a/lib/cadet/assessments/assessments.ex +++ b/lib/cadet/assessments/assessments.ex @@ -905,10 +905,10 @@ defmodule Cadet.Assessments do Public internal api to submit new answers for a question. Possible return values are: `{:ok, nil}` -> success `{:error, error}` -> failed. `error` is in the format of `{http_response_code, error message}` - + Note: In the event of `find_or_create_submission` failing due to a race condition, error will be: `{:bad_request, "Missing or invalid parameter(s)"}` - + """ def answer_question( question = %Question{}, @@ -1190,10 +1190,10 @@ defmodule Cadet.Assessments do @doc """ Unpublishes grading for a submission and send notification to student. Requires admin or staff who is group leader of student. - + Only manually graded assessments can be individually unpublished. We can only unpublish all submissions for auto-graded assessments. - + Returns `{:ok, nil}` on success, otherwise `{:error, {status, message}}`. """ def unpublish_grading(submission_id, cr = %CourseRegistration{}) @@ -1230,10 +1230,10 @@ defmodule Cadet.Assessments do @doc """ Publishes grading for a submission and send notification to student. Requires admin or staff who is group leader of student and all answers to be graded. - + Only manually graded assessments can be individually published. We can only publish all submissions for auto-graded assessments. - + Returns `{:ok, nil}` on success, otherwise `{:error, {status, message}}`. """ def publish_grading(submission_id, cr = %CourseRegistration{}) @@ -1277,7 +1277,7 @@ defmodule Cadet.Assessments do @doc """ Publishes grading for a submission and send notification to student. This function is used by the auto-grading system to publish grading. Bypasses Course Reg checks. - + Returns `{:ok, nil}` on success, otherwise `{:error, {status, message}}`. """ def publish_grading(submission_id) @@ -1318,7 +1318,7 @@ defmodule Cadet.Assessments do @doc """ Publishes grading for all graded submissions for an assessment and sends notifications to students. Requires admin. - + Returns `{:ok, nil}` on success, otherwise `{:error, {status, message}}`. """ def publish_all_graded(publisher = %CourseRegistration{}, assessment_id) do @@ -1379,7 +1379,7 @@ defmodule Cadet.Assessments do @doc """ Unpublishes grading for all submissions with grades published for an assessment and sends notifications to students. Requires admin role. - + Returns `{:ok, nil}` on success, otherwise `{:error, {status, message}}`. """ @@ -1577,7 +1577,7 @@ defmodule Cadet.Assessments do @doc """ Fetches top answers for the given question, based on the contest relative_score - + Used for contest leaderboard fetching """ def fetch_top_relative_score_answers(question_id, number_of_answers) do @@ -1608,7 +1608,7 @@ defmodule Cadet.Assessments do @doc """ Fetches top answers for the given question, based on the contest popular_score - + Used for contest leaderboard fetching """ def fetch_top_popular_score_answers(question_id, number_of_answers) do @@ -1818,12 +1818,12 @@ defmodule Cadet.Assessments do @doc """ Function returning submissions under a grader. This function returns only the fields that are exposed in the /grading endpoint. - + The input parameters are the user and query parameters. Query parameters are used to filter the submissions. - + The return value is `{:ok, %{"count": count, "data": submissions}}` - + # Parameters - `pageSize`: Integer. The number of submissions to return. Default is 10. - `offset`: Integer. The number of submissions to skip. Default is 0. @@ -1837,7 +1837,7 @@ defmodule Cadet.Assessments do - `username`: String. User username. - `type`: String. Assessment Config type. - `isManuallyGraded`: Boolean. Whether the assessment is manually graded. - + # Implementation Uses helper functions to build the filter query. Helper functions are separated by tables in the database. """ diff --git a/lib/cadet/jobs/autograder/grading_job.ex b/lib/cadet/jobs/autograder/grading_job.ex index 8f06d674a..1ff832641 100644 --- a/lib/cadet/jobs/autograder/grading_job.ex +++ b/lib/cadet/jobs/autograder/grading_job.ex @@ -46,10 +46,10 @@ defmodule Cadet.Autograder.GradingJob do Exposed as public function in case future mix tasks are needed to regrade certain submissions. Manual grading can also be triggered from iex with this function. - + Takes in submission to be graded. Submission will be graded regardless of its assessment's close_by date or submission status. - + Every answer will be regraded regardless of its current autograding status. """ def force_grade_individual_submission(submission = %Submission{}, overwrite \\ false) do diff --git a/lib/cadet_web/admin_views/admin_teams_view.ex b/lib/cadet_web/admin_views/admin_teams_view.ex index 87b82d67c..1f6891093 100644 --- a/lib/cadet_web/admin_views/admin_teams_view.ex +++ b/lib/cadet_web/admin_views/admin_teams_view.ex @@ -7,11 +7,11 @@ defmodule CadetWeb.AdminTeamsView do @doc """ Renders a list of team formation overviews in JSON format. - + ## Parameters - + * `teamFormationOverviews` - A list of team formation overviews to be rendered. - + """ def render("index.json", %{team_formation_overviews: team_formation_overviews}) do render_many(team_formation_overviews, CadetWeb.AdminTeamsView, "team_formation_overview.json", @@ -21,11 +21,11 @@ defmodule CadetWeb.AdminTeamsView do @doc """ Renders a single team formation overview in JSON format. - + ## Parameters - + * `team_formation_overview` - The team formation overview to be rendered. - + """ def render("team_formation_overview.json", %{team_formation_overview: team_formation_overview}) do %{ diff --git a/lib/cadet_web/views/team_view.ex b/lib/cadet_web/views/team_view.ex index f3d1f3285..c93f684c1 100644 --- a/lib/cadet_web/views/team_view.ex +++ b/lib/cadet_web/views/team_view.ex @@ -7,11 +7,11 @@ defmodule CadetWeb.TeamView do @doc """ Renders the JSON representation of team formation overview. - + ## Parameters - + * `teamFormationOverview` - A map containing team formation overview data. - + """ def render("index.json", %{teamFormationOverview: teamFormationOverview}) do %{