master courses: do some locked restrictions+icons for quizzes
closes #MC-62 Change-Id: I39f72492d622749d44fc6c29e96768b25f5b1a7a Reviewed-on: https://gerrit.instructure.com/99812 Tested-by: Jenkins Reviewed-by: Jeremy Stanley <jeremy@instructure.com> Product-Review: James Williams <jamesw@instructure.com> QA-Review: James Williams <jamesw@instructure.com>
This commit is contained in:
parent
ba69f5e14f
commit
0517c58e71
|
@ -513,6 +513,7 @@ class Quizzes::QuizzesApiController < ApplicationController
|
|||
# @returns Quiz
|
||||
def destroy
|
||||
if authorized_action(@quiz, @current_user, :delete)
|
||||
return render_unauthorized_action if editing_restricted?(@quiz)
|
||||
@quiz.destroy
|
||||
if accepts_jsonapi?
|
||||
head :no_content
|
||||
|
|
|
@ -264,6 +264,7 @@ class Quizzes::QuizzesController < ApplicationController
|
|||
|
||||
def edit
|
||||
if authorized_action(@quiz, @current_user, :update)
|
||||
return render_unauthorized_action if editing_restricted?(@quiz)
|
||||
add_crumb(@quiz.title, named_context_url(@context, :context_quiz_url, @quiz))
|
||||
@assignment = @quiz.assignment
|
||||
|
||||
|
@ -479,6 +480,7 @@ class Quizzes::QuizzesController < ApplicationController
|
|||
|
||||
def destroy
|
||||
if authorized_action(@quiz, @current_user, :delete)
|
||||
return render_unauthorized_action if editing_restricted?(@quiz)
|
||||
respond_to do |format|
|
||||
if @quiz.destroy
|
||||
format.html { redirect_to course_quizzes_url(@context) }
|
||||
|
|
|
@ -176,6 +176,7 @@ module Importers
|
|||
end
|
||||
end
|
||||
item ||= context.quizzes.temp_record
|
||||
item.mark_as_importing!(migration)
|
||||
new_record = item.new_record? || item.deleted?
|
||||
|
||||
hash[:due_at] ||= hash[:due_date]
|
||||
|
@ -223,7 +224,8 @@ module Importers
|
|||
item.save!
|
||||
build_assignment = false
|
||||
|
||||
if question_data
|
||||
skip_questions = migration.for_master_course_import? && item.edit_types_locked_for_overwrite_on_import.include?(:content)
|
||||
if question_data && !skip_questions
|
||||
question_data[:qq_ids] ||= {}
|
||||
hash[:questions] ||= []
|
||||
|
||||
|
|
|
@ -82,6 +82,17 @@ class Quizzes::Quiz < ActiveRecord::Base
|
|||
# simply_versioned callback updating the version.
|
||||
after_save :link_assignment_overrides, :if => :new_assignment_id?
|
||||
|
||||
include MasterCourses::Restrictor
|
||||
restrict_columns :content, [:title, :description]
|
||||
restrict_columns :settings, [
|
||||
:quiz_type, :assignment_group_id, :shuffle_answers, :time_limit,
|
||||
:anonymous_submissions, :scoring_policy, :allowed_attempts, :hide_results,
|
||||
:one_time_results, :show_correct_answers, :show_correct_answers_last_attempt,
|
||||
:hide_correct_answers_at, :one_question_at_a_time, :cant_go_back, :access_code,
|
||||
:ip_filter, :require_lockdown_browser, :require_lockdown_browser_for_results,
|
||||
:lock_at, :unlock_at
|
||||
]
|
||||
|
||||
# override has_one relationship provided by simply_versioned
|
||||
def current_version_unidirectional
|
||||
versions.limit(1)
|
||||
|
|
|
@ -51,6 +51,10 @@ class Quizzes::QuizQuestion < ActiveRecord::Base
|
|||
serialize :question_data
|
||||
after_save :update_quiz
|
||||
|
||||
include MasterCourses::CollectionRestrictor
|
||||
self.collection_owner_association = :quiz
|
||||
restrict_columns :content, [:question_data, :position, :quiz_group_id]
|
||||
|
||||
workflow do
|
||||
state :active
|
||||
state :deleted
|
||||
|
|
|
@ -260,6 +260,9 @@ module Quizzes
|
|||
hash['links']['quiz_statistics'] = hash.delete(:quiz_statistics_url)
|
||||
hash['links']['quiz_reports'] = hash.delete(:quiz_reports_url)
|
||||
end
|
||||
if serializer_option(:include_master_course_restrictions)
|
||||
hash.merge!(quiz.master_course_api_restriction_data)
|
||||
end
|
||||
hash
|
||||
end
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ $answersSprite: url(/images/answers_sprite.png);
|
|||
// quiz index
|
||||
li.quiz {
|
||||
cursor: pointer;
|
||||
.ellipses, i[class^=icon-]:not(.icon-publish):not(.icon-quiz):not(.icon-post-to-sis) {
|
||||
.ellipses, i[class^=icon-]:not(.icon-publish):not(.icon-quiz):not(.icon-post-to-sis):not(.icon-lock) {
|
||||
color: darken($ic-color-neutral, 40);
|
||||
}
|
||||
.ig-title {
|
||||
|
@ -2256,3 +2256,11 @@ li.quiz.quiz-loading-overrides {
|
|||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.master-course-cell i {
|
||||
margin-right: 12px;
|
||||
}
|
||||
.options-spacer {
|
||||
width: 52px;
|
||||
}
|
||||
|
||||
|
|
|
@ -46,9 +46,21 @@
|
|||
</span>
|
||||
{{/if}}
|
||||
<span class="sis-button" data-view="sis-button"></span>
|
||||
<span class="master-course-cell">
|
||||
{{#if is_master_course_content}}
|
||||
{{#if restricted_by_master_course}}
|
||||
<i class="icon-lock"></i>
|
||||
{{else}}
|
||||
<i class="icon-unlock icon-Line"></i>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</span>
|
||||
<span data-view="publish-icon" class="publish-icon"></span>
|
||||
|
||||
<div class="inline-block">
|
||||
{{#if restricted_by_master_course}}
|
||||
<div class="options-spacer"></div>
|
||||
{{else}}
|
||||
<a class="al-trigger al-trigger-gray" role="button" aria-haspopup="true" aria-owns="ui-id-{{id}}-1" href="#">
|
||||
<i class="icon-settings"></i>
|
||||
<i class="icon-mini-arrow-down"></i>
|
||||
|
@ -73,6 +85,7 @@
|
|||
</li>
|
||||
{{>ExternalTools/external_tools_menu quiz_menu_tools}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -112,7 +112,7 @@
|
|||
|
||||
<% if can_do(@quiz, @current_user, :update) || can_do(@quiz, @current_user, :grade) || can_do(@quiz, @current_user, :review_grades) %>
|
||||
<div class="header-group-right">
|
||||
<% if can_do(@quiz, @current_user, :update) %>
|
||||
<% if can_do(@quiz, @current_user, :update) && !editing_restricted?(@quiz) %>
|
||||
<a href="<%= edit_course_quiz_path(@context, @quiz) %>" class='btn edit_assignment_link quiz-edit-button' role='button'>
|
||||
<i class="icon-edit"></i> <%= admin_edit_title %>
|
||||
</a>
|
||||
|
@ -144,7 +144,7 @@
|
|||
</li>
|
||||
<% end %>
|
||||
|
||||
<% if can_do(@quiz, @current_user, :update) %>
|
||||
<% if can_do(@quiz, @current_user, :update) && !editing_restricted?(@quiz) %>
|
||||
<% if @quiz.locked? %>
|
||||
<%= form_for @quiz, as: :quiz, method: :put, :url => context_url(@context, :context_quiz_url, @quiz.id), :html => { :id => 'quiz_unlock_form' } do |f| %>
|
||||
<%= hidden_field_tag 'quiz[locked]', false %>
|
||||
|
@ -215,7 +215,7 @@
|
|||
<% end %>
|
||||
|
||||
<li role="presentation">
|
||||
<% if @quiz.grants_right?(@current_user, :delete) %>
|
||||
<% if @quiz.grants_right?(@current_user, :delete) && !editing_restricted?(@quiz) %>
|
||||
<a href="<%= context_url(@context, :context_quiz_url, @quiz) %>" class="delete_quiz_link" tabindex="-1" role="menuitem">
|
||||
<i class="icon-trash"><span class="screenreader-only"><%= admin_delete_title %></span></i> <%= admin_delete_title %>
|
||||
</a>
|
||||
|
|
|
@ -52,6 +52,12 @@ module Api::V1::Quiz
|
|||
|
||||
def quizzes_json(quizzes, context, user, session, options={})
|
||||
options.merge!(description_formatter: description_formatter(context, user))
|
||||
check_for_restrictions = master_courses? && context.grants_right?(user, session, :manage_assignments)
|
||||
if check_for_restrictions
|
||||
MasterCourses::Restrictor.preload_restrictions(quizzes)
|
||||
options[:include_master_course_restrictions] = true
|
||||
end
|
||||
|
||||
quizzes.map do |quiz|
|
||||
quiz_json(quiz, context, user, session, options)
|
||||
end
|
||||
|
|
|
@ -403,39 +403,51 @@ describe MasterCourses::MasterMigration do
|
|||
@copy_to = course_factory
|
||||
sub = @template.add_child_course!(@copy_to)
|
||||
|
||||
#TODO: quizzes and quiz questions
|
||||
#quiz = @copy_from.quizzes.create!
|
||||
#qq = quiz.quiz_questions.create!(:question_data => {'question_name' => 'test question', 'question_type' => 'essay_question'})
|
||||
quiz = @copy_from.quizzes.create!
|
||||
qq = quiz.quiz_questions.create!(:question_data => {'question_name' => 'test question', 'question_type' => 'essay_question'})
|
||||
bank = @copy_from.assessment_question_banks.create!(:title => 'bank')
|
||||
aq = bank.assessment_questions.create!(:question_data => {'question_name' => 'test question', 'question_type' => 'essay_question'})
|
||||
|
||||
run_master_migration
|
||||
|
||||
#copied_quiz = @copy_to.quizzes.where(:migration_id => mig_id(quiz)).first
|
||||
#copied_qq = copied_quiz.quiz_questions.where(:migration_id => mig_id(qq)).first
|
||||
copied_quiz = @copy_to.quizzes.where(:migration_id => mig_id(quiz)).first
|
||||
copied_qq = copied_quiz.quiz_questions.where(:migration_id => mig_id(qq)).first
|
||||
copied_bank = @copy_to.assessment_question_banks.where(:migration_id => mig_id(bank)).first
|
||||
copied_aq = copied_bank.assessment_questions.where(:migration_id => mig_id(aq)).first
|
||||
|
||||
new_child_text = "some childish text"
|
||||
copied_aq.question_data['question_text'] = new_child_text
|
||||
copied_aq.save!
|
||||
copied_qd = copied_qq.question_data
|
||||
copied_qd['question_text'] = new_child_text
|
||||
copied_qq.question_data = copied_qd
|
||||
copied_qq.save!
|
||||
|
||||
bank_child_tag = sub.child_content_tags.polymorphic_where(:content => copied_bank).first
|
||||
expect(bank_child_tag.downstream_changes).to include("assessment_questions_content") # treats all assessment questions like a column
|
||||
quiz_child_tag = sub.child_content_tags.polymorphic_where(:content => copied_quiz).first
|
||||
expect(quiz_child_tag.downstream_changes).to include("quiz_questions_content") # treats all assessment questions like a column
|
||||
|
||||
new_master_text = "some mastery text"
|
||||
bank.update_attribute(:title, new_master_text)
|
||||
aq.question_data['question_text'] = new_master_text
|
||||
aq.save!
|
||||
quiz.update_attribute(:title, new_master_text)
|
||||
qd = qq.question_data
|
||||
qd['question_text'] = new_master_text
|
||||
qq.question_data = qd
|
||||
qq.save!
|
||||
|
||||
[bank].each {|c| c.class.where(:id => c).update_all(:updated_at => 2.seconds.from_now)} # ensure it gets copied
|
||||
[bank, quiz].each {|c| c.class.where(:id => c).update_all(:updated_at => 2.seconds.from_now)} # ensure it gets copied
|
||||
|
||||
run_master_migration # re-copy all the content - but don't actually overwrite anything because it got changed downstream
|
||||
|
||||
expect(copied_bank.reload.title).to_not eq new_master_text
|
||||
expect(copied_aq.reload.question_data['question_text']).to_not eq new_master_text
|
||||
expect(copied_quiz.reload.title).to_not eq new_master_text
|
||||
expect(copied_qq.reload.question_data['question_text']).to_not eq new_master_text
|
||||
|
||||
[bank].each do |c|
|
||||
[bank, quiz].each do |c|
|
||||
mtag = @template.content_tag_for(c)
|
||||
Timecop.freeze(2.seconds.from_now) do
|
||||
mtag.update_attribute(:restrictions, {:content => true}) # should touch the content
|
||||
|
@ -446,6 +458,8 @@ describe MasterCourses::MasterMigration do
|
|||
|
||||
expect(copied_bank.reload.title).to eq new_master_text
|
||||
expect(copied_aq.reload.question_data['question_text']).to eq new_master_text
|
||||
expect(copied_quiz.reload.title).to eq new_master_text
|
||||
expect(copied_qq.reload.question_data['question_text']).to eq new_master_text
|
||||
end
|
||||
|
||||
context "master courses + external migrations" do
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
require_relative '../common'
|
||||
|
||||
describe "master courses - child courses - quiz locking" do
|
||||
include_context "in-process server selenium tests"
|
||||
|
||||
before :once do
|
||||
Account.default.enable_feature!(:master_courses)
|
||||
|
||||
@copy_from = course_factory(:active_all => true)
|
||||
@template = MasterCourses::MasterTemplate.set_as_master_course(@copy_from)
|
||||
@original_quiz = @copy_from.quizzes.create!(:title => "blah", :description => "bloo")
|
||||
@tag = @template.create_content_tag_for!(@original_quiz)
|
||||
|
||||
course_with_teacher(:active_all => true)
|
||||
@copy_to = @course
|
||||
@quiz_copy = @copy_to.quizzes.new(:title => "blah", :description => "bloo") # just create a copy directly instead of doing a real migration
|
||||
@quiz_copy.migration_id = @tag.migration_id
|
||||
@quiz_copy.save!
|
||||
end
|
||||
|
||||
before :each do
|
||||
user_session(@teacher)
|
||||
end
|
||||
|
||||
it "should not show the cog-menu options on the index when locked" do
|
||||
@tag.update_attribute(:restrictions, {:content => true, :settings => true})
|
||||
|
||||
get "/courses/#{@copy_to.id}/quizzes"
|
||||
|
||||
expect(f('.master-course-cell')).to contain_css('.icon-lock')
|
||||
|
||||
expect(f('.quiz')).to_not contain_css('.al-trigger')
|
||||
end
|
||||
|
||||
it "should show the cog-menu options on the index when not locked" do
|
||||
get "/courses/#{@copy_to.id}/quizzes"
|
||||
|
||||
expect(f('.master-course-cell')).to contain_css('.icon-unlock')
|
||||
|
||||
expect(f('.quiz')).to contain_css('.al-trigger')
|
||||
end
|
||||
|
||||
it "should not show the edit/delete options on the show page when locked" do
|
||||
@tag.update_attribute(:restrictions, {:content => true, :settings => true})
|
||||
|
||||
get "/courses/#{@copy_to.id}/quizzes/#{@quiz_copy.id}"
|
||||
|
||||
expect(f('#content')).to_not contain_css('.quiz-edit-button')
|
||||
f('.al-trigger').click
|
||||
expect(f('.al-options')).to_not contain_css('.delete_quiz_link')
|
||||
end
|
||||
|
||||
it "should show the edit/delete cog-menu options on the show when not locked" do
|
||||
get "/courses/#{@copy_to.id}/quizzes/#{@quiz_copy.id}"
|
||||
|
||||
expect(f('#content')).to contain_css('.quiz-edit-button')
|
||||
f('.al-trigger').click
|
||||
expect(f('.al-options')).to contain_css('.delete_quiz_link')
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue