allow assigning section-subsets to appointment group
closes #6832 Test plan: - in a separate branch (master), create a new appointment group and assign it a group category or section - switch to this branch and migrate; the appointment group should still be assigned to the group or section you chose earlier - create a new appointment group and assign it some course sections - verify that students in those sections can reserve appointments, but not students that do not belong to those sections Change-Id: I1662374c5e6d2e5e9f7d6b54b0bc91420f150b7a Reviewed-on: https://gerrit.instructure.com/8765 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jon Jensen <jon@instructure.com>
This commit is contained in:
parent
b0178fef6c
commit
6416844817
|
@ -4,10 +4,11 @@ define [
|
|||
'compiled/calendar/TimeBlockList'
|
||||
'jst/calendar/editAppointmentGroup'
|
||||
'jst/calendar/genericSelect'
|
||||
'jst/calendar/sectionCheckboxes'
|
||||
'jquery.ajaxJSON'
|
||||
'jquery.disableWhileLoading'
|
||||
'jquery.instructure_forms'
|
||||
], ($, I18n, TimeBlockList, editAppointmentGroupTemplate, genericSelectTemplate) ->
|
||||
], ($, I18n, TimeBlockList, editAppointmentGroupTemplate, genericSelectTemplate, sectionCheckboxesTemplate) ->
|
||||
|
||||
class EditAppointmentGroupDetails
|
||||
constructor: (selector, @apptGroup, @contextChangeCB, @closeCB) ->
|
||||
|
@ -29,17 +30,13 @@ define [
|
|||
@form.find("select.context_id").change()
|
||||
|
||||
@form.find(".group_category").attr('disabled', true)
|
||||
@form.find(".course_section").attr('disabled', true)
|
||||
@form.find('input[name="section_ids[]"]').attr('disabled', true)
|
||||
@form.find(".group-signup-checkbox").attr('disabled', true)
|
||||
if @apptGroup.participant_type == 'Group'
|
||||
@form.find(".group-signup-checkbox").prop('checked', true)
|
||||
@form.find(".group_category").val(@apptGroup.sub_context_code)
|
||||
@form.find(".group_category").val(@apptGroup.sub_context_codes[0])
|
||||
else
|
||||
@form.find(".group-signup-checkbox").prop('checked', false)
|
||||
if @apptGroup.sub_context_code
|
||||
@form.find(".course_section").val(@apptGroup.sub_context_code)
|
||||
else
|
||||
@form.find(".course_section").val("all")
|
||||
else
|
||||
@form.attr('action', @currentContextInfo.create_appointment_group_url)
|
||||
|
||||
|
@ -143,9 +140,9 @@ define [
|
|||
params['appointment_group[context_code]'] = data.context_code
|
||||
|
||||
if data.use_group_signup == '1' && data.group_category_id
|
||||
params['appointment_group[sub_context_code]'] = data.group_category_id
|
||||
else if data.section_id && data.section_id != 'all'
|
||||
params['appointment_group[sub_context_code]'] = data.section_id
|
||||
params['appointment_group[sub_context_codes]'] = [data.group_category_id]
|
||||
else if data['section_ids[]']?.length > 0
|
||||
params['appointment_group[sub_context_codes]'] = data['section_ids[]']
|
||||
|
||||
# TODO: Provide UI for specifying this
|
||||
params['appointment_group[min_appointments_per_participant]'] = 1
|
||||
|
@ -166,11 +163,10 @@ define [
|
|||
|
||||
# Update the sections and groups lists in the scheduler
|
||||
if @currentContextInfo.course_sections
|
||||
sectionsInfo =
|
||||
cssClass: 'course_section'
|
||||
name: 'section_id'
|
||||
collection: [ { id: 'all', name: "All Sections"} ].concat @currentContextInfo.course_sections
|
||||
@form.find(".section_select").html(genericSelectTemplate(sectionsInfo))
|
||||
for courseSection in @currentContextInfo.course_sections
|
||||
courseSection.selected = courseSection.asset_string in this.apptGroup.sub_context_codes
|
||||
sectionsInfo = { sections: @currentContextInfo.course_sections }
|
||||
@form.find('.section_select').html(sectionCheckboxesTemplate(sectionsInfo))
|
||||
|
||||
if !@currentContextInfo.group_categories || @currentContextInfo.group_categories.length == 0
|
||||
@form.find(".group-signup-checkbox").attr('disabled', true).prop('checked', false).change()
|
||||
|
|
|
@ -41,6 +41,7 @@ define [
|
|||
|
||||
group = {
|
||||
contexts: @calendar.contexts
|
||||
sub_context_codes: []
|
||||
}
|
||||
|
||||
@createDialog = new EditAppointmentGroupDialog(group, @dialogCloseCB)
|
||||
|
|
|
@ -64,7 +64,7 @@ class CalendarEventsApiController < ApplicationController
|
|||
if authorized_action(@event, @current_user, :reserve)
|
||||
begin
|
||||
if params[:participant_id] && @event.appointment_group.grants_right?(@current_user, session, :manage)
|
||||
participant = @event.appointment_group.possible_participants.find_by_id(params[:participant_id].to_i)
|
||||
participant = @event.appointment_group.possible_participants.detect { |p| p.id == params[:participant_id].to_i }
|
||||
else
|
||||
participant = @event.appointment_group.participant_for(@current_user)
|
||||
participant = nil if participant && params[:participant_id] && params[:participant_id].to_i != participant.id
|
||||
|
|
|
@ -12,11 +12,11 @@
|
|||
<%= before_label :dates, "Date(s)" %> <%= date_string(asset.start_at, asset.end_at, :no_words) %>
|
||||
<%= before_label :signup_type, "Signup Type" %> <%=
|
||||
asset.participant_type == 'Group' ?
|
||||
t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_context.name) :
|
||||
t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_contexts.first.name) :
|
||||
t(:individual_signup, "Individual") %>
|
||||
<%= before_label :course, "Course" %> <%= asset.context.name %>
|
||||
<% if asset.available_slots -%>
|
||||
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.available_slots %>
|
||||
<% end -%>
|
||||
|
||||
<%= t :instructions, "Sign up for a time slot at the following link: %{link}", :link => content(:link) %>
|
||||
<%= t :instructions, "Sign up for a time slot at the following link: %{link}", :link => content(:link) %>
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
<%= before_label :dates, "Date(s)" %> <%= date_string(asset.start_at, asset.end_at, :no_words) %><br>
|
||||
<%= before_label :signup_type, "Signup Type" %> <%=
|
||||
asset.participant_type == 'Group' ?
|
||||
t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_context.name) :
|
||||
t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_contexts.first.name) :
|
||||
t(:individual_signup, "Individual") %><br>
|
||||
<%= before_label :course, "Course" %> <%= asset.context.name %><br>
|
||||
<% if asset.available_slots -%>
|
||||
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.available_slots %><br>
|
||||
<% end -%>
|
||||
<br>
|
||||
<a href="<%= content(:link) %>"><%= t :instructions, "Sign up for a time slot" %></a>
|
||||
<a href="<%= content(:link) %>"><%= t :instructions, "Sign up for a time slot" %></a>
|
||||
|
|
|
@ -12,11 +12,11 @@
|
|||
<%= before_label :dates, "Date(s)" %> <%= date_string(asset.start_at, asset.end_at, :no_words) %>
|
||||
<%= before_label :signup_type, "Signup Type" %> <%=
|
||||
asset.participant_type == 'Group' ?
|
||||
t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_context.name) :
|
||||
t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_contexts.first.name) :
|
||||
t(:individual_signup, "Individual") %>
|
||||
<%= before_label :course, "Course" %> <%= asset.context.name %>
|
||||
<% if asset.available_slots -%>
|
||||
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.available_slots %>
|
||||
<% end -%>
|
||||
|
||||
<%= t :instructions, "Sign up for a time slot at the following link: %{link}", :link => content(:link) %>
|
||||
<%= t :instructions, "Sign up for a time slot at the following link: %{link}", :link => content(:link) %>
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
<%= before_label :dates, "Date(s)" %> <%= date_string(asset.start_at, asset.end_at, :no_words) %><br>
|
||||
<%= before_label :signup_type, "Signup Type" %> <%=
|
||||
asset.participant_type == 'Group' ?
|
||||
t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_context.name) :
|
||||
t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_contexts.first.name) :
|
||||
t(:individual_signup, "Individual") %><br>
|
||||
<%= before_label :course, "Course" %> <%= asset.context.name %><br>
|
||||
<% if asset.available_slots -%>
|
||||
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.available_slots %><br>
|
||||
<% end -%>
|
||||
<br>
|
||||
<a href="<%= content(:link) %>"><%= t :instructions, "Sign up for a time slot" %></a>
|
||||
<a href="<%= content(:link) %>"><%= t :instructions, "Sign up for a time slot" %></a>
|
||||
|
|
|
@ -28,7 +28,11 @@ class AppointmentGroup < ActiveRecord::Base
|
|||
has_many :appointments_participants, :through => :_appointments, :source => :child_events, :conditions => "calendar_events.workflow_state <> 'deleted'", :order => :start_at
|
||||
belongs_to :context, :polymorphic => true
|
||||
alias_method :effective_context, :context
|
||||
belongs_to :sub_context, :polymorphic => true
|
||||
has_many :appointment_group_sub_contexts, :include => :sub_context
|
||||
|
||||
def sub_contexts
|
||||
appointment_group_sub_contexts.map &:sub_context
|
||||
end
|
||||
|
||||
before_validation :default_values
|
||||
before_save :update_cached_values
|
||||
|
@ -38,14 +42,6 @@ class AppointmentGroup < ActiveRecord::Base
|
|||
validates_length_of :description, :maximum => maximum_long_text_length, :allow_nil => true, :allow_blank => true
|
||||
validates_presence_of :context
|
||||
validates_inclusion_of :participant_visibility, :in => ['private', 'protected'] # presumably we might add public if we decide to show appointments on the public calendar feed
|
||||
validates_each :sub_context do |record, attr, value|
|
||||
if record.participant_type == 'User'
|
||||
record.errors.add(attr, t('errors.invalid_course_section', 'Invalid course section')) unless value.blank? || value.is_a?(CourseSection) && value.course == record.context
|
||||
else
|
||||
record.errors.add(attr, t('errors.missing_group_category', 'Group appointments must have a group category')) unless value.present? && value.is_a?(GroupCategory)
|
||||
record.errors.add(attr, t('errors.invalid_group_category', 'Invalid group category')) unless value && value.context == record.context
|
||||
end
|
||||
end
|
||||
validates_each :appointments do |record, attr, value|
|
||||
next unless record.new_appointments.present? || record.validation_event_override
|
||||
appointments = value
|
||||
|
@ -59,8 +55,8 @@ class AppointmentGroup < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
attr_accessible :title, :description, :location_name, :location_address, :context, :sub_context_code, :participants_per_appointment, :min_appointments_per_participant, :max_appointments_per_participant, :new_appointments, :participant_visibility, :cancel_reason
|
||||
attr_readonly :context_id, :context_type, :context_code, :sub_context_id, :sub_context_type, :sub_context_code
|
||||
attr_accessible :title, :description, :location_name, :location_address, :context, :sub_context_codes, :participants_per_appointment, :min_appointments_per_participant, :max_appointments_per_participant, :new_appointments, :participant_visibility, :cancel_reason
|
||||
attr_readonly :context_id, :context_type, :context_code
|
||||
|
||||
# when creating/updating an appointment, you can give it a list of (new)
|
||||
# appointment times. these will be added to the existing appointment times
|
||||
|
@ -83,27 +79,42 @@ class AppointmentGroup < ActiveRecord::Base
|
|||
super
|
||||
end
|
||||
|
||||
def sub_context_code=(code)
|
||||
def sub_context_codes=(codes)
|
||||
if new_record?
|
||||
self.sub_context = case code
|
||||
when /\Acourse_section_(.*)/; CourseSection.find_by_id($1)
|
||||
when /\Agroup_category_(.*)/; GroupCategory.find_by_id($1)
|
||||
sub_contexts = codes.map do |code|
|
||||
context = case code
|
||||
when /\Acourse_section_(.*)/; CourseSection.find_by_id($1)
|
||||
when /\Agroup_category_(.*)/; GroupCategory.find_by_id($1)
|
||||
else next
|
||||
end
|
||||
AppointmentGroupSubContext.new :appointment_group => self,
|
||||
:sub_context => context,
|
||||
:sub_context_code => code
|
||||
end
|
||||
write_attribute(:sub_context_code, sub_context ? code : nil)
|
||||
self.appointment_group_sub_contexts = sub_contexts.compact
|
||||
end
|
||||
end
|
||||
|
||||
def sub_context_codes
|
||||
appointment_group_sub_contexts.map &:sub_context_code
|
||||
end
|
||||
|
||||
# complements :reserve permission
|
||||
named_scope :reservable_by, lambda { |user|
|
||||
codes = user.appointment_context_codes
|
||||
{:conditions => [<<-COND, codes[:primary], codes[:secondary]]}
|
||||
workflow_state = 'active'
|
||||
AND context_code IN (?)
|
||||
AND (
|
||||
sub_context_code IS NULL
|
||||
OR sub_context_code IN (?)
|
||||
)
|
||||
COND
|
||||
{
|
||||
:select => "DISTINCT appointment_groups.*",
|
||||
:joins => "LEFT JOIN appointment_group_sub_contexts sc " \
|
||||
"ON appointment_groups.id = sc.appointment_group_id",
|
||||
:conditions => [<<-COND, codes[:primary], codes[:secondary]]
|
||||
workflow_state = 'active'
|
||||
AND context_code IN (?)
|
||||
AND (
|
||||
sc.sub_context_code IS NULL
|
||||
OR sc.sub_context_code IN (?)
|
||||
)
|
||||
COND
|
||||
}
|
||||
}
|
||||
# complements :manage permission
|
||||
named_scope :manageable_by, lambda { |*options|
|
||||
|
@ -115,14 +126,19 @@ class AppointmentGroup < ActiveRecord::Base
|
|||
codes[:full] &= restrict_to_codes
|
||||
codes[:limited] &= restrict_to_codes
|
||||
end
|
||||
{:conditions => [<<-COND, codes[:full] + codes[:limited], codes[:full], codes[:secondary]]}
|
||||
workflow_state <> 'deleted'
|
||||
AND context_code IN (?)
|
||||
AND (
|
||||
context_code IN (?)
|
||||
OR sub_context_code IN (?)
|
||||
)
|
||||
COND
|
||||
{
|
||||
:select => "DISTINCT appointment_groups.*",
|
||||
:joins => "LEFT JOIN appointment_group_sub_contexts sc " \
|
||||
"ON appointment_groups.id = sc.appointment_group_id",
|
||||
:conditions => [<<-COND, codes[:full] + codes[:limited], codes[:full], codes[:secondary]]
|
||||
workflow_state <> 'deleted'
|
||||
AND context_code IN (?)
|
||||
AND (
|
||||
context_code IN (?)
|
||||
OR sc.sub_context_code IN (?)
|
||||
)
|
||||
COND
|
||||
}
|
||||
}
|
||||
named_scope :current, lambda {
|
||||
{:conditions => ["end_at >= ?", Time.zone.today.to_datetime.utc]}
|
||||
|
@ -138,7 +154,7 @@ class AppointmentGroup < ActiveRecord::Base
|
|||
given { |user, session|
|
||||
next false if deleted?
|
||||
next false unless cached_context_grants_right?(user, nil, :manage_calendar)
|
||||
next true if sub_context_type == 'CourseSection' && context.section_visibilities_for(user).any?{ |v| sub_context_id == v[:course_section_id] }
|
||||
next true if appointment_group_sub_contexts.any? { |sc| sc.sub_context_type == 'CourseSection' && context.section_visibilities_for(user).any?{ |v| sc.sub_context_id == v[:course_section_id] } }
|
||||
!context.visibility_limited_to_course_sections?(user)
|
||||
}
|
||||
can :manage and can :manage_calendar and can :read and can :read_appointment_participants and
|
||||
|
@ -175,7 +191,7 @@ class AppointmentGroup < ActiveRecord::Base
|
|||
def possible_users
|
||||
participant_type == 'User' ?
|
||||
possible_participants.uniq :
|
||||
possible_participants.map(&:participants).flatten.uniq
|
||||
possible_participants.flatten.map(&:participants).flatten.uniq
|
||||
end
|
||||
|
||||
def instructors
|
||||
|
@ -185,13 +201,32 @@ class AppointmentGroup < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def possible_participants(registration_status=nil)
|
||||
participants = AppointmentGroup.possible_participants(participant_type, context)
|
||||
participants = if participant_type == 'User'
|
||||
sub_contexts.empty? ?
|
||||
context.participating_students :
|
||||
sub_contexts.map(&:participating_students).flatten
|
||||
else
|
||||
sub_contexts.map(&:groups).flatten
|
||||
end
|
||||
participant_ids = self.participant_ids
|
||||
registered = participants.select { |p| participant_ids.include?(p.id) }
|
||||
|
||||
participants = case registration_status
|
||||
when 'registered'; participants.scoped(:conditions => ["#{participant_table}.id IN (?)", participant_ids + [0]])
|
||||
when 'unregistered'; participants.scoped(:conditions => ["#{participant_table}.id NOT IN (?)", participant_ids + [0]])
|
||||
else participants
|
||||
when 'registered'; registered
|
||||
when 'unregistered'; participants - registered
|
||||
else participants
|
||||
end
|
||||
|
||||
two_tier_cmp = lambda do |a, b, attr1, attr2|
|
||||
cmp = a.send(attr1) <=> b.send(attr1)
|
||||
cmp == 0 ? a.send(attr2) <=> b.send(attr2) : cmp
|
||||
end
|
||||
|
||||
if participant_type == 'User'
|
||||
participants.sort { |a,b| two_tier_cmp.call(a, b, :sortable_name, :id) }
|
||||
else
|
||||
participants.sort { |a,b| two_tier_cmp.call(a, b, :name, :id) }
|
||||
end
|
||||
participants.order((participant_type == 'User' ? User.sortable_name_order_by_clause("users") : Group.case_insensitive("groups.name")) + ", #{participant_table}.id")
|
||||
end
|
||||
|
||||
def participant_ids
|
||||
|
@ -204,19 +239,11 @@ class AppointmentGroup < ActiveRecord::Base
|
|||
Kernel.const_get(participant_type).table_name
|
||||
end
|
||||
|
||||
def self.possible_participants(participant_type, context)
|
||||
if participant_type == 'User'
|
||||
context.participating_students
|
||||
else
|
||||
context.active_groups
|
||||
end
|
||||
end
|
||||
|
||||
def eligible_participant?(participant)
|
||||
return false unless participant && participant.class.base_ar_class.name == participant_type
|
||||
codes = participant.appointment_context_codes
|
||||
return false unless codes[:primary].include?(context_code)
|
||||
return false unless sub_context_code.nil? || codes[:secondary].include?(sub_context_code)
|
||||
return false unless sub_context_codes.empty? || (codes[:secondary] & sub_context_codes).present?
|
||||
true
|
||||
end
|
||||
|
||||
|
@ -235,6 +262,9 @@ class AppointmentGroup < ActiveRecord::Base
|
|||
participant = if participant_type == 'User'
|
||||
user
|
||||
else
|
||||
# can't have more than one group_category
|
||||
raise "inconsistent appointment group" if sub_contexts.size > 1
|
||||
sub_context_id = sub_contexts.first.id
|
||||
user.groups.detect{ |g| g.group_category_id == sub_context_id }
|
||||
end
|
||||
participant if participant && eligible_participant?(participant)
|
||||
|
@ -285,7 +315,9 @@ class AppointmentGroup < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def participant_type
|
||||
sub_context_type == 'GroupCategory' ? 'Group' : 'User'
|
||||
types = appointment_group_sub_contexts.map(&:participant_type).uniq
|
||||
raise "inconsistent participant types in appointment group" if types.size > 1
|
||||
types.first || 'User'
|
||||
end
|
||||
|
||||
def available_slots
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
#
|
||||
# Copyright (C) 2012 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 AppointmentGroupSubContext < ActiveRecord::Base
|
||||
belongs_to :appointment_group
|
||||
belongs_to :sub_context, :polymorphic => true
|
||||
|
||||
attr_accessible :appointment_group, :sub_context, :sub_context_code
|
||||
|
||||
validates_each :sub_context do |record, attr, value|
|
||||
if record.participant_type == 'User'
|
||||
record.errors.add(attr, t('errors.invalid_course_section', 'Invalid course section')) unless value.blank? || value.is_a?(CourseSection) && value.course == record.appointment_group.context
|
||||
else
|
||||
record.errors.add(attr, t('errors.missing_group_category', 'Group appointments must have a group category')) unless value.present? && value.is_a?(GroupCategory)
|
||||
record.errors.add(attr, t('errors.invalid_group_category', 'Invalid group category')) unless value && value.context == record.appointment_group.context
|
||||
end
|
||||
end
|
||||
|
||||
def participant_type
|
||||
sub_context_type == 'GroupCategory' ? 'Group' : 'User'
|
||||
end
|
||||
end
|
|
@ -107,9 +107,19 @@ button.single_item_done_button
|
|||
td
|
||||
vertical-align: top
|
||||
|
||||
.context_id, .section_select select, .group_select select
|
||||
.context_id, .group_select select
|
||||
width: 150px
|
||||
|
||||
.section_select ul
|
||||
list-style-type: none
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
.section_select li
|
||||
list-style-type: none
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
.delete-block-link
|
||||
+accessible_text_replacement
|
||||
display: block
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
<div class="section-signup">
|
||||
<b>{{#t "course_section"}}Limit Signups to{{/t}}</b><br />
|
||||
<div class="section_select">
|
||||
<select name="section_id"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group-signup" style="display: none">
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<ul>
|
||||
{{#each sections}}
|
||||
<li>
|
||||
<input type="checkbox" name="section_ids[]" value="{{asset_string}}" id="{{asset_string}}_checkbox" {{#if selected}}checked{{/if}}>
|
||||
<label for="{{asset_string}}_checkbox">{{name}}</label>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
|
@ -0,0 +1,26 @@
|
|||
class CreateAppointmentGroupSubContexts < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :appointment_group_sub_contexts do |t|
|
||||
t.references :appointment_group, :limit => 8
|
||||
t.integer :sub_context_id, :limit => 8
|
||||
t.string :sub_context_type
|
||||
t.string :sub_context_code
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :appointment_group_sub_contexts, :id
|
||||
|
||||
AppointmentGroup.all.each do |ag|
|
||||
next unless ag.sub_context_id
|
||||
sc = ag.appointment_group_sub_contexts.build
|
||||
sc.sub_context_id = ag.sub_context_id
|
||||
sc.sub_context_type = ag.sub_context_type
|
||||
sc.sub_context_code = ag.sub_context_code
|
||||
sc.save!
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :appointment_group_sub_contexts
|
||||
end
|
||||
end
|
|
@ -103,7 +103,7 @@ module Api::V1::CalendarEvent
|
|||
@user_json_is_admin = nil # when returning multiple groups, @current_user may be admin over some contexts but not others. so we need to recheck
|
||||
|
||||
include = options[:include] || []
|
||||
hash = api_json(group, user, session, :only => %w{id context_code created_at description end_at location_address location_name max_appointments_per_participant min_appointments_per_participant participants_per_appointment start_at sub_context_code title updated_at workflow_state participant_visibility})
|
||||
hash = api_json(group, user, session, :only => %w{id context_code created_at description end_at location_address location_name max_appointments_per_participant min_appointments_per_participant participants_per_appointment start_at title updated_at workflow_state participant_visibility}, :methods => :sub_context_codes)
|
||||
hash['requiring_action'] = group.requiring_action?(user)
|
||||
if group.new_appointments.present?
|
||||
hash['new_appointments'] = group.new_appointments.map{ |event| calendar_event_json(event, user, session, :skip_details => true, :appointment_group_id => group.id) }
|
||||
|
|
|
@ -30,13 +30,13 @@ describe AppointmentGroupsController, :type => :integration do
|
|||
'max_appointments_per_participant', 'min_appointments_per_participant',
|
||||
'participant_type', 'participant_visibility',
|
||||
'participants_per_appointment', 'requiring_action', 'start_at',
|
||||
'sub_context_code', 'title', 'updated_at', 'url', 'workflow_state'
|
||||
'sub_context_codes', 'title', 'updated_at', 'url', 'workflow_state'
|
||||
]
|
||||
|
||||
it 'should return manageable appointment groups' do
|
||||
ag1 = @course.appointment_groups.create(:title => "something")
|
||||
cat = @course.group_categories.create
|
||||
ag2 = @course.appointment_groups.create(:title => "another", :sub_context_code => cat.asset_string)
|
||||
ag2 = @course.appointment_groups.create(:title => "another", :sub_context_codes => [cat.asset_string])
|
||||
ag3 = Course.create.appointment_groups.create(:title => "inaccessible")
|
||||
ag4 = @course.appointment_groups.create(:title => "past", :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]])
|
||||
|
||||
|
@ -76,7 +76,7 @@ describe AppointmentGroupsController, :type => :integration do
|
|||
mygroup = cat.groups.create(:context => @course)
|
||||
mygroup.users << @me
|
||||
@me.reload
|
||||
ag7 = @course.appointment_groups.create(:title => "double yay", :sub_context_code => cat.asset_string, :new_appointments => [["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]])
|
||||
ag7 = @course.appointment_groups.create(:title => "double yay", :sub_context_codes => [cat.asset_string], :new_appointments => [["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]])
|
||||
ag7.publish!
|
||||
ag8 = @course.appointment_groups.create(:title => "past", :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]])
|
||||
ag8.publish!
|
||||
|
@ -224,10 +224,10 @@ describe AppointmentGroupsController, :type => :integration do
|
|||
it 'should create a new appointment group with a sub_context' do
|
||||
json = api_call(:post, "/api/v1/appointment_groups",
|
||||
{:controller => 'appointment_groups', :action => 'create', :format => 'json'},
|
||||
{:appointment_group => {:context_code => @course.asset_string, :sub_context_code => @course.default_section.asset_string, :title => "ohai"} })
|
||||
{:appointment_group => {:context_code => @course.asset_string, :sub_context_codes => [@course.default_section.asset_string], :title => "ohai"} })
|
||||
json.keys.sort.should eql expected_fields
|
||||
json['workflow_state'].should eql 'pending'
|
||||
json['sub_context_code'].should eql @course.default_section.asset_string
|
||||
json['sub_context_codes'].should eql [@course.default_section.asset_string]
|
||||
end
|
||||
|
||||
it 'should enforce update permissions' do
|
||||
|
@ -253,10 +253,10 @@ describe AppointmentGroupsController, :type => :integration do
|
|||
ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
|
||||
json = api_call(:put, "/api/v1/appointment_groups/#{ag.id}",
|
||||
{:controller => 'appointment_groups', :action => 'update', :format => 'json', :id => ag.id.to_s},
|
||||
{:appointment_group => {:title => "lol", :sub_context_code => @course.default_section.asset_string} })
|
||||
{:appointment_group => {:title => "lol", :sub_context_codes => [@course.default_section.asset_string]} })
|
||||
json.keys.sort.should eql expected_fields
|
||||
json['title'].should eql 'lol'
|
||||
json['sub_context_code'].should be_nil
|
||||
json['sub_context_codes'].should eql []
|
||||
end
|
||||
|
||||
it 'should publish an appointment group in an update through the api' do
|
||||
|
@ -305,7 +305,7 @@ describe AppointmentGroupsController, :type => :integration do
|
|||
},
|
||||
'groups' => proc {
|
||||
cat = @course.group_categories.create
|
||||
@ag = @course.appointment_groups.create(:title => "yay", :sub_context_code => cat.asset_string, :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]])
|
||||
@ag = @course.appointment_groups.create(:title => "yay", :sub_context_codes => [cat.asset_string], :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]])
|
||||
@ag.publish!
|
||||
group1 = cat.groups.create(:context => @course)
|
||||
group1.users << student_in_course(:course => @course, :active_all => true).user
|
||||
|
|
|
@ -129,7 +129,7 @@ describe CalendarEventsApiController, :type => :integration do
|
|||
}
|
||||
|
||||
cat = @course.group_categories.create
|
||||
ag2 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :sub_context_code => cat.asset_string, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
|
||||
ag2 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :sub_context_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
|
||||
event2 = ag2.appointments.first
|
||||
group_ids = []
|
||||
group_student_ids = []
|
||||
|
@ -178,7 +178,7 @@ describe CalendarEventsApiController, :type => :integration do
|
|||
3.times { event1.reserve_for(student_in_course(:course => @course, :active_all => true).user, @teacher) }
|
||||
|
||||
cat = @course.group_categories.create
|
||||
group2 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :sub_context_code => cat.asset_string, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
|
||||
group2 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :sub_context_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
|
||||
group2.publish!
|
||||
event2 = group2.appointments.first
|
||||
g = cat.groups.create(:context => @course)
|
||||
|
@ -269,7 +269,7 @@ describe CalendarEventsApiController, :type => :integration do
|
|||
othergroup = cat.groups.create(:context => @course)
|
||||
othergroup.users << otherguy
|
||||
@me.reload
|
||||
ag2 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :sub_context_code => cat.asset_string, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
|
||||
ag2 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :sub_context_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
|
||||
ag2.publish!
|
||||
event2 = ag2.appointments.first
|
||||
my_group_appointment = event2.reserve_for(mygroup, @me)
|
||||
|
@ -333,7 +333,7 @@ describe CalendarEventsApiController, :type => :integration do
|
|||
@group.users << @other_guy
|
||||
@other_group = cat.groups.create(:context => @course)
|
||||
@me.reload
|
||||
@ag2 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :sub_context_code => cat.asset_string, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
|
||||
@ag2 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :sub_context_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
|
||||
@ag2.publish!
|
||||
@event3 = @ag2.appointments.first
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ end
|
|||
def appointment_group_model(opts={})
|
||||
@course ||= opts.delete(:course) || course_model
|
||||
if sub_context = opts.delete(:sub_context)
|
||||
opts[:sub_context_code] = sub_context.asset_string
|
||||
opts[:sub_context_codes] = [sub_context.asset_string]
|
||||
end
|
||||
@appointment_group = @course.appointment_groups.create!(valid_appointment_group_attributes.merge(opts))
|
||||
@appointment_group
|
||||
|
|
|
@ -28,7 +28,7 @@ describe AppointmentGroup do
|
|||
AppointmentGroup.new(
|
||||
:title => "test",
|
||||
:context => @course,
|
||||
:sub_context_code => CourseSection.create.asset_string
|
||||
:sub_context_codes => [CourseSection.create.asset_string]
|
||||
).should_not be_valid
|
||||
end
|
||||
|
||||
|
@ -36,7 +36,7 @@ describe AppointmentGroup do
|
|||
AppointmentGroup.new(
|
||||
:title => "test",
|
||||
:context => @course,
|
||||
:sub_context_code => GroupCategory.create.asset_string
|
||||
:sub_context_codes => [GroupCategory.create.asset_string]
|
||||
).should_not be_valid
|
||||
end
|
||||
|
||||
|
@ -44,10 +44,10 @@ describe AppointmentGroup do
|
|||
group = AppointmentGroup.new(
|
||||
:title => "test",
|
||||
:context => @course,
|
||||
:sub_context_code => Account.create.asset_string
|
||||
:sub_context_codes => [Account.create.asset_string]
|
||||
)
|
||||
group.should be_valid
|
||||
group.sub_context_code.should be_nil
|
||||
group.sub_context_codes.should be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -88,10 +88,20 @@ describe AppointmentGroup do
|
|||
end
|
||||
|
||||
context "permissions" do
|
||||
def student_in_section(section)
|
||||
@user = user
|
||||
enrollment = @course.enroll_user(@user, 'StudentEnrollment', :section => section)
|
||||
enrollment.workflow_state = 'active'
|
||||
enrollment.save!
|
||||
@user
|
||||
end
|
||||
|
||||
before do
|
||||
course_with_teacher(:active_all => true)
|
||||
@teacher = @user
|
||||
other_section = @course.course_sections.create!
|
||||
section1 = @course.default_section
|
||||
section2 = @course.course_sections.create!
|
||||
section3 = @course.course_sections.create!
|
||||
other_course = Course.create!
|
||||
gc = @course.group_categories.create!
|
||||
@user_group = @course.groups.create!(:group_category => gc)
|
||||
|
@ -100,22 +110,30 @@ describe AppointmentGroup do
|
|||
@student = @user
|
||||
@user_group.users << @user
|
||||
|
||||
@student_in_section2 = student_in_section(section2)
|
||||
@student_in_section3 = student_in_section(section3)
|
||||
|
||||
user(:active_all => true)
|
||||
@course.enroll_user(@user, 'TaEnrollment', :section => other_section, :limit_privileges_to_course_section => true).accept!
|
||||
@course.enroll_user(@user, 'TaEnrollment', :section => section2, :limit_privileges_to_course_section => true).accept!
|
||||
@ta = @user
|
||||
|
||||
@g1 = AppointmentGroup.create(:title => "test", :context => @course)
|
||||
@g1.publish!
|
||||
@g2 = AppointmentGroup.create(:title => "test", :context => @course)
|
||||
@g3 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_code => @course.default_section.asset_string)
|
||||
@g3 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [@course.default_section.asset_string])
|
||||
@g3.publish!
|
||||
@g4 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_code => gc.asset_string)
|
||||
@g4 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [gc.asset_string])
|
||||
@g4.publish!
|
||||
@g5 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_code => other_section.asset_string)
|
||||
@g5 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [section2.asset_string])
|
||||
@g5.publish!
|
||||
@g6 = AppointmentGroup.create(:title => "test", :context => other_course)
|
||||
@g6.publish!
|
||||
@groups = [@g1, @g2, @g3, @g4, @g5]
|
||||
|
||||
# multiple sub_contexts
|
||||
@g7 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [@course.default_section.asset_string, section2.asset_string])
|
||||
@g7.publish!
|
||||
|
||||
@groups = [@g1, @g2, @g3, @g4, @g5, @g7]
|
||||
end
|
||||
|
||||
it "should return only appointment groups that are reservable for the user" do
|
||||
|
@ -137,7 +155,7 @@ describe AppointmentGroup do
|
|||
|
||||
# student can reserve course-level ones, as well as section-specific ones
|
||||
visible_groups = AppointmentGroup.reservable_by(@student).sort_by(&:id)
|
||||
visible_groups.should eql [@g1, @g3, @g4]
|
||||
visible_groups.should eql [@g1, @g3, @g4, @g7]
|
||||
@g1.grants_right?(@student, nil, :reserve).should be_true
|
||||
@g2.grants_right?(@student, nil, :reserve).should be_false # not active yet
|
||||
@g2.eligible_participant?(@student).should be_true # though an admin could reserve on his behalf
|
||||
|
@ -148,29 +166,34 @@ describe AppointmentGroup do
|
|||
@user_group.should eql(@g4.participant_for(@student))
|
||||
@g5.grants_right?(@student, nil, :reserve).should be_false
|
||||
@g6.grants_right?(@student, nil, :reserve).should be_false
|
||||
@g7.grants_right?(@student, nil, :reserve).should be_true
|
||||
@g7.grants_right?(@student_in_section2, nil, :reserve).should be_true
|
||||
@g7.grants_right?(@student_in_section3, nil, :reserve).should be_false
|
||||
end
|
||||
|
||||
|
||||
it "should return only appointment groups that are manageable by the user" do
|
||||
# teacher can manage everything in the course
|
||||
visible_groups = AppointmentGroup.manageable_by(@teacher).sort_by(&:id)
|
||||
visible_groups.should eql [@g1, @g2, @g3, @g4, @g5]
|
||||
visible_groups.should eql [@g1, @g2, @g3, @g4, @g5, @g7]
|
||||
@g1.grants_right?(@teacher, nil, :manage).should be_true
|
||||
@g2.grants_right?(@teacher, nil, :manage).should be_true
|
||||
@g3.grants_right?(@teacher, nil, :manage).should be_true
|
||||
@g4.grants_right?(@teacher, nil, :manage).should be_true
|
||||
@g5.grants_right?(@teacher, nil, :manage).should be_true
|
||||
@g6.grants_right?(@teacher, nil, :manage).should be_false
|
||||
@g7.grants_right?(@teacher, nil, :manage).should be_true
|
||||
|
||||
# ta can only manage stuff in section
|
||||
visible_groups = AppointmentGroup.manageable_by(@ta).sort_by(&:id)
|
||||
visible_groups.should eql [@g5]
|
||||
visible_groups.should eql [@g5, @g7]
|
||||
@g1.grants_right?(@ta, nil, :manage).should be_false
|
||||
@g2.grants_right?(@ta, nil, :manage).should be_false
|
||||
@g3.grants_right?(@ta, nil, :manage).should be_false
|
||||
@g4.grants_right?(@ta, nil, :manage).should be_false
|
||||
@g5.grants_right?(@ta, nil, :manage).should be_true
|
||||
@g6.grants_right?(@ta, nil, :manage).should be_false
|
||||
@g7.grants_right?(@ta, nil, :manage).should be_true
|
||||
|
||||
# student can't manage anything
|
||||
visible_groups = AppointmentGroup.manageable_by(@student).sort_by(&:id)
|
||||
|
@ -310,4 +333,58 @@ describe AppointmentGroup do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "possible_participants" do
|
||||
before do
|
||||
course_with_teacher(:active_all => true)
|
||||
@teacher = @user
|
||||
|
||||
@users, @sections = [], []
|
||||
2.times do
|
||||
@sections << section = @course.course_sections.create!
|
||||
enrollment = student_in_course(:active_all => true)
|
||||
@enrollment.course_section = section
|
||||
@enrollment.save!
|
||||
@users << @user
|
||||
end
|
||||
|
||||
@group1 = group(:name => "group1", :group_context => @course)
|
||||
@group1.participating_users << @users.last
|
||||
@group1.save!
|
||||
@gc = @group1.group_category
|
||||
@group2 = @gc.groups.create!(:name => "group2")
|
||||
|
||||
@ag = @course.appointment_groups.create(:title => "test", :participants_per_appointment => 2, :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]])
|
||||
end
|
||||
|
||||
it "should return possible participants" do
|
||||
@ag.possible_participants.should eql @users
|
||||
end
|
||||
|
||||
it "should respect course_section sub_contexts" do
|
||||
@ag.appointment_group_sub_contexts.create! :sub_context => @sections.first
|
||||
@ag.possible_participants.should eql [@users.first]
|
||||
end
|
||||
|
||||
it "should respect group sub_contexts" do
|
||||
@ag.appointment_group_sub_contexts.create! :sub_context => @gc
|
||||
@ag.possible_participants.should eql [@group1, @group2]
|
||||
@ag.possible_users.should eql [@users.last]
|
||||
end
|
||||
|
||||
it "should allow filtering on registration status" do
|
||||
@ag.appointments.first.reserve_for(@users.first, @users.first)
|
||||
@ag.possible_participants.should eql @users
|
||||
@ag.possible_participants('registered').should eql [@users.first]
|
||||
@ag.possible_participants('unregistered').should eql [@users.last]
|
||||
end
|
||||
|
||||
it "should allow filtering on registration status (for groups)" do
|
||||
@ag.appointment_group_sub_contexts.create! :sub_context => @gc, :sub_context_code => @gc.asset_string
|
||||
@ag.appointments.first.reserve_for(@group1, @users.first)
|
||||
@ag.possible_participants.should eql [@group1, @group2]
|
||||
@ag.possible_participants('registered').should eql [@group1]
|
||||
@ag.possible_participants('unregistered').should eql [@group2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -163,7 +163,7 @@ describe CalendarEvent do
|
|||
g1 = @course.appointment_groups.create(:title => "foo")
|
||||
g1.publish!
|
||||
a1 = g1.appointments.create.reserve_for(@student, @student)
|
||||
g2 = @course.appointment_groups.create(:title => "foo", :sub_context_code => @course.default_section.asset_string)
|
||||
g2 = @course.appointment_groups.create(:title => "foo", :sub_context_codes => [@course.default_section.asset_string])
|
||||
g2.publish!
|
||||
a2 = g2.appointments.create.reserve_for(@student, @student)
|
||||
pe = @course.calendar_events.create!
|
||||
|
@ -280,7 +280,7 @@ describe CalendarEvent do
|
|||
@group = c1.groups.create(:context => @course)
|
||||
@group.users << @student1 << @student2
|
||||
|
||||
@ag2 = AppointmentGroup.create!(:title => "test", :context => @course, :sub_context_code => c1.asset_string)
|
||||
@ag2 = AppointmentGroup.create!(:title => "test", :context => @course, :sub_context_codes => [c1.asset_string])
|
||||
@ag2.publish!
|
||||
@appointment2 = @ag2.appointments.create(:start_at => '2012-01-01 12:00:00', :end_at => '2012-01-01 13:00:00')
|
||||
|
||||
|
@ -445,7 +445,7 @@ describe CalendarEvent do
|
|||
c2 = @course.group_categories.create
|
||||
g2 = c2.groups.create(:context => @course)
|
||||
|
||||
ag = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_code => c1.asset_string,
|
||||
ag = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [c1.asset_string],
|
||||
:new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']]
|
||||
)
|
||||
appointment = ag.appointments.first
|
||||
|
|
|
@ -198,7 +198,7 @@ describe "scheduler" do
|
|||
student2.conversations.first.messages.size.should eql 6 # unregistered/all * 2 + registered/all (ug1)
|
||||
student3.conversations.first.messages.size.should eql 6 # unregistered/all * 3
|
||||
student4.conversations.first.messages.size.should eql 4 # unregistered/all * 2 (not in any group)
|
||||
student5.conversations.first.messages.size.should eql 4 # unregistered/all * 2 (not in default section)
|
||||
student5.conversations.first.messages.size.should eql 2 # unregistered/all * 1 (doesn't meet any sub_context criteria)
|
||||
end
|
||||
|
||||
it "should validate the appointment group shows up on the calendar" do
|
||||
|
@ -255,7 +255,7 @@ describe "scheduler" do
|
|||
|
||||
# group appointment group
|
||||
gc = @course.group_categories.create!(:name => "Blah Groups")
|
||||
title = create_appointment_group :sub_context_code => gc.asset_string,
|
||||
title = create_appointment_group :sub_context_codes => [gc.asset_string],
|
||||
:title => "group ag"
|
||||
ag = AppointmentGroup.find_by_title(title)
|
||||
2.times do |i|
|
||||
|
|
|
@ -348,10 +348,11 @@ Spec::Runner.configure do |config|
|
|||
end
|
||||
|
||||
def group(opts={})
|
||||
opts.delete(:active_all)
|
||||
if opts[:group_context]
|
||||
@group = opts[:group_context].groups.create!
|
||||
@group = opts.delete(:group_context).groups.create! opts
|
||||
else
|
||||
@group = Group.create!
|
||||
@group = Group.create! opts
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue