add endpoint for assignment audit events

This new endpoint returns all AnonymousOrModerationEvents related to
the assignment and submission in question.

closes GRADE-1576

Test Plan
 - Create an auditable (anonymous or moderated) assignment.
 - Make some changes to the assignment.
 - As a grader, grade that assignment via SpeedGrader.
 - Leave some comments.
 - Via Postman or similar, make a json GET request to the endpoint
 (courses/:course_id/assignments/:assignment_id/submissions/
    :submission_id/audit_events)
 - Verify that all changes to the assignment, grades, and comments
   show up as events in the response under the key "audit_events".

Change-Id: I3e122b0b6501d3be71cc5ab67ee791e1905facb2
Reviewed-on: https://gerrit.instructure.com/164590
Reviewed-by: Jeremy Neander <jneander@instructure.com>
Reviewed-by: Keith T. Garner <kgarner@instructure.com>
Tested-by: Jenkins
QA-Review: Gary Mei <gmei@instructure.com>
Product-Review: Keith T. Garner <kgarner@instructure.com>
This commit is contained in:
Gary Mei 2018-09-14 16:38:23 -05:00
parent 9f2d62a16b
commit cc015d9502
5 changed files with 129 additions and 0 deletions

View File

@ -287,6 +287,18 @@ class SubmissionsController < SubmissionsBaseController
super
end
def audit_events
return render_unauthorized_action unless @context.grants_right?(@current_user, :view_audit_trail)
audit_events = AnonymousOrModerationEvent.events_for_submission(
assignment_id: params[:assignment_id],
submission_id: params[:submission_id]
)
respond_to do |format|
format.json { render json: { audit_events: audit_events }, status: :ok }
end
end
def lookup_existing_attachments
if params[:submission][:file_ids].is_a?(Array)
attachment_ids = params[:submission][:file_ids]

View File

@ -100,6 +100,10 @@ class AnonymousOrModerationEvent < ApplicationRecord
validate :payload_student_id_present
end
def self.events_for_submission(assignment_id:, submission_id:)
self.where(assignment_id: assignment_id, submission_id: [nil, submission_id]).order(:created_at)
end
EVENT_TYPES.each do |event_type|
scope event_type, -> { where(event_type: event_type) }
end

View File

@ -283,6 +283,7 @@ CanvasRails::Application.routes.draw do
get 'turnitin/:asset_string' => 'submissions#turnitin_report', as: :turnitin_report
post 'vericite/resubmit' => 'submissions#resubmit_to_vericite', as: :resubmit_to_vericite
get 'vericite/:asset_string' => 'submissions#vericite_report', as: :vericite_report
get 'audit_events' => 'submissions#audit_events', as: :audit_events
end
get :rubric
resource :rubric_association, path: :rubric do

View File

@ -744,6 +744,76 @@ describe SubmissionsController do
end
end
describe "GET audit_events" do
before(:once) do
@course = Course.create!
first_student = course_with_user("StudentEnrollment", course: @course, name: "First", active_all: true).user
second_student = course_with_user("StudentEnrollment", course: @course, name: "Second", active_all: true).user
@teacher = course_with_user("TeacherEnrollment", course: @course, name: "Teacher", active_all: true).user
@assignment = @course.assignments.create!(name: "anonymous", anonymous_grading: true, updating_user: @teacher)
@submission = @assignment.submissions.find_by!(user: first_student)
@submission.submission_comments.create!(author: first_student, comment: "Student comment")
@submission.submission_comments.create!(author: @teacher, comment: "Teacher comment")
@unrelated_submission = @assignment.submissions.find_by!(user: second_student)
@teacher.account.role_overrides.create!(permission: :view_audit_trail, role: teacher_role, enabled: true)
end
before(:each) do
user_session(@teacher)
end
let(:params) do
{
assignment_id: @assignment.id,
course_id: @course.id,
submission_id: @submission.id
}
end
it "renders unauthorized if user does not have view_audit_trail permission" do
@teacher.account.role_overrides.where(permission: :view_audit_trail).destroy_all
get :audit_events, params: params, format: :json
expect(response).to have_http_status(:unauthorized)
end
it "renders ok if user does have view_audit_trail permission" do
get :audit_events, params: params, format: :json
expect(response).to have_http_status(:ok)
end
it "returns only related audit events" do
@unrelated_submission.submission_comments.create!(author: @teacher, comment: "unrelated Teacher comment")
@course.assignments.create!(name: "unrelated", anonymous_grading: true, updating_user: @teacher)
get :audit_events, params: params, format: :json
audit_events = json_parse(response.body).fetch("audit_events")
expect(audit_events.count).to be 3
end
it "returns the assignment audit events" do
get :audit_events, params: params, format: :json
assignment_audit_events = json_parse(response.body).fetch("audit_events").select do |event|
event.fetch("anonymous_or_moderation_event").fetch("event_type").include?("assignment_")
end
expect(assignment_audit_events.count).to be 1
end
it "returns the submission audit events" do
get :audit_events, params: params, format: :json
submission_audit_events = json_parse(response.body).fetch("audit_events").select do |event|
event.fetch("anonymous_or_moderation_event").fetch("event_type").include?("submission_")
end
expect(submission_audit_events.count).to be 2
end
it "returns the audit events in order of created at" do
get :audit_events, params: params, format: :json
audit_event_ids = json_parse(response.body).fetch("audit_events").map do |event|
event.fetch("anonymous_or_moderation_event").fetch("id")
end
expect(audit_event_ids).to eql audit_event_ids.sort
end
end
describe "copy_attachments_to_submissions_folder" do
before(:once) do
course_with_student

View File

@ -100,4 +100,46 @@ describe AnonymousOrModerationEvent do
end
end
end
describe "#events_for_submission" do
let(:course) { Course.create! }
let(:teacher) { course_with_user("TeacherEnrollment", name: "Teacher", course: course, active_all: true).user }
let(:student) { course_with_user("StudentEnrollment", name: "Student", course: course, active_all: true).user }
let(:assignment) { course.assignments.create!(name: "anonymous", anonymous_grading: true, updating_user: teacher) }
let(:submission) { assignment.submit_homework(student, body: "please give good grade") }
let(:events) do
AnonymousOrModerationEvent.events_for_submission(assignment_id: assignment.id, submission_id: submission.id)
end
before :each do
submission.submission_comments.create!(author: teacher, comment: "no")
end
it "includes AnonymousOrModerationEvents related to assignment and submission" do
expect(events.count).to be 2
end
it "includes AnonymousOrModerationEvents of event_type assignment_*" do
assignment_events = events.select { |event| event.event_type.include?("assignment_") }
expect(assignment_events.count).to be 1
end
it "includes AnonymousOrModerationEvents of event_type submission_comment_*" do
submission_comment_events = events.select { |event| event.event_type.include?("submission_comment_") }
expect(submission_comment_events.count).to be 1
end
it "does not include AnonymousOrModerationEvents not related to assignment" do
expect {
course.assignments.create!(name: "another assignment", anonymous_grading: true, updating_user: teacher)
}.not_to change { events.count }
end
it "does not include AnonymousOrModerationEvents not related to submission" do
second_student = course_with_user("StudentEnrollment", name: "Student", course: course, active_all: true).user
expect {
assignment.submit_homework(second_student, body: "please give bad grade")
}.not_to change { events.count }
end
end
end