Skip to content

Commit

Permalink
add live feedback saving
Browse files Browse the repository at this point in the history
add frontend logic for returning live feedback once retrieved
add backend logic and routes for saving live feedback
  • Loading branch information
syoopie committed Aug 19, 2024
1 parent ac01e3b commit 3ff87f0
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ def initialize_student_hash(students)
students.to_h { |student| [student, nil] }
end

def fetch_hash_for_live_feedback_assessment(submissions, live_feedback_codes, students)
def fetch_hash_for_live_feedback_assessment(submissions, assessment_live_feedbacks, students)
student_hash = initialize_student_hash(students)

populate_hash(submissions, student_hash, live_feedback_codes)
populate_hash(submissions, student_hash, assessment_live_feedbacks)
student_hash
end

def populate_hash(submissions, student_hash, assessment_live_feedback_code)
def populate_hash(submissions, student_hash, assessment_live_feedbacks)
submissions.each do |submission|
submitter_course_user = submission.creator.course_users.select do |u|
u.course_id == @assessment.course_id
Expand All @@ -23,14 +23,20 @@ def populate_hash(submissions, student_hash, assessment_live_feedback_code)
# Initialize the feedback count array with zeros for each user
feedback_count = Array.new(@question_order_hash.size, 0)

user_assessment_live_feedback_code = assessment_live_feedback_code.select do |feedback_code|
feedback_code.creator == submitter_course_user
user_assessment_live_feedback = assessment_live_feedbacks.select do |live_feedback|
live_feedback.creator == submitter_course_user
end

user_assessment_live_feedback_code.each do |feedback_code|
index = @question_order_hash[feedback_code.question_id]
# If no feedback is given, do not increment the feedback count
feedback_count[index] += 1 unless feedback_code.comments.empty?
user_assessment_live_feedback.each do |feedback|
index = @question_order_hash[feedback.question_id]
# For each feedback, get all the code and all the corresponding comments
# If there is at least 1 comment within ALL the code, increment the feedback count
feedback.code.each do |code|
unless code.comments.empty?
feedback_count[index] += 1
break
end
end
end

student_hash[submitter_course_user] = [submission, feedback_count]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true
class Course::Assessment::Submission::SubmissionsController < \
class Course::Assessment::Submission::SubmissionsController <
Course::Assessment::Submission::Controller
include Course::Assessment::Submission::SubmissionsControllerServiceConcern
include Signals::EmissionConcern
Expand Down Expand Up @@ -102,9 +102,42 @@ def generate_live_feedback
response_status, response_body = @answer.generate_live_feedback
response_body['feedbackUrl'] = ENV.fetch('CODAVERI_URL')

course_user = CourseUser.find_by(course_id: @assessment.course_id, user_id: @submission.creator_id)
live_feedback = Course::Assessment::LiveFeedback.create_with_codes(
@submission.assessment_id,
@answer.question_id,
course_user.id,
response_body['transactionId'],
@answer.actable.files
)

if response_status == 200
params[:live_feedback_id] = live_feedback.id
params[:feedback_files] = response_body['data']['feedbackFiles']
save_live_feedback
end

response_body['liveFeedbackId'] = live_feedback.id
render json: response_body, status: response_status
end

def save_live_feedback
live_feedback = Course::Assessment::LiveFeedback.find_by(id: params[:live_feedback_id])
return head :bad_request if live_feedback.nil?

feedback_files = params[:feedback_files]
feedback_files.each do |file|
filename = file[:path]
file[:feedbackLines].each do |feedback_line|
Course::Assessment::LiveFeedbackComment.create(
code_id: live_feedback.code.find_by(filename: filename).id,
line_number: feedback_line[:linenum],
comment: feedback_line[:feedback]
)
end
end
end

# Reload the current answer or reset it, depending on parameters.
# current_answer has the most recent copy of the answer.
def reload_answer
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/course/statistics/assessments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ def live_feedback_statistics
preload(course: :course_users).first
submissions = Course::Assessment::Submission.where(assessment_id: assessment_params[:id]).
preload(creator: :course_users)
live_feedback_codes = Course::Assessment::LiveFeedbackCode.where(assessment_id: assessment_params[:id])
assessment_live_feedbacks = Course::Assessment::LiveFeedback.where(assessment_id: assessment_params[:id])

@course_users_hash = preload_course_users_hash(@assessment.course)

load_course_user_students_info
create_question_order_hash

@student_live_feedback_hash = fetch_hash_for_live_feedback_assessment(submissions,
live_feedback_codes,
assessment_live_feedbacks,
@all_students)
end

Expand Down
26 changes: 26 additions & 0 deletions app/models/course/assessment/live_feedback.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,30 @@ class Course::Assessment::LiveFeedback < ApplicationRecord
belongs_to :creator, class_name: 'CourseUser', foreign_key: 'creator_course_id'
has_many :code, class_name: 'Course::Assessment::LiveFeedbackCode', foreign_key: 'feedback_id',
dependent: :destroy

def self.create_with_codes(assessment_id, question_id, course_user_id, feedback_id, files)
live_feedback = new(
assessment_id: assessment_id,
question_id: question_id,
creator_course_id: course_user_id,
feedback_id: feedback_id
)

if live_feedback.save
files.each do |file|
live_feedback_code = Course::Assessment::LiveFeedbackCode.new(
feedback_id: live_feedback.id,
filename: file.filename,
content: file.content
)
unless live_feedback_code.save
Rails.logger.error "Failed to save live_feedback_code: #{live_feedback_code.errors.full_messages.join(', ')}"
end
end
live_feedback
else
Rails.logger.error "Failed to save live_feedback: #{live_feedback.errors.full_messages.join(', ')}"
nil
end
end
end
10 changes: 10 additions & 0 deletions client/app/api/course/Assessment/Submissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@ export default class SubmissionsAPI extends BaseAssessmentAPI {
});
}

saveLiveFeedback(submissionId, liveFeedbackId, feedbackFiles) {
return this.client.post(
`${this.#urlPrefix}/${submissionId}/save_live_feedback`,
{
live_feedback_id: liveFeedbackId,
feedback_files: feedbackFiles,
},
);
}

createProgrammingAnnotation(submissionId, answerId, fileId, params) {
const url = `${this.#urlPrefix}/${submissionId}/answers/${answerId}/programming/files/${fileId}/annotations`;
return this.client.post(url, params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,14 @@ const LiveHelpStatisticsTable: FC<Props> = (props) => {
.sort((a, b) => a.courseUser.name.localeCompare(b.courseUser.name))
.sort(
(a, b) =>
b.liveFeedbackCount.reduce((sum, count) => sum + (count || 0), 0) -
a.liveFeedbackCount.reduce((sum, count) => sum + (count || 0), 0),
(b.liveFeedbackCount?.reduce(
(sum, count) => sum + (typeof count === 'number' ? count : 0),
0,
) ?? 0) -
(a.liveFeedbackCount?.reduce(
(sum, count) => sum + (typeof count === 'number' ? count : 0),
0,
) ?? 0),
)
.sort(
(a, b) =>
Expand Down Expand Up @@ -161,13 +167,9 @@ const LiveHelpStatisticsTable: FC<Props> = (props) => {
},
title: t(translations.questionIndex, { index: index + 1 }),
cell: (datum): ReactNode => {
return typeof datum.liveFeedbackCount?.[index] === 'number' ? (
renderNonNullAttemptCountCell(datum.liveFeedbackCount?.[index])
) : (
<div className="bg-gray-300 p-[1rem]">
<Box>-</Box>
</div>
);
return typeof datum.liveFeedbackCount?.[index] === 'number'
? renderNonNullAttemptCountCell(datum.liveFeedbackCount?.[index])
: null;
},
sortable: true,
csvDownloadable: true,
Expand Down Expand Up @@ -196,7 +198,7 @@ const LiveHelpStatisticsTable: FC<Props> = (props) => {
cell: (datum): ReactNode => {
const totalFeedbackCount = datum.liveFeedbackCount
? datum.liveFeedbackCount.reduce((sum, count) => sum + (count || 0), 0)
: 0;
: null;
return (
<div className="p-[1rem]">
<Box>{totalFeedbackCount}</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import translations from '../../translations';
import { convertAnswerDataToInitialValue } from '../../utils/answers';
import { buildErrorMessage, formatAnswer } from '../utils';
import { fetchSubmission, getEvaluationResult } from '..';
import liveFeedback from '../../reducers/liveFeedback';

const JOB_POLL_DELAY_MS = 500;
export const STALE_ANSWER_ERR = 'stale_answer';
Expand Down Expand Up @@ -288,6 +289,7 @@ export function generateLiveFeedback({
type: actionTypes.LIVE_FEEDBACK_REQUEST,
payload: {
questionId,
liveFeedbackId: response.data?.liveFeedbackId,
feedbackUrl: response.data?.feedbackUrl,
token: response.data?.data?.token,
},
Expand All @@ -306,9 +308,11 @@ export function generateLiveFeedback({
}

export function fetchLiveFeedback({
submissionId,
answerId,
questionId,
feedbackUrl,
liveFeedbackId,
feedbackToken,
successMessage,
noFeedbackMessage,
Expand All @@ -318,6 +322,11 @@ export function fetchLiveFeedback({
.fetchLiveFeedback(feedbackUrl, feedbackToken)
.then((response) => {
if (response.status === 200) {
CourseAPI.assessment.submissions.saveLiveFeedback(
submissionId,
liveFeedbackId,
response.data?.data?.feedbackFiles ?? [],
);
handleFeedbackOKResponse({
dispatch,
response,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,11 @@ class VisibleSubmissionEditIndex extends Component {
dispatch,
liveFeedback,
assessment: { questionIds },
match: { params },
} = this.props;

const liveFeedbackId =
liveFeedback?.feedbackByQuestion?.[questionId].liveFeedbackId;
const feedbackToken =
liveFeedback?.feedbackByQuestion?.[questionId].pendingFeedbackToken;
const questionIndex = questionIds.findIndex((id) => id === questionId) + 1;
Expand All @@ -224,9 +227,11 @@ class VisibleSubmissionEditIndex extends Component {
);
dispatch(
fetchLiveFeedback({
submissionId: params.submissionId,
answerId,
questionId,
feedbackUrl: liveFeedback?.feedbackUrl,
liveFeedbackId,
feedbackToken,
successMessage,
noFeedbackMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default function (state = initialState, action) {
isRequestingLiveFeedback: true,
pendingFeedbackToken: null,
answerId: null,
liveFeedbackId: null,
feedbackFiles: {},
};
} else {
Expand All @@ -27,18 +28,20 @@ export default function (state = initialState, action) {
});
}
case actions.LIVE_FEEDBACK_REQUEST: {
const { token, questionId, feedbackUrl } = action.payload;
const { token, questionId, liveFeedbackId, feedbackUrl } = action.payload;
return produce(state, (draft) => {
draft.feedbackUrl ??= feedbackUrl;
if (!(questionId in draft)) {
draft.feedbackByQuestion[questionId] = {
isRequestingLiveFeedback: false,
liveFeedbackId,
pendingFeedbackToken: token,
};
} else {
draft.feedbackByQuestion[questionId] = {
isRequestingLiveFeedback: false,
...draft.feedbackByQuestion[questionId],
liveFeedbackId,
pendingFeedbackToken: token,
};
}
Expand Down
2 changes: 1 addition & 1 deletion client/app/types/course/statistics/assessmentStatistics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,5 @@ export interface AssessmentLiveFeedbackStatistics {
courseUser: StudentInfo;
groups: { name: string }[];
workflowState?: WorkflowState;
liveFeedbackCount: number[]; // Will already be ordered by question
liveFeedbackCount?: number[]; // Will already be ordered by question
}
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@
post :generate_feedback, on: :member
get :fetch_submitted_feedback, on: :member
post :generate_live_feedback, on: :member
post :save_live_feedback, on: :member
get :download_all, on: :collection
get :download_statistics, on: :collection
patch :publish_all, on: :collection
Expand Down

0 comments on commit 3ff87f0

Please sign in to comment.