give plugins a way to inject alternate grade export formats
Change-Id: Icd4ddc52cc3190ba28fb1d93821642ef2b119a3c Reviewed-on: https://gerrit.instructure.com/3323 Reviewed-by: JT Olds <jt@instructure.com> Tested-by: Hudson <hudson@instructure.com>
This commit is contained in:
parent
9ab40ec477
commit
128fc92e32
|
@ -823,7 +823,7 @@ class Course < ActiveRecord::Base
|
|||
return "unpublished" unless statuses.size > 0
|
||||
# to fake a course-level grade publishing status, we look at all possible
|
||||
# enrollments, and return statuses if we find any, in this order.
|
||||
["error", "unpublished", "pending", "publishing", "published"].each do |status|
|
||||
["error", "unpublished", "pending", "publishing", "published", "unpublishable"].each do |status|
|
||||
return status if statuses.has_key?(status)
|
||||
end
|
||||
return "error"
|
||||
|
@ -842,7 +842,18 @@ class Course < ActiveRecord::Base
|
|||
:last_publish_attempt_at => last_publish_attempt_at
|
||||
|
||||
send_later_if_production(:send_final_grades_to_endpoint, publishing_user)
|
||||
send_at(last_publish_attempt_at + settings[:success_timeout].to_i.seconds, :expire_pending_grade_publishing_statuses, last_publish_attempt_at) if settings[:success_timeout].present? && settings[:wait_for_success]
|
||||
send_at(last_publish_attempt_at + settings[:success_timeout].to_i.seconds, :expire_pending_grade_publishing_statuses, last_publish_attempt_at) if settings[:success_timeout].present? && settings[:wait_for_success] && Rails.env.production?
|
||||
end
|
||||
|
||||
def self.valid_grade_export_types
|
||||
@valid_grade_export_types ||= {
|
||||
"instructure_csv" => {
|
||||
:name => "Instructure formatted CSV",
|
||||
:callback => lambda { |course, enrollments, publishing_pseudonym|
|
||||
course.generate_grade_publishing_csv_output(enrollments, publishing_pseudonym)
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def send_final_grades_to_endpoint(publishing_user)
|
||||
|
@ -854,35 +865,46 @@ class Course < ActiveRecord::Base
|
|||
raise "endpoint undefined" if settings[:publish_endpoint].nil? || settings[:publish_endpoint].empty?
|
||||
|
||||
enrollments = self.student_enrollments.scoped({:include => [:user, :course_section]}).find(:all, :order => "users.sortable_name")
|
||||
enrollment_ids = []
|
||||
|
||||
publishing_pseudonym = publishing_user.pseudonyms.active.find_by_account_id(self.root_account_id, :order => "sis_user_id DESC")
|
||||
|
||||
errors = []
|
||||
posts_to_make = []
|
||||
ignored_enrollment_ids = []
|
||||
|
||||
if Course.valid_grade_export_types.has_key?(settings[:format_type])
|
||||
callback = Course.valid_grade_export_types[settings[:format_type]][:callback]
|
||||
posts_to_make, ignored_enrollments_ids = callback.call(self, enrollments,
|
||||
publishing_pseudonym)
|
||||
end
|
||||
|
||||
Enrollment.update ignored_enrollment_ids, [{ :grade_publishing_status => "unpublishable" }] * ignored_enrollment_ids.size
|
||||
|
||||
posts_to_make.each do |enrollment_ids, res, mime_type|
|
||||
begin
|
||||
SSLCommon.post_data(settings[:publish_endpoint], res, mime_type)
|
||||
Enrollment.update enrollment_ids, [{ :grade_publishing_status => (settings[:wait_for_success] == "yes" ? "publishing" : "published") }] * enrollment_ids.size
|
||||
rescue => e
|
||||
errors << e
|
||||
Enrollment.update enrollment_ids, [{ :grade_publishing_status => "error" }] * enrollment_ids.size
|
||||
end
|
||||
end
|
||||
|
||||
raise errors[0] if errors.size > 0
|
||||
end
|
||||
|
||||
def generate_grade_publishing_csv_output(enrollments, publishing_pseudonym)
|
||||
enrollment_ids = []
|
||||
res = FasterCSV.generate do |csv|
|
||||
|
||||
csv << ["publisher_id", "publisher_sis_id", "section_id", "section_sis_id", "student_id", "student_sis_id", "enrollment_id", "enrollment_status", "grade"]
|
||||
|
||||
enrollments.each do |enrollment|
|
||||
enrollment_ids << enrollment.id
|
||||
enrollment.user.pseudonyms.active.find_all_by_account_id(self.root_account_id).each do |user_pseudonym|
|
||||
csv << [publishing_pseudonym.try(:id), publishing_pseudonym.try(:sis_user_id), enrollment.course_section.id, enrollment.course_section.sis_source_id, user_pseudonym.id, user_pseudonym.sis_user_id, enrollment.id, enrollment.workflow_state, enrollment.computed_final_grade]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
publish_status = "error"
|
||||
error = nil
|
||||
begin
|
||||
SSLCommon.post_data(settings[:publish_endpoint], res, 'text/csv')
|
||||
publish_status = (settings[:wait_for_success] == "yes" ? "publishing" : "published")
|
||||
rescue => e
|
||||
error = e
|
||||
end
|
||||
|
||||
Enrollment.update enrollment_ids, [{ :grade_publishing_status => publish_status }] * enrollment_ids.size
|
||||
|
||||
raise error unless error.nil?
|
||||
return [[enrollment_ids, res, "text/csv"]], []
|
||||
end
|
||||
|
||||
def expire_pending_grade_publishing_statuses(last_publish_attempt_at)
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
<% fields_for :settings, OpenObject.new(settings) do |f| %>
|
||||
<table style="width: 500px;" class="formtable">
|
||||
<tr>
|
||||
<td><%= f.label :enabled, "Enabled:" %></td>
|
||||
<td><%= f.check_box :enabled, {}, 'true', 'false' %></td>
|
||||
<td colspan=2><%= f.check_box :enabled, {}, 'true', 'false' %>
|
||||
<%= f.label :enabled, "Enabled:" %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= f.label :format_type, "Output format type:" %></td>
|
||||
<td><%= f.select :format_type, Course.valid_grade_export_types.keys.map{|k| [Course.valid_grade_export_types[k][:name], k]}, {} %>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= f.label :publish_endpoint, "Endpoint to publish to:" %></td>
|
||||
<td><%= f.text_field :publish_endpoint %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= f.label :wait_for_success, "Wait for success notifications:" %></td>
|
||||
<td><%= f.check_box :wait_for_success, {}, 'yes', 'no' %></td>
|
||||
<td colspan=2><%= f.check_box :wait_for_success, {}, 'yes', 'no' %>
|
||||
<%= f.label :wait_for_success, "Wait for success notifications:" %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= f.label :success_timeout, "Success notification timeout (in seconds):" %></td>
|
||||
|
|
|
@ -62,6 +62,7 @@ Canvas::Plugin.register 'common_cartridge_importer', :export_system, {
|
|||
}
|
||||
}
|
||||
Canvas::Plugin.register('grade_export', :sis, {
|
||||
:name => "Grade Export",
|
||||
:description => 'Grade Export for SIS',
|
||||
:website => 'http://www.instructure.com',
|
||||
:author => 'Instructure',
|
||||
|
@ -71,5 +72,6 @@ Canvas::Plugin.register('grade_export', :sis, {
|
|||
:settings => { :enabled => "false",
|
||||
:publish_endpoint => "",
|
||||
:wait_for_success => "no",
|
||||
:success_timeout => "600" }
|
||||
:success_timeout => "600",
|
||||
:format_type => "instructure_csv" }
|
||||
})
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
||||
require 'socket'
|
||||
|
||||
describe Course do
|
||||
before(:each) do
|
||||
|
@ -456,6 +457,7 @@ describe Course, "backup" do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def course_to_backup
|
||||
@course = course
|
||||
group = @course.assignment_groups.create!(:name => "Some Assignment Group")
|
||||
|
@ -466,3 +468,54 @@ def course_to_backup
|
|||
topic.discussion_entries.create!(:message => "just a test")
|
||||
@course
|
||||
end
|
||||
|
||||
describe Course, 'grade_publishing' do
|
||||
before(:each) do
|
||||
@course = Course.new
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
Course.valid_grade_export_types.delete("test_export")
|
||||
PluginSetting.settings_for_plugin('grade_export')[:format_type] = "instructure_csv"
|
||||
PluginSetting.settings_for_plugin('grade_export')[:enabled] = "false"
|
||||
PluginSetting.settings_for_plugin('grade_export')[:publish_endpoint] = ""
|
||||
end
|
||||
|
||||
it 'should pass a quick sanity check' do
|
||||
user = User.new
|
||||
Course.valid_grade_export_types["test_export"] = {
|
||||
:name => "test export",
|
||||
:callback => lambda {|course, enrollments, publishing_pseudonym|
|
||||
course.should == @course
|
||||
publishing_pseudonym.should == nil
|
||||
return [[[], "test-jt-data\r\n\r\n", "application/jtmimetype"]], []
|
||||
}}
|
||||
PluginSetting.settings_for_plugin('grade_export')[:enabled] = "true"
|
||||
PluginSetting.settings_for_plugin('grade_export')[:format_type] = "test_export"
|
||||
|
||||
server = TCPServer.open(0)
|
||||
port = server.addr[1]
|
||||
PluginSetting.settings_for_plugin('grade_export')[:publish_endpoint] = "http://localhost:#{port}/endpoint"
|
||||
post_lines = []
|
||||
server_thread = Thread.new(server, post_lines) { |server, post_lines|
|
||||
client = server.accept
|
||||
loop do
|
||||
line = client.readline
|
||||
post_lines << line.strip unless line =~ /^Host: localhost:/
|
||||
break if line =~ /test-jt-data/
|
||||
end
|
||||
client.puts("HTTP/1.1 200 OK\nContent-Length: 0\n\n")
|
||||
client.close
|
||||
server.close
|
||||
}
|
||||
@course.publish_final_grades(user)
|
||||
server_thread.join
|
||||
post_lines.should == [
|
||||
"POST /endpoint HTTP/1.1",
|
||||
"Accept: */*",
|
||||
"Content-Type: application/jtmimetype",
|
||||
"Content-Length: 16",
|
||||
"",
|
||||
"test-jt-data"]
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue