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
|
return "unpublished" unless statuses.size > 0
|
||||||
# to fake a course-level grade publishing status, we look at all possible
|
# to fake a course-level grade publishing status, we look at all possible
|
||||||
# enrollments, and return statuses if we find any, in this order.
|
# 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)
|
return status if statuses.has_key?(status)
|
||||||
end
|
end
|
||||||
return "error"
|
return "error"
|
||||||
|
@ -842,7 +842,18 @@ class Course < ActiveRecord::Base
|
||||||
:last_publish_attempt_at => last_publish_attempt_at
|
:last_publish_attempt_at => last_publish_attempt_at
|
||||||
|
|
||||||
send_later_if_production(:send_final_grades_to_endpoint, publishing_user)
|
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
|
end
|
||||||
|
|
||||||
def send_final_grades_to_endpoint(publishing_user)
|
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?
|
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")
|
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")
|
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|
|
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"]
|
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|
|
enrollments.each do |enrollment|
|
||||||
enrollment_ids << enrollment.id
|
enrollment_ids << enrollment.id
|
||||||
enrollment.user.pseudonyms.active.find_all_by_account_id(self.root_account_id).each do |user_pseudonym|
|
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]
|
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
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
return [[enrollment_ids, res, "text/csv"]], []
|
||||||
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?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def expire_pending_grade_publishing_statuses(last_publish_attempt_at)
|
def expire_pending_grade_publishing_statuses(last_publish_attempt_at)
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
<% fields_for :settings, OpenObject.new(settings) do |f| %>
|
<% fields_for :settings, OpenObject.new(settings) do |f| %>
|
||||||
<table style="width: 500px;" class="formtable">
|
<table style="width: 500px;" class="formtable">
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= f.label :enabled, "Enabled:" %></td>
|
<td colspan=2><%= f.check_box :enabled, {}, 'true', 'false' %>
|
||||||
<td><%= f.check_box :enabled, {}, 'true', 'false' %></td>
|
<%= 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>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= f.label :publish_endpoint, "Endpoint to publish to:" %></td>
|
<td><%= f.label :publish_endpoint, "Endpoint to publish to:" %></td>
|
||||||
<td><%= f.text_field :publish_endpoint %></td>
|
<td><%= f.text_field :publish_endpoint %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= f.label :wait_for_success, "Wait for success notifications:" %></td>
|
<td colspan=2><%= f.check_box :wait_for_success, {}, 'yes', 'no' %>
|
||||||
<td><%= f.check_box :wait_for_success, {}, 'yes', 'no' %></td>
|
<%= f.label :wait_for_success, "Wait for success notifications:" %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= f.label :success_timeout, "Success notification timeout (in seconds):" %></td>
|
<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, {
|
Canvas::Plugin.register('grade_export', :sis, {
|
||||||
|
:name => "Grade Export",
|
||||||
:description => 'Grade Export for SIS',
|
:description => 'Grade Export for SIS',
|
||||||
:website => 'http://www.instructure.com',
|
:website => 'http://www.instructure.com',
|
||||||
:author => 'Instructure',
|
:author => 'Instructure',
|
||||||
|
@ -71,5 +72,6 @@ Canvas::Plugin.register('grade_export', :sis, {
|
||||||
:settings => { :enabled => "false",
|
:settings => { :enabled => "false",
|
||||||
:publish_endpoint => "",
|
:publish_endpoint => "",
|
||||||
:wait_for_success => "no",
|
: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 File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
||||||
|
require 'socket'
|
||||||
|
|
||||||
describe Course do
|
describe Course do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
|
@ -456,6 +457,7 @@ describe Course, "backup" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def course_to_backup
|
def course_to_backup
|
||||||
@course = course
|
@course = course
|
||||||
group = @course.assignment_groups.create!(:name => "Some Assignment Group")
|
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")
|
topic.discussion_entries.create!(:message => "just a test")
|
||||||
@course
|
@course
|
||||||
end
|
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