Skip to content
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

yupei/add live feedback history #7511

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9ea941d
feat(statisticsTable): pass current answer ID to FE
bivanalhar Jan 30, 2024
702ee4d
feat(statistics): render question details in each answer
bivanalhar Feb 13, 2024
80d4b09
fix(statistics): pass id of last attempt answer to FE
bivanalhar Feb 16, 2024
aff3ff4
refactor(type): expose all question types for reusing
bivanalhar Feb 14, 2024
363b898
feat(statistics): display answer for some question types
bivanalhar Feb 19, 2024
6574eef
refactor(statistics page): cancel -> close in answer details box
bivanalhar Feb 19, 2024
ff9d84d
fix(text-response): change requirements
bivanalhar Apr 7, 2024
be5a54f
test(answer): add test for stats answer controller
bivanalhar Aug 1, 2024
8ffc69f
refactor assessment statistics
bivanalhar Jan 30, 2024
9ff0557
feat(answer_stats): implement forum answer view
bivanalhar Feb 20, 2024
3826bba
feat(answer_stats): implement programming answer view
bivanalhar Feb 20, 2024
f72c004
refactor(statistics): emphasis on type name (display details)
bivanalhar Jan 29, 2024
6c7d998
feat(past_answers): page for all past answers
bivanalhar Feb 21, 2024
5e5281c
fix(attempt_count): count only graded answers
bivanalhar Feb 21, 2024
4c1c7b9
refactor(statistics): minor refactoring
bivanalhar Feb 21, 2024
31bec0a
feat(statistics): add comment and link to past answers
bivanalhar Feb 25, 2024
418ebf7
refactor(test-cases): refurnish test cases indicator chip
bivanalhar Feb 25, 2024
b9b86f0
test(stats_answer): modify answers controller spec
bivanalhar Mar 6, 2024
290bd17
Merge branch 'bivan/teacher-stats-past-answer-3' into yupei/add-live-…
syoopie Aug 22, 2024
a94876e
add optional cursorStart variable to EditorField (Ace Editor)
syoopie Aug 27, 2024
a23faea
add optional maxWidth to Prompt that defaults to sm
syoopie Aug 27, 2024
d7dd766
add question ids to be retrieved on the stats page
syoopie Aug 27, 2024
6837a5b
(feat) Live feedback history display on the stats page
syoopie Aug 27, 2024
f940b47
(test) add live feedback history tests
syoopie Aug 28, 2024
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
43 changes: 24 additions & 19 deletions app/controllers/concerns/course/statistics/submissions_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,14 @@
def answer_statistics_hash
submission_answer_statistics = Course::Assessment::Answer.find_by_sql(<<-SQL.squish
WITH
attempt_count AS (
SELECT
caa.question_id,
caa.submission_id,
COUNT(*) AS attempt_count
FROM course_assessment_answers caa
JOIN course_assessment_submissions cas ON caa.submission_id = cas.id
WHERE cas.assessment_id = #{assessment_params[:id]}
GROUP BY caa.question_id, caa.submission_id
),
attempt_info AS (
SELECT
caa_ranked.question_id,
caa_ranked.submission_id,
jsonb_agg(jsonb_build_array(caa_ranked.grade, caa_ranked.correct, caa_ranked.workflow_state)) AS submission_info
jsonb_agg(jsonb_build_array(caa_ranked.id, caa_ranked.grade, caa_ranked.correct, caa_ranked.workflow_state)) AS submission_info

Check warning on line 32 in app/controllers/concerns/course/statistics/submissions_concern.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/concerns/course/statistics/submissions_concern.rb#L32

Added line #L32 was not covered by tests
FROM (
SELECT
caa_inner.id,

Check warning on line 35 in app/controllers/concerns/course/statistics/submissions_concern.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/concerns/course/statistics/submissions_concern.rb#L35

Added line #L35 was not covered by tests
caa_inner.question_id,
caa_inner.submission_id,
caa_inner.correct,
Expand All @@ -57,19 +48,33 @@
) AS caa_ranked
WHERE caa_ranked.row_num <= 2
GROUP BY caa_ranked.question_id, caa_ranked.submission_id
),

Check warning on line 51 in app/controllers/concerns/course/statistics/submissions_concern.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/concerns/course/statistics/submissions_concern.rb#L51

Added line #L51 was not covered by tests

attempt_count AS (
SELECT
caa.question_id,
caa.submission_id,
COUNT(*) AS attempt_count
FROM course_assessment_answers caa
JOIN course_assessment_submissions cas ON caa.submission_id = cas.id
WHERE cas.assessment_id = #{assessment_params[:id]} AND caa.workflow_state != 'attempting'
GROUP BY caa.question_id, caa.submission_id

Check warning on line 61 in app/controllers/concerns/course/statistics/submissions_concern.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/concerns/course/statistics/submissions_concern.rb#L53-L61

Added lines #L53 - L61 were not covered by tests
)
SELECT
attempt_count.question_id,
attempt_count.submission_id,
attempt_count.attempt_count,
CASE WHEN jsonb_array_length(attempt_info.submission_info) = 1 OR attempt_info.submission_info->0->>2 != 'attempting'
CASE WHEN jsonb_array_length(attempt_info.submission_info) = 1 OR attempt_info.submission_info->0->>3 != 'attempting'

Check warning on line 64 in app/controllers/concerns/course/statistics/submissions_concern.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/concerns/course/statistics/submissions_concern.rb#L64

Added line #L64 was not covered by tests
THEN attempt_info.submission_info->0->>0 ELSE attempt_info.submission_info->1->>0
END AS grade,
CASE WHEN jsonb_array_length(attempt_info.submission_info) = 1 OR attempt_info.submission_info->0->>2 != 'attempting'
END AS last_attempt_answer_id,
attempt_info.question_id,
attempt_info.submission_id,
attempt_count.attempt_count,
CASE WHEN jsonb_array_length(attempt_info.submission_info) = 1 OR attempt_info.submission_info->0->>3 != 'attempting'

Check warning on line 70 in app/controllers/concerns/course/statistics/submissions_concern.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/concerns/course/statistics/submissions_concern.rb#L66-L70

Added lines #L66 - L70 were not covered by tests
THEN attempt_info.submission_info->0->>1 ELSE attempt_info.submission_info->1->>1
END AS grade,
CASE WHEN jsonb_array_length(attempt_info.submission_info) = 1 OR attempt_info.submission_info->0->>3 != 'attempting'
THEN attempt_info.submission_info->0->>2 ELSE attempt_info.submission_info->1->>2

Check warning on line 74 in app/controllers/concerns/course/statistics/submissions_concern.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/concerns/course/statistics/submissions_concern.rb#L72-L74

Added lines #L72 - L74 were not covered by tests
END AS correct
FROM attempt_count
JOIN attempt_info
FROM attempt_info
LEFT JOIN attempt_count

Check warning on line 77 in app/controllers/concerns/course/statistics/submissions_concern.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/concerns/course/statistics/submissions_concern.rb#L76-L77

Added lines #L76 - L77 were not covered by tests
ON attempt_count.question_id = attempt_info.question_id AND attempt_count.submission_id = attempt_info.submission_id
SQL
)
Expand Down
69 changes: 69 additions & 0 deletions app/controllers/course/statistics/answers_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true
class Course::Statistics::AnswersController < Course::Statistics::Controller
helper Course::Assessment::Submission::SubmissionsHelper.name.sub(/Helper$/, '')

Check warning on line 3 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L2-L3

Added lines #L2 - L3 were not covered by tests

MAX_ANSWERS_COUNT = 10

Check warning on line 5 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L5

Added line #L5 was not covered by tests

def question_answer_details
@answer = Course::Assessment::Answer.find(answer_params[:id])
@submission = @answer.submission
@assessment = @submission.assessment

Check warning on line 10 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L7-L10

Added lines #L7 - L10 were not covered by tests

@submission_question = Course::Assessment::SubmissionQuestion.
where(submission_id: @answer.submission_id, question_id: @answer.question_id).
includes({ discussion_topic: :posts }).first

Check warning on line 14 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L12-L14

Added lines #L12 - L14 were not covered by tests

@all_answers = fetch_all_answers(@answer.submission_id, @answer.question_id)
end

Check warning on line 17 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L16-L17

Added lines #L16 - L17 were not covered by tests

def all_answers
@submission_question = Course::Assessment::SubmissionQuestion.find(submission_question_params[:id])
question_id = @submission_question.question_id
submission_id = @submission_question.submission_id

Check warning on line 22 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L19-L22

Added lines #L19 - L22 were not covered by tests

@question = Course::Assessment::Question.find(question_id)
@submission = Course::Assessment::Submission.find(submission_id)
@assessment = @submission.assessment

Check warning on line 26 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L24-L26

Added lines #L24 - L26 were not covered by tests

@submission_question = Course::Assessment::SubmissionQuestion.
where(submission_id: submission_id, question_id: question_id).
includes({ discussion_topic: :posts }).first
@question_index = question_index(question_id)
@all_answers = Course::Assessment::Answer.
unscope(:order).
order(:created_at).
where(submission_id: submission_id, question_id: question_id)
end

Check warning on line 36 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L28-L36

Added lines #L28 - L36 were not covered by tests

private

Check warning on line 38 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L38

Added line #L38 was not covered by tests

def answer_params
params.permit(:id)
end

Check warning on line 42 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L40-L42

Added lines #L40 - L42 were not covered by tests

def submission_question_params
params.permit(:id)
end

Check warning on line 46 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L44-L46

Added lines #L44 - L46 were not covered by tests

def question_index(question_id)
question_ids = Course::QuestionAssessment.
where(assessment_id: @assessment.id).
order(:weight).
pluck(:question_id)

Check warning on line 52 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L48-L52

Added lines #L48 - L52 were not covered by tests

question_ids.index(question_id)
end

Check warning on line 55 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L54-L55

Added lines #L54 - L55 were not covered by tests

def fetch_all_answers(submission_id, question_id)
answers = Course::Assessment::Answer.
unscope(:order).
order(created_at: :desc).
where(submission_id: submission_id, question_id: question_id)

Check warning on line 61 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L57-L61

Added lines #L57 - L61 were not covered by tests

current_answer = answers.find(&:current_answer?)
past_answers = answers.where(current_answer: false).limit(MAX_ANSWERS_COUNT - 1).to_a.reverse
past_answers.unshift(current_answer)

Check warning on line 65 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L63-L65

Added lines #L63 - L65 were not covered by tests

past_answers
end
end

Check warning on line 69 in app/controllers/course/statistics/answers_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/answers_controller.rb#L67-L69

Added lines #L67 - L69 were not covered by tests
43 changes: 37 additions & 6 deletions app/controllers/course/statistics/assessments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
fetch_all_ancestor_assessments
create_question_related_hash

@assessment_autograded = @question_auto_gradable_status_hash.any? { |_, value| value }
@assessment_autograded = @question_hash.any? { |_, (_, _, auto_gradable)| auto_gradable }

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L21 was not covered by tests
@student_submissions_hash = fetch_hash_for_main_assessment(submissions, @all_students)
end

Expand Down Expand Up @@ -54,6 +54,12 @@
@all_students)
end

def live_feedback_history
@live_feedbacks = fetch_live_feedbacks
@live_feedback_details_hash = build_live_feedback_details_hash(@live_feedbacks)
@question = Course::Assessment::Question.find(params[:question_id])
end

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

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L57-L61

Added lines #L57 - L61 were not covered by tests

private

def assessment_params
Expand All @@ -79,11 +85,8 @@

def create_question_related_hash
create_question_order_hash
@question_maximum_grade_hash = @assessment.questions.to_h do |q|
[q.id, q.maximum_grade]
end
@question_auto_gradable_status_hash = @assessment.questions.to_h do |q|
[q.id, q.auto_gradable?]
@question_hash = @assessment.questions.to_h do |q|
[q.id, [q.maximum_grade, q.question_type, q.auto_gradable?]]

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

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L88-L89

Added lines #L88 - L89 were not covered by tests
end
end

Expand All @@ -92,4 +95,32 @@
[q.question_id, q.weight]
end
end

def fetch_live_feedbacks
Course::Assessment::LiveFeedback.where(assessment_id: assessment_params[:id],
creator_course_id: params[:course_user_id],
question_id: params[:question_id]).
order(created_at: :asc).includes(:code, code: :comments)
end

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

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L99-L104

Added lines #L99 - L104 were not covered by tests

def build_live_feedback_details_hash(live_feedbacks)
live_feedbacks.each_with_object({}) do |lf, hash|
hash[lf.id] = lf.code.map do |code|
{
code: {
id: code.id,
filename: code.filename,
content: code.content
},
comments: code.comments.map do |comment|
{
id: comment.id,
line_number: comment.line_number,
comment: comment.comment
}
end
}
end
end
end

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

View check run for this annotation

Codecov / codecov/patch

app/controllers/course/statistics/assessments_controller.rb#L106-L125

Added lines #L106 - L125 were not covered by tests
end
7 changes: 7 additions & 0 deletions app/views/course/statistics/answers/_answer.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true
specific_answer = answer.specific

json.id answer.id
json.grade answer.grade
json.questionType question.question_type
json.partial! specific_answer, answer: specific_answer, can_grade: false
26 changes: 26 additions & 0 deletions app/views/course/statistics/answers/all_answers.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true
json.question do
json.id @question.id
json.title @question.title
json.maximumGrade @question.maximum_grade
json.description format_ckeditor_rich_text(@question.description)
json.type @question.question_type
json.questionNumber @question_index + 1

json.partial! @question, question: @question.specific, can_grade: false, answer: @all_answers.first
end

json.allAnswers @all_answers do |answer|
json.partial! 'answer', answer: answer, question: @question
json.createdAt answer.created_at&.iso8601
json.currentAnswer answer.current_answer
json.workflowState answer.workflow_state
end

json.submissionId @submission.id

posts = @submission_question.discussion_topic.posts

json.comments posts do |post|
json.partial! post, post: post if post.published?
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true
question = @answer.question

json.question do
json.id question.id
json.title question.title
json.maximumGrade question.maximum_grade
json.description format_ckeditor_rich_text(question.description)
json.type question.question_type

json.partial! question, question: question.specific, can_grade: false, answer: @answer
end

json.answer do
json.partial! 'answer', answer: @answer, question: question
end

json.allAnswers @all_answers do |answer|
json.partial! 'answer', answer: answer, question: question
json.createdAt answer.created_at&.iso8601
json.currentAnswer answer.current_answer
json.workflowState answer.workflow_state
end

posts = @submission_question.discussion_topic.posts

json.comments posts do |post|
json.partial! post, post: post if post.published?
end

json.submissionId @submission.id
json.submissionQuestionId @submission_question.id
7 changes: 5 additions & 2 deletions app/views/course/statistics/assessments/_answer.json.jbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ json.grader do
end

json.answers answers.each do |answer|
json.id answer.id
maximum_grade, question_type, = @question_hash[answer.question_id]

json.lastAttemptAnswerId answer.last_attempt_answer_id
json.grade answer.grade
json.maximumGrade @question_maximum_grade_hash[answer.question_id]
json.maximumGrade maximum_grade
json.questionType question_type
end
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# frozen_string_literal: true
json.attemptStatus answers.each do |answer|
json.isAutograded @question_auto_gradable_status_hash[answer.question_id]
_, _, auto_gradable = @question_hash[answer.question_id]

json.lastAttemptAnswerId answer.last_attempt_answer_id
json.isAutograded auto_gradable
json.attemptCount answer.attempt_count
json.correct answer.correct
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true
json.files @live_feedback_details_hash[live_feedback_id].each do |live_feedback_details|
json.id live_feedback_details[:code][:id]
json.filename live_feedback_details[:code][:filename]
json.content live_feedback_details[:code][:content]
json.language @question.specific.language[:name]
json.comments live_feedback_details[:comments].map do |comment|
json.lineNumber comment[:line_number]
json.comment comment[:comment]
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true
json.liveFeedbackHistory do
json.array! @live_feedbacks.map do |live_feedback|
json.id live_feedback.id
json.createdAt live_feedback.created_at&.iso8601
json.partial! 'live_feedback_history_details', live_feedback_id: live_feedback.id
end
end

json.question do
json.id @question.id
json.title @question.title
json.description format_ckeditor_rich_text(@question.description)
end
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ json.array! @student_live_feedback_hash.each do |course_user, (submission, live_
json.groups @group_names_hash[course_user.id] do |name|
json.name name
end

json.liveFeedbackCount live_feedback_count
json.questionIds(@question_order_hash.keys.sort_by { |key| @question_order_hash[key] })
end
18 changes: 18 additions & 0 deletions client/app/api/course/Statistics/AllAnswerStatistics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { QuestionType } from 'types/course/assessment/question';
import { QuestionAllAnswerDisplayDetails } from 'types/course/statistics/assessmentStatistics';

import { APIResponse } from 'api/types';

import BaseCourseAPI from '../Base';

export default class AllAnswerStatisticsAPI extends BaseCourseAPI {
get #urlPrefix(): string {
return `/courses/${this.courseId}/statistics/submission_question`;
}

fetchAllAnswers(
submissionQuestionId: number,
): APIResponse<QuestionAllAnswerDisplayDetails<keyof typeof QuestionType>> {
return this.client.get(`${this.#urlPrefix}/${submissionQuestionId}`);
}
}
18 changes: 18 additions & 0 deletions client/app/api/course/Statistics/AnswerStatistics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { QuestionType } from 'types/course/assessment/question';
import { QuestionAnswerDetails } from 'types/course/statistics/assessmentStatistics';

import { APIResponse } from 'api/types';

import BaseCourseAPI from '../Base';

export default class AnswerStatisticsAPI extends BaseCourseAPI {
get #urlPrefix(): string {
return `/courses/${this.courseId}/statistics/answer`;
}

fetchQuestionAnswerDetails(
answerId: number,
): APIResponse<QuestionAnswerDetails<keyof typeof QuestionType>> {
return this.client.get(`${this.#urlPrefix}/${answerId}`);
}
}
12 changes: 12 additions & 0 deletions client/app/api/course/Statistics/AssessmentStatistics.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LiveFeedbackHistoryState } from 'types/course/assessment/submission/liveFeedback';
import {
AncestorAssessmentStats,
AssessmentLiveFeedbackStatistics,
Expand Down Expand Up @@ -42,4 +43,15 @@ export default class AssessmentStatisticsAPI extends BaseCourseAPI {
`${this.#urlPrefix}/${assessmentId}/live_feedback_statistics`,
);
}

fetchLiveFeedbackHistory(
assessmentId: string | number,
questionId: string | number,
courseUserId: string | number,
): APIResponse<LiveFeedbackHistoryState> {
return this.client.get(
`${this.#urlPrefix}/${assessmentId}/live_feedback_history`,
{ params: { question_id: questionId, course_user_id: courseUserId } },
);
}
}
Loading