Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Course::Assessment::LiveFeedback::ThreadConcern

def safe_create_and_save_thread_info
submission_question = Course::Assessment::SubmissionQuestion.where(
submission_id: @submission, question_id: @answer.question
attempt_id: @submission, question_id: @answer.question
).first

submission_question.with_lock do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def update_all_answer_status(submission_answers)
def build_answer_object(question, answer, submitted_answer)
{
id: answer.id,
submission_id: answer.submission_id,
attempt_id: answer.attempt_id,
question_id: question.id,
workflow_state: 'submitted',
correct: submitted_answer['exprTestcaseResults'].all? { |tc| tc['result']['success'] },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def submission_status_hash
def process_all_submissions
create_new_submissions_if_not_existing

@submission_hash = Course::Assessment::Submission.where(assessment: @assessment).to_h do |s|
@submission_hash = @assessment.submissions.to_h do |s|
[s.creator_id, s]
end

Expand All @@ -59,8 +59,12 @@ def process_submission(submission, cm_submission)
end

def create_new_submissions_if_not_existing
existing_submission_user_ids = Course::Assessment::Submission.where(assessment: @assessment).
pluck(:creator_id)
# `creator_id` is not a column on Submission's own (small) table — it lives on
# `course_assessment_attempts` (Step 2c). Unqualified, `pluck` can't tell which joined table's
# `creator_id` to read (Submission's own `acts_as :experience_points_record` default scope also
# joins `course_experience_points_records`, which ALSO has a `creator_id` column), so Postgres
# rejects it as ambiguous — same fix as `submissions_controller.rb#user_ids_without_submission`.
existing_submission_user_ids = @assessment.submissions.pluck('course_assessment_attempts.creator_id')
koditsu_submission_user_ids = @cu_submission_hash.keys.map { |creator, _| creator.id }
user_ids_without_submission = koditsu_submission_user_ids - existing_submission_user_ids

Expand All @@ -77,8 +81,7 @@ def create_new_submissions_if_not_existing

def create_new_submission_for(creator, course_user)
User.with_stamper(creator) do
new_submission = @assessment.submissions.new(creator: creator,
course_user: course_user)
new_submission = @assessment.build_submission(creator: creator, course_user: course_user)
success = @assessment.create_new_submission(new_submission, course_user)

raise ActiveRecord::Rollback unless success
Expand All @@ -96,7 +99,13 @@ def update_submission(cm_submission, state, submitted_at)
end

def process_submission_answers(submission, cm_submission)
answers = Course::Assessment::Answer.includes(:question).where(submission_id: cm_submission.id)
# `cm_submission.id` is now the small Submission table's OWN id, not the Attempt id the
# `attempt_id` FK needs — `course_assessment_submissions.id` is an independent serial column
# (see the Task 1 backfill migration), not guaranteed equal to `attempt_id`. This was silently
# correct pre-split, when `cm_submission` was a `Submission` instance mapped directly onto
# `course_assessment_attempts` (so `.id` and `attempt_id` were the same value); the split
# exposed it. `attempt_id` is a real, undelegated column already on Submission's own table.
answers = Course::Assessment::Answer.includes(:question).where(attempt_id: cm_submission.attempt_id)

build_answer_hash(answers)

Expand Down
8 changes: 4 additions & 4 deletions app/controllers/concerns/course/statistics/counts_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def num_attempted_students_hash

attempted_submissions_count = ActiveRecord::Base.connection.execute("
SELECT cas.assessment_id AS id, COUNT(DISTINCT cas.creator_id) AS count
FROM course_assessment_submissions cas
FROM course_assessment_attempts cas
WHERE
cas.creator_id IN (#{@all_students.map(&:user_id).join(', ')})
AND cas.assessment_id IN (#{@assessments.pluck(:id).join(', ')})
Expand All @@ -26,7 +26,7 @@ def num_submitted_students_hash

submitted_submissions_count = ActiveRecord::Base.connection.execute("
SELECT cas.assessment_id AS id, COUNT(DISTINCT cas.creator_id) AS count
FROM course_assessment_submissions cas
FROM course_assessment_attempts cas
WHERE
cas.creator_id IN (#{@all_students.map(&:user_id).join(', ')})
AND cas.assessment_id IN (#{@assessments.pluck(:id).join(', ')})
Expand All @@ -45,7 +45,7 @@ def num_late_students_hash
@reference_times_hash = reference_times_hash(@assessments.pluck(:id), current_course.id)
all_submissions = ActiveRecord::Base.connection.execute("
SELECT cu.id AS course_user_id, cas.assessment_id, MAX(cas.submitted_at) as submitted_at
FROM course_assessment_submissions cas
FROM course_assessment_attempts cas
JOIN course_users cu
ON cu.user_id = cas.creator_id
WHERE
Expand All @@ -64,7 +64,7 @@ def latest_submission_time_hash

latest_submissions = ActiveRecord::Base.connection.execute("
SELECT cas.assessment_id AS id, MAX(cas.submitted_at) AS latest_submitted_at
FROM course_assessment_submissions cas
FROM course_assessment_attempts cas
WHERE
cas.creator_id IN (#{@all_students.map(&:user_id).join(', ')})
AND cas.assessment_id IN (#{@assessments.pluck(:id).join(', ')})
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/concerns/course/statistics/grades_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ def grade_statistics_hash
SELECT ca.assessment_id AS id, AVG(ca.grade) AS avg, STDDEV(ca.grade) AS stdev
FROM (
SELECT cas.creator_id, cas.assessment_id, SUM(caa.grade) AS grade
FROM course_assessment_submissions cas
JOIN course_assessment_answers caa ON cas.id = caa.submission_id
FROM course_assessment_attempts cas
JOIN course_assessment_answers caa ON cas.id = caa.attempt_id
WHERE
cas.creator_id IN (#{@all_students.map(&:user_id).join(', ')})
AND cas.assessment_id IN (#{@assessments.pluck(:id).join(', ')})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ def answer_statistics_hash
SELECT
caa_inner.id,
caa_inner.question_id,
caa_inner.submission_id,
caa_inner.attempt_id AS submission_id,
caa_inner.correct,
caa_inner.grade,
caa_inner.workflow_state,
ROW_NUMBER() OVER (PARTITION BY caa_inner.question_id, caa_inner.submission_id ORDER BY caa_inner.created_at DESC) AS row_num
ROW_NUMBER() OVER (PARTITION BY caa_inner.question_id, caa_inner.attempt_id ORDER BY caa_inner.created_at DESC) AS row_num
FROM
course_assessment_answers caa_inner
JOIN
course_assessment_submissions cas_inner ON caa_inner.submission_id = cas_inner.id
course_assessment_attempts cas_inner ON caa_inner.attempt_id = cas_inner.id
WHERE
cas_inner.assessment_id = #{assessment_params[:id]}
) AS caa_ranked
Expand All @@ -53,12 +53,12 @@ def answer_statistics_hash
attempt_count AS (
SELECT
caa.question_id,
caa.submission_id,
caa.attempt_id AS submission_id,
COUNT(*) AS attempt_count
FROM course_assessment_answers caa
JOIN course_assessment_submissions cas ON caa.submission_id = cas.id
JOIN course_assessment_attempts cas ON caa.attempt_id = cas.id
WHERE cas.assessment_id = #{assessment_params[:id]} AND caa.workflow_state != 'attempting'
GROUP BY caa.question_id, caa.submission_id
GROUP BY caa.question_id, caa.attempt_id
)
SELECT
CASE WHEN jsonb_array_length(attempt_info.submission_info) = 1 OR attempt_info.submission_info->0->>3 != 'attempting'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def duration_statistics_hash
FROM (
SELECT cas.creator_id, cas.assessment_id,
EXTRACT(EPOCH FROM cas.submitted_at) - EXTRACT(EPOCH FROM cas.created_at) AS duration
FROM course_assessment_submissions cas
FROM course_assessment_attempts cas
WHERE
cas.creator_id IN (#{@all_students.map(&:user_id).join(', ')})
AND cas.assessment_id IN (#{@assessments.pluck(:id).join(', ')})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ def submissions
if @assessment.submissions.loaded?
@assessment.submissions.select { |s| s.creator_id == current_user.id }
else
@assessment.submissions.where(creator_id: current_user.id)
@assessment.submissions.by_user(current_user)
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def create_topic_subscription

# Ensure all group managers get a notification when someone adds a programming annotation
# to the answer.
answer_course_user = @answer.submission.course_user
answer_course_user = @answer.submission.submission.course_user
answer_course_user.my_managers.each do |manager|
@discussion_topic.ensure_subscribed_by(manager.user)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def index
def create # rubocop:disable Metrics/AbcSize
authorize! :access, @assessment

existing_submission = @assessment.submissions.find_by(creator: current_user)
existing_submission = @assessment.submissions.by_user(current_user).first
create_success_response(existing_submission) and return if existing_submission

ActiveRecord::Base.transaction do
Expand Down Expand Up @@ -110,7 +110,7 @@ def generate_live_feedback
system_thread = Course::Assessment::LiveFeedback::Thread.
joins(:submission_question).
where(
submission_question: { submission_id: @submission.id, question_id: @answer.question.id },
submission_question: { attempt_id: @submission.id, question_id: @answer.question.id },
is_active: true
).
first
Expand Down Expand Up @@ -141,7 +141,7 @@ def fetch_live_feedback_chat
submission = answer.submission
question = answer.question

submission_question = Course::Assessment::SubmissionQuestion.where(submission_id: submission.id,
submission_question = Course::Assessment::SubmissionQuestion.where(attempt_id: submission.id,
question_id: question.id).first

@thread = Course::Assessment::LiveFeedback::Thread.where(submission_question_id: submission_question.id).
Expand Down Expand Up @@ -423,7 +423,12 @@ def course_user_ids

def user_ids_without_submission
existing_submissions = @assessment.submissions.by_users(course_user_ids.pluck(:user_id))
user_ids_with_submission = existing_submissions.pluck(:creator_id)
# `creator_id` is not a column on Submission's own (small) table — it lives on
# `course_assessment_attempts` (Step 2c). Unqualified, Rails can't tell `pluck` which joined
# table's `creator_id` to read (Submission's own `acts_as :experience_points_record` default
# scope also joins `course_experience_points_records`, which ALSO has a `creator_id` column),
# so Postgres rejects it as ambiguous. Qualify explicitly.
user_ids_with_submission = existing_submissions.pluck('course_assessment_attempts.creator_id')
course_user_ids.pluck(:user_id) - user_ids_with_submission
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def create_topic_subscription
@discussion_topic.ensure_subscribed_by(@submission_question.submission.creator)

# Ensure all group managers get a notification when someone comments on this submission question
submission_question_course_user = @submission_question.submission.course_user
submission_question_course_user = @submission_question.submission.submission.course_user
submission_question_course_user.my_managers.each do |manager|
@discussion_topic.ensure_subscribed_by(manager.user)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ class Course::Assessment::SubmissionQuestion::SubmissionQuestionsController < Co
load_resource :assessment, class: 'Course::Assessment', through: :course, parent: false

def all_answers
@submission = @assessment.submissions.find(all_answers_params[:submission_id])
# `all_answers_params[:submission_id]` is, by the statistics feature's own established
# convention (see `Course::Statistics::AssessmentsController#submission_statistics`/
# `#live_feedback_statistics`, which serialize `Attempt#id` under the wire key `id`/
# `submissionId`), an Attempt id — not Submission's own (small-table) id. Find by attempt, then
# navigate to the real Submission for `authorize!` (CanCan's `can :read, ...Submission` rules
# match on subject class — see `Answer#can_read_grade?`'s identical fix, Step 2d). Genuine bug
# the split exposed; not listed in the brief's file set, found via the acceptance gate.
@submission = @assessment.attempts.find(all_answers_params[:submission_id]).submission
authorize!(:read, @submission)
@submission_question = @submission.
submission_questions.
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/course/material/materials_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ def material_params
def create_submission
current_course_user = current_course.course_users.find_by(user: current_user)
@assessment = @folder.owner
existing_submission = @assessment.submissions.find_by(creator: current_user)
existing_submission = @assessment.submissions.by_user(current_user).first
unless existing_submission
@submission = @assessment.submissions.new(course_user: current_course_user)
@submission = @assessment.build_submission(course_user: current_course_user)
@submission.session_id = authentication_service.generate_authentication_token
success = @assessment.create_new_submission(@submission, current_user)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def fetch_plagiarism_data_submissions(submission_ids)
ccu.id AS creator_course_user_id,
ccu.name AS creator_course_user_name,
vcu.id AS viewer_course_user_id
FROM course_assessment_submissions cas
FROM course_assessment_attempts cas
INNER JOIN course_assessments ca ON cas.assessment_id = ca.id
INNER JOIN course_lesson_plan_items clpi ON clpi.actable_id = ca.id AND clpi.actable_type = 'Course::Assessment'
INNER JOIN course_assessment_tabs tab ON ca.tab_id = tab.id
Expand Down
13 changes: 9 additions & 4 deletions app/controllers/course/statistics/aggregate_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,19 @@ def fetch_course_get_help_data(start_date, end_date)
get_help_data = Course::Assessment::LiveFeedback::Message.find_by_sql(<<-SQL)
SELECT DISTINCT ON (t.submission_creator_id, s.assessment_id, sq.question_id)
m.id, m.content, m.created_at, t.submission_creator_id,
s.assessment_id, sq.submission_id, sq.question_id,
s.assessment_id, sub.id AS submission_id, sq.question_id,
COUNT(*) OVER (
PARTITION BY t.submission_creator_id, s.assessment_id, sq.question_id
) AS message_count
FROM live_feedback_messages m
INNER JOIN live_feedback_threads t ON m.thread_id = t.id
INNER JOIN course_assessment_submission_questions sq ON t.submission_question_id = sq.id
INNER JOIN course_assessment_submissions s ON sq.submission_id = s.id
INNER JOIN course_assessment_attempts s ON sq.attempt_id = s.id
-- `sq.attempt_id`/`s.id` is the Attempt's own id, not the (course-coupled) Submission's own
-- (small-table) id the JSON response's `submissionId` is expected to be — post-split, these
-- are two different id spaces (Step 1's extension table has its own serial `id`). Genuine bug
-- the split exposed; not listed in the brief's file set, found via the acceptance gate.
INNER JOIN course_assessment_submissions sub ON sub.attempt_id = s.id
INNER JOIN course_assessments a ON s.assessment_id = a.id
INNER JOIN course_assessment_tabs tab ON a.tab_id = tab.id
INNER JOIN course_assessment_categories cat ON tab.category_id = cat.id
Expand Down Expand Up @@ -145,10 +150,10 @@ def correctness_hash
ON tab.category_id = cat.id
INNER JOIN course_assessments ca
ON ca.tab_id = tab.id
INNER JOIN course_assessment_submissions cas
INNER JOIN course_assessment_attempts cas
ON cas.assessment_id = ca.id
INNER JOIN course_assessment_answers caa
ON caa.submission_id = cas.id
ON caa.attempt_id = cas.id
INNER JOIN course_assessment_questions caq
ON caa.question_id = caq.id
INNER JOIN course_users cu
Expand Down
Loading