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:
Cameron Matheson 2012-02-10 15:26:55 -07:00
parent b0178fef6c
commit 6416844817
21 changed files with 296 additions and 109 deletions

View File

@ -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()

View File

@ -41,6 +41,7 @@ define [
group = {
contexts: @calendar.contexts
sub_context_codes: []
}
@createDialog = new EditAppointmentGroupDialog(group, @dialogCloseCB)

View File

@ -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

View File

@ -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) %>

View File

@ -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>

View File

@ -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) %>

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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">

View File

@ -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>

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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|

View File

@ -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