assignment-level turnitin settings, closes #4938

Change-Id: I036391d450eed4ebe81e98ee8d0f106a26c164c3
Reviewed-on: https://gerrit.instructure.com/5802
Reviewed-by: Jon Jensen <jon@instructure.com>
Tested-by: Hudson <hudson@instructure.com>
This commit is contained in:
Jon Jensen 2011-09-23 18:12:43 -06:00
parent d48772c40b
commit b3cd35662e
12 changed files with 310 additions and 20 deletions

View File

@ -29,7 +29,7 @@ class Assignment < ActiveRecord::Base
:assignment_group, :unlock_at, :lock_at, :group_category_name,
:peer_review_count, :peer_reviews_due_at, :peer_reviews_assign_at, :grading_standard_id,
:peer_reviews, :automatic_peer_reviews, :grade_group_students_individually,
:notify_of_update, :time_zone_edited, :turnitin_enabled,
:notify_of_update, :time_zone_edited, :turnitin_enabled, :turnitin_settings,
:set_custom_field_values, :context, :position, :allowed_extensions
attr_accessor :original_id
@ -71,6 +71,7 @@ class Assignment < ActiveRecord::Base
self.title = val
end
serialize :turnitin_settings, Hash
# file extensions allowed for online_upload submission
serialize :allowed_extensions, Array
@ -155,7 +156,47 @@ class Assignment < ActiveRecord::Base
end
true
end
def turnitin_settings
read_attribute(:turnitin_settings) || default_turnitin_settings
end
def turnitin_settings=(settings)
unless settings.nil?
settings.delete_if { |key, value| !default_turnitin_settings.has_key?(key.to_sym) }
settings[:created] = turnitin_settings[:created] if turnitin_settings[:created]
settings[:originality_report_visibility] = 'immediate' unless ['immediate', 'after_grading', 'after_due_date'].include?(settings[:originality_report_visibility])
[:s_paper_check, :internet_check, :journal_check, :exclude_biblio, :exclude_quoted].each do |key|
settings[key] = '0' unless settings[key] == '1'
end
exclude_value = settings[:exclude_value].to_i
settings[:exclude_type] = '0' unless ['0', '1', '2'].include?(settings[:exclude_type])
settings[:exclude_value] = case settings[:exclude_type]
when '0': ''
when '1': [exclude_value, 1].max.to_s
when '2': (0..100).include?(exclude_value) ? exclude_value.to_s : '0'
end
end
write_attribute :turnitin_settings, settings
end
def default_turnitin_settings
{
:originality_report_visibility => 'immediate',
:s_paper_check => '1',
:internet_check => '1',
:journal_check => '1',
:exclude_biblio => '1',
:exclude_quoted => '1',
:exclude_type => '0',
:exclude_value => ''
}
end
def default_values
raise "Assignments can only be assigned to Course records" if self.context_type && self.context_type != "Course"
self.context_code = "#{self.context_type.underscore}_#{self.context_id}"

View File

@ -170,6 +170,19 @@ class Submission < ActiveRecord::Base
given {|user| user && self.assessment_requests.map{|a| a.assessor_id}.include?(user.id) }
can :read and can :comment
given { |user, session|
grants_right?(user, session, :read) &&
turnitin_data &&
(assignment.cached_context_grants_right?(user, session, :manage_grades) ||
case assignment.turnitin_settings[:originality_report_visibility]
when 'immediate': true
when 'after_grading': graded?
when 'after_due_date': assignment.due_at && assignment.due_at < Time.now.utc
end
)
}
can :view_turnitin_report
end
on_update_send_to_streams do
@ -275,9 +288,8 @@ class Submission < ActiveRecord::Base
turnitin = Turnitin::Client.new(*self.context.turnitin_settings)
self.turnitin_data ||= {}
submission_response = []
assignment_response = turnitin.createAssignment(self.assignment) unless turnitin_data[:assignment_id]
if assignment_response || true
enrollment_response = turnitin.enrollStudent(self.context, self.user) unless turnitin_data[:user_id]
if turnitin.createOrUpdateAssignment(self.assignment)
enrollment_response = turnitin.enrollStudent(self.context, self.user) # TODO: track this elsewhere so we don't have to do the API call on every submission
if enrollment_response
submission_response = turnitin.submitPaper(self)
end

View File

@ -180,7 +180,7 @@
<%
turntitin = nil
url = '#'
if submission && (submission.current_submission_graded? || can_do(@context, @current_user, :manage_grades))
if can_do(submission, @current_user, :view_turnitin_report)
if submission.submission_type == 'online_text_entry'
turnitin = submission.turnitin_data && submission.turnitin_data[submission.asset_string]
url = context_url(@context, :context_assignment_submission_turnitin_report_url, assignment.id, @student.id, submission.asset_string)

View File

@ -262,6 +262,9 @@
<div style="margin-top: 10px;">
<%= f.check_box :turnitin_enabled %>
<%= f.label :turnitin_enabled, :en => "Enable Turnitin Submission Evaluations" %>
<div id="assignment_turnitin_settings" style="<%= hidden unless assignment && assignment.turnitin_enabled %>; margin-left: 20px;">
<a href="#" class="show_turnitin_settings" style="font-size: 0.9em"><%= t 'links.advanced_turnitin_settings', 'Advanced Settings...' %></a>
</div>
</div>
<% end %>
</div>
@ -300,3 +303,4 @@
<%= render :partial => "shared/add_assignment_group" %>
<%= render :partial => "groups/add_group_category" %>
<%= render :partial => "shared/turnitin_settings", :locals => {:assignment => assignment} %>

View File

@ -0,0 +1,105 @@
<%
settings = assignment.turnitin_settings
settings[:exclude_small_matches] = '1' if settings[:exclude_type] != '0'
@settings = OpenObject.new(settings)
%>
<% form_for :settings, :url => assignment && assignment.id ? context_url(@context, :context_assignment_url, assignment) : context_url(@context, :context_assignments_url), :html => {:id => "turnitin_settings_form", :method => "PUT", :style => "padding: 1em; display: none", :title => t('titles.turnitin_options', "Turnitin Settings")} do |f| %>
<p>
<label for="settings_originality_report_visibility">
<%= t :originality_report_visibility, "Students can see the originality report: %{when}",
:when => f.select(:originality_report_visibility, [
[t('originality_report_visible_immediately', 'immediately'), 'immediate'],
[t('originality_report_visible_after_grading', 'after the assignment is graded'), 'after_grading'],
[t('originality_report_visible_after_due_date', 'after the due date'), 'after_due_date']])
%>
</label>
</p>
<p>
<b><%= before_label :compare_against, "Compare against" %></b><br />
<%= f.check_box :s_paper_check, :id => :settings_student_paper_check %> <%= f.label :student_paper_check, :en => "other student papers" %><br />
<%= f.check_box :internet_check %> <%= f.label :internet_check, :en => "internet database" %><br />
<%= f.check_box :journal_check %> <%= f.label :journal_check, :en => "journals, periodicals and publications" %><br />
</p>
<p>
<b><%= before_label :dont_consider, "Don't consider" %></b><br />
<%= f.check_box :exclude_biblio %> <%= f.label :exclude_biblio, :en => "bibliographic material" %><br />
<%= f.check_box :exclude_quoted %> <%= f.label :exclude_quoted, :en => "quoted material" %><br />
<%= f.check_box :exclude_small_matches %> <%= f.label :exclude_small_matches, :en => "small matches" %><br />
<span id="exclude_small_matches_options" style="display:block;padding-left:1.5em">
<%= f.radio_button :exclude_type, '1', :id => :settings_exclude_fewer_than_count %>
<label for="settings_exclude_fewer_than_count"><%= t :settings_exclude_fewer_than_count, "fewer than %{count} words", :count => text_field_tag('settings[exclude_value_count]', @settings.exclude_type == '1' ? @settings.exclude_value : '', :size => 3, :id => :settings_exclude_value_count, :title => t('titles.exclude_count', 'Exclude matches with fewer than this many words')) %></label><br />
<%= f.radio_button :exclude_type, '2', :id => :settings_exclude_less_than_percent %>
<label for="settings_exclude_less_than_percent"><%= t :settings_exclude_less_than_percent, "less than %{percent} percent of the document", :percent => text_field_tag('settings[exclude_value_percent]', @settings.exclude_type == '2' ? @settings.exclude_value : '', :size => 3, :id => :settings_exclude_value_percent, :title => t('titles.exclude_percent', 'Exclude matches that comprise less than this percentage of the document')) %></label><br />
</span>
</p>
<p>
<button type="submit" class="button update_button"><%= t 'buttons.update_settings', "Update Settings" %></button>
<button type="button" class="button-secondary cancel_button"><%= t '#buttons.cancel', "Cancel" %></button>
</p>
<% end %>
<% js_block :i18n_scope => 'shared/turnitin_settings' do %>
<script>
$(document).ready(function() {
var noSubmit = <%= assignment.new_record? ? 'true' : 'false' %>;
$('#settings_exclude_small_matches').change(function() {
$('#exclude_small_matches_options').showIf($(this).attr('checked'))
}).change();
$('#exclude_small_matches_options label input').click(function(e) {
e.preventDefault();
$(this).parent().prevAll('input').first().attr('checked', true);
$(this).focus();
});
var $form = $('#turnitin_settings_form');
$('.show_turnitin_settings').click(function(e) {
e.preventDefault();
$form.data('parent_form', $(this).closest('form'));
$form.show().dialog({
width: 400,
height: 'auto',
autoSize: true,
modal: true,
autoOpen: false
}).dialog('open');
});
$form.formSubmit({
preventDegradeToFormSubmit: true,
processData: function(data) {
var new_data = {};
data['settings[exclude_value]'] = (data['settings[exclude_type]'] == '2' ? data['settings[exclude_value_percent]'] : data['settings[exclude_value_count]']);
var $parentForm = $form.data('parent_form');
$.each(data, function(key, value) {
if (key.match(/^settings/)) {
key = key.replace(/^settings/, 'assignment[turnitin_settings]');
if (noSubmit) {
var $node = $parentForm.find('input[name="' + key + '"]');
if (!$node.length) {
$node = $('<input type="hidden">').attr('name', key).appendTo($parentForm);
}
$node.val(value);
}
}
new_data[key] = value;
});
return new_data;
},
beforeSubmit: function(data) {
$(this).find('button').attr('disabled', true).filter('.update_button').text(I18n.t('messages.updating_settings', 'Updating Settings...'));
},
success: function(data) {
$(this).find('button').attr('disabled', false).filter('.update_button').text(I18n.t('buttons.update_settings', 'Update Settings'));
$(this).dialog('close');
},
error: function(data) {
$(this).formErrors(data);
$(this).find('button').attr('disabled', false).filter('.update_button').text(I18n.t('errors.error_updating_settings', 'Error Updating Settings'));
},
noSubmit: noSubmit
});
$form.find('.cancel_button').click(function(event) {
$form.dialog('close');
});
});
</script>
<% end %>

View File

@ -288,7 +288,7 @@
<% end %>
<br/>
<% if @submission.turnitin_data && (@submission.current_submission_graded? || can_do(@context, @current_user, :manage_grades)) %>
<% if can_do(@submission, @current_user, :view_turnitin_report) %>
<% if (turnitin_score = @submission.turnitin_data[@submission.asset_string]) && turnitin_score[:similarity_score] %>
<a href="<%= context_url(@context, :context_assignment_submission_turnitin_report_url, @submission.assignment_id, @submission.user_id, @submission.asset_string) %>" target="_blank" class="not_external turnitin_similarity_score <%= turnitin_score[:state] %>_score"><%= turnitin_score[:similarity_score] %> %</a>
<% end %>

View File

@ -79,7 +79,7 @@
<% @submission.attachments.each do |attachment| %>
<li>
<div class="ui-listview-text ui-listview-right">
<% if @submission.turnitin_data && (@submission.current_submission_graded? || can_do(@context, @current_user, :manage_grades)) && (turnitin_score = @submission.turnitin_data[attachment.asset_string]) && turnitin_score[:similarity_score] %>
<% if can_do(@submission, @current_user, :view_turnitin_report) && (turnitin_score = @submission.turnitin_data[attachment.asset_string]) && turnitin_score[:similarity_score] %>
<a href="<%= context_url(@context, :context_assignment_submission_turnitin_report_url, @submission.assignment_id, @submission.user_id, attachment.asset_string) %>" target="_blank" class="not_external turnitin_similarity_score <%= turnitin_score[:state] %>_score"><%= turnitin_score[:similarity_score] %> %</a>
<% end %>
<span><%= number_to_human_size(attachment.size) %></span>

View File

@ -0,0 +1,9 @@
class TurnitinSettings < ActiveRecord::Migration
def self.up
add_column :assignments, :turnitin_settings, :text
end
def self.down
remove_column :assignments, :turnitin_settings
end
end

View File

@ -98,19 +98,15 @@ module Turnitin
res.css("userid")[0].content rescue nil
end
def createAssignment(assignment)
def createOrUpdateAssignment(assignment)
return true if assignment.turnitin_settings[:current]
course = assignment.context
today = ActiveSupport::TimeWithZone.new(Time.now, Time.zone).to_date
# s_paper_check - 1/0, check student paper repository (is this the CURRENT student's repo, or all other students on the same paper???)
# internet_check - 1/0, check internet repo
# journal_check - 1/0, check journals, periodicals, publications
settings = assignment.turnitin_settings.dup
# institution_check - 1/0, check institution
# submit_papers_to - 0=none, 1=standard, 2=institution
# exclude_biblio - 1/0, exclude bibliographic material
# exclude_quoted - 1/0, exclude quoted material
# exclude_type - 0=none, 1=by_word_count, 2=by_percentage
# exclude_value - goes with exclude_type, either num or pct based on type
res = sendRequest(:create_assignment, '2',
res = sendRequest(:create_assignment, settings.delete(:created) ? '3' : '2', settings.merge({
:user => course,
:course => course,
:assignment => assignment,
@ -121,8 +117,17 @@ module Turnitin
:s_view_report => "1",
:late_accept_flag => '1',
:post => true
)
res.css("assignmentid")[0].content rescue nil
}))
begin
ret = res.css("assignmentid")[0].content
assignment.turnitin_settings = assignment.turnitin_settings
assignment.turnitin_settings[:current] = true
assignment.turnitin_settings[:created] = true
assignment.save
ret
rescue
nil
end
end
def submitPaper(submission)

View File

@ -308,6 +308,9 @@ jQuery(function($){
$("#assignment_group_category").val("");
}
}).change();
$("#assignment_turnitin_enabled").change(function() {
$("#assignment_turnitin_settings").showIf($(this).attr('checked'));
}).change();
$("#assignment_peer_reviews").change(function() {
$("#assignment_peer_reviews_options").showIf($(this).attr('checked'));
if(!$(this).attr('checked')) {

View File

@ -19,12 +19,16 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
describe Turnitin::Client do
it "should submit attached files to turnitin" do
def turnitin_assignment
course_with_student(:active_all => true)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.turnitin_enabled = true
@assignment.save
end
it "should submit attached files to turnitin" do
turnitin_assignment
@submission = @assignment.submit_homework(@user, :submission_type => 'online_upload', :attachments => [attachment_model(:context => @user, :content_type => 'text/plain')])
@submission.reload
@submission.context.should_receive(:turnitin_settings).at_least(:once).and_return([:my_settings])
@ -33,7 +37,36 @@ describe Turnitin::Client do
api = Turnitin::Client.new('test_account', 'sekret')
Turnitin::Client.should_receive(:new).with(:my_settings).and_return(api)
api.should_receive(:createAssignment).with(@assignment).and_return(true)
api.should_receive(:createOrUpdateAssignment).with(@assignment).and_return(true)
api.should_receive(:enrollStudent).with(@course, @user).and_return(true)
Attachment.stub!(:instantiate).and_return(@attachment)
@attachment.should_receive(:open).and_return(:my_stub)
api.should_receive(:sendRequest).with(:submit_paper, '2', hash_including(:pdata => :my_stub))
@submission.submit_to_turnitin
end
it "should use the assignment's turnitin settings" do
turnitin_assignment
settings = {
:originality_report_visibility => 'after_grading',
:s_paper_check => '0',
:internet_check => '0',
:journal_check => '0',
:exclude_biblio => '0',
:exclude_quoted => '0',
:exclude_type => '1',
:exclude_value => '5'
}
@assignment.update_attributes(:turnitin_settings => settings)
@submission = @assignment.submit_homework(@user, :submission_type => 'online_upload', :attachments => [attachment_model(:context => @user, :content_type => 'text/plain')])
@submission.reload
@submission.context.should_receive(:turnitin_settings).at_least(:once).and_return([:my_settings])
job = Delayed::Job.last(:conditions => { :tag => 'Submission#submit_to_turnitin'})
job.should_not be_nil
api = Turnitin::Client.new('test_account', 'sekret')
Turnitin::Client.should_receive(:new).with(:my_settings).and_return(api)
api.should_receive(:sendRequest).with(:create_assignment, '2', hash_including(settings)).and_return(Nokogiri('<assignmentid>12345</assignmentid>'))
api.should_receive(:enrollStudent).with(@course, @user).and_return(true)
Attachment.stub!(:instantiate).and_return(@attachment)
@attachment.should_receive(:open).and_return(:my_stub)

View File

@ -288,4 +288,82 @@ describe "assignment selenium tests" do
driver.find_element(:css, '#rubrics .rubric .rubric_title .displaying .title').should include_text('new rubric')
end
context "turnitin" do
before do
course_with_teacher_logged_in
account = Account.default
account.turnitin_account_id = 'asdf'
account.turnitin_shared_secret = 'asdf'
account.save
@course.account = account
@course.save
end
def change_turnitin_settings
driver.find_element(:css, '.submission_type_option').should be_displayed
driver.find_element(:css, '.submission_type_option > option[value="online"]').click
driver.find_element(:id, 'assignment_online_text_entry').click
driver.find_element(:id, 'assignment_turnitin_settings').should_not be_displayed
driver.find_element(:id, 'assignment_turnitin_enabled').click
driver.find_element(:id, 'assignment_turnitin_settings').should be_displayed
driver.find_element(:css, '.show_turnitin_settings').click
wait_for_animations
driver.find_element(:id, 'turnitin_settings_form').should be_displayed
driver.find_element(:css, '#settings_originality_report_visibility > option[value="after_due_date"]').click # immediate -> after_due_date
driver.find_element(:id, 'settings_student_paper_check').click # 1 -> 0
driver.find_element(:id, 'settings_internet_check').click # 1 -> 0
driver.find_element(:id, 'settings_journal_check').click # 1 -> 0
driver.find_element(:id, 'settings_exclude_biblio').click # 1 -> 0
driver.find_element(:id, 'settings_exclude_quoted').click # 1 -> 0
driver.find_element(:id, 'settings_exclude_small_matches').click # 0 -> 1
driver.find_element(:id, 'settings_exclude_fewer_than_count').click # 0 -> 1
driver.find_element(:id, 'settings_exclude_value_count').send_keys("5") # '' -> 5
driver.find_element(:id, 'turnitin_settings_form').submit
wait_for_ajaximations
end
def expected_settings
{
'originality_report_visibility' => 'after_due_date',
's_paper_check' => '0',
'internet_check' => '0',
'journal_check' => '0',
'exclude_biblio' => '0',
'exclude_quoted' => '0',
'exclude_type' => '1',
'exclude_value' => '5'
}
end
it "should create turnitin settings" do
expect {
get "/courses/#{@course.id}/assignments/new"
driver.find_element(:id, 'assignment_title').send_keys('test assignment')
change_turnitin_settings
}.to_not change{ Assignment.count } # although we "saved" the dialog, we haven't actually posted anything yet
driver.find_element(:id, 'edit_assignment_form').submit
wait_for_ajaximations
assignment = Assignment.first(:order => "id desc")
assignment.turnitin_settings.should eql(expected_settings)
end
it "should edit turnitin settings" do
assignment = @course.assignments.create!(
:name => 'test assignment',
:due_at => (Time.now.utc + 2.days),
:assignment_group => @course.assignment_groups.create!(:name => "default")
)
get "/courses/#{@course.id}/assignments/#{assignment.id}/edit"
change_turnitin_settings
assignment.reload
assignment.turnitin_settings.should eql(expected_settings)
end
end
end