Add API for bulk uploading custom columns
When updating custom columns in database, we can use the API for efficient bulk uploads closes GRADE-1350 Test Plan - Send a PUT request to /api/v1/courses/:id/custom_gradebook_column_data - The request body should have an array of objects with attributes column_id, user_id, and content - An example below {column_data: [column_id: string, user_id: string, content: string] } - Make sure the auth token in Postman is configured properly (bearer token) - Send the request - Check to see if the data appears in the gradebook Change-Id: I90e747d5d92478b1e3dd101e4f254dfd392486ed Reviewed-on: https://gerrit.instructure.com/156647 Reviewed-by: Spencer Olson <solson@instructure.com> Reviewed-by: Keith T. Garner <kgarner@instructure.com> QA-Review: Adrian Packel <apackel@instructure.com> Tested-by: Jenkins Product-Review: Keith T. Garner <kgarner@instructure.com>
This commit is contained in:
parent
4c3d0fc3be
commit
83bddc402c
|
@ -39,6 +39,7 @@ class CustomGradebookColumnDataApiController < ApplicationController
|
|||
before_action :require_context, :require_user
|
||||
|
||||
include Api::V1::CustomGradebookColumn
|
||||
include Api::V1::Progress
|
||||
|
||||
# @API List entries for a column
|
||||
#
|
||||
|
@ -99,6 +100,51 @@ class CustomGradebookColumnDataApiController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# @API Bulk update column data
|
||||
#
|
||||
# Set the content of custom columns
|
||||
#
|
||||
# @argument column_data[] [Required, Array]
|
||||
# Column content. Setting this to an empty string will delete the data object.
|
||||
#
|
||||
# @example_request
|
||||
#
|
||||
# {
|
||||
# "column_data": [
|
||||
# {
|
||||
# "column_id": example_column_id,
|
||||
# "user_id": example_student_id,
|
||||
# "content": example_content
|
||||
# },
|
||||
# {
|
||||
# "column_id": example_column_id,
|
||||
# "user_id": example_student_id,
|
||||
# "content: example_content
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
#
|
||||
# @returns Progress
|
||||
|
||||
def bulk_update
|
||||
bulk_update_params = params.permit(column_data: [:user_id, :column_id, :content])
|
||||
column_data_as_array = bulk_update_params.to_h[:column_data]
|
||||
raise ActionController::BadRequest if column_data_as_array.blank?
|
||||
|
||||
column_ids = column_data_as_array.map { |entry| entry.fetch(:column_id) }
|
||||
|
||||
cc = @context.custom_gradebook_columns.find(column_ids)
|
||||
cc.each do |col|
|
||||
return render_unauthorized_action unless authorized_action? col, @current_user, :read
|
||||
end
|
||||
|
||||
user_ids = column_data_as_array.map { |entry| entry.fetch(:user_id)&.to_i }
|
||||
return render_unauthorized_action if (user_ids - allowed_users.pluck(:id)).any?
|
||||
|
||||
progress = CustomGradebookColumnDatum.queue_bulk_update_custom_columns(@context, column_data_as_array)
|
||||
render json: progress_json(progress, @current_user, session)
|
||||
end
|
||||
|
||||
def allowed_users
|
||||
@context.students_visible_to(@current_user, include: %i{inactive completed})
|
||||
end
|
||||
|
|
|
@ -29,4 +29,32 @@ class CustomGradebookColumnDatum < ActiveRecord::Base
|
|||
}
|
||||
can :update
|
||||
end
|
||||
|
||||
def self.queue_bulk_update_custom_columns(context, column_data)
|
||||
progress = Progress.create!(context: context, tag: "custom_columns_submissions_update")
|
||||
progress.process_job(self, :process_bulk_update_custom_columns, {}, context, column_data)
|
||||
progress
|
||||
end
|
||||
|
||||
def self.process_bulk_update_custom_columns(_, context, column_data)
|
||||
Delayed::Batch.serial_batch(priority: Delayed::LOW_PRIORITY, n_strand: ["bulk_update_submissions", context.root_account.global_id]) do
|
||||
custom_gradebook_columns = context.custom_gradebook_columns.preload(:custom_gradebook_column_data)
|
||||
column_data.each do |data_point|
|
||||
column_id = data_point.fetch(:column_id)
|
||||
custom_column = custom_gradebook_columns.find { |custom_col| custom_col.id == column_id.to_i }
|
||||
next if custom_column.blank?
|
||||
content = data_point.fetch(:content)
|
||||
user_id = data_point.fetch(:user_id)
|
||||
if content.present?
|
||||
CustomGradebookColumnDatum.unique_constraint_retry do
|
||||
datum = custom_column.custom_gradebook_column_data.find_or_initialize_by(user_id: user_id)
|
||||
datum.content = content
|
||||
datum.save!
|
||||
end
|
||||
else
|
||||
custom_column.custom_gradebook_column_data.find_by(user_id: user_id)&.destroy!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1913,6 +1913,7 @@ CanvasRails::Application.routes.draw do
|
|||
prefix = "courses/:course_id/custom_gradebook_columns/:id/data"
|
||||
get prefix, action: :index, as: "course_custom_gradebook_column_data"
|
||||
put "#{prefix}/:user_id", action: :update, as: "course_custom_gradebook_column_datum"
|
||||
put "courses/:course_id/custom_gradebook_column_data", action: :bulk_update, as: "course_custom_gradebook_column_bulk_data"
|
||||
end
|
||||
|
||||
scope(controller: :content_exports_api) do
|
||||
|
|
|
@ -39,6 +39,7 @@ describe CustomGradebookColumnDataApiController, type: :request do
|
|||
@user = @teacher
|
||||
|
||||
@col = @course.custom_gradebook_columns.create! title: "Notes", position: 1
|
||||
@second_col = @course.custom_gradebook_columns.create! title: "Notes2", position: 2
|
||||
end
|
||||
|
||||
describe 'index' do
|
||||
|
@ -204,4 +205,121 @@ describe CustomGradebookColumnDataApiController, type: :request do
|
|||
check.("shmarg")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'bulk update' do
|
||||
def bulk_update(args)
|
||||
api_call(:put,
|
||||
"/api/v1/courses/#{@course.id}/custom_gradebook_column_data",
|
||||
{
|
||||
course_id: @course.to_param,
|
||||
action: "bulk_update",
|
||||
controller: "custom_gradebook_column_data_api", format: "json"
|
||||
},
|
||||
{
|
||||
"column_data" => [
|
||||
{
|
||||
"column_id" => args.first[:column_id],
|
||||
"user_id" => args.first[:student_id],
|
||||
"content" => args.first[:content]
|
||||
}
|
||||
]
|
||||
})
|
||||
end
|
||||
|
||||
it 'passes the contents to the api call successfully' do
|
||||
@user = @teacher
|
||||
contents = [
|
||||
{
|
||||
column_id: @col.to_param,
|
||||
student_id: @student1.to_param,
|
||||
content: 'Column 1, Student 1'
|
||||
}
|
||||
]
|
||||
|
||||
json = bulk_update(contents)
|
||||
expect(json.fetch('workflow_state')).to eq "queued"
|
||||
end
|
||||
|
||||
it 'passes muliple contents to the api call successfully' do
|
||||
@user = @teacher
|
||||
contents = [
|
||||
{
|
||||
column_id: @col.to_param,
|
||||
student_id: @student1.to_param,
|
||||
content: 'Column 1, Student 1'
|
||||
},
|
||||
{
|
||||
column_id: @second_col.to_param,
|
||||
student_id: @student2.to_param,
|
||||
content: 'Column 2, Student 2'
|
||||
}
|
||||
]
|
||||
|
||||
json = api_call :put,
|
||||
"/api/v1/courses/#{@course.id}/custom_gradebook_column_data",
|
||||
{
|
||||
course_id: @course.to_param,
|
||||
action: "bulk_update",
|
||||
controller: "custom_gradebook_column_data_api", format: "json"
|
||||
},
|
||||
{
|
||||
"column_data" => [
|
||||
{
|
||||
"column_id" => contents.first[:column_id],
|
||||
"user_id" => contents.first[:student_id],
|
||||
"content" => contents.first[:content]
|
||||
},
|
||||
{
|
||||
"column_id" => contents.second[:column_id],
|
||||
"user_id" => contents.second[:student_id],
|
||||
"content" => contents.second[:content]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
expect(json.fetch('workflow_state')).to eq "queued"
|
||||
end
|
||||
|
||||
it 'throws 401 status when updating non existing student' do
|
||||
@user = @teacher
|
||||
contents = [
|
||||
{
|
||||
column_id: @col.to_param,
|
||||
student_id: -1.to_param,
|
||||
content: 'Non existing student 1'
|
||||
}
|
||||
]
|
||||
|
||||
bulk_update(contents)
|
||||
assert_status(401)
|
||||
end
|
||||
|
||||
it 'throws 400 status when passing empty input' do
|
||||
@user = @teacher
|
||||
api_call :put,
|
||||
"/api/v1/courses/#{@course.id}/custom_gradebook_column_data",
|
||||
{
|
||||
course_id: @course.to_param,
|
||||
action: "bulk_update",
|
||||
controller: "custom_gradebook_column_data_api", format: "json"
|
||||
}, {}
|
||||
|
||||
assert_status(400)
|
||||
end
|
||||
|
||||
it 'throws 400 status when passing empty array in column_data' do
|
||||
@user = @teacher
|
||||
|
||||
api_call :put,
|
||||
"/api/v1/courses/#{@course.id}/custom_gradebook_column_data",
|
||||
{
|
||||
course_id: @course.to_param,
|
||||
action: "bulk_update",
|
||||
controller: "custom_gradebook_column_data_api", format: "json"
|
||||
},
|
||||
{ "column_data" => [] }
|
||||
|
||||
assert_status(400)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
#
|
||||
# Copyright (C) 2018 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
describe Api::V1::CustomGradebookColumnDatum do
|
||||
describe "custom_gradebook_column_bulk_upload" do
|
||||
before :once do
|
||||
course_with_teacher(active_all: true)
|
||||
@first_student = student_in_course(active_all: true, course: @course).user
|
||||
@second_student = student_in_course(active_all: true, course: @course).user
|
||||
@first_col = @course.custom_gradebook_columns.create!(title: "cc1", position: 1)
|
||||
@second_col = @course.custom_gradebook_columns.create!(title: "cc2", position: 2)
|
||||
|
||||
CustomGradebookColumnDatum.process_bulk_update_custom_columns({}, @course, [
|
||||
{
|
||||
"column_id": @first_col.id.to_s,
|
||||
"user_id": @first_student.id.to_s,
|
||||
"content": "first column, first student"
|
||||
},
|
||||
{
|
||||
"column_id": @first_col.id.to_s,
|
||||
"user_id": @second_student.id.to_s,
|
||||
"content": "first column, second student"
|
||||
},
|
||||
{
|
||||
"column_id": @second_col.id.to_s,
|
||||
"user_id": @first_student.id.to_s,
|
||||
"content": "second column, first student"
|
||||
},
|
||||
{
|
||||
"column_id": @second_col.id.to_s,
|
||||
"user_id": @second_student.id.to_s,
|
||||
"content": "second column, second student"
|
||||
}
|
||||
])
|
||||
end
|
||||
|
||||
it "adds a datum for a matching student and column" do
|
||||
data = @course.custom_gradebook_columns.find_by!(id: @first_col.id).
|
||||
custom_gradebook_column_data.where(user_id: @first_student.id)
|
||||
expect(data.count).to eql 1
|
||||
end
|
||||
|
||||
it "checks content exists for the first student in the first column" do
|
||||
data = @course.custom_gradebook_columns.find_by!(id: @first_col.id).
|
||||
custom_gradebook_column_data.find_by!(user_id: @first_student.id).content
|
||||
expect(data).to eql "first column, first student"
|
||||
end
|
||||
|
||||
it "adds data for multiple students for a column" do
|
||||
data = @course.custom_gradebook_columns.find_by!(id: @first_col.id).
|
||||
custom_gradebook_column_data
|
||||
expect(data.count).to eql 2
|
||||
end
|
||||
|
||||
it "adds data for multiple columns" do
|
||||
data = @course.custom_gradebook_columns.where(id: [@first_col.id, @second_col.id])
|
||||
expect(data.count).to eql 2
|
||||
end
|
||||
|
||||
it "does not create new columns when column doesn't exist" do
|
||||
CustomGradebookColumnDatum.process_bulk_update_custom_columns({}, @course, [
|
||||
{
|
||||
"column_id": (@second_col.id + 1001).to_s,
|
||||
"user_id": @second_student.id.to_s,
|
||||
"content": "first column, second student"
|
||||
},
|
||||
])
|
||||
|
||||
data = @course.custom_gradebook_columns.where(id: @second_col.id + 1001)
|
||||
expect(data.count).to eql 0
|
||||
end
|
||||
|
||||
it "updates the content for existing student and column" do
|
||||
CustomGradebookColumnDatum.process_bulk_update_custom_columns({}, @course, [
|
||||
{
|
||||
"column_id": @second_col.id.to_s,
|
||||
"user_id": @second_student.id.to_s,
|
||||
"content": "2, 2"
|
||||
}
|
||||
])
|
||||
|
||||
data = @course.custom_gradebook_columns.find_by!(id: @second_col.id).
|
||||
custom_gradebook_column_data.find_by!(user_id: @second_student.id).content
|
||||
expect(data).to eql "2, 2"
|
||||
end
|
||||
|
||||
it "can pass the column ID as a number" do
|
||||
CustomGradebookColumnDatum.process_bulk_update_custom_columns({}, @course, [
|
||||
{
|
||||
"column_id": @second_col.id,
|
||||
"user_id": @second_student.id,
|
||||
"content": "2, 2"
|
||||
}
|
||||
])
|
||||
|
||||
data = @course.custom_gradebook_columns.find_by!(id: @second_col.id).
|
||||
custom_gradebook_column_data.find_by!(user_id: @second_student.id).content
|
||||
expect(data).to eql "2, 2"
|
||||
end
|
||||
|
||||
it "can pass the column ID as a string" do
|
||||
CustomGradebookColumnDatum.process_bulk_update_custom_columns({}, @course, [
|
||||
{
|
||||
"column_id": @second_col.id.to_s,
|
||||
"user_id": @second_student.id.to_s,
|
||||
"content": "2, 2"
|
||||
}
|
||||
])
|
||||
|
||||
data = @course.custom_gradebook_columns.find_by!(id: @second_col.id).
|
||||
custom_gradebook_column_data.find_by!(user_id: @second_student.id).content
|
||||
expect(data).to eql "2, 2"
|
||||
end
|
||||
|
||||
it "does not update content in deleted columns" do
|
||||
@course.custom_gradebook_columns.find_by!(id: @second_col.id).
|
||||
custom_gradebook_column_data.find_by!(user_id: @first_student.id).delete
|
||||
@course.custom_gradebook_columns.find_by!(id: @second_col.id).
|
||||
custom_gradebook_column_data.find_by!(user_id: @second_student.id).delete
|
||||
@course.custom_gradebook_columns.find_by!(id: @second_col.id).delete
|
||||
|
||||
CustomGradebookColumnDatum.process_bulk_update_custom_columns({}, @course, [
|
||||
{
|
||||
"column_id": @second_col.id.to_s,
|
||||
"user_id": @second_student.id.to_s,
|
||||
"content": "3, 2"
|
||||
},
|
||||
])
|
||||
|
||||
data = @course.custom_gradebook_columns.where(id: @second_col.id)
|
||||
expect(data.count).to eql 0
|
||||
end
|
||||
|
||||
it "destroys data when uploading empty string" do
|
||||
CustomGradebookColumnDatum.process_bulk_update_custom_columns({}, @course, [
|
||||
{
|
||||
"column_id": @first_col.id.to_s,
|
||||
"user_id": @first_student.id.to_s,
|
||||
"content": ""
|
||||
},
|
||||
])
|
||||
|
||||
data = @course.custom_gradebook_columns.find_by!(id: @first_col.id).
|
||||
custom_gradebook_column_data.find_by(user_id: @first_student.id)
|
||||
expect(data).to eql nil
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue