Merge remote-tracking branch 'origin/master' into dev/fft
Conflicts: app/controllers/collections_controller.rb app/views/layouts/application.html.erb config/build.js lib/tasks/parallel_exclude.rb Change-Id: Ic9664c29d1469c13b514343915c5929dfb15c6ad
This commit is contained in:
commit
77138c4d4a
|
@ -15,6 +15,7 @@ db/*.sqlite
|
|||
db/demo
|
||||
config/GEM_HOME
|
||||
config/*.yml
|
||||
config/build.js
|
||||
config/environments/*-local.rb
|
||||
models.dot
|
||||
db/*sql
|
||||
|
@ -63,6 +64,7 @@ public/optimized
|
|||
!node_modules
|
||||
chromedriver.log
|
||||
public/plugins/
|
||||
public/javascripts/plugins/
|
||||
app/coffeescripts/plugins/
|
||||
app/views/jst/plugins/
|
||||
app/stylesheets/plugins/
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -48,7 +48,7 @@ gem 'require_relative', '1.0.1'
|
|||
gem 'ritex', '1.0.1'
|
||||
gem 'rscribd', '1.2.0'
|
||||
gem 'ruby-net-ldap', '0.0.4', :require => 'net/ldap'
|
||||
gem 'ruby-saml-mod', '0.1.14'
|
||||
gem 'ruby-saml-mod', '0.1.15'
|
||||
gem 'rubycas-client', '2.2.1'
|
||||
gem 'rubyzip', '0.9.4', :require => 'zip/zip'
|
||||
gem 'sanitize', '2.0.3'
|
||||
|
|
|
@ -93,10 +93,6 @@ define [
|
|||
item.render($contextsList)
|
||||
@contextSelectorItems[item.context.asset_string] = item
|
||||
|
||||
for contextCode in @apptGroup.context_codes when @contextSelectorItems[contextCode]
|
||||
@contextSelectorItems[contextCode].setState('on')
|
||||
@contextSelectorItems[contextCode].lock()
|
||||
|
||||
if @apptGroup.sub_context_codes.length > 0
|
||||
if @apptGroup.sub_context_codes[0].match /^group_category_/
|
||||
for c, item of @contextSelectorItems
|
||||
|
@ -113,6 +109,10 @@ define [
|
|||
item = @contextSelectorItems[context]
|
||||
item.sectionChange()
|
||||
item.lock()
|
||||
else
|
||||
for contextCode in @apptGroup.context_codes when @contextSelectorItems[contextCode]
|
||||
@contextSelectorItems[contextCode].setState('on')
|
||||
@contextSelectorItems[contextCode].lock()
|
||||
|
||||
$('.ag_contexts_done').click preventDefault closeCB
|
||||
|
||||
|
|
|
@ -6,9 +6,12 @@ define [
|
|||
class TimeBlockRow
|
||||
constructor: (@TimeBlockList, data={}) ->
|
||||
@locked = data.locked
|
||||
timeoutId = null
|
||||
@$row = $(timeBlockRowTemplate(data)).bind
|
||||
focusin: @focus
|
||||
focusout: => @$row.removeClass('focused')
|
||||
focusin: =>
|
||||
clearTimeout timeoutId
|
||||
@focus()
|
||||
focusout: => timeoutId = setTimeout((=> @$row.removeClass('focused')), 50)
|
||||
@inputs = {}
|
||||
unless @locked
|
||||
@$row.find('.date_field').date_field()
|
||||
|
|
|
@ -11,6 +11,7 @@ define [
|
|||
# we don't currently save the model directly, rather we do inbox actions
|
||||
inboxAction: (options) ->
|
||||
defaults =
|
||||
url: @url()
|
||||
method: 'POST'
|
||||
success: (data) => @list.updateItem(data)
|
||||
options = $.extend(true, {}, defaults, options)
|
||||
|
|
|
@ -62,8 +62,13 @@ define [
|
|||
|
||||
updated: (conversation, $node) ->
|
||||
@emptyCheck()
|
||||
if @isActive(conversation) and conversation.messages?[0]
|
||||
@app.addMessages(conversation.messages, 'prepend', 'slide')
|
||||
if @isActive(conversation.id) and conversation.get('workflow_state') is 'unread'
|
||||
@markAsUnread = setTimeout =>
|
||||
conversation.inboxAction
|
||||
method: 'PUT'
|
||||
data: {conversation: {workflow_state: 'read'}}
|
||||
success: (data) -> data.defer_visibility_check = true
|
||||
, 2000
|
||||
|
||||
removed: (data, $node) ->
|
||||
@emptyCheck()
|
||||
|
@ -99,11 +104,11 @@ define [
|
|||
@active and @active.id is id
|
||||
|
||||
deactivate: ->
|
||||
return unless @active and @item(@active.id)
|
||||
@$item(@active.id)?.removeClass('selected')
|
||||
if @scope is 'unread' # TODO: do an ajax request to set unread state, then remove when we deselect, depending on visible-ness
|
||||
@removeItem(@active)
|
||||
return unless @active and item = @item(@active.id)
|
||||
delete @active
|
||||
@$item(item.id)?.removeClass('selected')
|
||||
@removeItem(item) unless item.get('visible')
|
||||
clearTimeout @markAsUnread
|
||||
|
||||
ensureSelected: (id, activate=true) ->
|
||||
if activate # deselect any existing selection(s) ... soon we will have bulk conversation actions, so this will make more sense
|
||||
|
|
|
@ -32,7 +32,8 @@ define [
|
|||
data[pre + "[grade]"] = curves[idx]
|
||||
cnt++
|
||||
if cnt == 0
|
||||
@$dialog.errorBox I18n.t("errors.none_to_update", "None to Update")
|
||||
errorBox = @$dialog.errorBox I18n.t("errors.none_to_update", "None to Update")
|
||||
setTimeout((-> errorBox.fadeOut(-> errorBox.remove())), 3500)
|
||||
return false
|
||||
data
|
||||
success: (data) =>
|
||||
|
|
|
@ -17,6 +17,8 @@ define [
|
|||
'jquery.disableWhileLoading'
|
||||
], (I18n, helpDialogTemplate, $, _, INST, htmlEscape, preventDefault) ->
|
||||
|
||||
showEmail = not ENV.current_user_id
|
||||
|
||||
helpDialog =
|
||||
defaultLinks: [
|
||||
{
|
||||
|
@ -60,7 +62,7 @@ define [
|
|||
role is 'user' or
|
||||
(ENV.current_user_roles and role in ENV.current_user_roles)
|
||||
locals =
|
||||
showEmail: not ENV.current_user_id
|
||||
showEmail: showEmail
|
||||
helpLinks: links
|
||||
showBadBrowserMessage: INST.browser.ie
|
||||
browserVersion: INST.browser.version
|
||||
|
@ -75,7 +77,10 @@ define [
|
|||
initTicketForm: ->
|
||||
$form = @$dialog.find('#create_ticket').formSubmit
|
||||
disableWhileLoading: true
|
||||
required: ['error[subject]', 'error[comments]', 'error[user_perceived_severity]']
|
||||
required: ->
|
||||
requiredFields = ['error[subject]', 'error[comments]', 'error[user_perceived_severity]']
|
||||
requiredFields.push 'error[email]' if showEmail
|
||||
requiredFields
|
||||
success: =>
|
||||
@$dialog.dialog('close')
|
||||
$form.find(':input').val('')
|
||||
|
|
|
@ -53,7 +53,7 @@ define [
|
|||
items = @modelize(items)
|
||||
doTransitions = (items.length <= 1)
|
||||
for item in items
|
||||
if not item.get('visible')
|
||||
if not item.get('visible') and not item.get('defer_visibility_check')
|
||||
@_removeItem(item, doTransitions)
|
||||
else if @itemMap[item.id]?
|
||||
@_updateItem(item, doTransitions)
|
||||
|
|
|
@ -287,50 +287,10 @@ class AccountsController < ApplicationController
|
|||
@terms = @account.enrollment_terms.active
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { render :json => @current_batch.to_json(:include => :sis_batch_log_entries) }
|
||||
format.json { render :json => @current_batch.try(:api_json) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def sis_import_submit
|
||||
raise "SIS imports can only be executed on root accounts" unless @account.root_account?
|
||||
raise "SIS imports can only be executed on enabled accounts" unless @account.allow_sis_import
|
||||
|
||||
if authorized_action(@account, @current_user, :manage_sis)
|
||||
SisBatch.transaction do
|
||||
if !@account.current_sis_batch || !@account.current_sis_batch.importing?
|
||||
batch = SisBatch.create_with_attachment(@account, params[:import_type], params[:attachment])
|
||||
|
||||
if params[:batch_mode].to_i > 0
|
||||
batch.batch_mode = true
|
||||
if params[:batch_mode_term_id].present?
|
||||
batch.batch_mode_term = @account.enrollment_terms.active.find(params[:batch_mode_term_id])
|
||||
end
|
||||
end
|
||||
|
||||
batch.options ||= {}
|
||||
if params[:override_sis_stickiness].to_i > 0
|
||||
batch.options[:override_sis_stickiness] = true
|
||||
[:add_sis_stickiness, :clear_sis_stickiness].each do |option|
|
||||
batch.options[option] = true if params[option].to_i > 0
|
||||
end
|
||||
end
|
||||
|
||||
batch.save!
|
||||
|
||||
@account.current_sis_batch_id = batch.id
|
||||
@account.save
|
||||
batch.process
|
||||
render :json => batch.to_json(:include => :sis_batch_log_entries),
|
||||
:as_text => true
|
||||
else
|
||||
render :json => {:error=>true, :error_message=> t(:sis_import_in_process_notice, "An SIS import is already in process."), :batch_in_progress=>true}.to_json,
|
||||
:as_text => true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def courses_redirect
|
||||
redirect_to course_url(params[:id])
|
||||
|
|
|
@ -24,7 +24,7 @@ class AnnouncementsController < ApplicationController
|
|||
add_crumb(t(:announcements_crumb, "Announcements"))
|
||||
if authorized_action(@context, @current_user, :read)
|
||||
return if @context.class.const_defined?('TAB_ANNOUNCEMENTS') && !tab_enabled?(@context.class::TAB_ANNOUNCEMENTS)
|
||||
@announcements = @context.active_announcements.paginate(:page => params[:page]).reject{|a| a.locked_for?(@current_user, :check_policies => true) }
|
||||
@announcements = @context.active_announcements.paginate(:page => params[:page], :order => 'posted_at DESC').reject{|a| a.locked_for?(@current_user, :check_policies => true) }
|
||||
log_asset_access("announcements:#{@context.asset_string}", "announcements", "other")
|
||||
respond_to do |format|
|
||||
format.html { render }
|
||||
|
|
|
@ -922,16 +922,17 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def content_tag_redirect(context, tag, error_redirect_symbol)
|
||||
url_params = { :module_item_id => tag.id }
|
||||
if tag.content_type == 'Assignment'
|
||||
redirect_to named_context_url(context, :context_assignment_url, tag.content_id)
|
||||
redirect_to named_context_url(context, :context_assignment_url, tag.content_id, url_params)
|
||||
elsif tag.content_type == 'WikiPage'
|
||||
redirect_to named_context_url(context, :context_wiki_page_url, tag.content.url)
|
||||
redirect_to named_context_url(context, :context_wiki_page_url, tag.content.url, url_params)
|
||||
elsif tag.content_type == 'Attachment'
|
||||
redirect_to named_context_url(context, :context_file_url, tag.content_id)
|
||||
redirect_to named_context_url(context, :context_file_url, tag.content_id, url_params)
|
||||
elsif tag.content_type == 'Quiz'
|
||||
redirect_to named_context_url(context, :context_quiz_url, tag.content_id)
|
||||
redirect_to named_context_url(context, :context_quiz_url, tag.content_id, url_params)
|
||||
elsif tag.content_type == 'DiscussionTopic'
|
||||
redirect_to named_context_url(context, :context_discussion_topic_url, tag.content_id)
|
||||
redirect_to named_context_url(context, :context_discussion_topic_url, tag.content_id, url_params)
|
||||
elsif tag.content_type == 'ExternalUrl'
|
||||
@tag = tag
|
||||
@module = tag.context_module
|
||||
|
|
|
@ -67,7 +67,7 @@ class AssignmentsController < ApplicationController
|
|||
end
|
||||
@locked = @assignment.locked_for?(@current_user, :check_policies => true, :deep_check_if_needed => true)
|
||||
@unlocked = !@locked || @assignment.grants_rights?(@current_user, session, :update)[:update]
|
||||
@assignment_module = @assignment.context_module_tag
|
||||
@assignment_module = ContextModuleItem.find_tag_with_preferred([@assignment], params[:module_item_id])
|
||||
@assignment.context_module_action(@current_user, :read) if @unlocked && !@assignment.new_record?
|
||||
if @assignment.grants_right?(@current_user, session, :grade)
|
||||
visible_student_ids = @context.enrollments_visible_to(@current_user).find(:all, :select => 'user_id').map(&:user_id)
|
||||
|
|
|
@ -64,11 +64,14 @@ class CollectionsController < ApplicationController
|
|||
|
||||
SETTABLE_ATTRIBUTES = %w(name visibility)
|
||||
|
||||
# @API List collections
|
||||
# @API List user/group collections
|
||||
#
|
||||
# Returns the visible collections for the given user, returned most-recently-created first.
|
||||
# If the given user is the current user, then all collections will be
|
||||
# returned, otherwise only public collections will be returned.
|
||||
# Returns the visible collections for the given group or user, returned
|
||||
# most-recently-created first. If the given context is the current user or
|
||||
# a group to which the current user belongs, then all collections will be
|
||||
# returned, otherwise only public collections will be returned. In the former
|
||||
# case, if no collections exist for the context, a default, private
|
||||
# collection will be created and returned.
|
||||
#
|
||||
# @example_request
|
||||
# curl -H 'Authorization: Bearer <token>' \
|
||||
|
@ -80,10 +83,7 @@ class CollectionsController < ApplicationController
|
|||
scope = @context.collections.active.newest_first
|
||||
view_private = is_authorized_action?(@context.collections.new(:visibility => 'private'), @current_user, :read)
|
||||
|
||||
if view_private && scope.empty?
|
||||
name = @context.try(:default_collection_name)
|
||||
@context.collections.create(:name => name, :visibility => 'private') if name
|
||||
end
|
||||
ensure_default_collection_for(@context) if view_private
|
||||
|
||||
unless view_private
|
||||
scope = scope.public
|
||||
|
@ -93,6 +93,38 @@ class CollectionsController < ApplicationController
|
|||
render :json => collections_json(@collections, @current_user, session)
|
||||
end
|
||||
|
||||
# @API List pinnable collections
|
||||
#
|
||||
# Returns the list of collections to which the current user has permission to
|
||||
# post. For each possible collection context (the current user and each
|
||||
# community she belongs to) if no collections exist for the context,
|
||||
# a default, private collection will be created and included in the returned
|
||||
# list.
|
||||
#
|
||||
# @example_request
|
||||
# curl -H 'Authorization: Bearer <token>' \
|
||||
# https://<canvas>/api/v1/collections
|
||||
#
|
||||
def list
|
||||
route = polymorphic_url([:api, :v1, :collections])
|
||||
|
||||
# make sure there is a default colleciton for the current user and all
|
||||
# communities to which they belong
|
||||
ensure_default_collection_for(@current_user)
|
||||
current_communities = @current_user.current_groups.scoped(:joins => :group_category, :conditions => { :group_categories => { :role => 'communities' } }).all
|
||||
if current_communities.present?
|
||||
preload_groups_collections_counts(current_communities)
|
||||
current_communities.each{ |g| ensure_default_collection_for(g) }
|
||||
end
|
||||
|
||||
scope = Collection.active.newest_first.scoped(:conditions => [<<-SQL, @current_user.id, current_communities.map(&:id)])
|
||||
(context_type='User' AND context_id=?) OR (context_type='Group' AND context_id IN (?))
|
||||
SQL
|
||||
|
||||
@collections = Api.paginate(scope, self, route)
|
||||
render :json => collections_json(@collections, @current_user, session)
|
||||
end
|
||||
|
||||
# @API Get a single collection
|
||||
#
|
||||
# Returns information on an individual collection. If the collection is
|
||||
|
@ -251,5 +283,26 @@ class CollectionsController < ApplicationController
|
|||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_default_collection_for(context)
|
||||
precount = @collections_counts.try(:[], context.id) if context.is_a?(Group)
|
||||
|
||||
if (precount.present? && precount == 0) || (!precount.present? && context.collections.active.empty?)
|
||||
name = context.try(:default_collection_name)
|
||||
context.collections.create(:name => name, :visibility => 'private') if name
|
||||
end
|
||||
end
|
||||
|
||||
def preload_groups_collections_counts(groups)
|
||||
counts_data = Collection.connection.execute(Collection.send(:sanitize_sql_array, [<<-SQL, groups.map(&:id)])).to_a
|
||||
SELECT context_id AS group_id, COUNT(*) AS collections_count
|
||||
FROM collections
|
||||
WHERE context_id IN (?) AND context_type='Group' AND workflow_state='active'
|
||||
GROUP BY context_id
|
||||
SQL
|
||||
@collections_counts = {}
|
||||
counts_data.each do |cd|
|
||||
@collections_counts[cd['group_id'].to_i] = cd['collections_count'].to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -269,16 +269,25 @@ class ContextModulesController < ApplicationController
|
|||
@modules = @context.context_modules.active
|
||||
@tags = @context.context_module_tags.active.sort_by{|t| t.position ||= 999}
|
||||
result = {}
|
||||
result[:current_item] = @tags.detect{|t| t.content_type == type && t.content_id == id }
|
||||
if !result[:current_item]
|
||||
obj = @context.find_asset(params[:id], [:attachment, :discussion_topic, :assignment, :quiz, :wiki_page, :content_tag])
|
||||
|
||||
if obj.is_a?(ContentTag)
|
||||
result[:current_item] = @tags.detect{|t| t.id == obj.id }
|
||||
elsif obj.is_a?(DiscussionTopic) && obj.assignment_id
|
||||
result[:current_item] = @tags.detect{|t| t.content_type == 'Assignment' && t.content_id == obj.assignment_id }
|
||||
elsif obj.is_a?(Quiz) && obj.assignment_id
|
||||
result[:current_item] = @tags.detect{|t| t.content_type == 'Assignment' && t.content_id == obj.assignment_id }
|
||||
possible_tags = @tags.find_all {|t| t.content_type == type && t.content_id == id }
|
||||
if possible_tags.size > 1
|
||||
# if there's more than one tag for the item, but the caller didn't
|
||||
# specify which one they want, we don't want to return any information.
|
||||
# this way the module item prev/next links won't appear with misleading navigation info.
|
||||
if params[:module_item_id]
|
||||
result[:current_item] = possible_tags.detect { |t| t.id == params[:module_item_id].to_i }
|
||||
end
|
||||
else
|
||||
result[:current_item] = possible_tags.first
|
||||
if !result[:current_item]
|
||||
obj = @context.find_asset(params[:id], [:attachment, :discussion_topic, :assignment, :quiz, :wiki_page, :content_tag])
|
||||
if obj.is_a?(ContentTag)
|
||||
result[:current_item] = @tags.detect{|t| t.id == obj.id }
|
||||
elsif obj.is_a?(DiscussionTopic) && obj.assignment_id
|
||||
result[:current_item] = @tags.detect{|t| t.content_type == 'Assignment' && t.content_id == obj.assignment_id }
|
||||
elsif obj.is_a?(Quiz) && obj.assignment_id
|
||||
result[:current_item] = @tags.detect{|t| t.content_type == 'Assignment' && t.content_id == obj.assignment_id }
|
||||
end
|
||||
end
|
||||
end
|
||||
result[:current_item].evaluate_for(@current_user) rescue nil
|
||||
|
|
|
@ -222,6 +222,10 @@ class ConversationsController < ApplicationController
|
|||
# @argument filter [optional, course_id|group_id|user_id]
|
||||
# Used when generating "visible" in the API response. See the explanation
|
||||
# under the index API action
|
||||
# @argument auto_mark_as_read Boolean, default true. If true, unread
|
||||
# conversations will be automatically marked as read. This will default
|
||||
# to false in a future API release, so clients should explicitly send
|
||||
# true if that is the desired behavior.
|
||||
#
|
||||
# @response_field participants Array of relevant users. Includes current
|
||||
# user. If there are forwarded messages in this conversation, the authors
|
||||
|
@ -306,7 +310,7 @@ class ConversationsController < ApplicationController
|
|||
return redirect_to conversations_path(:scope => scope, :id => @conversation.conversation_id, :message => params[:message])
|
||||
end
|
||||
|
||||
@conversation.update_attribute(:workflow_state, "read") if @conversation.unread?
|
||||
@conversation.update_attribute(:workflow_state, "read") if @conversation.unread? && auto_mark_as_read?
|
||||
messages = @conversation.messages
|
||||
ConversationMessage.send(:preload_associations, messages, :asset)
|
||||
submissions = messages.map(&:submission).compact
|
||||
|
@ -974,6 +978,7 @@ class ConversationsController < ApplicationController
|
|||
}
|
||||
end
|
||||
|
||||
# TODO API v2: default to true, like we do in the UI
|
||||
def interleave_submissions
|
||||
params[:interleave_submissions] || !api_request?
|
||||
end
|
||||
|
@ -986,4 +991,10 @@ class ConversationsController < ApplicationController
|
|||
def blank_fallback
|
||||
params[:blank_avatar_fallback] || @blank_fallback
|
||||
end
|
||||
|
||||
# TODO API v2: default to false, like we do in the UI
|
||||
def auto_mark_as_read?
|
||||
params[:auto_mark_as_read] ||= api_request?
|
||||
Canvas::Plugin.value_to_boolean(params[:auto_mark_as_read])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -191,6 +191,8 @@ class DiscussionTopicsController < ApplicationController
|
|||
else
|
||||
format.html do
|
||||
|
||||
@context_module_tag = ContextModuleItem.find_tag_with_preferred([@topic, @topic.root_topic, @topic.assignment], params[:module_item_id])
|
||||
@sequence_asset = @context_module_tag.try(:content)
|
||||
env_hash = {
|
||||
:TOPIC => {
|
||||
:ID => @topic.id,
|
||||
|
|
|
@ -54,6 +54,8 @@ class EnrollmentsApiController < ApplicationController
|
|||
# @response_field user_id The unique id of the user.
|
||||
# @response_field html_url The URL to the Canvas web UI page for this course enrollment.
|
||||
# @response_field grades[html_url] The URL to the Canvas web UI page for the user's grades, if this is a student enrollment.
|
||||
# @response_field grades[current_grade] The user's current grade in the class. Only included if user has permissions to view this grade.
|
||||
# @response_field grades[final_grade] The user's final grade for the class. Only included if user has permissions to view this grade.
|
||||
# @response_field user[id] The unique id of the user.
|
||||
# @response_field user[login_id] The unique login of the user.
|
||||
# @response_field user[name] The name of the user.
|
||||
|
@ -239,9 +241,11 @@ class EnrollmentsApiController < ApplicationController
|
|||
# Returns an ActiveRecord scope of enrollments on success, false on failure.
|
||||
def course_index_enrollments(scope_arguments)
|
||||
if authorized_action(@context, @current_user, :read_roster)
|
||||
scope_arguments[:conditions].include?(:workflow_state) ?
|
||||
@context.enrollments.scoped(scope_arguments) :
|
||||
@context.current_enrollments.scoped(scope_arguments)
|
||||
scope = @context.enrollments_visible_to(@current_user, :type => :all, :include_priors => true).scoped(scope_arguments)
|
||||
unless scope_arguments[:conditions].include?(:workflow_state)
|
||||
scope = scope.scoped(:conditions => ['enrollments.workflow_state NOT IN (?)', ['rejected', 'completed', 'deleted', 'inactive']])
|
||||
end
|
||||
scope
|
||||
else
|
||||
false
|
||||
end
|
||||
|
|
|
@ -124,8 +124,6 @@ class FilesController < ApplicationController
|
|||
if authorized_action(@attachment,@current_user,:read)
|
||||
if @attachment.grants_right?(@current_user, nil, :download)
|
||||
@headers = false
|
||||
@tag = @attachment.context_module_tag
|
||||
@module = @attachment.context_module_tag.context_module rescue nil
|
||||
render
|
||||
else
|
||||
show
|
||||
|
|
|
@ -175,7 +175,7 @@ class GradebooksController < ApplicationController
|
|||
cancel_cache_buster
|
||||
Enrollment.recompute_final_score_if_stale @context
|
||||
send_data(
|
||||
@context.gradebook_to_csv(:include_sis_id => @context.grants_rights?(@current_user, session, :read_sis, :manage_sis).values.any?),
|
||||
@context.gradebook_to_csv(:include_sis_id => @context.grants_rights?(@current_user, session, :read_sis, :manage_sis).values.any?, :user => @current_user),
|
||||
:type => "text/csv",
|
||||
:filename => t('grades_filename', "Grades").gsub(/ /, "_") + "-" + @context.name.to_s.gsub(/ /, "_") + ".csv",
|
||||
:disposition => "attachment"
|
||||
|
|
|
@ -20,8 +20,7 @@
|
|||
#
|
||||
# Group memberships are the objects that tie users and groups together.
|
||||
#
|
||||
# A Group Membership object looks like:
|
||||
# !!!javascript
|
||||
# @object Group Membership
|
||||
# {
|
||||
# // The id of the membership object
|
||||
# id: 92
|
||||
|
@ -62,6 +61,8 @@ class GroupMembershipsController < ApplicationController
|
|||
# curl https://<canvas>/api/v1/groups/<group_id>/memberships \
|
||||
# -F 'filter_states[]=invited&filter_states[]=requested' \
|
||||
# -H 'Authorization: Bearer <token>'
|
||||
#
|
||||
# @returns [Group Membership]
|
||||
def index
|
||||
if authorized_action(@group, @current_user, :read_roster)
|
||||
memberships_route = polymorphic_url([:api_v1, @group, :memberships])
|
||||
|
@ -91,14 +92,7 @@ class GroupMembershipsController < ApplicationController
|
|||
# -F 'user_id=self'
|
||||
# -H 'Authorization: Bearer <token>'
|
||||
#
|
||||
# @example_response
|
||||
# {
|
||||
# id: 102,
|
||||
# group_id: 6,
|
||||
# user_id: 3,
|
||||
# workflow_state: "requested",
|
||||
# moderator: false
|
||||
# }
|
||||
# @returns Group Membership
|
||||
def create
|
||||
@user = api_find(User, params[:user_id])
|
||||
if authorized_action(GroupMembership.new(:group => @group, :user => @user), @current_user, :create)
|
||||
|
@ -123,14 +117,7 @@ class GroupMembershipsController < ApplicationController
|
|||
# -F 'moderator=true'
|
||||
# -H 'Authorization: Bearer <token>'
|
||||
#
|
||||
# @example_response
|
||||
# {
|
||||
# id: 102,
|
||||
# group_id: 6,
|
||||
# user_id: 3,
|
||||
# workflow_state: "accepted",
|
||||
# moderator: true
|
||||
# }
|
||||
# @returns Group Membership
|
||||
def update
|
||||
find_membership
|
||||
if authorized_action(@membership, @current_user, :update)
|
||||
|
|
|
@ -30,9 +30,7 @@
|
|||
# context for many other types of functionality and interaction, such as
|
||||
# collections, discussions, wikis, and shared files.
|
||||
#
|
||||
# A Group object looks like:
|
||||
#
|
||||
# !!!javascript
|
||||
# @object Group
|
||||
# {
|
||||
# // The ID of the group.
|
||||
# id: 17,
|
||||
|
@ -170,17 +168,7 @@ class GroupsController < ApplicationController
|
|||
# curl https://<canvas>/api/v1/groups/<group_id> \
|
||||
# -H 'Authorization: Bearer <token>'
|
||||
#
|
||||
# @example_response
|
||||
# {
|
||||
# id: 13,
|
||||
# name: "Mary's Group",
|
||||
# description: "A group for my friends",
|
||||
# is_public: false,
|
||||
# join_level: "parent_context_request",
|
||||
# members_count: 3,
|
||||
# avatar_url: "https://<canvas>/files/avatar_image.png",
|
||||
# group_category_id: 2,
|
||||
# }
|
||||
# @returns Group
|
||||
def show
|
||||
find_group
|
||||
|
||||
|
@ -262,17 +250,7 @@ class GroupsController < ApplicationController
|
|||
# -F 'join_level=parent_context_auto_join' \
|
||||
# -H 'Authorization: Bearer <token>'
|
||||
#
|
||||
# @example_response
|
||||
# {
|
||||
# id: 25,
|
||||
# name: "Math Teachers",
|
||||
# description: "A place to gather resources for our classes.",
|
||||
# is_public: true,
|
||||
# join_level: "parent_context_auto_join",
|
||||
# members_count: 13,
|
||||
# avatar_url: "https://<canvas>/files/avatar_image.png",
|
||||
# group_category_id: 7
|
||||
# }
|
||||
# @returns Group
|
||||
def create
|
||||
# only allow community groups from the api right now
|
||||
if api_request?
|
||||
|
@ -338,17 +316,7 @@ class GroupsController < ApplicationController
|
|||
# -F 'join_level=parent_context_request' \
|
||||
# -H 'Authorization: Bearer <token>'
|
||||
#
|
||||
# @example_response
|
||||
# {
|
||||
# id: 25,
|
||||
# name: "Algebra Teachers",
|
||||
# description: "A place to gather resources for our classes.",
|
||||
# is_public: true,
|
||||
# join_level: "parent_context_request",
|
||||
# members_count: 13,
|
||||
# avatar_url: "https://<canvas>/files/avatar_image.png",
|
||||
# group_category_id: 7
|
||||
# }
|
||||
# @returns Group
|
||||
def update
|
||||
find_group
|
||||
if !api_request? && params[:group] && params[:group][:group_category_id]
|
||||
|
@ -386,17 +354,7 @@ class GroupsController < ApplicationController
|
|||
# -X DELETE \
|
||||
# -H 'Authorization: Bearer <token>'
|
||||
#
|
||||
# @example_response
|
||||
# {
|
||||
# id: 144,
|
||||
# name: "My Group",
|
||||
# description: null,
|
||||
# is_public: false,
|
||||
# join_level: "invitation_only",
|
||||
# members_count: 0,
|
||||
# avatar_url: "https://<canvas>/files/avatar_image.png",
|
||||
# group_category_id: 9
|
||||
# }
|
||||
# @returns Group
|
||||
def destroy
|
||||
find_group
|
||||
if authorized_action(@group, @current_user, :delete)
|
||||
|
|
|
@ -41,13 +41,7 @@ class PseudonymSessionsController < ApplicationController
|
|||
@is_cas = @domain_root_account.cas_authentication? && @is_delegated
|
||||
@is_saml = @domain_root_account.saml_authentication? && @is_delegated
|
||||
if @is_cas && !params[:no_auto]
|
||||
if session[:exit_frame]
|
||||
session.delete(:exit_frame)
|
||||
render :template => 'shared/exit_frame', :layout => false, :locals => {
|
||||
:url => login_url(params)
|
||||
}
|
||||
return
|
||||
elsif params[:ticket]
|
||||
if params[:ticket]
|
||||
# handle the callback from CAS
|
||||
logger.info "Attempting CAS login with ticket #{params[:ticket]} in account #{@domain_root_account.id}"
|
||||
st = CASClient::ServiceTicket.new(params[:ticket], login_url)
|
||||
|
|
|
@ -134,6 +134,8 @@ class QuizzesController < ApplicationController
|
|||
@locked_reason = @quiz.locked_for?(@current_user, :check_policies => true, :deep_check_if_needed => true)
|
||||
@locked = @locked_reason && !@quiz.grants_right?(@current_user, session, :update)
|
||||
|
||||
@context_module_tag = ContextModuleItem.find_tag_with_preferred([@quiz, @quiz.assignment], params[:module_item_id])
|
||||
@sequence_asset = @context_module_tag.try(:content)
|
||||
@quiz.context_module_action(@current_user, :read) if !@locked
|
||||
|
||||
@assignment = @quiz.assignment
|
||||
|
|
|
@ -91,9 +91,15 @@ class SisImportsApiController < ApplicationController
|
|||
#
|
||||
# @argument clear_sis_stickiness ["1"] This option, if present, will clear "stickiness" from all fields touched by this import. Requires that 'override_sis_stickiness' is also provided. If 'add_sis_stickiness' is also provided, 'clear_sis_stickiness' will overrule the behavior of 'add_sis_stickiness'
|
||||
def create
|
||||
if authorized_action(@account, @current_user, :manage)
|
||||
if authorized_action(@account, @current_user, :manage_sis)
|
||||
params[:import_type] ||= 'instructure_csv'
|
||||
raise "invalid import type parameter" unless SisBatch.valid_import_types.has_key?(params[:import_type])
|
||||
|
||||
if !api_request? && @account.current_sis_batch.try(:importing?)
|
||||
return render :json => {:error=>true, :error_message=> t(:sis_import_in_process_notice, "An SIS import is already in process."), :batch_in_progress=>true}.to_json,
|
||||
:as_text => true
|
||||
end
|
||||
|
||||
file_obj = nil
|
||||
if params.has_key?(:attachment)
|
||||
file_obj = params[:attachment]
|
||||
|
@ -156,6 +162,12 @@ class SisImportsApiController < ApplicationController
|
|||
unless Setting.get('skip_sis_jobs_account_ids', '').split(',').include?(@account.global_id.to_s)
|
||||
batch.process
|
||||
end
|
||||
|
||||
unless api_request?
|
||||
@account.current_sis_batch_id = batch.id
|
||||
@account.save
|
||||
end
|
||||
|
||||
render :json => batch.api_json
|
||||
end
|
||||
end
|
||||
|
@ -164,7 +176,7 @@ class SisImportsApiController < ApplicationController
|
|||
#
|
||||
# Get the status of an already created SIS import.
|
||||
def show
|
||||
if authorized_action(@account, @current_user, :manage)
|
||||
if authorized_action(@account, @current_user, :manage_sis)
|
||||
@batch = SisBatch.find(params[:id])
|
||||
raise "Sis Import not found" unless @batch
|
||||
raise "Batch does not match account" unless @batch.account.id == @account.id
|
||||
|
|
|
@ -310,7 +310,7 @@ class SubmissionsApiController < ApplicationController
|
|||
|
||||
def visible_user_ids
|
||||
scope = if @section
|
||||
@context.enrollments_visible_to(@current_user, false, false, [@section.id])
|
||||
@context.enrollments_visible_to(@current_user, :section_ids => [@section.id])
|
||||
else
|
||||
@context.enrollments_visible_to(@current_user)
|
||||
end
|
||||
|
|
|
@ -17,6 +17,74 @@
|
|||
#
|
||||
|
||||
# @API Submissions
|
||||
#
|
||||
# @object Submission
|
||||
# {
|
||||
# // The submissions's assignment id
|
||||
# assignment_id: 23,
|
||||
#
|
||||
# // The submission's assignment (see the assignments API) (optional)
|
||||
# assignment: Assignment
|
||||
#
|
||||
# // The submission's course (see the course API) (optional)
|
||||
# course: Course
|
||||
#
|
||||
# // If multiple submissions have been made, this is the attempt number.
|
||||
# attempt: 1,
|
||||
#
|
||||
# // The content of the submission, if it was submitted directly in a
|
||||
# // text field.
|
||||
# body: "There are three factors too...",
|
||||
#
|
||||
# // The grade for the submission, translated into the assignment grading
|
||||
# // scheme (so a letter grade, for example).
|
||||
# grade: "A-",
|
||||
#
|
||||
# // A boolean flag which is false if the student has re-submitted since
|
||||
# // the submission was last graded.
|
||||
# grade_matches_current_submission: true,
|
||||
#
|
||||
# // URL to the submission. This will require the user to log in.
|
||||
# html_url: "http://example.com/courses/255/assignments/543/submissions/134",
|
||||
#
|
||||
# // URL to the submission preview. This will require the user to log in.
|
||||
# preview_url: "http://example.com/courses/255/assignments/543/submissions/134?preview=1",
|
||||
#
|
||||
# // The raw score
|
||||
# score: 13.5
|
||||
#
|
||||
# // Associated comments for a submission (optional)
|
||||
# submission_comments: [
|
||||
# {
|
||||
# author_id: 134
|
||||
# author_name: "Toph Beifong",
|
||||
# comment: "Well here's the thing...",
|
||||
# created_at: "2012-01-01T01:00:00Z",
|
||||
# media_comment: {
|
||||
# content-type: "audio/mp4",
|
||||
# display_name: "something",
|
||||
# media_id: "3232",
|
||||
# media_type: "audio",
|
||||
# url: "http://example.com/media_url"
|
||||
# }
|
||||
# }
|
||||
# ],
|
||||
#
|
||||
# // The types of submission
|
||||
# // ex: ("online_text_entry"|"online_url"|"online_upload"|"media_recording")
|
||||
# submission_type: "online_text_entry",
|
||||
#
|
||||
# // The timestamp when the assignment was submitted, if an actual
|
||||
# // submission has been made.
|
||||
# submitted_at: "2012-01-01T01:00:00Z",
|
||||
#
|
||||
# // The URL of the submission if the submission is a "online_url" submission.
|
||||
# url: null,
|
||||
#
|
||||
# // The id of the user who created the submission
|
||||
# user_id: 134
|
||||
# }
|
||||
#
|
||||
class SubmissionsController < ApplicationController
|
||||
include GoogleDocs
|
||||
before_filter :get_course_from_section, :only => :create
|
||||
|
|
|
@ -325,17 +325,7 @@ class UsersController < ApplicationController
|
|||
#
|
||||
# Submission:
|
||||
#
|
||||
# !!!javascript
|
||||
# {
|
||||
# 'type': 'Submission',
|
||||
# 'grade': '12',
|
||||
# 'score': 12,
|
||||
# 'assignment': {
|
||||
# 'title': 'Assignment 3',
|
||||
# 'id': 5678,
|
||||
# 'points_possible': 15
|
||||
# }
|
||||
# }
|
||||
# Returns an API {api:Submissions:Submission Submission} with its Course and Assignment data.
|
||||
#
|
||||
# Conference:
|
||||
#
|
||||
|
@ -1114,7 +1104,7 @@ class UsersController < ApplicationController
|
|||
enrollments = student.student_enrollments.active.all(:include => :course)
|
||||
enrollments.each do |enrollment|
|
||||
should_include = enrollment.course.user_has_been_teacher?(@teacher) &&
|
||||
enrollment.course.enrollments_visible_to(@teacher, true).find_by_id(enrollment.id) &&
|
||||
enrollment.course.enrollments_visible_to(@teacher, :include_priors => true).find_by_id(enrollment.id) &&
|
||||
enrollment.course.grants_right?(@current_user, :read_reports)
|
||||
if should_include
|
||||
Enrollment.recompute_final_score_if_stale(enrollment.course, student) { enrollment.reload }
|
||||
|
@ -1134,7 +1124,7 @@ class UsersController < ApplicationController
|
|||
redirect_to_referrer_or_default(root_url)
|
||||
elsif authorized_action(course, @current_user, :read_reports)
|
||||
Enrollment.recompute_final_score_if_stale(course)
|
||||
@courses[course] = teacher_activity_report(@teacher, course, course.enrollments_visible_to(@teacher, true))
|
||||
@courses[course] = teacher_activity_report(@teacher, course, course.enrollments_visible_to(@teacher, :include_priors => true))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -336,10 +336,11 @@ module ApplicationHelper
|
|||
|
||||
# Returns a <script> tag for each registered js_bundle
|
||||
def include_js_bundles
|
||||
paths = js_bundles.map do |(bundle,plugin)|
|
||||
paths = js_bundles.inject([]) do |ary, (bundle, plugin)|
|
||||
base_url = js_base_url
|
||||
base_url = "/plugins/#{plugin}#{base_url}" if plugin
|
||||
"#{base_url}/compiled/bundles/#{bundle}.js"
|
||||
base_url += "/plugins/#{plugin}" if plugin
|
||||
ary.concat(Canvas::RequireJs.extensions_for(bundle, 'plugins/')) unless use_optimized_js?
|
||||
ary << "#{base_url}/compiled/bundles/#{bundle}.js"
|
||||
end
|
||||
javascript_include_tag *paths
|
||||
end
|
||||
|
|
|
@ -24,6 +24,7 @@ class Assignment < ActiveRecord::Base
|
|||
include HasContentTags
|
||||
include CopyAuthorizedLinks
|
||||
include Mutable
|
||||
include ContextModuleItem
|
||||
|
||||
attr_accessible :title, :name, :description, :due_at, :points_possible,
|
||||
:min_score, :max_score, :mastery_score, :grading_type, :submission_types,
|
||||
|
@ -40,7 +41,6 @@ class Assignment < ActiveRecord::Base
|
|||
has_one :quiz
|
||||
belongs_to :assignment_group
|
||||
has_one :discussion_topic, :conditions => ['discussion_topics.root_topic_id IS NULL'], :order => 'created_at'
|
||||
has_one :context_module_tag, :as => :content, :class_name => 'ContentTag', :conditions => ['content_tags.tag_type = ? AND workflow_state != ?', 'context_module', 'deleted'], :include => {:context_module => [:context_module_progressions, :content_tags]}
|
||||
has_many :learning_outcome_tags, :as => :content, :class_name => 'ContentTag', :conditions => ['content_tags.tag_type = ? AND content_tags.workflow_state != ?', 'learning_outcome', 'deleted'], :include => :learning_outcome
|
||||
has_one :rubric_association, :as => :association, :conditions => ['rubric_associations.purpose = ?', "grading"], :order => :created_at, :include => :rubric
|
||||
has_one :rubric, :through => :rubric_association
|
||||
|
@ -115,7 +115,8 @@ class Assignment < ActiveRecord::Base
|
|||
:process_if_quiz,
|
||||
:default_values,
|
||||
:update_submissions_if_details_changed,
|
||||
:maintain_group_category_attribute
|
||||
:maintain_group_category_attribute,
|
||||
:process_if_topic
|
||||
|
||||
after_save :update_grades_if_details_changed,
|
||||
:generate_reminders_if_changed,
|
||||
|
@ -335,12 +336,13 @@ class Assignment < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def context_module_action(user, action, points=nil)
|
||||
self.context_module_tag.context_module_action(user, action, points) if self.context_module_tag
|
||||
if self.submission_types == 'discussion_topic' && self.discussion_topic && self.discussion_topic.context_module_tag
|
||||
self.discussion_topic.context_module_tag.context_module_action(user, action, points)
|
||||
elsif self.submission_types == 'online_quiz' && self.quiz && self.quiz.context_module_tag
|
||||
self.quiz.context_module_tag.context_module_action(user, action, points)
|
||||
tags_to_update = self.context_module_tags.to_a
|
||||
if self.submission_types == 'discussion_topic' && self.discussion_topic
|
||||
tags_to_update += self.discussion_topic.context_module_tags
|
||||
elsif self.submission_types == 'online_quiz' && self.quiz
|
||||
tags_to_update += self.quiz.context_module_tags
|
||||
end
|
||||
tags_to_update.each { |tag| tag.context_module_action(user, action, points) }
|
||||
end
|
||||
|
||||
set_broadcast_policy do |p|
|
||||
|
@ -494,6 +496,15 @@ class Assignment < ActiveRecord::Base
|
|||
end
|
||||
protected :process_if_quiz
|
||||
|
||||
def process_if_topic
|
||||
if self.submission_types == "discussion_topic"
|
||||
#8569: discussion topics don't have lock-after date, so clear this on conversion
|
||||
self.lock_at = nil
|
||||
end
|
||||
self
|
||||
end
|
||||
protected :process_if_topic
|
||||
|
||||
def grading_scheme
|
||||
if self.grading_standard
|
||||
self.grading_standard.grading_scheme
|
||||
|
@ -680,17 +691,16 @@ class Assignment < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def locked_for?(user=nil, opts={})
|
||||
@locks ||= {}
|
||||
locked = false
|
||||
return false if opts[:check_policies] && self.grants_right?(user, nil, :update)
|
||||
@locks[user ? user.id : 0] ||= Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
|
||||
Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
|
||||
locked = false
|
||||
if (self.unlock_at && self.unlock_at > Time.now)
|
||||
locked = {:asset_string => self.asset_string, :unlock_at => self.unlock_at}
|
||||
elsif (self.lock_at && self.lock_at <= Time.now)
|
||||
locked = {:asset_string => self.asset_string, :lock_at => self.lock_at}
|
||||
elsif (self.could_be_locked && self.context_module_tag && self.context_module_tag.locked_for?(user, opts[:deep_check_if_needed]))
|
||||
locked = {:asset_string => self.asset_string, :context_module => self.context_module_tag.context_module.attributes}
|
||||
elsif self.could_be_locked && item = locked_by_module_item?(user, opts[:deep_check_if_needed])
|
||||
locked = {:asset_string => self.asset_string, :context_module => item.context_module.attributes}
|
||||
end
|
||||
locked
|
||||
end
|
||||
|
@ -1237,10 +1247,6 @@ class Assignment < ActiveRecord::Base
|
|||
|
||||
named_scope :no_graded_quizzes_or_topics, :conditions=>"submission_types NOT IN ('online_quiz', 'discussion_topic')"
|
||||
|
||||
named_scope :with_context_module_tags, lambda {
|
||||
{:include => :context_module_tag }
|
||||
}
|
||||
|
||||
named_scope :with_submissions, lambda {
|
||||
{:include => :submissions }
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
class Attachment < ActiveRecord::Base
|
||||
attr_accessible :context, :folder, :filename, :display_name, :user, :locked, :position, :lock_at, :unlock_at, :uploaded_data
|
||||
include HasContentTags
|
||||
|
||||
include ContextModuleItem
|
||||
|
||||
belongs_to :context, :polymorphic => true
|
||||
belongs_to :cloned_item
|
||||
belongs_to :folder
|
||||
|
@ -29,7 +30,6 @@ class Attachment < ActiveRecord::Base
|
|||
has_one :media_object
|
||||
has_many :submissions
|
||||
has_many :attachment_associations
|
||||
has_one :context_module_tag, :as => :content, :class_name => 'ContentTag', :conditions => ['content_tags.tag_type = ? AND workflow_state != ?', 'context_module', 'deleted'], :include => {:context_module => :context_module_progressions}
|
||||
belongs_to :root_attachment, :class_name => 'Attachment'
|
||||
belongs_to :scribd_mime_type
|
||||
belongs_to :scribd_account
|
||||
|
@ -112,7 +112,8 @@ class Attachment < ActiveRecord::Base
|
|||
send_later_enqueue_args(:submit_to_scribd!, { :n_strand => 'scribd', :max_attempts => 1 })
|
||||
end
|
||||
|
||||
send_later(:infer_encoding) if self.encoding.nil? && self.content_type =~ /text/
|
||||
# try an infer encoding if it would be useful to do so
|
||||
send_later(:infer_encoding) if self.encoding.nil? && self.content_type =~ /text/ && self.context_type != 'SisBatch'
|
||||
if respond_to?(:process_attachment_with_processing) && thumbnailable? && !attachment_options[:thumbnails].blank? && parent_id.nil?
|
||||
temp_file = temp_path || create_temp_file
|
||||
self.class.attachment_options[:thumbnails].each { |suffix, size| send_later_if_production(:create_thumbnail_size, suffix) }
|
||||
|
@ -990,17 +991,16 @@ class Attachment < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def locked_for?(user, opts={})
|
||||
@locks ||= {}
|
||||
return false if opts[:check_policies] && self.grants_right?(user, nil, :update)
|
||||
return {:manually_locked => true} if self.locked || (self.folder && self.folder.locked?)
|
||||
@locks[user ? user.id : 0] ||= Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
|
||||
Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
|
||||
locked = false
|
||||
if (self.unlock_at && Time.now < self.unlock_at)
|
||||
locked = {:asset_string => self.asset_string, :unlock_at => self.unlock_at}
|
||||
elsif (self.lock_at && Time.now > self.lock_at)
|
||||
locked = {:asset_string => self.asset_string, :lock_at => self.lock_at}
|
||||
elsif (self.could_be_locked && self.context_module_tag && !self.context_module_tag.available_for?(user, opts[:deep_check_if_needed]))
|
||||
locked = {:asset_string => self.asset_string, :context_module => self.context_module_tag.context_module.attributes}
|
||||
elsif self.could_be_locked && item = locked_by_module_item?(user, opts[:deep_check_if_needed])
|
||||
locked = {:asset_string => self.asset_string, :context_module => item.context_module.attributes}
|
||||
end
|
||||
locked
|
||||
end
|
||||
|
@ -1033,7 +1033,7 @@ class Attachment < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def context_module_action(user, action)
|
||||
self.context_module_tag.context_module_action(user, action) if self.context_module_tag
|
||||
self.context_module_tags.each { |tag| tag.context_module_action(user, action) }
|
||||
end
|
||||
|
||||
include Workflow
|
||||
|
|
|
@ -33,7 +33,7 @@ class ContentTag < ActiveRecord::Base
|
|||
validates_presence_of :context, :unless => proc { |tag| tag.context_id && tag.context_type }
|
||||
validates_length_of :comments, :maximum => maximum_text_length, :allow_nil => true, :allow_blank => true
|
||||
before_save :default_values
|
||||
after_save :enforce_unique_in_modules
|
||||
after_save :update_could_be_locked
|
||||
after_save :touch_context_module
|
||||
after_save :touch_context_if_learning_outcome
|
||||
include CustomValidations
|
||||
|
@ -86,23 +86,17 @@ class ContentTag < ActiveRecord::Base
|
|||
def context_name
|
||||
self.context.name rescue ""
|
||||
end
|
||||
|
||||
def enforce_unique_in_modules
|
||||
if self.workflow_state != 'deleted' && self.content_id && self.content_id > 0 && self.tag_type == 'context_module' && self.content_type != 'ContextExternalTool'
|
||||
tags = ContentTag.find_all_by_content_id_and_content_type_and_tag_type_and_context_id_and_context_type(self.content_id, self.content_type, 'context_module', self.context_id, self.context_type)
|
||||
tags.select{|t| t != self }.each do |tag|
|
||||
tag.destroy
|
||||
end
|
||||
end
|
||||
|
||||
def update_could_be_locked
|
||||
if self.content_id && self.content_type
|
||||
klass = self.content_type.constantize
|
||||
if klass.new.respond_to?(:could_be_locked=)
|
||||
self.content_type.constantize.update_all({:could_be_locked => true}, {:id => self.content_id}) rescue nil
|
||||
klass.update_all({:could_be_locked => true}, {:id => self.content_id})
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
def confirm_valid_module_requirements
|
||||
self.context_module && self.context_module.confirm_valid_requirements
|
||||
end
|
||||
|
|
|
@ -303,7 +303,6 @@ class ContextModule < ActiveRecord::Base
|
|||
added_item
|
||||
else
|
||||
return nil unless item
|
||||
added_item ||= ContentTag.find_by_content_id_and_content_type_and_context_id_and_context_type_and_tag_type(item.id, item.class.to_s, self.context_id, self.context_type, 'context_module')
|
||||
title = params[:title] || (item.title rescue item.name)
|
||||
added_item ||= self.content_tags.build(:context => context)
|
||||
added_item.attributes = {
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
#
|
||||
# Copyright (C) 2011 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/>.
|
||||
#
|
||||
|
||||
# This isn't a record on its own, but a module included in other records such
|
||||
# as Attachment and Assignment.
|
||||
#
|
||||
# ContextModules contain items indirectly, through ContentTags that contain the
|
||||
# information on position in the module, progression requirements, etc.
|
||||
module ContextModuleItem
|
||||
# set up the association for the AR class that included this module
|
||||
def self.included(klass)
|
||||
klass.has_many :context_module_tags, :as => :content, :class_name => 'ContentTag', :conditions => ['content_tags.tag_type = ? AND content_tags.workflow_state != ?', 'context_module', 'deleted'], :include => {:context_module => [:context_module_progressions, :content_tags]}
|
||||
end
|
||||
|
||||
# Check if this item is locked for the given user.
|
||||
# If we are locked, this will return the module item (ContentTag) that is
|
||||
# locking the item for the given user
|
||||
def locked_by_module_item?(user, deep_check)
|
||||
if self.context_module_tags.present? && self.context_module_tags.all? { |tag| tag.locked_for?(user, deep_check) }
|
||||
item = self.context_module_tags.first
|
||||
end
|
||||
item || false
|
||||
end
|
||||
|
||||
# searches the ContextModuleItems in objs_to_search, in order, for the first
|
||||
# context module tag -- returns the tag with id preferred_id if given and it
|
||||
# exists
|
||||
#
|
||||
# If no preferred is found, but more than one tag exists for the same obj, we
|
||||
# return nothing, since we can't know which tag is appropriate to return.
|
||||
def self.find_tag_with_preferred(objs_to_search, preferred_id)
|
||||
objs_to_search.each do |obj|
|
||||
next unless obj.present?
|
||||
tag = obj.context_module_tags.find_by_id(preferred_id)
|
||||
return tag if tag
|
||||
end
|
||||
objs_to_search.each do |obj|
|
||||
next unless obj.present?
|
||||
tags = obj.context_module_tags.to_a
|
||||
return nil if tags.size > 1
|
||||
tag = tags.first
|
||||
return tag if tag
|
||||
end
|
||||
return nil
|
||||
end
|
||||
end
|
|
@ -71,13 +71,13 @@ class Course < ActiveRecord::Base
|
|||
has_many :course_sections
|
||||
has_many :active_course_sections, :class_name => 'CourseSection', :conditions => {:workflow_state => 'active'}
|
||||
has_many :enrollments, :include => [:user, :course], :conditions => ['enrollments.workflow_state != ?', 'deleted'], :dependent => :destroy
|
||||
has_many :current_enrollments, :class_name => 'Enrollment', :conditions => ['enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ?', 'rejected', 'completed', 'deleted', 'inactive'], :include => :user
|
||||
has_many :current_enrollments, :class_name => 'Enrollment', :conditions => "enrollments.workflow_state NOT IN ('rejected', 'completed', 'deleted', 'inactive')", :include => :user
|
||||
has_many :prior_enrollments, :class_name => 'Enrollment', :include => [:user, :course], :conditions => "enrollments.workflow_state = 'completed'"
|
||||
has_many :students, :through => :student_enrollments, :source => :user
|
||||
has_many :all_students, :through => :all_student_enrollments, :source => :user
|
||||
has_many :participating_students, :through => :enrollments, :source => :user, :conditions => "enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') and enrollments.workflow_state = 'active'"
|
||||
has_many :student_enrollments, :class_name => 'Enrollment', :conditions => ["enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment')", 'deleted', 'completed', 'rejected', 'inactive'], :include => :user
|
||||
has_many :all_student_enrollments, :class_name => 'Enrollment', :conditions => ["enrollments.workflow_state != ? AND enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment')", 'deleted'], :include => :user
|
||||
has_many :student_enrollments, :class_name => 'Enrollment', :conditions => "enrollments.workflow_state NOT IN ('rejected', 'completed', 'deleted', 'inactive') AND enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment')", :include => :user
|
||||
has_many :all_student_enrollments, :class_name => 'Enrollment', :conditions => "enrollments.workflow_state != 'deleted' AND enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment')", :include => :user
|
||||
has_many :all_real_students, :through => :all_real_student_enrollments, :source => :user
|
||||
has_many :all_real_student_enrollments, :class_name => 'StudentEnrollment', :conditions => ["enrollments.workflow_state != ?", 'deleted'], :include => :user
|
||||
has_many :detailed_enrollments, :class_name => 'Enrollment', :conditions => ['enrollments.workflow_state != ?', 'deleted'], :include => {:user => {:pseudonym => :communication_channel}}
|
||||
|
@ -120,7 +120,7 @@ class Course < ActiveRecord::Base
|
|||
has_many :all_discussion_topics, :as => :context, :class_name => "DiscussionTopic", :include => :user, :dependent => :destroy
|
||||
has_many :discussion_entries, :through => :discussion_topics, :include => [:discussion_topic, :user], :dependent => :destroy
|
||||
has_many :announcements, :as => :context, :class_name => 'Announcement', :dependent => :destroy
|
||||
has_many :active_announcements, :as => :context, :class_name => 'Announcement', :conditions => ['discussion_topics.workflow_state != ?', 'deleted'], :order => 'created_at DESC'
|
||||
has_many :active_announcements, :as => :context, :class_name => 'Announcement', :conditions => ['discussion_topics.workflow_state != ?', 'deleted']
|
||||
has_many :attachments, :as => :context, :dependent => :destroy, :extend => Attachment::FindInContextAssociation
|
||||
has_many :active_images, :as => :context, :class_name => 'Attachment', :conditions => ["attachments.file_state != ? AND attachments.content_type LIKE 'image%'", 'deleted'], :order => 'attachments.display_name', :include => :thumbnail
|
||||
has_many :active_assignments, :as => :context, :class_name => 'Assignment', :conditions => ['assignments.workflow_state != ?', 'deleted'], :order => 'assignments.title, assignments.position'
|
||||
|
@ -1259,7 +1259,8 @@ class Course < ActiveRecord::Base
|
|||
single = assignments.length == 1
|
||||
includes = [:user, :course_section]
|
||||
includes = {:user => :pseudonyms, :course_section => []} if options[:include_sis_id]
|
||||
student_enrollments = self.student_enrollments.scoped(:include => includes).find(:all, :order => User.sortable_name_order_by_clause('users'))
|
||||
scope = options[:user] ? self.enrollments_visible_to(options[:user]) : self.student_enrollments
|
||||
student_enrollments = scope.scoped(:include => includes).find(:all, :order => User.sortable_name_order_by_clause('users'))
|
||||
# remove duplicate enrollments for students enrolled in multiple sections
|
||||
seen_users = []
|
||||
student_enrollments.reject! { |e| seen_users.include?(e.user_id) ? true : (seen_users << e.user_id; false) }
|
||||
|
@ -2349,17 +2350,32 @@ class Course < ActiveRecord::Base
|
|||
|
||||
# returns a scope, not an array of users/enrollments
|
||||
def students_visible_to(user, include_priors=false)
|
||||
enrollments_visible_to(user, include_priors, true)
|
||||
enrollments_visible_to(user, :include_priors => include_priors, :return_users => true)
|
||||
end
|
||||
def enrollments_visible_to(user, include_priors=false, return_users=false, limit_to_section_ids=nil)
|
||||
def enrollments_visible_to(user, opts = {})
|
||||
visibilities = section_visibilities_for(user)
|
||||
if return_users
|
||||
scope = include_priors ? self.all_students : self.students
|
||||
relation = []
|
||||
relation << 'all' if opts[:include_priors]
|
||||
if opts[:type] == :all
|
||||
relation << 'user' if opts[:return_users]
|
||||
else
|
||||
scope = include_priors ? self.all_student_enrollments : self.student_enrollments
|
||||
relation << (opts[:type].try(:to_s) || 'student')
|
||||
end
|
||||
if limit_to_section_ids
|
||||
scope = scope.scoped(:conditions => { 'enrollments.course_section_id' => limit_to_section_ids.to_a })
|
||||
if opts[:return_users]
|
||||
relation.last << 's'
|
||||
else
|
||||
relation << 'enrollments'
|
||||
end
|
||||
relation = relation.join('_')
|
||||
# our relations don't all follow the same pattern
|
||||
relation = case relation
|
||||
when 'all_enrollments'; 'enrollments'
|
||||
when 'enrollments'; 'current_enrollments'
|
||||
else; relation
|
||||
end
|
||||
scope = self.send(relation.to_sym)
|
||||
if opts[:section_ids]
|
||||
scope = scope.scoped(:conditions => { 'enrollments.course_section_id' => opts[:section_ids].to_a })
|
||||
end
|
||||
unless visibilities.any?{|v|v[:admin]}
|
||||
scope = scope.scoped(:conditions => "enrollments.type != 'StudentViewEnrollment'")
|
||||
|
@ -2367,7 +2383,7 @@ class Course < ActiveRecord::Base
|
|||
# See also Users#messageable_users (same logic used to get users across multiple courses)
|
||||
case enrollment_visibility_level_for(user, visibilities)
|
||||
when :full then scope
|
||||
when :sections then scope.scoped({:conditions => "enrollments.course_section_id IN (#{visibilities.map{|s| s[:course_section_id]}.join(",")})"})
|
||||
when :sections then scope.scoped(:conditions => ["enrollments.course_section_id IN (?) OR (enrollments.limit_privileges_to_course_section=? AND enrollments.type IN ('TeacherEnrollment', 'TaEnrollment', 'DesignerEnrollment'))", visibilities.map{|s| s[:course_section_id]}, false])
|
||||
when :restricted then scope.scoped({:conditions => "enrollments.user_id IN (#{(visibilities.map{|s| s[:associated_user_id]}.compact + [user.id]).join(",")})"})
|
||||
else scope.scoped({:conditions => "FALSE"})
|
||||
end
|
||||
|
|
|
@ -23,6 +23,7 @@ class DiscussionTopic < ActiveRecord::Base
|
|||
include HasContentTags
|
||||
include CopyAuthorizedLinks
|
||||
include TextHelper
|
||||
include ContextModuleItem
|
||||
|
||||
attr_accessible :title, :message, :user, :delayed_post_at, :assignment,
|
||||
:plaintext_message, :podcast_enabled, :podcast_has_student_posts,
|
||||
|
@ -39,7 +40,6 @@ class DiscussionTopic < ActiveRecord::Base
|
|||
|
||||
has_many :discussion_entries, :order => :created_at, :dependent => :destroy
|
||||
has_many :root_discussion_entries, :class_name => 'DiscussionEntry', :include => [:user], :conditions => ['discussion_entries.parent_id IS NULL AND discussion_entries.workflow_state != ?', 'deleted']
|
||||
has_one :context_module_tag, :as => :content, :class_name => 'ContentTag', :conditions => ['content_tags.tag_type = ? AND workflow_state != ?', 'context_module', 'deleted'], :include => {:context_module => [:content_tags, :context_module_progressions]}
|
||||
has_one :external_feed_entry, :as => :asset
|
||||
belongs_to :external_feed
|
||||
belongs_to :context, :polymorphic => true
|
||||
|
@ -135,8 +135,8 @@ class DiscussionTopic < ActiveRecord::Base
|
|||
|
||||
attr_accessor :saved_by
|
||||
def update_assignment
|
||||
if !self.assignment_id && @old_assignment_id && self.context_module_tag
|
||||
self.context_module_tag.confirm_valid_module_requirements
|
||||
if !self.assignment_id && @old_assignment_id
|
||||
self.context_module_tags.each { |tag| tag.confirm_valid_module_requirements }
|
||||
end
|
||||
if @old_assignment_id
|
||||
Assignment.update_all({:workflow_state => 'deleted', :updated_at => Time.now.utc}, {:id => @old_assignment_id, :context_id => self.context_id, :context_type => self.context_type, :submission_types => 'discussion_topic'})
|
||||
|
@ -542,11 +542,12 @@ class DiscussionTopic < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def context_module_action(user, action, points=nil)
|
||||
self.context_module_tag.context_module_action(user, action, points) if self.context_module_tag
|
||||
tags_to_update = self.context_module_tags.to_a
|
||||
if self.for_assignment?
|
||||
self.assignment.context_module_tag.context_module_action(user, action, points) if self.assignment.context_module_tag
|
||||
tags_to_update += self.assignment.context_module_tags
|
||||
self.ensure_submission(user) if self.assignment.context.students.include?(user) && action == :contributed
|
||||
end
|
||||
tags_to_update.each { |tag| tag.context_module_action(user, action, points) }
|
||||
end
|
||||
|
||||
def ensure_submission(user)
|
||||
|
@ -597,16 +598,15 @@ class DiscussionTopic < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def locked_for?(user=nil, opts={})
|
||||
@locks ||= {}
|
||||
return false if opts[:check_policies] && self.grants_right?(user, nil, :update)
|
||||
@locks[user ? user.id : 0] ||= Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
|
||||
Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
|
||||
locked = false
|
||||
if (self.delayed_post_at && self.delayed_post_at > Time.now)
|
||||
locked = {:asset_string => self.asset_string, :unlock_at => self.delayed_post_at}
|
||||
elsif (self.assignment && l = self.assignment.locked_for?(user, opts))
|
||||
locked = l
|
||||
elsif (self.could_be_locked && self.context_module_tag && !self.context_module_tag.available_for?(user, opts[:deep_check_if_needed]))
|
||||
locked = {:asset_string => self.asset_string, :context_module => self.context_module_tag.context_module.attributes}
|
||||
elsif self.could_be_locked && item = locked_by_module_item?(user, opts[:deep_check_if_needed])
|
||||
locked = {:asset_string => self.asset_string, :context_module => item.context_module.attributes}
|
||||
elsif (self.root_topic && l = self.root_topic.locked_for?(user, opts))
|
||||
locked = l
|
||||
end
|
||||
|
|
|
@ -29,19 +29,20 @@ class Enrollment < ActiveRecord::Base
|
|||
has_many :pseudonyms, :primary_key => :user_id, :foreign_key => :user_id
|
||||
has_many :course_account_associations, :foreign_key => 'course_id', :primary_key => 'course_id'
|
||||
|
||||
validates_presence_of :user_id
|
||||
validates_presence_of :course_id
|
||||
validates_presence_of :user_id, :course_id
|
||||
validates_inclusion_of :limit_privileges_to_course_section, :in => [true, false]
|
||||
|
||||
before_save :assign_uuid
|
||||
before_save :assert_section
|
||||
before_save :update_user_account_associations_if_necessary
|
||||
before_save :audit_groups_for_deleted_enrollments
|
||||
before_validation :infer_privileges
|
||||
after_create :create_linked_enrollments
|
||||
after_save :clear_email_caches
|
||||
after_save :cancel_future_appointments
|
||||
after_save :update_linked_enrollments
|
||||
|
||||
attr_accessible :user, :course, :workflow_state, :course_section, :limit_priveleges_to_course_section, :limit_privileges_to_course_section
|
||||
attr_accessible :user, :course, :workflow_state, :course_section, :limit_privileges_to_course_section
|
||||
|
||||
def self.active_student_conditions(prefix = 'enrollments')
|
||||
"(#{prefix}.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND #{prefix}.workflow_state = 'active')"
|
||||
|
@ -355,6 +356,24 @@ class Enrollment < ActiveRecord::Base
|
|||
self.root_account_id = self.course_section.root_account_id rescue nil
|
||||
end
|
||||
|
||||
def infer_privileges
|
||||
# limit_privileges_to_course_section affects whether this user can see
|
||||
# users from other sections (for any purpose - messaging, roster, grading)
|
||||
# admins (teacher, ta, designer) that have this flag are also visible TO
|
||||
# users from any section (but not students/observers).
|
||||
# currently, this flag is actually only configurable for teachers and
|
||||
# TAs; designers are always course-wide, and so are students.
|
||||
# In the future, we should probably allow configuring it for students,
|
||||
# possibly section-wide (i.e. "Students in this section can see students
|
||||
# from all other sections")
|
||||
if self.is_a?(TeacherEnrollment) || self.is_a?(TaEnrollment)
|
||||
self.limit_privileges_to_course_section = false if self.limit_privileges_to_course_section.nil?
|
||||
else
|
||||
self.limit_privileges_to_course_section = false
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def course_name
|
||||
self.course.name || t('#enrollment.default_course_name', "Course")
|
||||
end
|
||||
|
@ -811,20 +830,6 @@ class Enrollment < ActiveRecord::Base
|
|||
read_attribute(:uuid)
|
||||
end
|
||||
|
||||
# overwrite the accessors to limit_priveleges and limit_privileges to return the value wherever
|
||||
# it exists.
|
||||
[:limit_privileges_to_course_section, :limit_priveleges_to_course_section].each do |method_name|
|
||||
define_method(method_name) do
|
||||
read_attribute(:limit_privileges_to_course_section).nil? ?
|
||||
read_attribute(:limit_priveleges_to_course_section) :
|
||||
read_attribute(:limit_privileges_to_course_section)
|
||||
end
|
||||
end
|
||||
|
||||
def limit_priveleges_to_course_section=(value)
|
||||
self.limit_privileges_to_course_section = value
|
||||
end
|
||||
|
||||
def self.limit_privileges_to_course_section!(course, user, limit)
|
||||
Enrollment.update_all({:limit_privileges_to_course_section => !!limit}, {:course_id => course.id, :user_id => user.id})
|
||||
user.touch
|
||||
|
|
|
@ -27,7 +27,10 @@ class ErrorReport < ActiveRecord::Base
|
|||
|
||||
before_save :guess_email
|
||||
|
||||
# Define a custom callback for external notification of an error report.
|
||||
define_callbacks :on_send_to_external
|
||||
# Setup callback to default behavior.
|
||||
on_send_to_external :send_via_email_or_post
|
||||
|
||||
attr_accessible
|
||||
|
||||
|
@ -141,12 +144,14 @@ class ErrorReport < ActiveRecord::Base
|
|||
distinct('category')
|
||||
end
|
||||
|
||||
on_send_to_external do |error_report|
|
||||
# Send the error report based on configuration either via a POST or email to an external location.
|
||||
def send_via_email_or_post
|
||||
error_report = self
|
||||
config = Canvas::Plugin.find('error_reporting').try(:settings) || {}
|
||||
|
||||
message_type = (error_report.backtrace || "").split("\n").first.match(/\APosted as[^_]*_([A-Z]*)_/)[1] rescue nil
|
||||
message_type ||= "ERROR"
|
||||
|
||||
|
||||
body = %{From #{error_report.email}, #{(error_report.user.name rescue "")}
|
||||
#{message_type} #{error_report.comments + "\n" if error_report.comments}
|
||||
#{"url: " + error_report.url + "\n" if error_report.url }
|
||||
|
@ -173,4 +178,6 @@ error_id: #{error_report.id}
|
|||
)
|
||||
end
|
||||
end
|
||||
private :send_via_email_or_post
|
||||
|
||||
end
|
||||
|
|
|
@ -62,7 +62,9 @@ class GradingStandard < ActiveRecord::Base
|
|||
# e.g. convert 89.7 to B+
|
||||
def self.score_to_grade(scheme, score)
|
||||
score = 0 if score < 0
|
||||
scheme.max_by {|s| score >= s[1] * 100 ? s[1] : -1 }[0]
|
||||
# assign the highest grade whose min cutoff is less than the score
|
||||
# if score is less than all scheme cutoffs, assign the lowest grade
|
||||
scheme.max_by {|s| score >= s[1] * 100 ? s[1] : -s[1] }[0]
|
||||
end
|
||||
|
||||
# e.g. convert B to 86
|
||||
|
|
|
@ -134,15 +134,18 @@ class Group < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def membership_for_user(user)
|
||||
self.group_memberships.find_by_user_id(user && user.id)
|
||||
return nil unless user.present?
|
||||
self.shard.activate { self.group_memberships.find_by_user_id(user.id) }
|
||||
end
|
||||
|
||||
def has_member?(user)
|
||||
self.participating_group_memberships.find_by_user_id(user && user.id)
|
||||
return nil unless user.present?
|
||||
self.shard.activate { self.participating_group_memberships.find_by_user_id(user.id) }
|
||||
end
|
||||
|
||||
def has_moderator?(user)
|
||||
self.participating_group_memberships.moderators.find_by_user_id(user && user.id)
|
||||
return nil unless user.present?
|
||||
self.shard.activate { self.participating_group_memberships.moderators.find_by_user_id(user.id) }
|
||||
end
|
||||
|
||||
def should_add_creator?
|
||||
|
|
|
@ -112,7 +112,7 @@ class GroupMembership < ActiveRecord::Base
|
|||
if (self.id_changed? || self.workflow_state_changed?) && self.active?
|
||||
UserFollow.create_follow(self.user, self.group)
|
||||
elsif self.destroyed? || (self.workflow_state_changed? && self.deleted?)
|
||||
user_follow = self.user.user_follows.find(:first, :conditions => { :followed_item_id => self.group_id, :followed_item_type => 'Group' })
|
||||
user_follow = self.user.shard.activate { self.user.user_follows.find(:first, :conditions => { :followed_item_id => self.group_id, :followed_item_type => 'Group' }) }
|
||||
user_follow.try(:destroy)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,6 +24,8 @@ class Quiz < ActiveRecord::Base
|
|||
include CopyAuthorizedLinks
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
extend ActionView::Helpers::SanitizeHelper::ClassMethods
|
||||
include ContextModuleItem
|
||||
|
||||
attr_accessible :title, :description, :points_possible, :assignment_id, :shuffle_answers,
|
||||
:show_correct_answers, :time_limit, :allowed_attempts, :scoring_policy, :quiz_type,
|
||||
:lock_at, :unlock_at, :due_at, :access_code, :anonymous_submissions, :assignment_group_id,
|
||||
|
@ -36,7 +38,6 @@ class Quiz < ActiveRecord::Base
|
|||
has_many :quiz_questions, :dependent => :destroy, :order => 'position'
|
||||
has_many :quiz_submissions, :dependent => :destroy
|
||||
has_many :quiz_groups, :dependent => :destroy, :order => 'position'
|
||||
has_one :context_module_tag, :as => :content, :class_name => 'ContentTag', :conditions => ['content_tags.tag_type = ? AND workflow_state != ?', 'context_module', 'deleted'], :include => {:context_module => [:context_module_progressions, :content_tags]}
|
||||
belongs_to :context, :polymorphic => true
|
||||
belongs_to :assignment
|
||||
belongs_to :cloned_item
|
||||
|
@ -218,8 +219,8 @@ class Quiz < ActiveRecord::Base
|
|||
attr_accessor :saved_by
|
||||
def update_assignment
|
||||
send_later_if_production(:set_unpublished_question_count) if self.id
|
||||
if !self.assignment_id && @old_assignment_id && self.context_module_tag
|
||||
self.context_module_tag.confirm_valid_module_requirements
|
||||
if !self.assignment_id && @old_assignment_id
|
||||
self.context_module_tags.each { |tag| tag.confirm_valid_module_requirements }
|
||||
end
|
||||
if !self.graded? && (@old_assignment_id || self.last_assignment_id)
|
||||
Assignment.update_all({:workflow_state => 'deleted', :updated_at => Time.now.utc}, {:id => [@old_assignment_id, self.last_assignment_id].compact, :submission_types => 'online_quiz'})
|
||||
|
@ -588,9 +589,8 @@ class Quiz < ActiveRecord::Base
|
|||
alias_method :to_s, :quiz_title
|
||||
|
||||
def locked_for?(user=nil, opts={})
|
||||
@locks ||= {}
|
||||
return false if opts[:check_policies] && self.grants_right?(user, nil, :update)
|
||||
@locks[user ? user.id : 0] ||= Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
|
||||
Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
|
||||
locked = false
|
||||
if (self.unlock_at && self.unlock_at > Time.now)
|
||||
sub = user && quiz_submissions.find_by_user_id(user.id)
|
||||
|
@ -607,10 +607,10 @@ class Quiz < ActiveRecord::Base
|
|||
if !sub || !sub.manually_unlocked
|
||||
locked = l
|
||||
end
|
||||
elsif (self.context_module_tag && !self.context_module_tag.available_for?(user, opts[:deep_check_if_needed]))
|
||||
elsif item = locked_by_module_item?(user, opts[:deep_check_if_needed])
|
||||
sub = user && quiz_submissions.find_by_user_id(user.id)
|
||||
if !sub || !sub.manually_unlocked
|
||||
locked = {:asset_string => self.asset_string, :context_module => self.context_module_tag.context_module.attributes}
|
||||
locked = {:asset_string => self.asset_string, :context_module => item.context_module.attributes}
|
||||
end
|
||||
end
|
||||
locked
|
||||
|
@ -618,8 +618,11 @@ class Quiz < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def context_module_action(user, action, points=nil)
|
||||
self.context_module_tag.context_module_action(user, action, points) if self.context_module_tag
|
||||
self.assignment.context_module_tag.context_module_action(user, action, points) if self.assignment && self.assignment.context_module_tag
|
||||
tags_to_update = self.context_module_tags.to_a
|
||||
if self.assignment
|
||||
tags_to_update += self.assignment.context_module_tags
|
||||
end
|
||||
tags_to_update.each { |tag| tag.context_module_action(user, action, points) }
|
||||
end
|
||||
|
||||
# virtual attribute
|
||||
|
|
|
@ -29,16 +29,18 @@ class ReportSnapshot < ActiveRecord::Base
|
|||
|
||||
def self.report_value_over_time(report, key)
|
||||
items = []
|
||||
now = Time.now.utc.to_i
|
||||
report['monthly'].each do |month|
|
||||
if month[key]
|
||||
date = Date
|
||||
stamp = ((Time.utc(month['year'], month['month'], 1).to_date >> 1) - 1.day).to_time.to_i
|
||||
items << [stamp*1000, month[key]]
|
||||
next if stamp > now
|
||||
items << [stamp.to_i*1000, month[key]]
|
||||
end
|
||||
end
|
||||
report['weekly'].each do |week|
|
||||
if week[key]
|
||||
stamp = (week['week'] * 604800) + ((week['year'] - 1970) * 31556926)
|
||||
next if stamp > now
|
||||
items << [stamp*1000, week[key]]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
class SisBatch < ActiveRecord::Base
|
||||
include Workflow
|
||||
belongs_to :account
|
||||
has_many :sis_batch_log_entries, :order => :created_at
|
||||
serialize :data
|
||||
serialize :options
|
||||
serialize :processing_errors, Array
|
||||
|
@ -205,7 +204,6 @@ class SisBatch < ActiveRecord::Base
|
|||
}
|
||||
data["processing_errors"] = self.processing_errors if self.processing_errors.present?
|
||||
data["processing_warnings"] = self.processing_warnings if self.processing_warnings.present?
|
||||
data["sis_batch_log_entries"] = self.sis_batch_log_entries if self.sis_batch_log_entries.present?
|
||||
return data.to_json
|
||||
end
|
||||
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
#
|
||||
# Copyright (C) 2011 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/>.
|
||||
#
|
||||
|
||||
class SisBatchLogEntry < ActiveRecord::Base
|
||||
validates_length_of :text, :maximum => maximum_text_length, :allow_nil => true, :allow_blank => true
|
||||
belongs_to :sis_batch
|
||||
|
||||
attr_accessible :text, :sis_batch
|
||||
|
||||
def text=(val)
|
||||
if !val || val.length < self.class.maximum_text_length
|
||||
write_attribute(:text, val)
|
||||
else
|
||||
write_attribute(:text, val[0,self.class.maximum_text_length])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -162,6 +162,7 @@ class StreamItem < ActiveRecord::Base
|
|||
hash['user_short_name'] = comment.author.short_name if comment.author
|
||||
hash
|
||||
end
|
||||
res[:course_id] = object.context.id
|
||||
when Collaboration
|
||||
res = object.attributes
|
||||
res['users'] = object.users.map{|u| prepare_user(u)}
|
||||
|
|
|
@ -205,44 +205,53 @@ class Submission < ActiveRecord::Base
|
|||
strip_tags((self.body || "").gsub(/\<\s*br\s*\/\>/, "\n<br/>").gsub(/\<\/p\>/, "</p>\n"))
|
||||
end
|
||||
|
||||
def check_turnitin_status(asset_string, attempt=1)
|
||||
def check_turnitin_status(attempt=1)
|
||||
self.turnitin_data ||= {}
|
||||
data = self.turnitin_data[asset_string]
|
||||
return unless data && data[:object_id]
|
||||
if data[:similarity_score].blank?
|
||||
if attempt < TURNITIN_RETRY
|
||||
turnitin = Turnitin::Client.new(*self.context.turnitin_settings)
|
||||
res = turnitin.generateReport(self, asset_string)
|
||||
if res[:similarity_score]
|
||||
data[:similarity_score] = res[:similarity_score].to_f
|
||||
data[:web_overlap] = res[:web_overlap].to_f
|
||||
data[:publication_overlap] = res[:publication_overlap].to_f
|
||||
data[:student_overlap] = res[:student_overlap].to_f
|
||||
data[:state] = 'failure'
|
||||
data[:state] = 'problem' if data[:similarity_score] < 75
|
||||
data[:state] = 'warning' if data[:similarity_score] < 50
|
||||
data[:state] = 'acceptable' if data[:similarity_score] < 25
|
||||
data[:state] = 'none' if data[:similarity_score] == 0
|
||||
data[:status] = 'scored'
|
||||
turnitin = nil
|
||||
needs_retry = false
|
||||
|
||||
# check all assets in the turnitin_data (self.turnitin_assets is only the
|
||||
# current assets) so that we get the status for assets of previous versions
|
||||
# of the submission as well
|
||||
self.turnitin_data.keys.each do |asset_string|
|
||||
data = self.turnitin_data[asset_string]
|
||||
next unless data && data.is_a?(Hash) && data[:object_id]
|
||||
if data[:similarity_score].blank?
|
||||
if attempt < TURNITIN_RETRY
|
||||
turnitin ||= Turnitin::Client.new(*self.context.turnitin_settings)
|
||||
res = turnitin.generateReport(self, asset_string)
|
||||
if res[:similarity_score]
|
||||
data[:similarity_score] = res[:similarity_score].to_f
|
||||
data[:web_overlap] = res[:web_overlap].to_f
|
||||
data[:publication_overlap] = res[:publication_overlap].to_f
|
||||
data[:student_overlap] = res[:student_overlap].to_f
|
||||
data[:state] = 'failure'
|
||||
data[:state] = 'problem' if data[:similarity_score] < 75
|
||||
data[:state] = 'warning' if data[:similarity_score] < 50
|
||||
data[:state] = 'acceptable' if data[:similarity_score] < 25
|
||||
data[:state] = 'none' if data[:similarity_score] == 0
|
||||
data[:status] = 'scored'
|
||||
else
|
||||
needs_retry ||= true
|
||||
end
|
||||
else
|
||||
send_at((5 * attempt).minutes.from_now, :check_turnitin_status, asset_string, attempt + 1)
|
||||
data[:status] = 'error'
|
||||
end
|
||||
else
|
||||
data[:status] = 'error'
|
||||
data[:status] = 'scored'
|
||||
end
|
||||
else
|
||||
data[:status] = 'scored'
|
||||
self.turnitin_data[asset_string] = data
|
||||
end
|
||||
self.turnitin_data[asset_string] = data
|
||||
|
||||
send_at((5 * attempt).minutes.from_now, :check_turnitin_status, attempt + 1) if needs_retry
|
||||
self.turnitin_data_changed!
|
||||
self.save
|
||||
data
|
||||
end
|
||||
|
||||
def turnitin_report_url(asset_string, user)
|
||||
if self.turnitin_data && self.turnitin_data[asset_string] && self.turnitin_data[asset_string][:similarity_score]
|
||||
turnitin = Turnitin::Client.new(*self.context.turnitin_settings)
|
||||
self.send_later(:check_turnitin_status, asset_string)
|
||||
self.send_later(:check_turnitin_status)
|
||||
if self.grants_right?(user, nil, :grade)
|
||||
turnitin.submissionReportUrl(self, asset_string)
|
||||
elsif self.grants_right?(user, nil, :view_turnitin_report)
|
||||
|
@ -278,7 +287,7 @@ class Submission < ActiveRecord::Base
|
|||
turnitin = Turnitin::Client.new(*self.context.turnitin_settings)
|
||||
reset_turnitin_assets
|
||||
|
||||
# 1. Make sure the assignment exists and user is enrolled
|
||||
# Make sure the assignment exists and user is enrolled
|
||||
assign_status = self.assignment.create_in_turnitin
|
||||
enroll_status = turnitin.enrollStudent(self.context, self.user)
|
||||
unless assign_status && enroll_status
|
||||
|
@ -296,18 +305,20 @@ class Submission < ActiveRecord::Base
|
|||
return false
|
||||
end
|
||||
|
||||
# 2. Submit the file(s)
|
||||
# Submit the file(s)
|
||||
submission_response = turnitin.submitPaper(self)
|
||||
submission_response.each do |res_asset_string, response|
|
||||
self.turnitin_data[res_asset_string].merge!(response)
|
||||
self.turnitin_data_changed!
|
||||
if response[:object_id]
|
||||
self.send_at(5.minutes.from_now, :check_turnitin_status, res_asset_string)
|
||||
elsif !(attempt < TURNITIN_RETRY)
|
||||
if !response[:object_id] && !(attempt < TURNITIN_RETRY)
|
||||
self.turnitin_data[res_asset_string][:status] = 'error'
|
||||
end
|
||||
end
|
||||
|
||||
self.send_at(5.minutes.from_now, :check_turnitin_status)
|
||||
self.save
|
||||
|
||||
# Schedule retry if there were failures
|
||||
submit_status = submission_response.present? && submission_response.values.all?{ |v| v[:object_id] }
|
||||
unless submit_status
|
||||
send_at(5.minutes.from_now, :submit_to_turnitin, attempt + 1) if attempt < TURNITIN_RETRY
|
||||
|
|
|
@ -1730,7 +1730,7 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def recent_stream_items(opts={})
|
||||
visible_stream_item_instances(opts).scoped(:include => :stream_item, :limit => 21).map(&:stream_item)
|
||||
visible_stream_item_instances(opts).scoped(:include => :stream_item, :limit => 21).map(&:stream_item).compact
|
||||
end
|
||||
memoize :recent_stream_items
|
||||
|
||||
|
|
|
@ -68,14 +68,25 @@ class UserFollow < ActiveRecord::Base
|
|||
#
|
||||
# this way both associations work as expected
|
||||
set_shard_override do |record|
|
||||
record.following_user.shard unless record.complementary_record
|
||||
record.following_user.shard unless record.complementary_record?
|
||||
end
|
||||
|
||||
after_create :create_complementary_record
|
||||
attr_accessor :complementary_record
|
||||
attr_writer :complementary_record
|
||||
|
||||
# returns true if the following user isn't on the same shard as the followed
|
||||
# item, and this UserFollow is the secondary copy that's on the followed
|
||||
# item's shard
|
||||
def complementary_record?
|
||||
if new_record?
|
||||
@complementary_record
|
||||
else
|
||||
self.shard != following_user.shard
|
||||
end
|
||||
end
|
||||
|
||||
def create_complementary_record
|
||||
if !complementary_record && followed_item.shard != following_user.shard
|
||||
if !complementary_record? && followed_item.shard != following_user.shard
|
||||
followed_item.shard.activate do
|
||||
UserFollow.create_follow(following_user, followed_item, true)
|
||||
end
|
||||
|
@ -85,10 +96,10 @@ class UserFollow < ActiveRecord::Base
|
|||
|
||||
after_destroy :destroy_complementary_record
|
||||
def destroy_complementary_record
|
||||
complementary_record.try(:destroy)
|
||||
find_complementary_record.try(:destroy)
|
||||
end
|
||||
|
||||
def complementary_record
|
||||
def find_complementary_record
|
||||
return nil if followed_item.shard == following_user.shard
|
||||
if self.shard == followed_item.shard
|
||||
finding_shard = following_user.shard
|
||||
|
@ -110,7 +121,7 @@ class UserFollow < ActiveRecord::Base
|
|||
# when a user follows a group or other user, they auto-follow all existing
|
||||
# collections in that context as well
|
||||
def check_auto_follow_collections
|
||||
return true if complementary_record
|
||||
return true if self.complementary_record?
|
||||
case followed_item
|
||||
when User, Group
|
||||
if !followed_item.collections.empty?
|
||||
|
@ -128,6 +139,40 @@ class UserFollow < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
after_destroy :check_auto_unfollow_collections
|
||||
|
||||
# when a user leaves a group, they auto-unfollow all private collections in
|
||||
# that group
|
||||
def check_auto_unfollow_collections
|
||||
return true if self.complementary_record?
|
||||
case followed_item
|
||||
when Group
|
||||
if !followed_item.collections.empty?
|
||||
UserFollow.send_later_enqueue_args(:auto_unfollow_collections_for,
|
||||
{ :priority => Delayed::LOW_PRIORITY },
|
||||
self.following_user_id,
|
||||
self.followed_item_type,
|
||||
self.followed_item_id)
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# this is called after the UserFollow object is destroyed, so it needs to
|
||||
# re-lookup the user and context
|
||||
def self.auto_unfollow_collections_for(following_user_id, followed_item_type, followed_item_id)
|
||||
if context = Object.const_get(followed_item_type).find_by_id(followed_item_id)
|
||||
following_user = User.find(following_user_id)
|
||||
context.collections.active.each do |coll|
|
||||
if !coll.grants_right?(following_user, :follow)
|
||||
user_follow = following_user.user_follows.scoped(:conditions => { :followed_item_type => 'Collection',
|
||||
:followed_item_id => coll.id }).first
|
||||
user_follow.try(:destroy)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
trigger.after(:insert) do |t|
|
||||
t.where("NEW.followed_item_type = 'Collection'") do
|
||||
<<-SQL
|
||||
|
|
|
@ -24,12 +24,12 @@ class WikiPage < ActiveRecord::Base
|
|||
include Workflow
|
||||
include HasContentTags
|
||||
include CopyAuthorizedLinks
|
||||
include ContextModuleItem
|
||||
|
||||
belongs_to :wiki, :touch => true
|
||||
belongs_to :wiki_with_participants, :class_name => 'Wiki', :foreign_key => 'wiki_id', :include => {:wiki_namespaces => :context }
|
||||
belongs_to :cloned_item
|
||||
belongs_to :user
|
||||
has_many :context_module_tags, :as => :content, :class_name => 'ContentTag', :conditions => ['content_tags.tag_type = ? AND workflow_state != ?', 'context_module', 'deleted'], :include => {:context_module => [:content_tags, :context_module_progressions]}
|
||||
has_many :wiki_page_comments, :order => "created_at DESC"
|
||||
acts_as_url :title, :scope => [:wiki_id, :not_deleted], :sync_url => true
|
||||
|
||||
|
@ -195,8 +195,7 @@ class WikiPage < ActiveRecord::Base
|
|||
|
||||
def locked_for?(context, user, opts={})
|
||||
return false unless self.could_be_locked
|
||||
@locks ||= {}
|
||||
@locks[user ? user.id : 0] ||= Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
|
||||
Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
|
||||
m = context_module_tag_for(context, user).context_module rescue nil
|
||||
locked = false
|
||||
if (m && !m.available_for?(user))
|
||||
|
|
|
@ -10,6 +10,8 @@ body
|
|||
min-height: 425px
|
||||
a
|
||||
color: #2571bd
|
||||
.spinner
|
||||
width: 50px
|
||||
#actions
|
||||
min-height: 42px
|
||||
background: #dddde1 url(/images/messages/actions-bg.png) 0 0 repeat-x
|
||||
|
@ -409,8 +411,6 @@ ul.messages, ul.messages.private, ul.messages.private li:hover
|
|||
padding: 0 0 0 1px
|
||||
overflow: hidden
|
||||
position: relative
|
||||
.spinner
|
||||
width: 50px
|
||||
#message_actions
|
||||
display: none
|
||||
position: absolute
|
||||
|
|
|
@ -23,16 +23,6 @@
|
|||
<span class="auth_info auth_base"><%= @account_config.auth_base %></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= f.blabel :log_in_url, :en => "Alternate Login URL" %></td>
|
||||
<td class="nobr">
|
||||
<%= f.text_field :log_in_url, :class => "auth_form", :style => "width: 450px;" %>
|
||||
<span class="auth_info log_in_url"><%= @account_config.log_in_url %></span>
|
||||
<span class="auth_form" style="font-size: smaller;">
|
||||
<br><%= t(:alternate_login_url_description, "An alternate URL for logging into CAS. You probably should not set this.") %>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="vertical-align: top; width: 200px;"><%= f.blabel :login_handle_name, :en => "Login Label" %></td>
|
||||
<td style="vertical-align: top;" class="nobr">
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</div>
|
||||
|
||||
<div class="form" style="<%= hidden if @current_batch && @current_batch.importing? %>">
|
||||
<% form_tag account_sis_import_submit_path(@account.id), :multipart => true, :id => "sis_importer" do %>
|
||||
<% form_tag account_sis_imports_path(@account.id), :multipart => true, :id => "sis_importer" do %>
|
||||
<p class="instruction"><%= mt(:select_file_instructions,
|
||||
"Select the zip file that you want imported. \n" +
|
||||
"For a description of how to generate these zip files, [please see this documentation](%{uri}).",
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<% elsif e.is_a?(AppointmentGroup) %>
|
||||
<a href="<%= appointment_group_url(e.id) %>"><%= e.title %></a>
|
||||
<% else %>
|
||||
<a href="<%= context_url(@context, :context_calendar_event_url, e.parent_calendar_event_id || e.id) %>"><%= e.title %></a>
|
||||
<a href="<%= calendar_url_for(e.effective_context, :event => e) %>"><%= e.title %></a>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="dates <%= "not_last" unless idx == events.length - 1 %>">
|
||||
|
|
|
@ -104,5 +104,5 @@ $(document).ready(function() {
|
|||
<%= render :partial => "shared/rubric_forms" %>
|
||||
<% end %>
|
||||
<%= render :partial => "shared/aligned_outcomes", :locals => {:asset => @assignment} %>
|
||||
<%= render :partial => "shared/sequence_footer", :locals => {:asset => @assignment} if @assignment.context_module_tag %>
|
||||
<%= render :partial => "shared/sequence_footer", :locals => {:asset => @assignment} if !@assignment.context_module_tags.empty? %>
|
||||
<% end %>
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
'other' => image_tag("blank.png", :class => "image", :alt => '')
|
||||
}
|
||||
%>
|
||||
<% criterion = completion_criteria && completion_criteria.find{|c| c[:id] == tag.id} %>
|
||||
<table id="context_module_item_<%= tag ? tag.id : "blank" %>" class="context_module_item <%= module_item.content_type_class if module_item %> <%= 'also_assignment' if module_item && module_item.graded? %> indent_<%= tag.try_rescue(:indent) || '0' %> <%= 'progression_requirement' if criterion %> <%= criterion[:type] if criterion %>_requirement" style="<%= hidden unless module_item %>">
|
||||
<% criterion = completion_criteria && completion_criteria.find{|c| c[:id] == tag.id}
|
||||
item_class = "#{module_item.content_type}_#{module_item.content_id}" if module_item
|
||||
%>
|
||||
<table id="context_module_item_<%= tag ? tag.id : "blank" %>" class="context_module_item <%= module_item.content_type_class if module_item %> <%= 'also_assignment' if module_item && module_item.graded? %> indent_<%= tag.try_rescue(:indent) || '0' %> <%= 'progression_requirement' if criterion %> <%= criterion[:type] if criterion %>_requirement <%= item_class %>" style="<%= hidden unless module_item %>">
|
||||
<tr>
|
||||
<td class="module_item_icons">
|
||||
<div class="nobr">
|
||||
|
|
|
@ -34,7 +34,7 @@ $(document).ready(function() {
|
|||
<h3><%= t('headings.next_steps', %{Next Steps}) %></h3>
|
||||
<ul class="wizard_options_list">
|
||||
<% if can_do @context, @current_user, :manage_content %>
|
||||
<li class="option download_step <%= 'completed' unless @context.attachments.active.empty? %>">
|
||||
<li class="option download_step <%= 'completed' unless @context.attachments.active.first.nil? %>">
|
||||
<a href="<%= context_url(@context, :context_imports_url) %>" class="header"><%= t('links.import', %{Import Content}) %></a>
|
||||
<div class="details" style="display: none;">
|
||||
<%= t 'details.import', %{If you've been using another course management system, you probably have stuff in there that you're going to want moved over to Canvas. We can walk you through the process of easily migrating your content into Canvas.} %>
|
||||
|
@ -42,7 +42,7 @@ $(document).ready(function() {
|
|||
</li>
|
||||
<% end %>
|
||||
<% if can_do @context, @current_user, :manage_content, :manage_assignments %>
|
||||
<li class="option assignments_step <%= 'completed' unless @context.assignments.active.empty? %>">
|
||||
<li class="option assignments_step <%= 'completed' unless @context.assignments.active.first.nil? %>">
|
||||
<a href="<%= context_url(@context, :context_assignments_url, :wizard => 1) %>" class="header"><%= t('links.assignments', %{Add Course Assignments}) %></a>
|
||||
<div class="details" style="display: none;">
|
||||
<%= t 'details.assignments', %{Add your assignments. You can just make a long list, or break them up into groups -- and even specify weights for each assignment group.} %>
|
||||
|
@ -50,7 +50,7 @@ $(document).ready(function() {
|
|||
</li>
|
||||
<% end %>
|
||||
<% if can_do @context, @current_user, :manage_students %>
|
||||
<li class="option <%= 'completed' unless @context.students.empty? %>">
|
||||
<li class="option <%= 'completed' unless @context.students.first.nil? %>">
|
||||
<a href="<%= context_url(@context, :context_details_url, :wizard => 1) %>#add_students" class="header"><%= t('links.students', %{Add Students to the Course}) %></a>
|
||||
<div class="details" style="display: none;">
|
||||
<%= t 'details.students', %{You'll definitely want some of these. What's the fun of teaching a course if nobody's even listening?} %>
|
||||
|
@ -58,7 +58,7 @@ $(document).ready(function() {
|
|||
</li>
|
||||
<% end %>
|
||||
<% if can_do @context, @current_user, :manage_content, :manage_files %>
|
||||
<li class="option download_step <%= 'completed' unless @context.attachments.active.empty? %>" style="display: none;">
|
||||
<li class="option download_step <%= 'completed' unless @context.attachments.active.first.nil? %>" style="display: none;">
|
||||
<a href="<%= context_url(@context, :context_files_url, :wizard => 1) %>" class="header"><%= t('links.files', %{Add Course Files}) %></a>
|
||||
<div class="details" style="display: none;">
|
||||
<%= t 'details.files', %{The Files tab is the place to share lecture slides, example documents, study helps -- anything your students will want to download. Uploading and organizing your files is easy with Canvas. We'll show you how.} %>
|
||||
|
@ -82,7 +82,7 @@ $(document).ready(function() {
|
|||
</li>
|
||||
<% end %>
|
||||
<% if can_do @context, @current_user, :manage_calendar %>
|
||||
<li class="option calendar_step <%= 'completed' unless @context.calendar_events.active.empty? %>">
|
||||
<li class="option calendar_step <%= 'completed' unless @context.calendar_events.active.first.nil? %>">
|
||||
<a href="<%= calendar_path(:wizard => 1) %>" class="header"><%= t('links.calendar', %{Add Course Calendar Events}) %></a>
|
||||
<div class="details" style="display: none;">
|
||||
<%= t 'details.calendar', %{Here's a great chance to get to know the calendar -- and add any non-assignment events you might have to the course. Don't worry, we'll help you through it.} %>
|
||||
|
|
|
@ -225,10 +225,7 @@
|
|||
</div>
|
||||
|
||||
<%=
|
||||
sequence_asset = @topic
|
||||
sequence_asset = @topic.root_topic if @topic.root_topic && !@topic.context_module_tag && @topic.root_topic.context_module_tag
|
||||
sequence_asset = @topic.assignment if @topic.assignment && !@topic.context_module_tag && @topic.assignment.context_module_tag
|
||||
render :partial => "shared/sequence_footer", :locals => {:asset => sequence_asset, :context => sequence_asset.context} if sequence_asset.context_module_tag
|
||||
render :partial => "shared/sequence_footer", :locals => {:asset => @sequence_asset, :context => @sequence_asset.context} if @sequence_asset
|
||||
%>
|
||||
<% end %>
|
||||
<% if @headers == false || @locked %>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
{{#if showBadBrowserMessage}}
|
||||
<div class="ui-state-error">
|
||||
<span class="ui-icon ui-icon-alert"></span>
|
||||
<strong>{{#t "wait_a_better_browser_may_help_solve_the_problem"}}Wait! A <a target="_blank" href="http://guides.instructure.com/s/2204/m/4214/l/41056-Which-browsers-does-Canvas-support-">better browser</a> may help solve the problem.{{/t}}</strong>
|
||||
<strong>{{#t "wait_a_different_browser_may_help_solve_the_problem"}}Wait! A <a target="_blank" href="http://guides.instructure.com/s/2204/m/4214/l/41056-Which-browsers-does-Canvas-support-">different browser</a> may help solve the problem.{{/t}}</strong>
|
||||
<div>{{#t "you_are_using_browser_version_version_try_again_using_the_latest_version_of_chrome_or_firefox"}}You are using Internet Explorer version {{browserVersion}}, Try again using the latest version of <a href="http://google.com/chrome">Chrome</a> or <a href="http://getfirefox.com">Firefox</a>{{/t}}</div>
|
||||
</div>
|
||||
{{else}}
|
||||
|
|
|
@ -8,42 +8,8 @@
|
|||
require = {
|
||||
translate: <%= use_optimized_js? %>,
|
||||
baseUrl: '<%= js_base_url %>',
|
||||
paths: {
|
||||
common: 'compiled/bundles/common',
|
||||
jqueryui: 'vendor/jqueryui',
|
||||
uploadify: '../flash/uploadify/jquery.uploadify.v2.1.4',
|
||||
use: 'vendor/use'
|
||||
},
|
||||
use: {
|
||||
'vendor/backbone': {
|
||||
deps: ['underscore', 'jquery'],
|
||||
attach: function(_, $){
|
||||
return Backbone;
|
||||
}
|
||||
},
|
||||
|
||||
// slick grid shim
|
||||
'vendor/slickgrid/lib/jquery.event.drag-2.0.min': {
|
||||
deps: ['jquery'],
|
||||
attach: '$'
|
||||
},
|
||||
'vendor/slickgrid/slick.core': {
|
||||
deps: ['jquery', 'use!vendor/slickgrid/lib/jquery.event.drag-2.0.min'],
|
||||
attach: 'Slick'
|
||||
},
|
||||
'vendor/slickgrid/slick.grid': {
|
||||
deps: ['use!vendor/slickgrid/slick.core'],
|
||||
attach: 'Slick'
|
||||
},
|
||||
'vendor/slickgrid/slick.editors': {
|
||||
deps: ['use!vendor/slickgrid/slick.core'],
|
||||
attach: 'Slick'
|
||||
},
|
||||
'vendor/slickgrid/plugins/slick.rowselectionmodel': {
|
||||
deps: ['use!vendor/slickgrid/slick.core'],
|
||||
attach: 'Slick'
|
||||
}
|
||||
}
|
||||
paths: <%= raw Canvas::RequireJs.paths %>,
|
||||
use: <%= raw Canvas::RequireJs.shims %>
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -220,8 +220,4 @@
|
|||
<%= render :partial => "shared/message_students" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%
|
||||
sequence_asset = @quiz
|
||||
sequence_asset = @quiz.assignment if @quiz.assignment && !@quiz.context_module_tag && @quiz.assignment.context_module_tag
|
||||
%>
|
||||
<%= render :partial => "shared/sequence_footer", :locals => {:asset => sequence_asset} if sequence_asset.context_module_tag %>
|
||||
<%= render :partial => "shared/sequence_footer", :locals => {:asset => @sequence_asset} if @sequence_asset %>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<%= image_tag "forward.png" %> <span class="text"><%= t(:next, "Next") %></span>
|
||||
<span class="title ellipsis"></span>
|
||||
</a>
|
||||
<a href="<%= context_url(context, :context_context_modules_item_details_url, asset.asset_string) %>" style="display: none;" class="sequence_details_url"> </a>
|
||||
<a href="<%= context_url(context, :context_context_modules_item_details_url, asset.asset_string, :module_item_id => params[:module_item_id]) %>" style="display: none;" class="sequence_details_url"> </a>
|
||||
<a href="<%= context_url(context, :context_context_modules_item_redirect_url, "{{ id }}") %>" class="module_item_url" style="display: none;"> </a>
|
||||
<a href="<%= context_url(context, :context_context_module_url, "{{ id }}") %>" class="module_url" style="display: none;"> </a>
|
||||
<div class="all">
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="not-ie" lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script type="text/javascript">
|
||||
top.location = '<%= url %>'
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<%= t('redirecting', %{Redirecting...}) %>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,96 @@
|
|||
({
|
||||
|
||||
// file optimizations
|
||||
optimize: "uglify",
|
||||
|
||||
// continue to let Jammit do its thing
|
||||
optimizeCss: "none",
|
||||
|
||||
// where to place optimized javascript, relative to this file
|
||||
dir: "../public/optimized",
|
||||
|
||||
// where the "app" is, relative to this file
|
||||
appDir: "../public/javascripts",
|
||||
|
||||
// base path for modules, relative to appDir
|
||||
baseUrl: "./",
|
||||
|
||||
translate: true,
|
||||
|
||||
paths: <%= paths %>,
|
||||
|
||||
// non-amd shims
|
||||
use: <%= shims %>,
|
||||
|
||||
// which modules should have their dependencies concatenated into them
|
||||
modules: [
|
||||
|
||||
// non "app" bundles, should be careful not to try to have too many of these
|
||||
{
|
||||
name: "compiled/tinymce",
|
||||
|
||||
// this stuff is already in common, should be able to make this a smaller
|
||||
// list since some things depend on others in the list, yes, its a bit crazy
|
||||
// this is the intersection of common and tinymce, we need to script this
|
||||
// config file...
|
||||
exclude: [
|
||||
'order',
|
||||
'i18n',
|
||||
'str/escapeRegex',
|
||||
'vendor/date',
|
||||
'jquery',
|
||||
'str/pluralize',
|
||||
'INST',
|
||||
'str/htmlEscape',
|
||||
'i18nObj',
|
||||
'vendor/jquery.scrollTo',
|
||||
'vendor/jqueryui/core',
|
||||
'vendor/jqueryui/widget',
|
||||
'vendor/jqueryui/mouse',
|
||||
'vendor/jqueryui/position',
|
||||
'translations/instructure',
|
||||
'i18n!instructure',
|
||||
'compiled/util/objectCollection',
|
||||
'vendor/spin',
|
||||
'vendor/jquery.spin',
|
||||
'jquery.google-analytics',
|
||||
'vendor/jquery.ba-hashchange',
|
||||
'vendor/jqueryui/effects/core',
|
||||
'vendor/jqueryui/effects/drop',
|
||||
'jquery.rails_flash_notifications',
|
||||
'translations/scribd',
|
||||
'i18n!scribd',
|
||||
'vendor/scribd.view',
|
||||
'jquery.dropdownList',
|
||||
'vendor/jqueryui/progressbar',
|
||||
'translations/media_comments',
|
||||
'i18n!media_comments',
|
||||
'vendor/jqueryui/button',
|
||||
'vendor/jqueryui/draggable',
|
||||
'jqueryui/draggable',
|
||||
'vendor/jqueryui/resizable',
|
||||
'vendor/jqueryui/dialog',
|
||||
'jquery.instructure_jquery_patches',
|
||||
'vendor/jqueryui/datepicker',
|
||||
'vendor/jqueryui/sortable',
|
||||
'jquery.scrollToVisible',
|
||||
'vendor/jqueryui/tabs',
|
||||
'jquery.disableWhileLoading',
|
||||
'jquery.keycodes',
|
||||
'jquery.instructure_date_and_time',
|
||||
'jquery.instructure_misc_plugins',
|
||||
'tinymce.editor_box',
|
||||
'jquery.instructure_forms',
|
||||
'jquery.ajaxJSON',
|
||||
'jquery.instructure_misc_helpers',
|
||||
'media_comments'
|
||||
]
|
||||
},
|
||||
|
||||
{ name: "common" },
|
||||
|
||||
// "apps"
|
||||
<%= app_bundles %>
|
||||
]
|
||||
})
|
||||
|
|
@ -975,4 +975,26 @@ ActiveRecord::ConnectionAdapters::SchemaStatements.class_eval do
|
|||
add_index_without_length_raise(table_name, column_name, options)
|
||||
end
|
||||
alias_method_chain :add_index, :length_raise
|
||||
|
||||
# in anticipation of having to re-run migrations due to integrity violations or
|
||||
# killing stuff that is holding locks too long
|
||||
def add_foreign_key_if_not_exists(from_table, to_table, options = {})
|
||||
return if self.adapter_name == 'SQLite'
|
||||
column = options[:column] || "#{to_table.to_s.singularize}_id"
|
||||
foreign_key_name = foreign_key_name(from_table, column, options)
|
||||
return if foreign_keys(from_table).find { |k| k.options[:name] == foreign_key_name }
|
||||
add_foreign_key(from_table, to_table, options)
|
||||
end
|
||||
|
||||
def remove_foreign_key_if_exists(table, options = {})
|
||||
return if self.adapter_name == 'SQLite'
|
||||
if Hash === options
|
||||
foreign_key_name = foreign_key_name(table, options[:column], options)
|
||||
else
|
||||
foreign_key_name = foreign_key_name(table, "#{options.to_s.singularize}_id")
|
||||
end
|
||||
|
||||
return unless foreign_keys(table).find { |k| k.options[:name] == foreign_key_name }
|
||||
remove_foreign_key(table, options)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,7 +30,7 @@ class ActiveRecord::Base
|
|||
'courses' => %w(section hidden_tabs sis_name sis_course_code),
|
||||
'discussion_topics' => %w(authorization_list_id),
|
||||
'enrollment_terms' => %w(sis_data sis_name),
|
||||
'enrollments' => %w(invitation_email can_participate_before_start_at),
|
||||
'enrollments' => %w(invitation_email can_participate_before_start_at limit_priveleges_to_course_sections),
|
||||
'groups' => %w(sis_name type groupable_id groupable_type),
|
||||
'notification_policies' => %w(user_id),
|
||||
'pseudonyms' => %w(sis_update_data deleted_unique_id sis_source_id crypted_webdav_access_code),
|
||||
|
|
|
@ -100,11 +100,19 @@ I18n.class_eval do
|
|||
end
|
||||
alias_method_chain :localize, :whitespace_removal
|
||||
|
||||
def translate_with_default_and_count_magic(key, *args)
|
||||
# Public: If a localizer has been set, use it to set the locale and then
|
||||
# delete it.
|
||||
#
|
||||
# Returns nothing.
|
||||
def set_locale_with_localizer
|
||||
if @localizer
|
||||
self.locale = @localizer.call
|
||||
@localizer = nil
|
||||
end
|
||||
end
|
||||
|
||||
def translate_with_default_and_count_magic(key, *args)
|
||||
set_locale_with_localizer
|
||||
|
||||
default = args.shift if args.first.is_a?(String) || args.size > 1
|
||||
options = args.shift || {}
|
||||
|
|
|
@ -13,6 +13,9 @@ def maintain_plugin_symlinks(local_path, plugin_path=nil)
|
|||
end
|
||||
|
||||
maintain_plugin_symlinks('public')
|
||||
# our new unified build.js and friends require these two symlinks
|
||||
maintain_plugin_symlinks('public/javascripts')
|
||||
maintain_plugin_symlinks('public/optimized')
|
||||
maintain_plugin_symlinks('app/coffeescripts')
|
||||
maintain_plugin_symlinks('app/views/jst')
|
||||
maintain_plugin_symlinks('app/stylesheets')
|
||||
|
|
|
@ -423,8 +423,8 @@ ActionController::Routing::Routes.draw do |map|
|
|||
account.resources :terms
|
||||
account.resources :sub_accounts
|
||||
account.avatars 'avatars', :controller => 'accounts', :action => 'avatars'
|
||||
account.sis_import 'sis_import', :controller => 'accounts', :action => 'sis_import'
|
||||
account.sis_import_submit 'sis_import_submit', :controller => 'accounts', :action => 'sis_import_submit'
|
||||
account.sis_import 'sis_import', :controller => 'accounts', :action => 'sis_import', :conditions => { :method => :get }
|
||||
account.resources :sis_imports, :controller => 'sis_imports_api', :only => [:create, :show]
|
||||
account.add_user 'users', :controller => 'users', :action => 'create', :conditions => {:method => :post}
|
||||
account.confirm_delete_user 'users/:user_id/delete', :controller => 'accounts', :action => 'confirm_delete_user'
|
||||
account.delete_user 'users/:user_id', :controller => 'accounts', :action => 'remove_user', :conditions => {:method => :delete}
|
||||
|
@ -861,6 +861,7 @@ ActionController::Routing::Routes.draw do |map|
|
|||
end
|
||||
|
||||
api.with_options(:controller => :collections) do |collections|
|
||||
collections.get "collections", :action => :list, :path_name => 'collections'
|
||||
collections.resources :collections, :path_prefix => "users/:user_id", :name_prefix => "user_", :only => [:index, :create]
|
||||
collections.resources :collections, :path_prefix => "groups/:group_id", :name_prefix => "group_", :only => [:index, :create]
|
||||
collections.resources :collections, :except => [:index, :create]
|
||||
|
|
|
@ -1,13 +1,28 @@
|
|||
development:
|
||||
username: put_your_zendesk_username_here
|
||||
password: put_your_zendesk_password_here
|
||||
# URL used in redirects, links, etc.
|
||||
site: http://support.put_your_zendesk_support_site_here.com
|
||||
auth_token: put_auth_token_from_zendes_website_here
|
||||
# URL to Zendesk API, not user facing. (HTTPS required)
|
||||
api_url: https://support.put_your_zendesk_support_site_here.com
|
||||
auth_token: put_auth_token_from_zendesk_website_here
|
||||
enabled: false
|
||||
|
||||
production:
|
||||
username: put_your_zendesk_username_here
|
||||
password: put_your_zendesk_password_here
|
||||
# URL used in redirects, links, etc.
|
||||
site: http://support.put_your_zendesk_support_site_here.com
|
||||
auth_token: put_auth_token_from_zendes_website_here
|
||||
# URL to Zendesk API, not user facing. (HTTPS required)
|
||||
api_url: https://support.put_your_zendesk_support_site_here.com
|
||||
auth_token: put_auth_token_from_zendesk_website_here
|
||||
enabled: true
|
||||
|
||||
test:
|
||||
username: test.username
|
||||
password: test.password
|
||||
# Must be https URL unless it is localhost or 127.0.0.1
|
||||
site: http://127.0.0.1
|
||||
api_url: http://127.0.0.1
|
||||
enabled: false
|
||||
auth_token:
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
class AddForeignKeys1 < ActiveRecord::Migration
|
||||
self.transactional = false
|
||||
tag :postdeploy
|
||||
|
||||
def self.up
|
||||
if Shard.current.default?
|
||||
add_foreign_key_if_not_exists :attachments, :scribd_mime_types, :delay_validation => true
|
||||
add_foreign_key_if_not_exists :notification_policies, :notifications, :delay_validation => true
|
||||
end
|
||||
|
||||
add_foreign_key_if_not_exists :abstract_courses, :accounts, :delay_validation => true
|
||||
add_foreign_key_if_not_exists :abstract_courses, :enrollment_terms, :delay_validation => true
|
||||
add_foreign_key_if_not_exists :abstract_courses, :accounts, :column => :root_account_id, :delay_validation => true
|
||||
add_foreign_key_if_not_exists :access_tokens, :users, :delay_validation => true
|
||||
add_foreign_key_if_not_exists :account_authorization_configs, :accounts, :delay_validation => true
|
||||
add_foreign_key_if_not_exists :account_notifications, :accounts, :delay_validation => true
|
||||
add_foreign_key_if_not_exists :account_reports, :accounts, :delay_validation => true
|
||||
add_foreign_key_if_not_exists :account_reports, :attachments, :delay_validation => true
|
||||
add_foreign_key_if_not_exists :account_users, :accounts, :delay_validation => true
|
||||
add_foreign_key_if_not_exists :accounts, :accounts, :column => :parent_account_id, :delay_validation => true
|
||||
add_foreign_key_if_not_exists :accounts, :accounts, :column => :root_account_id, :delay_validation => true
|
||||
add_foreign_key_if_not_exists :alert_criteria, :alerts, :delay_validation => true
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_foreign_key_if_exists :alert_criteria, :alerts
|
||||
remove_foreign_key_if_exists :accounts, :column => :root_account_id
|
||||
remove_foreign_key_if_exists :accounts, :column => :parent_account_id
|
||||
remove_foreign_key_if_exists :account_users, :accounts
|
||||
remove_foreign_key_if_exists :account_reports, :attachments
|
||||
remove_foreign_key_if_exists :account_reports, :accounts
|
||||
remove_foreign_key_if_exists :account_notifications, :accounts
|
||||
remove_foreign_key_if_exists :account_authorization_configs, :accounts
|
||||
remove_foreign_key_if_exists :access_tokens, :users
|
||||
remove_foreign_key_if_exists :abstract_courses, :column => :root_account_id
|
||||
remove_foreign_key_if_exists :abstract_courses, :enrollment_terms
|
||||
remove_foreign_key_if_exists :abstract_courses, :accounts
|
||||
|
||||
if Shard.current.default?
|
||||
remove_foreign_key_if_exists :notification_policies, :notifications
|
||||
remove_foreign_key_if_exists :attachments, :scribd_mime_types
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
class AddSisBatchesIndex < ActiveRecord::Migration
|
||||
tag :predeploy
|
||||
|
||||
def self.up
|
||||
add_index :sis_batches, [:account_id, :workflow_state, :created_at], :name => "index_sis_batches_for_accounts"
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_index :sis_batches, :name => "index_sis_batches_for_accounts"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
class DropSisBatchLogEntries < ActiveRecord::Migration
|
||||
tag :postdeploy
|
||||
|
||||
def self.up
|
||||
drop_table :sis_batch_log_entries
|
||||
end
|
||||
|
||||
def self.down
|
||||
create_table "sis_batch_log_entries", :force => true do |t|
|
||||
t.integer "sis_batch_id", :limit => 8
|
||||
t.string "log_type"
|
||||
t.text "text"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
class AddSisBatchesIndex < ActiveRecord::Migration
|
||||
tag :predeploy
|
||||
|
||||
self.transactional = false
|
||||
|
||||
def self.up
|
||||
# this index may or may not have been created on dev boxes
|
||||
remove_index :sis_batches, :name => "index_sis_batches_for_accounts" rescue nil
|
||||
|
||||
case connection.adapter_name
|
||||
when 'PostgreSQL'
|
||||
# select * from sis_batches where account_id = ? and workflow_state = 'created' order by created_at
|
||||
# select count(*) from sis_batches where account_id = ? and workflow_state = 'created'
|
||||
# this index is highly optimized for the sis batch job processor workflow
|
||||
connection.execute "CREATE INDEX CONCURRENTLY index_sis_batches_pending_for_accounts ON sis_batches (account_id, created_at) WHERE workflow_state = 'created'"
|
||||
# select * from sis_batches where account_id = ? order by created_at desc limit 1
|
||||
connection.execute "CREATE INDEX CONCURRENTLY index_sis_batches_account_id_created_at ON sis_batches (account_id, created_at)"
|
||||
else
|
||||
add_index :sis_batches, [:workflow_state, :account_id, :created_at], :name => "index_sis_batches_pending_for_accounts"
|
||||
add_index :sis_batches, [:account_id, :created_at], :name => "index_sis_batches_account_id_created_at"
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_index :sis_batches, :name => "index_sis_batches_pending_for_accounts"
|
||||
remove_index :sis_batches, :name => "index_sis_batches_account_id_created_at"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
class MigrateToLimitPrivilegesToCourseSection < ActiveRecord::Migration
|
||||
tag :predeploy, :postdeploy
|
||||
self.transactional = false
|
||||
|
||||
def self.up
|
||||
Enrollment.find_ids_in_ranges do |(start_id, end_id)|
|
||||
Enrollment.update_all 'limit_privileges_to_course_section=limit_priveleges_to_course_section', ["limit_privileges_to_course_section IS NULL AND id>=? AND id<=?", start_id, end_id]
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
class DropLimitPrivelegesToCourseSectionFromEnrollments < ActiveRecord::Migration
|
||||
tag :postdeploy
|
||||
|
||||
def self.up
|
||||
remove_column :enrollments, :limit_priveleges_to_course_section
|
||||
end
|
||||
|
||||
def self.down
|
||||
add_column :enrollments, :limit_priveleges_to_course_section, :boolean
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
class FixDefaultLimitPrivilegesToCourseSection < ActiveRecord::Migration
|
||||
tag :predeploy, :postdeploy
|
||||
self.transactional = false
|
||||
|
||||
def self.up
|
||||
Enrollment.find_ids_in_ranges do |(start_id, end_id)|
|
||||
Enrollment.update_all({ :limit_privileges_to_course_section => false }, ["type IN ('StudentEnrollment', 'ObserverEnrollment', 'StudentViewEnrollment', 'DesignerEnrollment') AND id>=? AND id <=?", start_id, end_id])
|
||||
Enrollment.update_all({ :limit_privileges_to_course_section => false }, ["type IN ('TeacherEnrollment', 'TaEnrollment') AND limit_privileges_to_course_section IS NULL AND id>=? AND id <=?", start_id, end_id])
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
class AddBasicIndicesToGroupCategories < ActiveRecord::Migration
|
||||
tag :postdeploy
|
||||
|
||||
def self.up
|
||||
add_index :group_categories, [:context_id, :context_type], :name => "index_group_categories_on_context"
|
||||
add_index :group_categories, :role, :name => "index_group_categories_on_role"
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_index :group_categories, :name => "index_group_categories_on_context"
|
||||
remove_index :group_categories, :name => "index_group_categories_on_role"
|
||||
end
|
||||
end
|
|
@ -37,6 +37,8 @@ module YARD::Templates::Helpers::BaseHelper
|
|||
else
|
||||
raise "couldn't find API link for #{args.first}"
|
||||
end
|
||||
elsif args.first.is_a?(String) && args.first =~ %r{^api:([^:]+):(.*)}
|
||||
link_url("#{$1.downcase}.html##{$2.gsub('+', ' ')}", args[1])
|
||||
else
|
||||
linkify_without_api(*args)
|
||||
end
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
module Api::V1::StreamItem
|
||||
include Api::V1::Context
|
||||
include Api::V1::Collection
|
||||
include Api::V1::Submission
|
||||
|
||||
def stream_item_json(stream_item, current_user, session)
|
||||
data = stream_item.stream_data(current_user.id)
|
||||
|
@ -72,22 +73,12 @@ module Api::V1::StreamItem
|
|||
hash['notification_category'] = data.notification_category
|
||||
hash['html_url'] = hash['url'] = data.url
|
||||
when 'Submission'
|
||||
hash['title'] = data.assignment.try(:title)
|
||||
hash['grade'] = data.grade
|
||||
hash['score'] = data.score
|
||||
hash['html_url'] = course_assignment_submission_url(context_id, data.assignment.id, data.user_id)
|
||||
hash['submission_comments'] = data.submission_comments.map do |comment|
|
||||
{
|
||||
'body' => comment.formatted_body,
|
||||
'user_name' => comment.user_short_name,
|
||||
'user_id' => comment.author_id,
|
||||
}
|
||||
end unless data.submission_comments.blank?
|
||||
hash['assignment'] = {
|
||||
'title' => hash['title'],
|
||||
'id' => data.assignment.try(:id),
|
||||
'points_possible' => data.assignment.try(:points_possible),
|
||||
}
|
||||
hash.merge! submission_json(Submission.find(data.id), Assignment.find(data.assignment.id), current_user, session, nil, ['submission_comments', 'assignment', 'course', 'html_url'])
|
||||
|
||||
# backwards compat from before using submission_json
|
||||
hash['assignment']['title'] = hash['assignment']['name']
|
||||
hash['title'] = hash['assignment']['name']
|
||||
hash['submission_comments'].each {|c| c['body'] = c['comment']}
|
||||
when /Conference/
|
||||
hash['web_conference_id'] = data.id
|
||||
hash['type'] = 'WebConference'
|
||||
|
@ -116,9 +107,9 @@ module Api::V1::StreamItem
|
|||
opts[:contexts] = contexts if contexts.present?
|
||||
|
||||
items = @current_user.shard.activate do
|
||||
scope = @current_user.visible_stream_item_instances(opts)
|
||||
scope = @current_user.visible_stream_item_instances(opts).scoped(:include => :stream_item)
|
||||
Api.paginate(scope, self, self.send(paginate_url, @context)).to_a
|
||||
end
|
||||
render :json => items.map { |i| stream_item_json(i.stream_item, @current_user, session) }
|
||||
render :json => items.map(&:stream_item).compact.map { |i| stream_item_json(i, @current_user, session) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,7 @@ module Api::V1::Submission
|
|||
include Api::V1::Assignment
|
||||
include Api::V1::Attachment
|
||||
include Api::V1::DiscussionTopics
|
||||
include Api::V1::Course
|
||||
|
||||
def submission_json(submission, assignment, user, session, context = nil, includes = [])
|
||||
context ||= assignment.context
|
||||
|
@ -58,10 +59,18 @@ module Api::V1::Submission
|
|||
hash['assignment'] = assignment_json(assignment, user, session)
|
||||
end
|
||||
|
||||
if includes.include?("course")
|
||||
hash['course'] = course_json(submission.context, user, session, ['html_url'], nil)
|
||||
end
|
||||
|
||||
if includes.include?("html_url")
|
||||
hash['html_url'] = course_assignment_submission_url(submission.context.id, assignment.id, user.id)
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
SUBMISSION_JSON_FIELDS = %w(user_id url score grade attempt submission_type submitted_at body assignment_id grade_matches_current_submission).freeze
|
||||
SUBMISSION_JSON_FIELDS = %w(user_id url score grade attempt submission_type submitted_at body assignment_id grade_matches_current_submission workflow_state).freeze
|
||||
SUBMISSION_OTHER_FIELDS = %w(attachments discussion_entries)
|
||||
|
||||
def submission_attempt_json(attempt, assignment, user, session, version_idx = nil, context = nil)
|
||||
|
@ -86,7 +95,7 @@ module Api::V1::Submission
|
|||
hash = api_json(attempt, user, session, :only => json_fields)
|
||||
|
||||
hash['preview_url'] = course_assignment_submission_url(
|
||||
@context, assignment, attempt[:user_id], 'preview' => '1',
|
||||
context, assignment, attempt[:user_id], 'preview' => '1',
|
||||
'version' => version_idx)
|
||||
|
||||
unless attempt.media_comment_id.blank?
|
||||
|
|
|
@ -247,14 +247,9 @@ module AuthenticationMethods
|
|||
|
||||
def initiate_cas_login(cas_client = nil)
|
||||
reset_session_for_login
|
||||
if @domain_root_account.account_authorization_config.log_in_url.present? && !in_oauth_flow?
|
||||
session[:exit_frame] = true
|
||||
delegated_auth_redirect(@domain_root_account.account_authorization_config.log_in_url)
|
||||
else
|
||||
config = { :cas_base_url => @domain_root_account.account_authorization_config.auth_base }
|
||||
cas_client ||= CASClient::Client.new(config)
|
||||
delegated_auth_redirect(cas_client.add_service_to_login_url(login_url))
|
||||
end
|
||||
config = { :cas_base_url => @domain_root_account.account_authorization_config.auth_base }
|
||||
cas_client ||= CASClient::Client.new(config)
|
||||
delegated_auth_redirect(cas_client.add_service_to_login_url(login_url))
|
||||
end
|
||||
|
||||
def initiate_saml_login(current_host=nil)
|
||||
|
|
|
@ -94,6 +94,11 @@ module BasicLTI
|
|||
hash['custom_canvas_course_id'] = context.id
|
||||
end
|
||||
|
||||
# need to set the locale here (instead of waiting for the first call to
|
||||
# I18n.t like we usually do), because otherwise we'll have the wrong code
|
||||
# for the launch_presentation_locale.
|
||||
I18n.set_locale_with_localizer
|
||||
|
||||
hash['context_id'] = context.opaque_identifier(:asset_string)
|
||||
hash['context_title'] = context.name
|
||||
hash['context_label'] = context.course_code rescue nil
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
module Canvas
|
||||
module RequireJs
|
||||
class << self
|
||||
def get_binding
|
||||
binding
|
||||
end
|
||||
|
||||
PATH_REGEX = %r{.*?/javascripts/(plugins/)?(.*)\.js\z}
|
||||
JS_ROOT = "#{Rails.root}/public/javascripts"
|
||||
|
||||
# get all regular canvas (and plugin) bundles
|
||||
def app_bundles
|
||||
app_bundles = (
|
||||
Dir["#{JS_ROOT}/compiled/bundles/*.js"] +
|
||||
Dir["#{JS_ROOT}/plugins/*/compiled/bundles/*.js"]
|
||||
).inject({}) { |hash, file|
|
||||
# plugins have their name prepended, since that's we do the paths
|
||||
name = file.sub(PATH_REGEX, '\2')
|
||||
unless name == 'compiled/bundles/common'
|
||||
hash[name] = { :name => name, :exclude => ['common', 'compiled/tinymce'] }
|
||||
end
|
||||
hash
|
||||
}
|
||||
|
||||
# inject any bundle extensions defined in plugins
|
||||
extensions_for("*").each do |bundle, extensions|
|
||||
if app_bundles["compiled/bundles/#{bundle}"]
|
||||
app_bundles["compiled/bundles/#{bundle}"][:include] = extensions
|
||||
else
|
||||
$stderr.puts "WARNING: can't extend #{bundle}, it doesn't exist"
|
||||
end
|
||||
end
|
||||
|
||||
app_bundles.values.sort_by{ |b| b[:name] }.to_json[1...-1].gsub(/,\{/, ",\n {")
|
||||
end
|
||||
|
||||
# get extensions for a particular bundle (or all, if "*")
|
||||
def extensions_for(bundle, plugin_path = '')
|
||||
result = {}
|
||||
Dir["#{JS_ROOT}/plugins/*/compiled/bundles/extensions/#{bundle}.js"].each do |file|
|
||||
name = file.sub(PATH_REGEX, '\2')
|
||||
b = name.sub(%r{.*/}, '')
|
||||
result[b] ||= []
|
||||
result[b] << plugin_path + name
|
||||
end
|
||||
bundle == '*' ? result : (result[bundle.to_s] || [])
|
||||
end
|
||||
|
||||
def paths
|
||||
@paths ||= {
|
||||
:common => 'compiled/bundles/common',
|
||||
:jqueryui => 'vendor/jqueryui',
|
||||
:uploadify => '../flash/uploadify/jquery.uploadify.v2.1.4',
|
||||
:use => 'vendor/use',
|
||||
}.update(plugin_paths).to_json.gsub(/([,{])/, "\\1\n ")
|
||||
end
|
||||
|
||||
def plugin_paths
|
||||
@plugin_paths ||= begin
|
||||
Dir['public/javascripts/plugins/*'].inject({}) { |hash, plugin|
|
||||
plugin = plugin.sub(%r{public/javascripts/plugins/}, '')
|
||||
hash[plugin] = "plugins/#{plugin}"
|
||||
hash
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def shims
|
||||
<<-JS.gsub(%r{\A +|^ {8}}, '')
|
||||
{
|
||||
'vendor/backbone': {
|
||||
deps: ['underscore', 'jquery'],
|
||||
attach: function(_, $){
|
||||
return Backbone;
|
||||
}
|
||||
},
|
||||
|
||||
// slick grid shim
|
||||
'vendor/slickgrid/lib/jquery.event.drag-2.0.min': {
|
||||
deps: ['jquery'],
|
||||
attach: '$'
|
||||
},
|
||||
'vendor/slickgrid/slick.core': {
|
||||
deps: ['jquery', 'use!vendor/slickgrid/lib/jquery.event.drag-2.0.min'],
|
||||
attach: 'Slick'
|
||||
},
|
||||
'vendor/slickgrid/slick.grid': {
|
||||
deps: ['use!vendor/slickgrid/slick.core'],
|
||||
attach: 'Slick'
|
||||
},
|
||||
'vendor/slickgrid/slick.editors': {
|
||||
deps: ['use!vendor/slickgrid/slick.core'],
|
||||
attach: 'Slick'
|
||||
},
|
||||
'vendor/slickgrid/plugins/slick.rowselectionmodel': {
|
||||
deps: ['use!vendor/slickgrid/slick.core'],
|
||||
attach: 'Slick'
|
||||
}
|
||||
}
|
||||
JS
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -29,6 +29,7 @@ module HasContentTags
|
|||
def check_if_associated_content_tags_need_updating
|
||||
@associated_content_tags_need_updating = false
|
||||
return if self.new_record?
|
||||
return if self.respond_to?(:context_type) && self.context_type == 'SisBatch'
|
||||
@associated_content_tags_need_updating = true if self.respond_to?(:title_changed?) && self.title_changed?
|
||||
@associated_content_tags_need_updating = true if self.respond_to?(:name_changed?) && self.name_changed?
|
||||
@associated_content_tags_need_updating = true if self.respond_to?(:display_name_changed?) && self.display_name_changed?
|
||||
|
|
|
@ -8,7 +8,13 @@ namespace :js do
|
|||
Rake::Task['js:generate'].invoke
|
||||
end
|
||||
puts "--> executing phantomjs tests"
|
||||
`erb spec/javascripts/runner.html.erb > spec/javascripts/runner.html`
|
||||
|
||||
require 'canvas/require_js'
|
||||
require 'erubis'
|
||||
output = Erubis::Eruby.new(File.read("#{Rails.root}/spec/javascripts/runner.html.erb")).
|
||||
result(Canvas::RequireJs.get_binding)
|
||||
File.open("#{Rails.root}/spec/javascripts/runner.html", 'w') { |f| f.write(output) }
|
||||
|
||||
phantomjs_output = `phantomjs spec/javascripts/support/qunit/test.js file:///#{Dir.pwd}/spec/javascripts/runner.html`
|
||||
exit_status = $?.exitstatus
|
||||
puts phantomjs_output
|
||||
|
@ -83,25 +89,19 @@ namespace :js do
|
|||
desc "optimize and build js for production"
|
||||
task :build do
|
||||
require 'config/initializers/plugin_symlinks'
|
||||
require 'parallel'
|
||||
require 'canvas/require_js'
|
||||
require 'erubis'
|
||||
|
||||
commands = []
|
||||
commands << ['canvas-lms', "node #{Rails.root}/node_modules/requirejs/bin/r.js -o #{Rails.root}/config/build.js 2>&1"]
|
||||
output = Erubis::Eruby.new(File.read("#{Rails.root}/config/build.js.erb")).
|
||||
result(Canvas::RequireJs.get_binding)
|
||||
File.open("#{Rails.root}/config/build.js", 'w') { |f| f.write(output) }
|
||||
|
||||
files = Dir[Rails.root+'vendor/plugins/*/config/build.js']
|
||||
files.each do |buildfile|
|
||||
plugin = buildfile.gsub(%r{.*/vendor/plugins/(.*)/config/build\.js}, '\\1')
|
||||
commands << ["#{plugin} plugin", "node #{Rails.root}/node_modules/requirejs/bin/r.js -o #{buildfile} 2>&1"]
|
||||
end
|
||||
|
||||
Parallel.each(commands, :in_threads => Parallel.processor_count) do |(plugin, command)|
|
||||
puts "--> Optimizing #{plugin}"
|
||||
optimize_time = Benchmark.realtime do
|
||||
output = `#{command}`
|
||||
raise "Error running js:build: \n#{output}\nABORTING" if $?.exitstatus != 0
|
||||
end
|
||||
puts "--> Optimized #{plugin} in #{optimize_time}"
|
||||
puts "--> Optimizing canvas-lms"
|
||||
optimize_time = Benchmark.realtime do
|
||||
output = `node #{Rails.root}/node_modules/requirejs/bin/r.js -o #{Rails.root}/config/build.js 2>&1`
|
||||
raise "Error running js:build: \n#{output}\nABORTING" if $?.exitstatus != 0
|
||||
end
|
||||
puts "--> Optimized canvas-lms in #{optimize_time}"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -13,6 +13,7 @@ module ParallelExclude
|
|||
'spec/apis/general_api_spec.rb',
|
||||
'spec/apis/user_content_spec.rb',
|
||||
'spec/apis/v1/groups_api_spec.rb',
|
||||
'spec/apis/v1/courses_api_spec.rb',
|
||||
'spec/apis/auth_spec.rb',
|
||||
'spec/integration/files_spec.rb',
|
||||
'spec/lib/acts_as_list.rb',
|
||||
|
@ -25,6 +26,8 @@ module ParallelExclude
|
|||
'spec/models/zip_file_import_spec.rb',
|
||||
'spec/models/content_migration_spec.rb',
|
||||
'spec/models/collections_spec.rb',
|
||||
'spec/lib/canvas/http_spec.rb',
|
||||
'spec/migrations/count_existing_collection_items_and_followers_spec.rb'
|
||||
]
|
||||
|
||||
test_files = FileList['vendor/plugins/*/spec_canvas/**/*_spec.rb'].exclude('vendor/plugins/*/spec_canvas/selenium/*_spec.rb') + FileList['spec/**/*_spec.rb'].exclude('spec/selenium/**/*_spec.rb')
|
||||
|
|
|
@ -87,7 +87,7 @@ unless ARGV.any? { |a| a =~ /\Agems/ }
|
|||
desc "Run all specs in spec directory with RCov (excluding plugin specs)"
|
||||
Spec::Rake::SpecTask.new(:rcov) do |t|
|
||||
t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
|
||||
t.spec_files = FileList['spec/**/*/*_spec.rb'].exclude('spec/selenium/*_spec.rb')
|
||||
t.spec_files = FileList['vendor/plugins/*/spec_canvas/**/*_spec.rb'].exclude('vendor/plugins/*/spec_canvas/selenium/*_spec.rb') + FileList['spec/**/*_spec.rb'].exclude('spec/selenium/**/*_spec.rb')
|
||||
t.rcov = true
|
||||
t.rcov_opts = lambda do
|
||||
IO.readlines("#{RAILS_ROOT}/spec/rcov.opts").map { |l| l.chomp.split " " }.flatten
|
||||
|
|
|
@ -195,7 +195,7 @@ module Turnitin
|
|||
|
||||
responses[asset_string] = object_id ?
|
||||
{ :object_id => object_id } :
|
||||
{ :error_code => rcode, :error_message => rmessage, :public_error_message => public_error_message(:rcode) }
|
||||
{ :error_code => rcode, :error_message => rmessage, :public_error_message => public_error_message(rcode) }
|
||||
end
|
||||
|
||||
responses
|
||||
|
|
|
@ -120,7 +120,7 @@ define([
|
|||
}
|
||||
$form.fillFormData(data, { object_name: "assignment" });
|
||||
$form.find(":text:first").focus().select();
|
||||
$("html,body").scrollToVisible($assignment);
|
||||
//$("html,body").scrollToVisible($assignment);
|
||||
}
|
||||
function hideGroupForm() {
|
||||
var $form = $("#add_group_form");
|
||||
|
@ -265,7 +265,7 @@ define([
|
|||
$assignment.find(".links,.move").css('display', '');
|
||||
$assignment.toggleClass('group_assignment_editable', assignment.permissions && assignment.permissions.update);
|
||||
addAssignmentToGroup($("#group_" + assignment.assignment_group_id), $assignment);
|
||||
$("html,body").scrollToVisible($assignment);
|
||||
//$("html,body").scrollToVisible($assignment);
|
||||
}
|
||||
function addAssignmentToGroup($group, $assignment) {
|
||||
var data = $assignment.getTemplateData({textValues: ['timestamp', 'title', 'position']}),
|
||||
|
@ -843,7 +843,7 @@ define([
|
|||
addAssignmentToGroup($assignment.parents(".assignment_group"), $assignment);
|
||||
$assignment.find(".links").hide();
|
||||
$assignment.loadingImage({image_size: 'small', paddingTop: 5});
|
||||
$("html,body").scrollToVisible($assignment);
|
||||
//$("html,body").scrollToVisible($assignment);
|
||||
|
||||
var isNew = false;
|
||||
if($assignment.attr('id') == "assignment_new") {
|
||||
|
|
|
@ -182,6 +182,14 @@ define([
|
|||
}, function() {
|
||||
});
|
||||
},
|
||||
itemClass: function(content_tag) {
|
||||
return content_tag.content_type + "_" + content_tag.content_id;
|
||||
},
|
||||
updateAllItemInstances: function(content_tag) {
|
||||
$(".context_module_item."+modules.itemClass(content_tag)+" .title").each(function() {
|
||||
$(this).text(content_tag.title);
|
||||
});
|
||||
},
|
||||
editModule: function($module) {
|
||||
var $form = $("#add_context_module_form");
|
||||
$form.data('current_module', $module);
|
||||
|
@ -279,6 +287,7 @@ define([
|
|||
$item.removeClass('indent_' + idx);
|
||||
}
|
||||
$item.addClass('indent_' + (data.indent || 0));
|
||||
$item.addClass(modules.itemClass(data));
|
||||
// don't just tack onto the bottom, put it in its correct position
|
||||
var $before = null;
|
||||
$module.find(".context_module_items").children().each(function() {
|
||||
|
@ -663,6 +672,7 @@ define([
|
|||
var $module = $("#context_module_" + data.content_tag.context_module_id);
|
||||
var $item = modules.addItemToModule($module, data.content_tag);
|
||||
$module.find(".context_module_items").sortable('refresh');
|
||||
modules.updateAllItemInstances(data.content_tag);
|
||||
modules.updateAssignmentData();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
|
|
|
@ -1134,7 +1134,15 @@ define([
|
|||
cnt++;
|
||||
}
|
||||
if(cnt === 0) {
|
||||
$curve_grade_dialog.errorBox(I18n.t('errors.none_to_update', 'None to Update'));
|
||||
var errorBox = $curve_grade_dialog.errorBox(
|
||||
I18n.t('errors.none_to_update', 'None to Update'));
|
||||
|
||||
setTimeout(function() {
|
||||
errorBox.fadeOut(function() {
|
||||
errorBox.remove();
|
||||
});
|
||||
}, 3500);
|
||||
|
||||
return false;
|
||||
}
|
||||
return data;
|
||||
|
|
|
@ -805,7 +805,8 @@ define([
|
|||
options.property_validations = $._addObjectName(options.property_validations, options.object_name);
|
||||
}
|
||||
if (options.required) {
|
||||
$.each(options.required, function(i, name) {
|
||||
var required = _.result(options, 'required')
|
||||
$.each(required, function(i, name) {
|
||||
if (!data[name]) {
|
||||
if (!errors[name]) {
|
||||
errors[name] = [];
|
||||
|
|
|
@ -118,27 +118,12 @@ $(document).ready(function(event) {
|
|||
var waitTime = 1500;
|
||||
$.ajaxJSON(location.href, 'GET', {}, function(data) {
|
||||
state = "updating";
|
||||
var sis_batch = data.sis_batch;
|
||||
var sis_batch = data;
|
||||
var progress = 0;
|
||||
if(sis_batch) {
|
||||
progress = Math.max($(".copy_progress").progressbar('option', 'value') || 0, sis_batch.progress);
|
||||
$(".copy_progress").progressbar('option', 'value', progress);
|
||||
$("#import_log").empty();
|
||||
if(sis_batch.sis_batch_log_entries) {
|
||||
for(var idx in sis_batch.sis_batch_log_entries) {
|
||||
var entry = sis_batch.sis_batch_log_entries[idx].sis_batch_log_entry;
|
||||
var lines = entry.text.split("\\n");
|
||||
if($("#import_log #log_" + entry.id).length == 0) {
|
||||
var $holder = $("<div id='log_" + entry.id + "'/>");
|
||||
for(var jdx in lines) {
|
||||
var $div = $("<div/>");
|
||||
$div.text(lines[jdx]);
|
||||
$holder.append($div);
|
||||
}
|
||||
$("#import_log").append($holder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!sis_batch || sis_batch.workflow_state == 'imported') {
|
||||
$("#sis_importer").hide();
|
||||
|
@ -185,7 +170,7 @@ $(document).ready(function(event) {
|
|||
$("#sis_importer").formSubmit({
|
||||
fileUpload: true,
|
||||
success: function(data) {
|
||||
if(data && data.sis_batch) {
|
||||
if(data && data.id) {
|
||||
startPoll();
|
||||
} else {
|
||||
//show error message
|
||||
|
@ -206,7 +191,7 @@ $(document).ready(function(event) {
|
|||
state = "checking";
|
||||
$.ajaxJSON(location.href, 'GET', {}, function(data) {
|
||||
state = "nothing";
|
||||
var sis_batch = data.sis_batch;
|
||||
var sis_batch = data;
|
||||
var progress = 0;
|
||||
if(sis_batch && (sis_batch.workflow_state == "importing" || sis_batch.workflow_state == "created")) {
|
||||
state = "nothing";
|
||||
|
|
|
@ -136,6 +136,7 @@ describe "Collections API", :type => :integration do
|
|||
def create_collections(context)
|
||||
@c1 = context.collections.create!(:name => 'test1', :visibility => 'private')
|
||||
@c2 = context.collections.create!(:name => 'test2', :visibility => 'public')
|
||||
[@c1, @c2]
|
||||
end
|
||||
|
||||
def create_collection_items(user)
|
||||
|
@ -642,4 +643,45 @@ describe "Collections API", :type => :integration do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "unscoped collections" do
|
||||
before do
|
||||
user_with_pseudonym
|
||||
group_model({:group_category => GroupCategory.communities_for(Account.default), :is_public => true})
|
||||
@group_membership = @group.add_user(@user, 'accepted', true)
|
||||
end
|
||||
|
||||
it "should list all pinnable collections" do
|
||||
@gc1, @gc2 = create_collections(@group)
|
||||
@uc1, @uc2 = create_collections(@user)
|
||||
json = api_call(:get, "/api/v1/collections", { :controller => "collections", :action => "list", :format => "json" })
|
||||
json.should == [@gc1, @gc2, @uc1, @uc2].sort_by(&:id).reverse.map{ |c| collection_json(c) }
|
||||
end
|
||||
|
||||
it "should create a default collection for each pinnable context" do
|
||||
json = api_call(:get, "/api/v1/collections", { :controller => "collections", :action => "list", :format => "json" })
|
||||
json.count.should == 2
|
||||
json.map{ |j| j['name']}.sort.should == [@user.default_collection_name, @group.default_collection_name].sort
|
||||
end
|
||||
|
||||
it "should not create a default collection for non-community groups" do
|
||||
@community = @group
|
||||
@group = group_model
|
||||
@group.add_user(@user, 'accepted', true)
|
||||
|
||||
json = api_call(:get, "/api/v1/collections", { :controller => "collections", :action => "list", :format => "json" })
|
||||
json.count.should == 2
|
||||
json.map{ |j| j['name']}.sort.should == [@user.default_collection_name, @community.default_collection_name].sort
|
||||
end
|
||||
|
||||
it "should not return collections the user does not have permission to pin to" do
|
||||
@community = @group
|
||||
@community2 = group_model({:group_category => GroupCategory.communities_for(Account.default), :is_public => true})
|
||||
@gc1, @gc2 = create_collections(@community)
|
||||
@uc1, @uc2 = create_collections(@user)
|
||||
@no1, @no2 = create_collections(@community2)
|
||||
json = api_call(:get, "/api/v1/collections", { :controller => "collections", :action => "list", :format => "json" })
|
||||
json.should == [@gc1, @gc2, @uc1, @uc2].sort_by(&:id).reverse.map{ |c| collection_json(c) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -743,6 +743,24 @@ describe ConversationsController, :type => :integration do
|
|||
})
|
||||
end
|
||||
|
||||
it "should auto-mark-as-read if unread" do
|
||||
conversation = conversation(@bob, :workflow_state => 'unread')
|
||||
|
||||
json = api_call(:get, "/api/v1/conversations/#{conversation.conversation_id}?scope=unread",
|
||||
{ :controller => 'conversations', :action => 'show', :id => conversation.conversation_id.to_s, :scope => 'unread', :format => 'json' })
|
||||
json["visible"].should be_false
|
||||
conversation.reload.should be_read
|
||||
end
|
||||
|
||||
it "should not auto-mark-as-read if auto_mark_as_read = false" do
|
||||
conversation = conversation(@bob, :workflow_state => 'unread')
|
||||
|
||||
json = api_call(:get, "/api/v1/conversations/#{conversation.conversation_id}?scope=unread&auto_mark_as_read=0",
|
||||
{ :controller => 'conversations', :action => 'show', :id => conversation.conversation_id.to_s, :scope => 'unread', :auto_mark_as_read => "0", :format => 'json' })
|
||||
json["visible"].should be_true
|
||||
conversation.reload.should be_unread
|
||||
end
|
||||
|
||||
it "should properly flag if starred in the response" do
|
||||
conversation1 = conversation(@bob)
|
||||
conversation2 = conversation(@billy, :starred => true)
|
||||
|
|
|
@ -49,7 +49,7 @@ describe EnrollmentsApiController, :type => :integration do
|
|||
'id' => new_enrollment.id,
|
||||
'user_id' => @unenrolled_user.id,
|
||||
'course_section_id' => @section.id,
|
||||
'limit_privileges_to_course_section' => true,
|
||||
'limit_privileges_to_course_section' => false,
|
||||
'enrollment_state' => 'active',
|
||||
'course_id' => @course.id,
|
||||
'type' => 'StudentEnrollment',
|
||||
|
@ -65,7 +65,6 @@ describe EnrollmentsApiController, :type => :integration do
|
|||
new_enrollment.root_account_id.should eql @course.account.id
|
||||
new_enrollment.user_id.should eql @unenrolled_user.id
|
||||
new_enrollment.course_section_id.should eql @section.id
|
||||
new_enrollment.limit_privileges_to_course_section.should eql true
|
||||
new_enrollment.workflow_state.should eql 'active'
|
||||
new_enrollment.course_id.should eql @course.id
|
||||
new_enrollment.should be_an_instance_of StudentEnrollment
|
||||
|
@ -207,7 +206,7 @@ describe EnrollmentsApiController, :type => :integration do
|
|||
'id' => new_enrollment.id,
|
||||
'user_id' => @unenrolled_user.id,
|
||||
'course_section_id' => @section.id,
|
||||
'limit_privileges_to_course_section' => true,
|
||||
'limit_privileges_to_course_section' => false,
|
||||
'enrollment_state' => 'active',
|
||||
'course_id' => @course.id,
|
||||
'type' => 'StudentEnrollment',
|
||||
|
@ -223,7 +222,6 @@ describe EnrollmentsApiController, :type => :integration do
|
|||
new_enrollment.root_account_id.should eql @course.account.id
|
||||
new_enrollment.user_id.should eql @unenrolled_user.id
|
||||
new_enrollment.course_section_id.should eql @section.id
|
||||
new_enrollment.limit_privileges_to_course_section.should eql true
|
||||
new_enrollment.workflow_state.should eql 'active'
|
||||
new_enrollment.course_id.should eql @course.id
|
||||
new_enrollment.should be_an_instance_of StudentEnrollment
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/../api_spec_helper')
|
||||
|
||||
describe UsersController, :type => :integration do
|
||||
|
||||
|
||||
before do
|
||||
course_with_student(:active_all => true)
|
||||
end
|
||||
|
@ -40,6 +42,10 @@ describe UsersController, :type => :integration do
|
|||
{ :controller => "users", :action => "activity_stream", :format => 'json' })
|
||||
json.size.should == 0
|
||||
google_docs_collaboration_model(:user_id => @user.id)
|
||||
@context = @course
|
||||
@topic1 = discussion_topic_model
|
||||
# introduce a dangling StreamItemInstance
|
||||
StreamItem.delete_all(:id => @user.visible_stream_item_instances.last.stream_item_id)
|
||||
json = api_call(:get, "/api/v1/users/activity_stream.json",
|
||||
{ :controller => "users", :action => "activity_stream", :format => 'json' })
|
||||
json.size.should == 1
|
||||
|
@ -194,6 +200,7 @@ describe UsersController, :type => :integration do
|
|||
@sub.save!
|
||||
json = api_call(:get, "/api/v1/users/activity_stream.json",
|
||||
{ :controller => "users", :action => "activity_stream", :format => 'json' })
|
||||
|
||||
json.should == [{
|
||||
'id' => StreamItem.last.id,
|
||||
'title' => "assignment 1",
|
||||
|
@ -204,24 +211,61 @@ describe UsersController, :type => :integration do
|
|||
'grade' => '12',
|
||||
'score' => 12,
|
||||
'html_url' => "http://www.example.com/courses/#{@course.id}/assignments/#{@assignment.id}/submissions/#{@user.id}",
|
||||
'workflow_state' => 'graded',
|
||||
|
||||
'assignment' => {
|
||||
'title' => 'assignment 1',
|
||||
'id' => @assignment.id,
|
||||
'points_possible' => 14.2,
|
||||
'assignment_group_id' => @assignment.assignment_group.id,
|
||||
'course_id' => @course.id,
|
||||
'description' => @assignment.description,
|
||||
'due_at' => @assignment.due_at,
|
||||
'grading_type' => @assignment.grading_type,
|
||||
'group_category_id' => nil,
|
||||
'html_url' => "http://www.example.com/courses/#{@course.id}/assignments/#{@assignment.id}",
|
||||
'muted' => @assignment.muted,
|
||||
'name' => @assignment.title,
|
||||
'position' => @assignment.position,
|
||||
'submission_types' => ['online_text_entry']
|
||||
},
|
||||
|
||||
|
||||
'assignment_id' => @assignment.id,
|
||||
'attempt' => nil,
|
||||
'body' => nil,
|
||||
'grade_matches_current_submission' => true,
|
||||
'preview_url' => "http://www.example.com/courses/#{@course.id}/assignments/#{@assignment.id}/submissions/#{@user.id}?preview=1",
|
||||
'submission_type' => nil,
|
||||
'submitted_at' => nil,
|
||||
'url' => nil,
|
||||
'user_id' => @sub.user_id,
|
||||
|
||||
'submission_comments' => [{
|
||||
'body' => '<p>c1</p>',
|
||||
'user_name' => 'teacher',
|
||||
'user_id' => @teacher.id,
|
||||
'body' => 'c1',
|
||||
'comment' => 'c1',
|
||||
'author_name' => 'teacher',
|
||||
'author_id' => @teacher.id,
|
||||
'created_at' => @sub.submission_comments[0].created_at.as_json,
|
||||
},
|
||||
{
|
||||
'body' => '<p>c2</p>',
|
||||
'user_name' => 'User',
|
||||
'user_id' => @user.id,
|
||||
'body' => 'c2',
|
||||
'comment' => 'c2',
|
||||
'author_name' => 'User',
|
||||
'author_id' => @user.id,
|
||||
'created_at' => @sub.submission_comments[1].created_at.as_json,
|
||||
},],
|
||||
|
||||
'course' => {
|
||||
'name' => @course.name,
|
||||
'end_at' => @course.end_at,
|
||||
'account_id' => @course.account_id,
|
||||
'start_at' => @course.start_at.as_json,
|
||||
'id' => @course.id,
|
||||
'course_code' => @course.course_code,
|
||||
'calendar' => { 'ics' => "http://www.example.com/feeds/calendars/course_#{@course.uuid}.ics" },
|
||||
'html_url' => course_url(@course, :host => HostUrl.context_host(@course)),
|
||||
},
|
||||
|
||||
'context_type' => 'Course',
|
||||
'course_id' => @course.id,
|
||||
}]
|
||||
|
@ -248,29 +292,66 @@ describe UsersController, :type => :integration do
|
|||
'grade' => nil,
|
||||
'score' => nil,
|
||||
'html_url' => "http://www.example.com/courses/#{@course.id}/assignments/#{@assignment.id}/submissions/#{@user.id}",
|
||||
'workflow_state' => 'unsubmitted',
|
||||
|
||||
'assignment' => {
|
||||
'title' => 'assignment 1',
|
||||
'id' => @assignment.id,
|
||||
'points_possible' => 14.2,
|
||||
'assignment_group_id' => @assignment.assignment_group.id,
|
||||
'course_id' => @course.id,
|
||||
'description' => @assignment.description,
|
||||
'due_at' => @assignment.due_at,
|
||||
'grading_type' => @assignment.grading_type,
|
||||
'group_category_id' => nil,
|
||||
'html_url' => "http://www.example.com/courses/#{@course.id}/assignments/#{@assignment.id}",
|
||||
'muted' => @assignment.muted,
|
||||
'name' => @assignment.title,
|
||||
'position' => @assignment.position,
|
||||
'submission_types' => ['online_text_entry']
|
||||
},
|
||||
|
||||
|
||||
'assignment_id' => @assignment.id,
|
||||
'attempt' => nil,
|
||||
'body' => nil,
|
||||
'grade_matches_current_submission' => nil,
|
||||
'preview_url' => "http://www.example.com/courses/#{@course.id}/assignments/#{@assignment.id}/submissions/#{@user.id}?preview=1",
|
||||
'submission_type' => nil,
|
||||
'submitted_at' => nil,
|
||||
'url' => nil,
|
||||
'user_id' => @sub.user_id,
|
||||
|
||||
'submission_comments' => [{
|
||||
'body' => '<p>c1</p>',
|
||||
'user_name' => 'teacher',
|
||||
'user_id' => @teacher.id,
|
||||
'body' => 'c1',
|
||||
'comment' => 'c1',
|
||||
'author_name' => 'teacher',
|
||||
'author_id' => @teacher.id,
|
||||
'created_at' => @sub.submission_comments[0].created_at.as_json,
|
||||
},
|
||||
{
|
||||
'body' => '<p>c2</p>',
|
||||
'user_name' => 'User',
|
||||
'user_id' => @user.id,
|
||||
'body' => 'c2',
|
||||
'comment' => 'c2',
|
||||
'author_name' => 'User',
|
||||
'author_id' => @user.id,
|
||||
'created_at' => @sub.submission_comments[1].created_at.as_json,
|
||||
},],
|
||||
|
||||
'course' => {
|
||||
'name' => @course.name,
|
||||
'end_at' => @course.end_at,
|
||||
'account_id' => @course.account_id,
|
||||
'start_at' => @course.start_at.as_json,
|
||||
'id' => @course.id,
|
||||
'course_code' => @course.course_code,
|
||||
'calendar' => { 'ics' => "http://www.example.com/feeds/calendars/course_#{@course.uuid}.ics" },
|
||||
'html_url' => course_url(@course, :host => HostUrl.context_host(@course)),
|
||||
},
|
||||
|
||||
'context_type' => 'Course',
|
||||
'course_id' => @course.id,
|
||||
}]
|
||||
end
|
||||
|
||||
|
||||
it "should format graded Submission without comments" do
|
||||
@assignment = @course.assignments.create!(:title => 'assignment 1', :description => 'hai', :points_possible => '14.2', :submission_types => 'online_text_entry')
|
||||
@teacher = User.create!(:name => 'teacher')
|
||||
|
@ -280,28 +361,13 @@ describe UsersController, :type => :integration do
|
|||
@sub.save!
|
||||
json = api_call(:get, "/api/v1/users/activity_stream.json",
|
||||
{ :controller => "users", :action => "activity_stream", :format => 'json' })
|
||||
json.should == [{
|
||||
'id' => StreamItem.last.id,
|
||||
'title' => "assignment 1",
|
||||
'message' => nil,
|
||||
'type' => 'Submission',
|
||||
'created_at' => StreamItem.last.created_at.as_json,
|
||||
'updated_at' => StreamItem.last.updated_at.as_json,
|
||||
'grade' => '12',
|
||||
'score' => 12,
|
||||
'html_url' => "http://www.example.com/courses/#{@course.id}/assignments/#{@assignment.id}/submissions/#{@user.id}",
|
||||
|
||||
'assignment' => {
|
||||
'title' => 'assignment 1',
|
||||
'id' => @assignment.id,
|
||||
'points_possible' => 14.2,
|
||||
},
|
||||
|
||||
'context_type' => 'Course',
|
||||
'course_id' => @course.id,
|
||||
}]
|
||||
json[0]['grade'].should == '12'
|
||||
json[0]['score'].should == 12
|
||||
json[0]['workflow_state'].should == 'graded'
|
||||
json[0]['submission_comments'].should == []
|
||||
end
|
||||
|
||||
|
||||
it "should not format ungraded Submission without comments" do
|
||||
@assignment = @course.assignments.create!(:title => 'assignment 1', :description => 'hai', :points_possible => '14.2', :submission_types => 'online_text_entry')
|
||||
@teacher = User.create!(:name => 'teacher')
|
||||
|
|
|
@ -61,7 +61,8 @@ describe 'Submissions API', :type => :integration do
|
|||
"submission_type"=>nil,
|
||||
"submission_comments"=>[],
|
||||
"grade_matches_current_submission"=>nil,
|
||||
"score"=>nil
|
||||
"score"=>nil,
|
||||
"workflow_state"=>nil
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -395,7 +396,8 @@ describe 'Submissions API', :type => :integration do
|
|||
"created_at"=>comment.created_at.as_json,
|
||||
"author_name"=>"User",
|
||||
"author_id"=>student1.id}],
|
||||
"score"=>13.5}
|
||||
"score"=>13.5,
|
||||
"workflow_state"=>"graded"}
|
||||
|
||||
# can't access other students' submissions
|
||||
@user = student2
|
||||
|
@ -516,7 +518,8 @@ describe 'Submissions API', :type => :integration do
|
|||
"user_id"=>student1.id,
|
||||
"preview_url" => "http://www.example.com/courses/#{@course.id}/assignments/#{a1.id}/submissions/#{student1.id}?preview=1&version=0",
|
||||
"grade_matches_current_submission"=>nil,
|
||||
"score"=>nil},
|
||||
"score"=>nil,
|
||||
"workflow_state" => "submitted"},
|
||||
{"grade"=>nil,
|
||||
"assignment_id" => a1.id,
|
||||
"media_comment" =>
|
||||
|
@ -533,7 +536,8 @@ describe 'Submissions API', :type => :integration do
|
|||
"user_id"=>student1.id,
|
||||
"preview_url" => "http://www.example.com/courses/#{@course.id}/assignments/#{a1.id}/submissions/#{student1.id}?preview=1&version=1",
|
||||
"grade_matches_current_submission"=>nil,
|
||||
"score"=>nil},
|
||||
"score"=>nil,
|
||||
"workflow_state" => "submitted"},
|
||||
{"grade"=>"A-",
|
||||
"assignment_id" => a1.id,
|
||||
"media_comment" =>
|
||||
|
@ -558,7 +562,8 @@ describe 'Submissions API', :type => :integration do
|
|||
"user_id"=>student1.id,
|
||||
"preview_url" => "http://www.example.com/courses/#{@course.id}/assignments/#{a1.id}/submissions/#{student1.id}?preview=1&version=2",
|
||||
"grade_matches_current_submission"=>true,
|
||||
"score"=>13.5}],
|
||||
"score"=>13.5,
|
||||
"workflow_state" => "graded"}],
|
||||
"attempt"=>3,
|
||||
"url"=>nil,
|
||||
"submission_type"=>"online_text_entry",
|
||||
|
@ -581,7 +586,8 @@ describe 'Submissions API', :type => :integration do
|
|||
"content-type" => "video/mp4",
|
||||
"url" => "http://www.example.com/users/#{@user.id}/media_download?entryId=54321&redirect=1&type=mp4",
|
||||
"display_name" => nil },
|
||||
"score"=>13.5},
|
||||
"score"=>13.5,
|
||||
"workflow_state"=>"graded"},
|
||||
{"grade"=>"F",
|
||||
"assignment_id" => a1.id,
|
||||
"body"=>nil,
|
||||
|
@ -598,7 +604,7 @@ describe 'Submissions API', :type => :integration do
|
|||
"submission_type"=>"online_url",
|
||||
"user_id"=>student2.id,
|
||||
"preview_url" => "http://www.example.com/courses/#{@course.id}/assignments/#{a1.id}/submissions/#{student2.id}?preview=1&version=0",
|
||||
"grade_matches_current_submission"=>true,
|
||||
"grade_matches_current_submission"=>true,
|
||||
"attachments" =>
|
||||
[
|
||||
{"content-type" => "image/png",
|
||||
|
@ -616,7 +622,8 @@ describe 'Submissions API', :type => :integration do
|
|||
"size" => sub2.attachment.size,
|
||||
},
|
||||
],
|
||||
"score"=>9}],
|
||||
"score"=>9,
|
||||
"workflow_state" => "graded"}],
|
||||
"attempt"=>1,
|
||||
"url"=>"http://www.instructure.com",
|
||||
"submission_type"=>"online_url",
|
||||
|
@ -642,7 +649,8 @@ describe 'Submissions API', :type => :integration do
|
|||
"score"=>9,
|
||||
"rubric_assessment"=>
|
||||
{"crit2"=>{"comments"=>"Hmm", "points"=>2},
|
||||
"crit1"=>{"comments"=>nil, "points"=>7}}}]
|
||||
"crit1"=>{"comments"=>nil, "points"=>7}},
|
||||
"workflow_state"=>"graded"}]
|
||||
json.sort_by { |h| h['user_id'] }.should == res.sort_by { |h| h['user_id'] }
|
||||
end
|
||||
|
||||
|
|
|
@ -128,74 +128,6 @@ describe AccountsController do
|
|||
end
|
||||
end
|
||||
|
||||
describe "SIS imports" do
|
||||
it "should set batch mode and term if given" do
|
||||
account_with_admin_logged_in
|
||||
@account.update_attribute(:allow_sis_import, true)
|
||||
post 'sis_import_submit', :account_id => @account.id, :import_type => 'instructure_csv', :batch_mode => '1'
|
||||
batch = SisBatch.last
|
||||
batch.should_not be_nil
|
||||
batch.batch_mode.should be_true
|
||||
batch.batch_mode_term.should be_nil
|
||||
batch.destroy
|
||||
|
||||
post 'sis_import_submit', :account_id => @account.id, :import_type => 'instructure_csv', :batch_mode => '1', :batch_mode_term_id => @account.enrollment_terms.first.id
|
||||
batch = SisBatch.last
|
||||
batch.should_not be_nil
|
||||
batch.batch_mode.should be_true
|
||||
batch.batch_mode_term.should == @account.enrollment_terms.first
|
||||
end
|
||||
|
||||
it "should set sis stickiness options if given" do
|
||||
account_with_admin_logged_in
|
||||
@account.update_attribute(:allow_sis_import, true)
|
||||
|
||||
post 'sis_import_submit', :account_id => @account.id,
|
||||
:import_type => 'instructure_csv'
|
||||
batch = SisBatch.last
|
||||
batch.should_not be_nil
|
||||
batch.options.should == {}
|
||||
batch.destroy
|
||||
|
||||
post 'sis_import_submit', :account_id => @account.id,
|
||||
:import_type => 'instructure_csv', :override_sis_stickiness => '1'
|
||||
batch = SisBatch.last
|
||||
batch.should_not be_nil
|
||||
batch.options.should == { :override_sis_stickiness => true }
|
||||
batch.destroy
|
||||
|
||||
post 'sis_import_submit', :account_id => @account.id,
|
||||
:import_type => 'instructure_csv', :override_sis_stickiness => '1',
|
||||
:add_sis_stickiness => '1'
|
||||
batch = SisBatch.last
|
||||
batch.should_not be_nil
|
||||
batch.options.should == { :override_sis_stickiness => true, :add_sis_stickiness => true }
|
||||
batch.destroy
|
||||
|
||||
post 'sis_import_submit', :account_id => @account.id,
|
||||
:import_type => 'instructure_csv', :override_sis_stickiness => '1',
|
||||
:clear_sis_stickiness => '1'
|
||||
batch = SisBatch.last
|
||||
batch.should_not be_nil
|
||||
batch.options.should == { :override_sis_stickiness => true, :clear_sis_stickiness => true }
|
||||
batch.destroy
|
||||
|
||||
post 'sis_import_submit', :account_id => @account.id,
|
||||
:import_type => 'instructure_csv', :clear_sis_stickiness => '1'
|
||||
batch = SisBatch.last
|
||||
batch.should_not be_nil
|
||||
batch.options.should == {}
|
||||
batch.destroy
|
||||
|
||||
post 'sis_import_submit', :account_id => @account.id,
|
||||
:import_type => 'instructure_csv', :add_sis_stickiness => '1'
|
||||
batch = SisBatch.last
|
||||
batch.should_not be_nil
|
||||
batch.options.should == {}
|
||||
batch.destroy
|
||||
end
|
||||
end
|
||||
|
||||
describe "add_account_user" do
|
||||
it "should allow adding a new account admin" do
|
||||
account_with_admin_logged_in
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue