Skip to content

feat(LiveFeedbackStatistics): add metric selector to Live Feedback statistics #7961

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
184 changes: 164 additions & 20 deletions app/controllers/course/statistics/assessments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,24 +108,175 @@
end

def create_student_live_feedback_hash
count_hash = Course::Assessment::LiveFeedback::Message.joins(:thread).
select('live_feedback_threads.submission_creator_id',
'live_feedback_threads.submission_question_id').
where.not(creator_id: User::SYSTEM_USER_ID).
where(live_feedback_threads: {
submission_question_id: @submission_question_id_hash.values,
submission_creator_id: @all_students.pluck(:user_id)
}).
group('live_feedback_threads.submission_creator_id',
'live_feedback_threads.submission_question_id').count
submission_hash = @submissions.to_h { |submission| [submission.creator_id, submission] }
message_grade_hash = fetch_message_grade_hash
prompt_hash = calculate_prompt_hash(message_grade_hash)
submission_hash = @submissions.index_by(&:creator_id)

Check warning on line 113 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L111-L113

Added lines #L111 - L113 were not covered by tests

final_grade_hash = Course::Assessment::Answer.where(

Check warning on line 115 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L115

Added line #L115 was not covered by tests
submission_id: @submissions.pluck(:id),
current_answer: true
).to_h { |answer| [[answer.submission_id, answer.question_id], answer&.grade&.to_f || 0] }

Check warning on line 118 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L118

Added line #L118 was not covered by tests

@student_live_feedback_hash = @all_students.to_h do |student|
submission = submission_hash[student.user_id]
live_feedback_count = get_live_feedback_count(count_hash, submission)
[student, [submission, live_feedback_count]]
live_feedback_data = build_live_feedback_data(submission, final_grade_hash, message_grade_hash, prompt_hash)

Check warning on line 122 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L122

Added line #L122 was not covered by tests

[student, [submission, live_feedback_data]]

Check warning on line 124 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L124

Added line #L124 was not covered by tests
end
end

# Fetches all user Get Help messages grouped by [submission_creator_id, submission_question_id],
# along with `grade_before` and `grade_after` relative to the message timestamps.
# The returned structure looks like:
# {
# [4, 70] => {
# messages: [
# { created_at: "2025-05-30T05:18:48.623076", content: "Explain the question" }
# ],
# grade_before: 0.0,
# grade_after: 75.0
# },
# [4, 72] => {
# messages: [
# { created_at: "2025-05-30T05:19:38.71754", content: "Where am I wrong?" },
# { created_at: "2025-05-30T05:19:47.08988", content: "How do I fix this?" },
# { created_at: "2025-05-30T05:25:04.50411", content: "I am stuck" }
# ],
# grade_before: 10.0,
# grade_after: 10.0
# },
# ...
# }
def fetch_message_grade_hash
student_ids = @all_students.pluck(:user_id)
submission_question_ids = @submission_question_id_hash.values

Check warning on line 152 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L151-L152

Added lines #L151 - L152 were not covered by tests

result = ActiveRecord::Base.connection.execute(

Check warning on line 154 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L154

Added line #L154 was not covered by tests
build_message_grade_sql(student_ids, submission_question_ids)
)

result.to_h do |row|
key = [row['submission_creator_id'], row['submission_question_id']]

Check warning on line 159 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L158-L159

Added lines #L158 - L159 were not covered by tests
[
key,

Check warning on line 161 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L161

Added line #L161 was not covered by tests
messages: JSON.parse(row['messages_json']),
grade_before: row['grade_before']&.to_f || 0,
grade_after: row['grade_after']&.to_f || 0
]
end
end

def build_message_grade_sql(student_ids, submission_question_ids)
<<-SQL

Check warning on line 170 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L170

Added line #L170 was not covered by tests
WITH feedback_messages AS (
#{feedback_messages_cte(student_ids, submission_question_ids)}
),
grades_before AS (
#{grades_before_cte}
),
grades_after AS (
#{grades_after_cte}
)
SELECT
f.submission_creator_id,
f.submission_question_id,
f.messages_json,
gb.grade_before,
ga.grade_after
FROM feedback_messages f
LEFT JOIN grades_before gb ON f.submission_creator_id = gb.submission_creator_id AND f.submission_question_id = gb.submission_question_id
LEFT JOIN grades_after ga ON f.submission_creator_id = ga.submission_creator_id AND f.submission_question_id = ga.submission_question_id
SQL
end

def feedback_messages_cte(student_ids, submission_question_ids)
<<-SQL

Check warning on line 193 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L193

Added line #L193 was not covered by tests
SELECT
lft.submission_creator_id,
lft.submission_question_id,
json_agg(json_build_object(
'created_at', m.created_at,
'content', m.content
) ORDER BY m.created_at) AS messages_json,
MIN(m.created_at) AS first_message_at,
MAX(m.created_at) AS last_message_at
FROM live_feedback_messages m
JOIN live_feedback_threads lft
ON lft.id = m.thread_id
WHERE m.creator_id != #{User::SYSTEM_USER_ID}
AND lft.submission_creator_id = ANY(ARRAY[#{student_ids.join(',')}])
AND lft.submission_question_id = ANY(ARRAY[#{submission_question_ids.join(',')}])
GROUP BY lft.submission_creator_id, lft.submission_question_id
SQL
end

def grades_before_cte
<<-SQL

Check warning on line 214 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L214

Added line #L214 was not covered by tests
SELECT DISTINCT ON (a.submission_id, a.question_id)
a.grade AS grade_before,
lft.submission_creator_id,
lft.submission_question_id
FROM feedback_messages f
JOIN live_feedback_threads lft ON lft.submission_creator_id = f.submission_creator_id AND lft.submission_question_id = f.submission_question_id
JOIN course_assessment_submission_questions sq ON sq.id = lft.submission_question_id
JOIN course_assessment_answers a ON a.submission_id = sq.submission_id AND a.question_id = sq.question_id
WHERE a.created_at < f.first_message_at
ORDER BY a.submission_id, a.question_id, a.created_at DESC
SQL
end

def grades_after_cte
<<-SQL

Check warning on line 229 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L229

Added line #L229 was not covered by tests
SELECT DISTINCT ON (a.submission_id, a.question_id)
a.grade AS grade_after,
lft.submission_creator_id,
lft.submission_question_id
FROM feedback_messages f
JOIN live_feedback_threads lft ON lft.submission_creator_id = f.submission_creator_id AND lft.submission_question_id = f.submission_question_id
JOIN course_assessment_submission_questions sq ON sq.id = lft.submission_question_id
JOIN course_assessment_answers a ON a.submission_id = sq.submission_id AND a.question_id = sq.question_id
WHERE a.created_at > f.last_message_at
ORDER BY a.submission_id, a.question_id, a.created_at ASC
SQL
end

def build_live_feedback_data(submission, final_grade_hash, message_grade_hash, prompt_hash)
@ordered_questions.map do |question_id|
submission_question_id = @submission_question_id_hash[[submission&.id, question_id]]
key = [submission&.creator_id, submission_question_id]

Check warning on line 246 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L244-L246

Added lines #L244 - L246 were not covered by tests

message_grade_data = message_grade_hash[key] || {}
grade_before = message_grade_data[:grade_before]
grade_after = message_grade_data[:grade_after]

Check warning on line 250 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L248-L250

Added lines #L248 - L250 were not covered by tests

prompt_data = prompt_hash[key] || { messages_sent: 0, word_count: 0 }

Check warning on line 252 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L252

Added line #L252 was not covered by tests

{
grade: final_grade_hash[[submission&.id, question_id]],
grade_diff: (grade_after && grade_before) ? (grade_after - grade_before).round(2) : 0,

Check warning on line 256 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L255-L256

Added lines #L255 - L256 were not covered by tests
word_count: prompt_data[:word_count],
messages_sent: prompt_data[:messages_sent]
}
end
end

def calculate_prompt_hash(message_hash)
message_hash.transform_values do |data|
messages = data[:messages] || []

Check warning on line 265 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L264-L265

Added lines #L264 - L265 were not covered by tests
{
messages_sent: messages.size,
word_count: messages.sum { |m| m['content'].to_s.split(/\s+/).size }

Check warning on line 268 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L267-L268

Added lines #L267 - L268 were not covered by tests
}
end
end

def fetch_messages_for_question(submission_question_id)
Course::Assessment::LiveFeedback::Message.

Check warning on line 274 in app/controllers/course/statistics/assessments_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L274

Added line #L274 was not covered by tests
joins(:thread).
where(live_feedback_threads: { submission_question_id: submission_question_id }).
order(:created_at)
end

def create_question_order_hash
@question_order_hash = @assessment.question_assessments.to_h do |q|
[q.question_id, q.weight]
Expand All @@ -140,11 +291,4 @@
[[sq.submission_id, sq.question_id], sq.id]
end
end

def get_live_feedback_count(count_hash, submission)
@ordered_questions.map do |question_id|
submission_question_id = @submission_question_id_hash[[submission&.id, question_id]]
count_hash[[submission&.creator_id, submission_question_id]] || 0
end
end
end
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true
json.array! @student_live_feedback_hash.each do |course_user, (submission, live_feedback_count)|
json.array! @student_live_feedback_hash.each do |course_user, (submission, live_feedback_data)|
json.partial! 'course_user', course_user: course_user
if submission.nil?
json.workflowState 'unstarted'
Expand All @@ -13,6 +13,6 @@ json.array! @student_live_feedback_hash.each do |course_user, (submission, live_
json.name name
end

json.liveFeedbackCount live_feedback_count
json.liveFeedbackData live_feedback_data
json.questionIds(@question_order_hash.keys.sort_by { |key| @question_order_hash[key] })
end
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const translations = defineMessages({
},
liveFeedback: {
id: 'course.assessment.statistics.liveFeedback',
defaultMessage: 'Live Feedback',
defaultMessage: 'Get Help',
},
gradesPerQuestion: {
id: 'course.assessment.statistics.gradesPerQuestion',
Expand Down
Loading