multiple contexts per appointment group

closes #7561

Test plan:
  - create some appointment groups:
    - make one with multiple courses
    - make one with a course, and then add more courses after saving
    - make one restricted to some (not all) sections in a course [1]
    - make one that has students sign up in groups [1]
  - make sure that only students that match the above criteria are able
    to see/reserve those appointment groups

  [1] you can only choose the group-signup option or restrict
      appointment groups to certain sections on creation (these options
      won't be availabe when editing later)

Change-Id: I1cff5fb4ed233882c2061f8fd8a7ba6f2d4959b0
Reviewed-on: https://gerrit.instructure.com/9407
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Jon Jensen <jon@instructure.com>
This commit is contained in:
Cameron Matheson 2012-04-18 17:47:03 -06:00
parent 81593a3680
commit a2a6579c10
71 changed files with 875 additions and 304 deletions

View File

@ -0,0 +1,162 @@
define [
'jquery'
'underscore'
'jst/calendar/contextSelector'
'jst/calendar/contextSelectorItem'
'compiled/fn/preventDefault'
], ($, _, contextSelectorTemplate, contextSelectorItemTemplate, preventDefault) ->
class ContextSelectorItem
constructor: (@context) ->
@state = 'off'
@locked = false
@sectionsLocked = false
pubsubHelper = (fn) =>
(sender) =>
return if sender is this
fn.apply(this)
$.subscribe '/contextSelector/disable', pubsubHelper(@disable)
$.subscribe '/contextSelector/enable', pubsubHelper(@enable)
$.subscribe '/contextSelector/uncheck', pubsubHelper(=> @setState('off'))
$.subscribe '/contextSelector/lockSections', @lockSections
render: ($list) ->
@$listItem = $(contextSelectorItemTemplate(@context))
@$listItem.appendTo($list)
@$sectionsList = @$listItem.find('.ag_sections')
@$listItem.find('.ag_sections_toggle').click preventDefault @toggleSections
@$contentCheckbox = @$listItem.find('[name="context_codes[]"]')
@$contentCheckbox.change preventDefault @change
@$sectionCheckboxes = @$listItem.find('[name="sections[]"]')
@$sectionCheckboxes.change @sectionChange
toggleSections: (jsEvent) =>
$(jsEvent.target).toggleClass('ag-sections-expanded')
@$sectionsList.toggleClass('hidden')
change: =>
newState = switch @state
when 'off' then 'on'
when 'on' then 'off'
when 'partial' then 'on'
@setState(newState)
setState: (state) =>
return if @locked
@state = state
switch @state
when 'on', 'off'
checked = @state == 'on'
@$contentCheckbox.prop('checked', checked)
@$contentCheckbox.prop('indeterminate', false)
@$sectionCheckboxes.prop('checked', checked)
$.publish("/contextSelector/enable", [this])
when 'partial'
@$contentCheckbox.prop('checked', true)
@$contentCheckbox.prop('indeterminate', true)
$.publish('/contextSelector/disable', [this])
$.publish('/contextSelector/uncheck', [this])
$.publish('/contextSelector/changed')
sectionChange: =>
switch @$sectionCheckboxes.filter(':checked').length
when 0
@setState('off')
when @$sectionCheckboxes.length
@setState('on')
else
@setState('partial')
disable: ->
@$contentCheckbox.prop('disabled', true)
@disableSections()
disableSections: ->
@$sectionCheckboxes.prop('disabled', true)
enable: ->
unless @locked
@$contentCheckbox.prop('disabled', false)
@enableSections()
enableSections: ->
unless @lockedSections
@$sectionCheckboxes.prop('disabled', false)
lock: ->
@locked = true
@disable()
$.publish('/contextSelector/lockSections')
lockSections: =>
@lockedSections = true
@disableSections()
isChecked: -> @state != 'off'
sections: ->
checked = @$sectionCheckboxes.filter(':checked')
if checked.length == @$sectionCheckboxes.length
[]
else
_.map(checked, (cb) -> cb.value)
class ContextSelector
constructor: (selector, @apptGroup, @contexts, contextsChangedCB, closeCB) ->
@$menu = $(selector).html contextSelectorTemplate()
$contextsList = @$menu.find('.ag-contexts')
$.subscribe('/contextSelector/changed', => contextsChangedCB @selectedContexts(), @selectedSections())
@contextSelectorItems = {}
for c in @contexts
item = new ContextSelectorItem(c)
item.render($contextsList)
@contextSelectorItems[item.context.asset_string] = item
if @apptGroup.sub_context_codes.length > 0
# if you choose sub_contexts when creating an appointment
# group, the appointment group is locked down
# TODO: be smarter about this
for subContextCode in @apptGroup.sub_context_codes
$("[value='#{subContextCode}']").prop('checked', true)
for c, item of @contextSelectorItems
item.sectionChange()
item.lock()
else
for contextCode in @apptGroup.context_codes
@contextSelectorItems[contextCode].setState('on')
@contextSelectorItems[contextCode].lock()
$('.ag_contexts_done').click preventDefault closeCB
contextsChangedCB(@selectedContexts(), @selectedSections())
selectedContexts: ->
contexts = _.chain(@contextSelectorItems)
.values()
.filter( (c) -> c.state != 'off')
.map( (c) -> c.context.asset_string)
.value()
numPartials = _.filter(contexts, (c) -> c.state == 'partial')
if numPartials > 1 or numPartials == 1 and contexts.length > 1
throw "invalid state"
contexts
selectedSections: ->
sections = _.chain(@contextSelectorItems)
.values()
.map( (c) -> c.sections())
.reject((ss) -> ss.length == 0)
.value()
throw "invalid state" if sections.length > 1
sections[0]

View File

@ -1,26 +1,34 @@
define [
'jquery'
'underscore'
'i18n!EditAppointmentGroupDetails'
'compiled/calendar/TimeBlockList'
'jst/calendar/editAppointmentGroup'
'jst/calendar/genericSelect'
'jst/calendar/sectionCheckboxes'
'compiled/calendar/ContextSelector'
'compiled/fn/preventDefault'
'jquery.ajaxJSON'
'jquery.disableWhileLoading'
'jquery.instructure_forms'
], ($, I18n, TimeBlockList, editAppointmentGroupTemplate, genericSelectTemplate, sectionCheckboxesTemplate) ->
], ($, _, I18n, TimeBlockList, editAppointmentGroupTemplate, genericSelectTemplate, sectionCheckboxesTemplate, ContextSelector, preventDefault) ->
class EditAppointmentGroupDetails
constructor: (selector, @apptGroup, @contextChangeCB, @closeCB) ->
constructor: (selector, @apptGroup, @contexts, @closeCB) ->
@currentContextInfo = null
$(selector).html editAppointmentGroupTemplate({
title: @apptGroup.title
contexts: @apptGroup.contexts
contexts: @contexts
appointment_group: @apptGroup
})
@contextsHash = {}
@contextsHash[c.asset_string] = c for c in @contexts
@form = $(selector).find("form")
@form.find("select.context_id").change(@contextChange).change()
@contextSelector = new ContextSelector('.ag-menu-container', @apptGroup, @contexts, @contextsChanged, @toggleContextsMenu)
if @apptGroup.id
@form.attr('action', @apptGroup.url)
@ -29,16 +37,17 @@ define [
@form.find(".context_id").val(@apptGroup.context_code).attr('disabled', true)
@form.find("select.context_id").change()
@form.find(".group_category").attr('disabled', true)
@form.find('input[name="section_ids[]"]').attr('disabled', true)
@form.find(".group-signup-checkbox").attr('disabled', true)
@disableGroups()
if @apptGroup.participant_type == 'Group'
@form.find(".group-signup-checkbox").prop('checked', true)
@form.find(".group_category").val(@apptGroup.sub_context_codes[0])
else
@form.find(".group-signup-checkbox").prop('checked', false)
else
@form.attr('action', @currentContextInfo.create_appointment_group_url)
# FIXME: put this url in ENV json or something
@form.attr('action', '/api/v1/appointment_groups')
@form.find('.ag_contexts_selector').click preventDefault @toggleContextsMenu
timeBlocks = ([appt.start, appt.end, true] for appt in @apptGroup.appointmentEvents || [] )
@timeBlockList = new TimeBlockList(@form.find(".time-block-list-body"), @form.find(".splitter"), timeBlocks)
@ -54,7 +63,6 @@ define [
checked = !!jsEvent.target.checked
@form.find('.per_appointment_groups_label').toggle(checked)
@form.find('.per_appointment_users_label').toggle(!checked)
@form.find(".section-signup").toggle(!checked)
@form.find(".group-signup").toggle(checked)
@form.find(".group-signup-checkbox").change()
@ -86,12 +94,6 @@ define [
if @apptGroup.workflow_state == 'active'
@form.find("#appointment-blocks-active-button").attr('disabled', true).prop('checked', true)
contextInfoForCode: (code) ->
for context in @apptGroup.contexts
if context.asset_string == code
return context
return null
saveWithoutPublishingClick: (jsEvent) =>
jsEvent.preventDefault()
@save(false)
@ -136,13 +138,23 @@ define [
params['appointment_group[participant_visibility]'] = if data.participant_visibility == '1' then 'protected' else 'private'
if create
params['appointment_group[context_code]'] = data.context_code
# get the context/section info from @contextSelector instead
delete data['context_codes[]']
delete data['sections[]']
contextCodes = @contextSelector.selectedContexts()
if contextCodes.length == 0
$('.ag_contexts_selector').errorBox(I18n.t 'context_required', 'You need to select a calendar')
return
else
params['appointment_group[context_codes]'] = contextCodes
if create
if data.use_group_signup == '1' && data.group_category_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[]']
else
sections = @contextSelector.selectedSections()
params['appointment_group[sub_context_codes]'] = sections if sections
# TODO: Provide UI for specifying this
params['appointment_group[min_appointments_per_participant]'] = 1
@ -155,31 +167,47 @@ define [
deferred = $.ajaxJSON @form.attr('action'), method, params, onSuccess, onError
@form.disableWhileLoading(deferred)
contextChange: (jsEvent) =>
context = $(jsEvent.target).val()
@currentContextInfo = @contextInfoForCode(context)
@apptGroup.contextInfo = @currentContextInfo
if @currentContextInfo == null then return
contextsChanged: (contextCodes, sectionCodes) =>
# dropdown text
if sectionCodes
sectionCode = sectionCodes[0]
section = _.chain(@contexts)
.pluck('course_sections')
.flatten()
.find((s) -> s.asset_string == sectionCode)
.value()
text = section.name
if sectionCodes.length > 1
text += I18n.t('and_n_sectionCodes', ' and %{n} others', n: sectionCodes.length - 1)
@form.find('.ag_contexts_selector').text(text)
else if contextCodes.length > 0
contextCode = contextCodes[0]
text = @contextsHash[contextCode].name
if contextCodes.length > 1
text += I18n.t('and_n_contexts', ' and %{n} others', n: contextCodes.length - 1)
@form.find('.ag_contexts_selector').text(text)
else
@form.find('.ag_contexts_selector').text(I18n.t 'select_calendars', 'Select Calendars')
# Update the sections and groups lists in the scheduler
if @currentContextInfo.course_sections
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))
# group selector
context = @contextsHash[contextCodes[0]]
if contextCodes.length == 1 and not sectionCodes and context.group_categories?.length > 0
@enableGroups(context)
else
@disableGroups()
if !@currentContextInfo.group_categories || @currentContextInfo.group_categories.length == 0
@form.find(".group-signup-checkbox").attr('disabled', true).prop('checked', false).change()
else if @currentContextInfo.group_categories
@form.find(".group-signup-checkbox").attr('disabled', false)
groupsInfo =
cssClass: 'group_category'
name: 'group_category_id'
collection: @currentContextInfo.group_categories
@form.find(".group_select").html(genericSelectTemplate(groupsInfo))
disableGroups: ->
@form.find(".group-signup-checkbox").attr('disabled', true)
@form.find("group-signup").hide()
@contextChangeCB(context)
enableGroups: (contextInfo) ->
@form.find(".group-signup-checkbox").attr('disabled', false)
groupsInfo =
cssClass: 'group_category'
name: 'group_category_id'
collection: contextInfo.group_categories
@form.find(".group_select").html(genericSelectTemplate(groupsInfo))
@form.find("group-signup").show()
# Update the edit and more options links with the new context, if this is a new group
if !@apptGroup.id
@form.attr('action', @currentContextInfo.create_appointment_group_url)
toggleContextsMenu: (jsEvent) =>
$('.ag_contexts_menu').toggleClass('hidden')

View File

@ -11,20 +11,20 @@ define [
width: 'auto'
resizable: false
title: I18n.t('titles.edit_appointment_group', "Edit Appointment Group")
# this is dumb, but it prevents the columns from wrapping when
# the context selector drop down gets too long
dialog.dialog('widget').find('#edit_event').css('overflow', 'visible')
class EditAppointmentGroupDialog
constructor: (@apptGroup, @parentCloseCB) ->
constructor: (@apptGroup, @contexts, @parentCloseCB) ->
@currentContextInfo = null
contextChange: (newContext) =>
# TODO: update the color?
closeCB: (saved) =>
dialog.dialog('close')
@parentCloseCB(saved)
show: =>
@appointmentGroupsForm = new EditAppointmentGroupDetails(dialog.find(".wrapper"), @apptGroup, @contextChange, @closeCB)
@appointmentGroupsForm = new EditAppointmentGroupDetails(dialog.find(".wrapper"), @apptGroup, @contexts, @closeCB)
buttons = if @apptGroup.workflow_state == 'active'
[

View File

@ -1,5 +1,6 @@
define [
'jquery',
'underscore'
'i18n!calendar'
'jst/calendar/appointmentGroupList'
'jst/calendar/schedulerRightSideAdminSection'
@ -11,7 +12,7 @@ define [
'jquery.instructure_misc_plugins'
'vendor/jquery.ba-tinypubsub'
'vendor/jquery.spin'
], ($, I18n, appointmentGroupListTemplate, schedulerRightSideAdminSectionTemplate, EditAppointmentGroupDialog, MessageParticipantsDialog, deleteItemTemplate) ->
], ($, _, I18n, appointmentGroupListTemplate, schedulerRightSideAdminSectionTemplate, EditAppointmentGroupDialog, MessageParticipantsDialog, deleteItemTemplate) ->
class Scheduler
constructor: (selector, @calendar) ->
@ -33,6 +34,9 @@ define [
@rightSideAdminSection = $(schedulerRightSideAdminSectionTemplate())
@rightSideAdminSection.find(".create_link").click @createClick
@appointmentGroupContexts = _.filter @contexts, (c) ->
c.can_create_appointment_groups
$.subscribe "CommonEvent/eventSaved", @eventSaved
$.subscribe "CommonEvent/eventDeleted", @eventDeleted
@ -40,11 +44,11 @@ define [
jsEvent.preventDefault()
group = {
contexts: @calendar.contexts
context_codes: []
sub_context_codes: []
}
@createDialog = new EditAppointmentGroupDialog(group, @dialogCloseCB)
@createDialog = new EditAppointmentGroupDialog(group, @appointmentGroupContexts, @dialogCloseCB)
@createDialog.show()
dialogCloseCB: (saved) =>
@ -138,9 +142,8 @@ define [
for appointmentEvent in group.appointmentEvents
group.signed_up += appointmentEvent.childEvents.length if appointmentEvent.childEvents
# look up the context name for the group
for contextInfo in @contexts when contextInfo.asset_string == group.context_code
group.context = contextInfo
# look up the context names for the group
group.contexts = _.filter(@contexts, (c) -> c.asset_string in group.context_codes)
group.published = group.workflow_state == "active"
@ -224,8 +227,7 @@ define [
group = @groups?[$(jsEvent.target).closest(".appointment-group-item").data('appointment-group-id')]
return unless group
group.contexts = @calendar.contexts
@createDialog = new EditAppointmentGroupDialog(group, @dialogCloseCB)
@createDialog = new EditAppointmentGroupDialog(group, @appointmentGroupContexts, @dialogCloseCB)
@createDialog.show()
deleteLinkClick: (jsEvent) =>

View File

@ -2,12 +2,13 @@ define [
'vendor/handlebars.vm'
'i18nObj'
'jquery'
'underscore'
'str/htmlEscape'
'compiled/util/semanticDateRange'
'jquery.instructure_date_and_time'
'jquery.instructure_misc_helpers'
'jquery.instructure_misc_plugins'
], (Handlebars, I18n, $, htmlEscape, semanticDateRange) ->
], (Handlebars, I18n, $, _, htmlEscape, semanticDateRange) ->
Handlebars.registerHelper name, fn for name, fn of {
t : (key, defaultValue, options) ->
@ -129,5 +130,10 @@ define [
#
eachProp: (context, options) ->
(options.fn(property: prop, value: context[prop]) for prop of context).join ''
# evaluates the block for each item in context and passes the result to $.toSentence
toSentence: (context, options) ->
results = _.map(context, (c) -> options.fn(c))
$.toSentence(results)
}
return Handlebars

View File

@ -20,7 +20,6 @@ class AppointmentGroupsController < ApplicationController
include Api::V1::CalendarEvent
before_filter :require_user
before_filter :get_context, :only => :create
before_filter :get_appointment_group, :only => [:show, :update, :destroy, :users, :groups]
def calendar_fragment(opts)
@ -51,8 +50,12 @@ class AppointmentGroupsController < ApplicationController
end
def create
contexts = get_contexts
raise ActiveRecord::RecordNotFound unless contexts.present?
publish = params[:appointment_group].delete(:publish) == '1'
@group = @context.appointment_groups.build(params[:appointment_group])
params[:appointment_group][:contexts] = contexts
@group = AppointmentGroup.new(params[:appointment_group])
if authorized_action(@group, @current_user, :manage)
if @group.save
@group.publish! if publish
@ -75,6 +78,8 @@ class AppointmentGroupsController < ApplicationController
end
def update
contexts = get_contexts
@group.contexts = contexts if contexts
if authorized_action(@group, @current_user, :update)
publish = params[:appointment_group].delete(:publish) == "1"
if @group.update_attributes(params[:appointment_group])
@ -119,13 +124,18 @@ class AppointmentGroupsController < ApplicationController
end
end
def get_context
@context = Context.find_by_asset_string(params[:appointment_group].delete(:context_code)) if params[:appointment_group] && params[:appointment_group][:context_code]
raise ActiveRecord::RecordNotFound unless @context
def get_contexts
if params[:appointment_group] && params[:appointment_group][:context_codes]
context_codes = params[:appointment_group].delete(:context_codes)
contexts = context_codes.map do |code|
Context.find_by_asset_string(code)
end
end
contexts
end
def get_appointment_group
@group = AppointmentGroup.find(params[:id].to_i)
@context = @group.context
@context = @group.contexts_for_user(@current_user).first # FIXME?
end
end

View File

@ -82,7 +82,7 @@ class CalendarsController < ApplicationController
:can_create_calendar_events => context.respond_to?("calendar_events") && context.calendar_events.new.grants_right?(@current_user, session, :create),
:can_create_assignments => context.respond_to?("assignments") && context.assignments.new.grants_right?(@current_user, session, :create),
:assignment_groups => context.respond_to?("assignments") ? context.assignment_groups.active.scoped(:select => "id, name").map {|g| { :id => g.id, :name => g.name } } : [],
:can_create_appointment_groups => context.respond_to?("appointment_groups") && context.appointment_groups.new.grants_right?(@current_user, session, :create),
:can_create_appointment_groups => context.respond_to?("appointment_groups") && AppointmentGroup.new(:contexts => [context]).grants_right?(@current_user, session, :create),
}
if context.respond_to?("course_sections")
info[:course_sections] = context.course_sections.active.scoped(:select => "id, name").map {|cs| { :id => cs.id, :asset_string => cs.asset_string, :name => cs.name } }

View File

@ -1,9 +1,12 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %>
http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %>
<% end %>
<% courses = asset.appointment_group.participant_type == 'Group' ?
asset.appointment_group.contexts.first.name :
asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %>
<% define_content :subject do %>
<%= t('subject', 'Reservation canceled for "%{appointment_name}" (%{course})', :appointment_name => asset.title, :course => asset.appointment_group.context.name) %>
<%= t('subject', 'Reservation canceled for "%{appointment_name}" (%{course})', :appointment_name => asset.title, :course => courses) %>
<% end %>
<%= t :message, '%{user} canceled his/her reservation for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %>
@ -16,7 +19,7 @@
<% else -%>
<%= before_label :attendee, "Attendee" %> <%= asset.context.name %>
<% end -%>
<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %>
<%= before_label :course, "Course" %> <%= courses %>
<% if asset.appointment_group.available_slots -%>
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.appointment_group.available_slots %>
<% end -%>

View File

@ -1,5 +1,5 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %>
http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %>
<% end %>
<%= t :message, '%{user} canceled his/her reservation for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %>
@ -12,7 +12,9 @@
<% else -%>
<%= before_label :attendee, "Attendee" %> <%= asset.context.name %><br>
<% end -%>
<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %><br>
<%= before_label :course, "Course" %> <%= asset.appointment_group.participant_type == 'Group' ?
asset.appointment_group.contexts.first.name :
asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %><br>
<% if asset.appointment_group.available_slots -%>
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.appointment_group.available_slots %><br>
<% end -%>
@ -20,4 +22,4 @@
<b><%= before_label :cancel_reason, "Reason for canceling" %></b><br>
<%= asset.cancel_reason || t(:no_reason_given, "none given") %><br>
<br>
<a href="<%= content :link %>"><%= t :instructions, "View the appointment" %></a>
<a href="<%= content :link %>"><%= t :instructions, "View the appointment" %></a>

View File

@ -1,3 +1,3 @@
<%= t :message, '%{user} canceled his/her reservation for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.context) %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.contexts.first) %>

View File

@ -1,9 +1,12 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %>
http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %>
<% end %>
<% courses = asset.appointment_group.participant_type == 'Group' ?
asset.appointment_group.contexts.first.name :
asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %>
<% define_content :subject do %>
<%= t('subject', 'Your time slot for "%{appointment_name}" has been canceled (%{course})', :appointment_name => asset.title, :course => asset.appointment_group.context.name) %>
<%= t('subject', 'Your time slot for "%{appointment_name}" has been canceled (%{course})', :appointment_name => asset.title, :course => courses) %>
<% end %>
<%= t :message, '%{user} canceled your time slot for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %>
@ -16,9 +19,9 @@
<% else -%>
<%= before_label :attendee, "Attendee" %> <%= asset.context.name %>
<% end -%>
<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %>
<%= before_label :course, "Course" %> <%= courses %>
<%= before_label :cancel_reason, "Reason for canceling" %>
<%= asset.cancel_reason || t(:no_reason_given, "none given") %>
<%= t :instructions, 'Sign up for a different time slot at the following link: %{link}', :link => content(:link) %>
<%= t :instructions, 'Sign up for a different time slot at the following link: %{link}', :link => content(:link) %>

View File

@ -1,5 +1,5 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %>
http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %>
<% end %>
<%= t :message, '%{user} canceled your time slot for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %><br>
@ -12,9 +12,11 @@
<% else -%>
<%= before_label :attendee, "Attendee" %> <%= asset.context.name %><br>
<% end -%>
<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %><br>
<%= before_label :course, "Course" %> <%= asset.appointment_group.participant_type == 'Group' ?
asset.appointment_group.contexts.first.name :
asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %><br>
<br>
<b><%= before_label :cancel_reason, "Reason for canceling" %></b><br>
<%= asset.cancel_reason || t(:no_reason_given, "none given") %><br>
<br>
<a href="<%= content(:link) %>"><%= t :instructions, 'Sign up for a different time slot' %></a>
<a href="<%= content(:link) %>"><%= t :instructions, 'Sign up for a different time slot' %></a>

View File

@ -1,3 +1,3 @@
<%= t :message, 'Your time slot for "%{appointment_name} was canceled".', :appointment_name => asset.title %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.context) %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.contexts.first) %>

View File

@ -1,16 +1,19 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.context) %>/appointment_groups
http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups
<% end %>
<% define_content :subject do %>
<%= t('subject', 'Appointments for "%{appointment_name}" have been canceled (%{course})', :appointment_name => asset.title, :course => asset.context.name) %>
<%= t('subject', 'Appointments for "%{appointment_name}" have been canceled (%{course})', :appointment_name => asset.title, :course => asset.contexts_for_user(user).map(&:name).join(", ")) %>
<% end %>
<%= t :message, 'All appointments for "%{appointment_name}" have been canceled.', :appointment_name => asset.title %>
<%= before_label :details, "Appointment details" %>
<%= before_label :dates, "Date(s)" %> <%= date_string(asset.start_at, asset.end_at, :no_words) %>
<%= before_label :course, "Course" %> <%= asset.context.name %>
<%= before_label :course, "Course" %> <%=
asset.participant_type == 'Group' ?
asset.contexts.first.name :
asset.contexts_for_user(user).map(&:name).join(", ") %><br>
<%= before_label :cancel_reason, "Reason for canceling" %>
<%= asset.cancel_reason || t(:no_reason_given, "none given") %>
<%= asset.cancel_reason || t(:no_reason_given, "none given") %>

View File

@ -2,7 +2,10 @@
<br>
<b><%= before_label :details, "Appointment details" %></b><br>
<%= before_label :dates, "Date(s)" %> <%= date_string(asset.start_at, asset.end_at, :no_words) %><br>
<%= before_label :course, "Course" %> <%= asset.context.name %><br>
<%= before_label :course, "Course" %> <%=
asset.participant_type == 'Group' ?
asset.contexts.first.name :
asset.contexts_for_user(user).map(&:name).join(", ") %><br>
<br>
<b><%= before_label :cancel_reason, "Reason for canceling" %></b><br>
<%= asset.cancel_reason || t(:no_reason_given, "none given") %>
<%= asset.cancel_reason || t(:no_reason_given, "none given") %>

View File

@ -1,3 +1,3 @@
<%= t :message, 'All appointments for "%{appointment_name} have been canceled".', :appointment_name => asset.title %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.context) %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.contexts.first) %>

View File

@ -1,9 +1,10 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.context) %>/appointment_groups/<%= asset.id %>
http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups/<%= asset.id %>
<% end %>
<% define_content :subject do %>
<%= t('subject', 'Appointment "%{appointment_name}" is available for signup (%{course})', :appointment_name => asset.title, :course => asset.context.name) %>
<%= t('subject', 'Appointment "%{appointment_name}" is available for signup (%{course})', :appointment_name => asset.title,
:course => asset.contexts_for_user(user).map(&:name).join(", ")) %>
<% end %>
<%= t :message, 'Time slots for "%{appointment_name}" are now available for signup.', :appointment_name => asset.title %>
@ -14,7 +15,10 @@
asset.participant_type == 'Group' ?
t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_contexts.first.name) :
t(:individual_signup, "Individual") %>
<%= before_label :course, "Course" %> <%= asset.context.name %>
<%= before_label :course, "Course" %> <%=
asset.participant_type == 'Group' ?
asset.contexts.first.name :
asset.contexts_for_user(user).map(&:name).join(", ") %>
<% if asset.available_slots -%>
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.available_slots %>
<% end -%>

View File

@ -1,5 +1,5 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.context) %>/appointment_groups/<%= asset.id %>
http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups/<%= asset.id %>
<% end %>
<%= t :message, 'Time slots for "%{appointment_name}" are now available for signup.', :appointment_name => asset.title %><br>
@ -10,7 +10,10 @@
asset.participant_type == 'Group' ?
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>
<%= before_label :course, "Course" %> <%=
asset.participant_type == 'Group' ?
asset.contexts.first.name :
asset.contexts_for_user(user).map(&:name).join(", ") %><br>
<% if asset.available_slots -%>
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.available_slots %><br>
<% end -%>

View File

@ -1,3 +1,3 @@
<%= t :message, 'Signups are open for "%{appointment_name}".', :appointment_name => asset.title %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.context) %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.contexts.first) %>

View File

@ -1,9 +1,9 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.context) %>/appointment_groups/<%= asset.id %>
http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups/<%= asset.id %>
<% end %>
<% define_content :subject do %>
<%= t('subject', 'Appointment "%{appointment_name}" is available for signup (%{course})', :appointment_name => asset.title, :course => asset.context.name) %>
<%= t('subject', 'Appointment "%{appointment_name}" is available for signup (%{course})', :appointment_name => asset.title, :course => asset.contexts_for_user(user).map(&:name).join(", ")) %>
<% end %>
<%= t :message, 'Time slots for "%{appointment_name}" are now available for signup (%{dates}).', :appointment_name => asset.title, :dates => date_string(asset.start_at, asset.end_at, :no_words) %>
<%= t :message, 'Time slots for "%{appointment_name}" are now available for signup (%{dates}).', :appointment_name => asset.title, :dates => date_string(asset.start_at, asset.end_at, :no_words) %>

View File

@ -1,9 +1,9 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.context) %>/appointment_groups/<%= asset.id %>
http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups/<%= asset.id %>
<% end %>
<% define_content :subject do %>
<%= t('subject', 'Appointment "%{appointment_name}" has been updated (%{course})', :appointment_name => asset.title, :course => asset.context.name) %>
<%= t('subject', 'Appointment "%{appointment_name}" has been updated (%{course})', :appointment_name => asset.title, :course => asset.contexts_for_user(user).map(&:name).join(", ")) %>
<% end %>
<%= t :message, 'Time slots have been added to "%{appointment_name}" and are available for signup.', :appointment_name => asset.title %>
@ -14,7 +14,10 @@
asset.participant_type == 'Group' ?
t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_contexts.first.name) :
t(:individual_signup, "Individual") %>
<%= before_label :course, "Course" %> <%= asset.context.name %>
<%= before_label :course, "Course" %> <%=
asset.participant_type == 'Group' ?
asset.contexts.first.name :
asset.contexts_for_user(user).map(&:name).join(", ") %>
<% if asset.available_slots -%>
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.available_slots %>
<% end -%>

View File

@ -1,5 +1,5 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.context) %>/appointment_groups/<%= asset.id %>
http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups/<%= asset.id %>
<% end %>
<%= t :message, 'Time slots have been added to "%{appointment_name}" and are available for signup.', :appointment_name => asset.title %><br>
@ -10,7 +10,10 @@
asset.participant_type == 'Group' ?
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>
<%= before_label :course, "Course" %> <%=
asset.participant_type == 'Group' ?
asset.contexts.first.name :
asset.contexts_for_user(user).map(&:name).join(", ") %><br>
<% if asset.available_slots -%>
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.available_slots %><br>
<% end -%>

View File

@ -1,3 +1,3 @@
<%= t :message, 'New time slots are available for "%{appointment_name}".', :appointment_name => asset.title %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.context) %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.contexts.first) %>

View File

@ -1,9 +1,9 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.context) %>/appointment_groups/<%= asset.id %>
http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups/<%= asset.id %>
<% end %>
<% define_content :subject do %>
<%= t('subject', 'Appointment "%{appointment_name}" has been updated (%{course})', :appointment_name => asset.title, :course => asset.context.name) %>
<%= t('subject', 'Appointment "%{appointment_name}" has been updated (%{course})', :appointment_name => asset.title, :course => asset.contexts_for_user(user).map(&:name).join(", ")) %>
<% end %>
<%= t :message, 'Time slots have been added to "%{appointment_name}" and are available for signup (%{dates}).', :appointment_name => asset.title, :dates => date_string(asset.start_at, asset.end_at, :no_words) %>
<%= t :message, 'Time slots have been added to "%{appointment_name}" and are available for signup (%{dates}).', :appointment_name => asset.title, :dates => date_string(asset.start_at, asset.end_at, :no_words) %>

View File

@ -1,9 +1,9 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %>
http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %>
<% end %>
<% define_content :subject do %>
<%= t('subject', 'User signed up for "%{appointment_name}" (%{course})', :appointment_name => asset.title, :course => asset.appointment_group.context.name) %>
<%= t('subject', 'User signed up for "%{appointment_name}" (%{course})', :appointment_name => asset.title, :course => asset.appointment_group.contexts_for_user(user).map(&:name).join(", ")) %>
<% end %>
<%= t :message, '%{user} has signed up for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %>
@ -16,7 +16,9 @@
<% else -%>
<%= before_label :attendee, "Attendee" %> <%= asset.context.name %>
<% end -%>
<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %>
<%= before_label :course, "Course" %> <%= courses = asset.appointment_group.participant_type == 'Group' ?
asset.appointment_group.contexts.first.name :
asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %>
<% if asset.appointment_group.available_slots -%>
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.appointment_group.available_slots %>
<% end -%>

View File

@ -1,5 +1,5 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %>
http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %>
<% end %>
<%= t :message, '%{user} has signed up for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %><br>
@ -12,9 +12,11 @@
<% else -%>
<%= before_label :attendee, "Attendee" %> <%= asset.context.name %><br>
<% end -%>
<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %><br>
<%= before_label :course, "Course" %> <%= asset.appointment_group.participant_type == 'Group' ?
asset.appointment_group.contexts.first.name :
asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %><br>
<% if asset.appointment_group.available_slots -%>
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.appointment_group.available_slots %><br>
<% end -%>
<br>
<a href="<%= content :link %>"><%= t :instructions, "View the appointment" %></a>
<a href="<%= content :link %>"><%= t :instructions, "View the appointment" %></a>

View File

@ -1,3 +1,3 @@
<%= t :message, '%{user} has signed up for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.context) %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.contexts.first) %>

View File

@ -1,5 +1,5 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %>
http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %>
<% end %>
<% define_content :subject do %>
@ -16,9 +16,11 @@
<% else -%>
<%= before_label :attendee, "Attendee" %> <%= asset.context.name %>
<% end -%>
<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %>
<%= before_label :course, "Course" %> <%= asset.appointment_group.participant_type == 'Group' ?
asset.appointment_group.contexts.first.name :
asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %>
<% if asset.appointment_group.available_slots -%>
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.appointment_group.available_slots %>
<% end -%>
<%= t :instructions, 'Sign up for a different time slot at the following link: %{link}', :link => content(:link) %>
<%= t :instructions, 'Sign up for a different time slot at the following link: %{link}', :link => content(:link) %>

View File

@ -1,5 +1,5 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %>
http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %>
<% end %>
<%= t :message, '%{user} has signed you up for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %><br>
@ -12,9 +12,11 @@
<% else -%>
<%= before_label :attendee, "Attendee" %> <%= asset.context.name %><br>
<% end -%>
<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %><br>
<%= before_label :course, "Course" %> <%= asset.appointment_group.participant_type == 'Group' ?
asset.appointment_group.contexts.first.name :
asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %><br>
<% if asset.appointment_group.available_slots -%>
<%= before_label :slots_remaining, "Available time slots" %> <%= asset.appointment_group.available_slots %><br>
<% end -%>
<br>
<a href="<%= content(:link) %>"><%= t :instructions, 'Sign up for a different time slot' %></a>
<a href="<%= content(:link) %>"><%= t :instructions, 'Sign up for a different time slot' %></a>

View File

@ -1,3 +1,3 @@
<%= t :message, 'You have been signed up for "%{appointment_name}".', :appointment_name => asset.title %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.context) %>
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.contexts.first) %>

View File

@ -1,9 +1,13 @@
<% define_content :link do %>
http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %>
http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %>
<% end %>
<% define_content :subject do %>
<%= t('subject', 'You have been signed up for "%{appointment_name}" (%{course})', :appointment_name => asset.title, :course => asset.appointment_group.context.name) %>
<%= t('subject', 'You have been signed up for "%{appointment_name}" (%{course})',
:appointment_name => asset.title,
:course => asset.appointment_group.participant_type == 'Group' ?
asset.appointment_group.contexts.first.name :
asset.appointment_group.contexts_for_user(user).map(&:name).join(", ")) %>
<% end %>
<%= t :message, '%{user} has signed you up for "%{appointment_name}" (%{date_and_time}).', :user => asset.updating_user.name, :appointment_name => asset.title, :date_and_time => datetime_string(asset.start_at, asset.end_at) %>
<%= t :message, '%{user} has signed you up for "%{appointment_name}" (%{date_and_time}).', :user => asset.updating_user.name, :appointment_name => asset.title, :date_and_time => datetime_string(asset.start_at, asset.end_at) %>

View File

@ -26,10 +26,13 @@ class AppointmentGroup < ActiveRecord::Base
# appointments_participants conditions have the correct table alias
has_many :_appointments, opts.merge(:conditions => opts[:conditions].gsub(/calendar_events\./, 'calendar_events_join.'))
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
has_many :appointment_group_contexts
has_many :appointment_group_sub_contexts, :include => :sub_context
def contexts
appointment_group_contexts.map &:context
end
def sub_contexts
appointment_group_sub_contexts.map &:sub_context
end
@ -40,7 +43,6 @@ class AppointmentGroup < ActiveRecord::Base
validates_length_of :title, :maximum => maximum_string_length
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 :appointments do |record, attr, value|
next unless record.new_appointments.present? || record.validation_event_override
@ -55,8 +57,14 @@ class AppointmentGroup < ActiveRecord::Base
end
end
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
def validate
if appointment_group_contexts.empty?
errors.add(:appointment_group_contexts,
t('errors.needs_contexts', 'Must have at least one context'))
end
end
attr_accessible :title, :description, :location_name, :location_address, :contexts, :sub_context_codes, :participants_per_appointment, :min_appointments_per_participant, :max_appointments_per_participant, :new_appointments, :participant_visibility, :cancel_reason
# when creating/updating an appointment, you can give it a list of (new)
# appointment times. these will be added to the existing appointment times
@ -73,12 +81,22 @@ class AppointmentGroup < ActiveRecord::Base
end
attr_accessor :validation_event_override
attr_accessor :cancel_reason
attr_accessor :context
def reload
remove_instance_variable :@new_appointments if @new_appointments
super
end
def contexts=(new_contexts)
new_contexts -= self.contexts
self.appointment_group_contexts += new_contexts.map do |context|
AppointmentGroupContext.create! :appointment_group => self,
:context => context
end
appointments.update_all :effective_context_code => contexts.map(&:asset_string).join(",")
end
def sub_context_codes=(codes)
if new_record?
sub_contexts = codes.map do |code|
@ -104,11 +122,13 @@ class AppointmentGroup < ActiveRecord::Base
codes = user.appointment_context_codes
{
:select => "DISTINCT appointment_groups.*",
:joins => "LEFT JOIN appointment_group_sub_contexts sc " \
:joins => "JOIN appointment_group_contexts agc " \
"ON appointment_groups.id = agc.appointment_group_id " \
"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 agc.context_code IN (?)
AND (
sc.sub_context_code IS NULL
OR sc.sub_context_code IN (?)
@ -128,13 +148,15 @@ class AppointmentGroup < ActiveRecord::Base
end
{
:select => "DISTINCT appointment_groups.*",
:joins => "LEFT JOIN appointment_group_sub_contexts sc " \
:joins => "JOIN appointment_group_contexts agc " \
"ON appointment_groups.id = agc.appointment_group_id " \
"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 agc.context_code IN (?)
AND (
context_code IN (?)
agc.context_code IN (?)
OR sc.sub_context_code IN (?)
)
COND
@ -153,9 +175,13 @@ class AppointmentGroup < ActiveRecord::Base
set_policy do
given { |user, session|
next false if deleted?
next false unless cached_context_grants_right?(user, nil, :manage_calendar)
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)
next false unless contexts.all? { |c| c.grants_right? user, nil, :manage_calendar }
if appointment_group_sub_contexts.present?
raise "you can't have multiple contexts and sub_contexts" if appointment_group_contexts.size > 1
context = contexts.first
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] } }
end
!contexts.all? { |c| c.visibility_limited_to_course_sections?(user) }
}
can :manage and can :manage_calendar and can :read and can :read_appointment_participants and
can :create and can :update and can :delete
@ -177,15 +203,15 @@ class AppointmentGroup < ActiveRecord::Base
set_broadcast_policy do
dispatch :appointment_group_published
to { possible_users }
whenever { context.available? && active? && workflow_state_changed? }
whenever { contexts.any?(&:available?) && active? && workflow_state_changed? }
dispatch :appointment_group_updated
to { possible_users }
whenever { context.available? && active? && new_appointments && !workflow_state_changed? }
whenever { contexts.any?(&:available?) && active? && new_appointments && !workflow_state_changed? }
dispatch :appointment_group_deleted
to { possible_users }
whenever { context.available? && deleted? && workflow_state_changed? }
whenever { contexts.any?(&:available?) && deleted? && workflow_state_changed? }
end
def possible_users
@ -195,17 +221,20 @@ class AppointmentGroup < ActiveRecord::Base
end
def instructors
sub_context_type == 'CourseSection' ?
context.participating_instructors.restrict_to_sections(sub_context_id).uniq :
context.participating_instructors.uniq
if sub_context_type == "CourseSection"
contexts.map { |c| c.participating_instructors.restrict_to_sections(sub_context_id) }.flatten.uniq
else
contexts.map(&:participating_instructors).flatten.uniq
end
end
def possible_participants(registration_status=nil)
participants = if participant_type == 'User'
sub_contexts.empty? ?
context.participating_students :
contexts.map(&:participating_students).flatten :
sub_contexts.map(&:participating_students).flatten
else
# FIXME?
sub_contexts.map(&:groups).flatten
end
participant_ids = self.participant_ids
@ -242,7 +271,7 @@ class AppointmentGroup < ActiveRecord::Base
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 (codes[:primary]. & appointment_group_contexts.map(&:context_code)).present?
return false unless sub_context_codes.empty? || (codes[:secondary] & sub_context_codes).present?
true
end
@ -337,7 +366,6 @@ class AppointmentGroup < ActiveRecord::Base
end
def default_values
self.context_code ||= context_string
self.participant_visibility ||= 'private'
end
@ -357,4 +385,21 @@ class AppointmentGroup < ActiveRecord::Base
self.appointments.map{ |a| a.destroy(false) }
end
end
def contexts_for_user(user)
context_codes = context_codes_for_user(user)
course_ids = appointment_group_contexts.all(:conditions => {:context_code => context_codes}).map(&:context_id)
Course.all(:conditions => {:id => course_ids})
end
def context_codes_for_user(user)
manageable_codes = user.manageable_appointment_context_codes
user_codes = user.appointment_context_codes[:primary] |
manageable_codes[:full] | manageable_codes[:limited]
context_codes & user_codes
end
def context_codes
appointment_group_contexts.map(&:context_code)
end
end

View File

@ -0,0 +1,30 @@
#
# 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 AppointmentGroupContext < ActiveRecord::Base
belongs_to :appointment_group
belongs_to :context, :polymorphic => true
attr_accessible :appointment_group, :context
before_validation :default_values
def default_values
self.context_code ||= context_string
end
end

View File

@ -24,10 +24,10 @@ class AppointmentGroupSubContext < ActiveRecord::Base
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
record.errors.add(attr, t('errors.invalid_course_section', 'Invalid course section')) unless value.blank? || value.is_a?(CourseSection) && record.appointment_group.contexts.any? { |c| c == value.course }
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
record.errors.add(attr, t('errors.invalid_group_category', 'Invalid group category')) unless value && record.appointment_group.contexts.any? { |c| c == value.context }
end
end

View File

@ -46,7 +46,6 @@ class Assignment < ActiveRecord::Base
has_one :rubric, :through => :rubric_association
has_one :teacher_enrollment, :class_name => 'TeacherEnrollment', :foreign_key => 'course_id', :primary_key => 'context_id', :include => :user, :conditions => ['enrollments.workflow_state = ?', 'active']
belongs_to :context, :polymorphic => true
alias_method :effective_context, :context
belongs_to :cloned_item
belongs_to :grading_standard
belongs_to :group_category

View File

@ -125,7 +125,13 @@ class CalendarEvent < ActiveRecord::Base
all_codes = codes | effectively_courses_codes
group_codes = codes.grep(/\Aappointment_group_\d+\z/)
codes -= group_codes
{:conditions => [<<-SQL, all_codes, codes, group_codes, effectively_courses_codes, codes]}
codes_conditions = codes.map { |code|
wildcard(quoted_table_name + '.effective_context_code', code, :delimiter => ',')
}.join(" OR ")
codes_conditions = self.connection.quote(false) if codes_conditions.blank?
{:conditions => [<<-SQL, all_codes, codes, group_codes, effectively_courses_codes]}
calendar_events.context_code IN (?)
AND (
( -- explicit contexts (e.g. course_123)
@ -137,7 +143,7 @@ class CalendarEvent < ActiveRecord::Base
)
OR ( -- own appointment_participants, or section events in the course
calendar_events.context_code IN (?)
AND calendar_events.effective_context_code IN (?)
AND (#{codes_conditions})
)
)
SQL
@ -186,13 +192,13 @@ class CalendarEvent < ActiveRecord::Base
if parent_event
self.effective_context_code = if appointment_group # appointment participant
appointment_group.context_code if appointment_group.participant_type == 'User'
else # e.g. section-level event
parent_event.context_code
end
appointment_group.appointment_group_contexts.map(&:context_code).join(',') if appointment_group.participant_type == 'User'
else # e.g. section-level event
parent_event.context_code
end
(locked? ? LOCKED_ATTRIBUTES : CASCADED_ATTRIBUTES).each{ |attr| send("#{attr}=", parent_event.send(attr)) }
elsif context.is_a?(AppointmentGroup)
self.effective_context_code = context.context_code
self.effective_context_code = context.appointment_group_contexts.map(&:context_code).join(",")
if new_record?
AppointmentGroup::EVENT_ATTRIBUTES.each { |attr| send("#{attr}=", attr == :description ? context.description_html : context.send(attr)) }
if locked?

View File

@ -162,7 +162,8 @@ class Course < ActiveRecord::Base
has_many :content_exports
has_many :course_imports
has_many :alerts, :as => :context, :include => :criteria
has_many :appointment_groups, :as => :context
has_many :appointment_group_contexts, :as => :context
has_many :appointment_groups, :through => :appointment_group_contexts
has_many :appointment_participants, :class_name => 'CalendarEvent', :foreign_key => :effective_context_code, :primary_key => :asset_string, :conditions => "workflow_state = 'locked' AND parent_calendar_event_id IS NOT NULL"
attr_accessor :import_source
has_many :zip_file_imports, :as => :context

View File

@ -1680,7 +1680,9 @@ class User < ActiveRecord::Base
events = CalendarEvent.active.for_user_and_context_codes(self, context_codes).between(Time.now.utc, opts[:end_at]).scoped(:limit => opts[:limit]).reject(&:hidden?)
events += Assignment.active.for_context_codes(context_codes).due_between(Time.now.utc, opts[:end_at]).scoped(:limit => opts[:limit]).include_submitted_count
events += AppointmentGroup.manageable_by(self, context_codes).intersecting(Time.now.utc, opts[:end_at]).scoped(:limit => opts[:limit])
appointment_groups = AppointmentGroup.manageable_by(self, context_codes).intersecting(Time.now.utc, opts[:end_at]).scoped(:limit => opts[:limit])
appointment_groups.each { |ag| ag.context = ag.contexts_for_user(self).first }
events += appointment_groups
events.sort_by{|e| [e.start_at, e.title] }.uniq.first(opts[:limit])
end

View File

@ -41,10 +41,11 @@ button.single_item_done_button
margin: 10px
.unpublished-ag
background-color: #f2f2f2
.ag-context
margin-bottom: 10px
color: #666
.ag-context a
color: #666
margin-bottom: 10px
display: block
.ag-x-of-x-signed-up
font-size: 11px
@ -120,6 +121,40 @@ button.single_item_done_button
margin: 0
padding: 0
.ag_contexts_selector
width: 225px
.ag-menu-container
position: relative
.ag_contexts_menu
position: absolute
width: 223px
background: white
border: 1px solid #b6b6b6
margin-top: -3px
padding-bottom: 5px
.ag-contexts
+reset_list
max-height: 150px
overflow-y: auto
.ag_sections
+reset_list
padding-left: 35px
.ag_sections_toggle
display: block
width: 16px
height: 16px
float: left
background-image: url(/images/jqueryui/icon_sprite.png)
background-position: -16px -80px
.ag-sections-expanded
background-position: 0px -64px
.ag_contexts_done
float: right
.hidden
display: none
.delete-block-link
+accessible_text_replacement
display: block
@ -154,6 +189,12 @@ button.single_item_done_button
.context_id
max-width: 200px
.select-calendar-container
margin: 0 0 1.5em
.group-signup
margin: 0 0 1.5em
#message_participants_form
padding: 10px
textarea

View File

@ -35,7 +35,9 @@
<span style="display: block;"><%= datetime_string(recent_event.start_at, :event, recent_event.end_at) %></span>
<% end %>
<% if show_context %>
<span style="display: block; font-size: 0.8em;"><%= recent_event.effective_context.short_name %></span>
<span style="display: block; font-size: 0.8em;">
<%= recent_event.is_a?(CalendarEvent) ? recent_event.effective_context.short_name : recent_event.context.short_name %>
</span>
<% end %>
</span>
</span>

View File

@ -32,9 +32,9 @@
{{/if}}
<h3><a class="view_calendar_link" href="#">{{title}}</a></h3>
{{#if context}}
<div class='ag-context'><a href="{{context.url}}">{{context.name}}</a></div>
{{/if}}
<div class='ag-context'>
{{#toSentence contexts}}<a href="{{url}}">{{name}}</a>{{/toSentence}}
</div>
{{#if location_name}}
<div class="ag-location">
<strong>{{#t "location"}}Location:{{/t}}</strong>

View File

@ -0,0 +1,4 @@
<div class="ag_contexts_menu hidden">
<ul class="ag-contexts"></ul>
<button class="button small-button ag_contexts_done">{{#t "done"}}Done{{/t}}</button>
</div>

View File

@ -0,0 +1,13 @@
<li>
<a class="ag_sections_toggle"></a>
<input type="checkbox" name="context_codes[]" value="{{asset_string}}" id="option_{{asset_string}}">
<label for="option_{{asset_string}}">{{name}}</label>
<ul class="ag_sections hidden">
{{#each course_sections}}
<li>
<input type="checkbox" name="sections[]" value="{{asset_string}}" id="option_{{asset_string}}">
<label for="option_{{asset_string}}">{{name}}</label>
</li>
{{/each}}
</ul>
</li>

View File

@ -8,33 +8,21 @@
<b>{{#t "location"}}Location{{/t}}</b><br />
<input type="text" name="location" style="width: 190px" value="{{appointment_group.location_name}}">
</p>
<p>
<div class="select-calendar-container">
<b>{{#t "calendar"}}Calendar{{/t}}</b><br />
<select name="context_code" class="context_id">
{{#each contexts}}
{{#if can_create_appointment_groups}}
<option value="{{asset_string}}">{{name}}</option>
{{/if}}
{{/each}}
</select>
</p>
<button class="button ag_contexts_selector"></button>
<div class="ag-menu-container"></div>
</div>
<p>
<input type="checkbox" class="group-signup-checkbox" id="group-signup-checkbox" name="use_group_signup" value="1" {{#ifEqual participant_type "Group"}}checked{{/ifEqual}}>
<label for="group-signup-checkbox">Have students sign up in groups.</label>
</p>
<p>
<div class="section-signup">
<b>{{#t "course_section"}}Limit Signups to{{/t}}</b><br />
<div class="section_select">
</div>
<div class="group-signup" style="display: none">
<b>{{#t "group_category"}}Group Category{{/t}}</b><br />
<div class="group_select">
<select name="group_category_id"></select>
</div>
<div class="group-signup" style="display: none">
<b>{{#t "group_category"}}Group Category{{/t}}</b><br />
<div class="group_select">
<select name="group_category_id"></select>
</div>
</div>
</p>
</div>
</div>
<div class="right-column-wrapper">
<div class="time-block-list-wrapper">

View File

@ -0,0 +1,17 @@
class CreateAppointmentGroupContexts < ActiveRecord::Migration
tag :predeploy
def self.up
create_table :appointment_group_contexts do |t|
t.references :appointment_group, :limit => 8
t.string :context_code
t.integer :context_id, :limit => 8
t.string :context_type
t.timestamps
end
end
def self.down
drop_table :appointment_group_contexts
end
end

View File

@ -0,0 +1,21 @@
class MigrateAppointmentGroupContexts < ActiveRecord::Migration
tag :postdeploy
def self.up
records = AppointmentGroup.all.map { |ag|
{
:appointment_group_id => ag.id,
:context_code => ag.context_code,
:context_type => ag.context_type,
:context_id => ag.context_id,
:updated_at => ag.updated_at,
:created_at => ag.created_at
}
}
bulk_insert :appointment_group_contexts, records
end
def self.down
end
end

View File

@ -37,8 +37,20 @@ module Api::V1::CalendarEvent
participant = nil
hash = api_json(event, user, session, :only => %w(id created_at updated_at start_at end_at all_day all_day_date title description location_address location_name workflow_state))
hash['context_code'] = event.context_code
hash['effective_context_code'] = event.effective_context_code if event.effective_context_code
appointment_group_id = (options[:appointment_group_id] || event.appointment_group.try(:id))
if event.effective_context_code
if appointment_group_id
codes_for_user = AppointmentGroup.find(appointment_group_id).contexts_for_user(user).map(&:asset_string)
hash['context_code'] = (event.effective_context_code.split(',') & codes_for_user).first
hash['effective_context_code'] = hash['context_code']
else
hash['effective_context_code'] = event.effective_context_code
end
end
hash['context_code'] ||= event.context_code
hash["child_events_count"] = event.child_events.size
hash['parent_event_id'] = event.parent_calendar_event_id
hash['hidden'] = event.hidden?
@ -50,7 +62,7 @@ module Api::V1::CalendarEvent
hash['group'] = group_json(event.context, user, session, :include => ['users'])
end
end
if appointment_group_id = (options[:appointment_group_id] || event.appointment_group.try(:id))
if appointment_group_id
hash['appointment_group_id'] = appointment_group_id
hash['appointment_group_url'] = api_v1_appointment_group_url(appointment_group_id)
if options[:current_participant] && event.has_asset?(options[:current_participant])
@ -99,11 +111,12 @@ module Api::V1::CalendarEvent
def appointment_group_json(group, user, session, options={})
orig_context = @context
@context = group.context
@context = group.contexts_for_user(user).first
@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 title updated_at workflow_state participant_visibility}, :methods => :sub_context_codes)
hash = api_json(group, user, session, :only => %w{id 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['context_codes'] = group.context_codes_for_user(user)
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

@ -25,7 +25,7 @@ describe AppointmentGroupsController, :type => :integration do
end
expected_fields = [
'appointments_count', 'context_code', 'created_at', 'description',
'appointments_count', 'context_codes', 'created_at', 'description',
'end_at', 'id', 'location_address', 'location_name',
'max_appointments_per_participant', 'min_appointments_per_participant',
'participant_type', 'participant_visibility',
@ -34,14 +34,14 @@ describe AppointmentGroupsController, :type => :integration do
]
it 'should return manageable appointment groups' do
ag1 = @course.appointment_groups.create(:title => "something")
ag1 = AppointmentGroup.create!(:title => "something", :contexts => [@course])
cat = @course.group_categories.create
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"]])
ag2 = AppointmentGroup.create!(:title => "another", :contexts => [@course], :sub_context_codes => [cat.asset_string])
ag3 = AppointmentGroup.create!(:title => "inaccessible", :contexts => [Course.create!])
ag4 = AppointmentGroup.create!(:title => "past", :contexts => [@course], :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]])
json = api_call(:get, "/api/v1/appointment_groups?scope=manageable", {
:controller => 'appointment_groups', :action => 'index', :format => 'json', :scope => 'manageable'})
:controller => 'appointment_groups', :action => 'index', :format => 'json', :scope => 'manageable'})
json.size.should eql 2
json.first.keys.sort.should eql expected_fields
json.first.slice('id', 'title', 'participant_type').should eql({'id' => ag1.id, 'title' => 'something', 'participant_type' => 'User'})
@ -49,36 +49,36 @@ describe AppointmentGroupsController, :type => :integration do
end
it "should return past manageable appointment groups, if requested" do
ag = @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"]])
ag = AppointmentGroup.create!(:title => "past", :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]], :contexts => [@course])
json = api_call(:get, "/api/v1/appointment_groups?scope=manageable&include_past_appointments=1", {
:controller => 'appointment_groups', :action => 'index', :format => 'json', :scope => 'manageable', :include_past_appointments => '1'})
json.size.should eql 1
end
it 'should return reservable appointment groups' do
ag1 = @course.appointment_groups.create(:title => "can't reserve")
ag1 = AppointmentGroup.create!(:title => "can't reserve", :contexts => [@course])
ag1.publish!
ag2 = Course.create.appointment_groups.create(:title => "me neither")
ag2 = AppointmentGroup.create!(:title => "me neither", :contexts => [Course.create!])
ag2.publish!
student_in_course :course => course(:active_all => true), :user => @me
ag3 = @course.appointment_groups.create(:title => "enrollment not active")
ag3 = AppointmentGroup.create!(:title => "enrollment not active", :contexts => [@course])
ag3.publish!
student_in_course :course => course(:active_all => true), :user => @me, :active_all => true
ag4 = @course.appointment_groups.create(:title => "unpublished")
ag5 = @course.appointment_groups.create(:title => "no times")
ag4 = AppointmentGroup.create!(:title => "unpublished", :contexts => [@course])
ag5 = AppointmentGroup.create!(:title => "no times", :contexts => [@course])
ag5.publish!
ag6 = @course.appointment_groups.create(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]])
ag6 = AppointmentGroup.create!(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]], :contexts => [@course])
ag6.publish!
cat = @course.group_categories.create
mygroup = cat.groups.create(:context => @course)
mygroup.users << @me
@me.reload
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 = AppointmentGroup.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"]], :contexts => [@course])
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 = AppointmentGroup.create!(:title => "past", :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]], :contexts => [@course])
ag8.publish!
json = api_call(:get, "/api/v1/appointment_groups?scope=reservable", {
@ -91,7 +91,7 @@ describe AppointmentGroupsController, :type => :integration do
it "should return past reservable appointment groups, if requested" do
student_in_course :course => course(:active_all => true), :user => @me, :active_all => true
ag = @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"]])
ag = AppointmentGroup.create!(:title => "past", :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]], :contexts => [@course])
ag.publish!
json = api_call(:get, "/api/v1/appointment_groups?scope=reservable&include_past_appointments=1", {
:controller => 'appointment_groups', :action => 'index', :format => 'json', :scope => 'reservable', :include_past_appointments => '1'})
@ -99,7 +99,7 @@ describe AppointmentGroupsController, :type => :integration do
end
it 'should paginate appointment groups' do
ids = 25.times.map { |i| @course.appointment_groups.create(:title => "#{i}").id }
ids = 25.times.map { |i| AppointmentGroup.create!(:title => "#{i}".object_id, :contexts => [@course]) }
json = api_call(:get, "/api/v1/appointment_groups?scope=manageable&per_page=10", {
:controller => 'appointment_groups', :action => 'index', :format => 'json',
:scope => 'manageable', :per_page => '10'})
@ -114,7 +114,7 @@ describe AppointmentGroupsController, :type => :integration do
end
it 'should include appointments and child_events, if requested' do
ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]])
ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]], :contexts => [@course])
student = student_in_course(:course => @course, :active_all => true).user
ag.appointments.first.reserve_for student, @me
@user = @me
@ -136,7 +136,7 @@ describe AppointmentGroupsController, :type => :integration do
end
it 'should get a manageable appointment group' do
ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course])
json = api_call(:get, "/api/v1/appointment_groups/#{ag.id}", {
:controller => 'appointment_groups', :action => 'show', :format => 'json', :id => ag.id.to_s})
@ -149,7 +149,7 @@ describe AppointmentGroupsController, :type => :integration do
end
it 'should include child_events, if requested' do
ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course])
student = student_in_course(:course => @course, :active_all => true).user
ag.appointments.first.reserve_for student, @me
@user = @me
@ -169,7 +169,7 @@ describe AppointmentGroupsController, :type => :integration do
it 'should get a reservable appointment group' do
student_in_course :course => course(:active_all => true), :user => @me, :active_all => true
@user = @me
ag = @course.appointment_groups.create(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]])
ag = AppointmentGroup.create!(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]], :contexts => [@course])
ag.publish!
json = api_call(:get, "/api/v1/appointment_groups/#{ag.id}", {
@ -182,7 +182,7 @@ describe AppointmentGroupsController, :type => :integration do
it 'should require action until the min has been met' do
student_in_course :course => course(:active_all => true), :user => @me, :active_all => true
@user = @me
ag = @course.appointment_groups.create(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]], :min_appointments_per_participant => 1)
ag = AppointmentGroup.create!(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]], :min_appointments_per_participant => 1, :contexts => [@course])
ag.publish!
appt = ag.appointments.first
@ -206,14 +206,14 @@ describe AppointmentGroupsController, :type => :integration do
@user = @me
raw_api_call(:post, "/api/v1/appointment_groups",
{:controller => 'appointment_groups', :action => 'create', :format => 'json'},
{:appointment_group => {:context_code => @course.asset_string, :title => "ohai"} })
{:appointment_group => {:context_codes => [@course.asset_string], :title => "ohai"} })
JSON.parse(response.body)['status'].should == 'unauthorized'
end
it 'should create a new appointment group' do
json = api_call(:post, "/api/v1/appointment_groups",
{:controller => 'appointment_groups', :action => 'create', :format => 'json'},
{:appointment_group => {:context_code => @course.asset_string, :title => "ohai", :new_appointments => {'0' => ["2012-01-01 12:00:00", "2012-01-01 13:00:00"]}} })
{:appointment_group => {:context_codes => [@course.asset_string], :title => "ohai", :new_appointments => {'0' => ["2012-01-01 12:00:00", "2012-01-01 13:00:00"]}} })
json.keys.sort.should eql((expected_fields + ['new_appointments']).sort)
json['start_at'].should eql "2012-01-01T12:00:00Z"
json['end_at'].should eql "2012-01-01T13:00:00Z"
@ -224,7 +224,7 @@ 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_codes => [@course.default_section.asset_string], :title => "ohai"} })
{:appointment_group => {:context_codes => [@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_codes'].should eql [@course.default_section.asset_string]
@ -233,7 +233,7 @@ describe AppointmentGroupsController, :type => :integration do
it 'should enforce update permissions' do
student_in_course :course => course(:active_all => true), :user => @me, :active_all => true
@user = @me
ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course])
raw_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"} })
@ -241,7 +241,7 @@ describe AppointmentGroupsController, :type => :integration do
end
it 'should update an appointment group' do
ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course])
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"} })
@ -250,7 +250,7 @@ describe AppointmentGroupsController, :type => :integration do
end
it 'should ignore updates to readonly fields' do
ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course])
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_codes => [@course.default_section.asset_string]} })
@ -260,7 +260,7 @@ describe AppointmentGroupsController, :type => :integration do
end
it 'should publish an appointment group in an update through the api' do
ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course])
ag.workflow_state.should == 'pending'
json = api_call(:put, "/api/v1/appointment_groups/#{ag.id}",
{:controller => 'appointment_groups', :action => 'update', :format => 'json', :id => ag.id.to_s},
@ -272,7 +272,7 @@ describe AppointmentGroupsController, :type => :integration do
it 'should publish an appointment group when creating through the api when requested' do
json = api_call(:post, "/api/v1/appointment_groups",
{:controller => 'appointment_groups', :action => 'create', :format => 'json'},
{:appointment_group => {:context_code => @course.asset_string, :title => "ohai", :new_appointments => {'0' => ["2012-01-01 12:00:00", "2012-01-01 13:00:00"]}, :publish => '1'} })
{:appointment_group => {:context_codes => [@course.asset_string], :title => "ohai", :new_appointments => {'0' => ["2012-01-01 12:00:00", "2012-01-01 13:00:00"]}, :publish => '1'} })
json['workflow_state'].should eql 'active'
AppointmentGroup.find(json['id']).workflow_state.should eql 'active'
end
@ -280,14 +280,14 @@ describe AppointmentGroupsController, :type => :integration do
it 'should enforce delete permissions' do
student_in_course :course => course(:active_all => true), :user => @me, :active_all => true
@user = @me
ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course])
raw_api_call(:delete, "/api/v1/appointment_groups/#{ag.id}",
{:controller => 'appointment_groups', :action => 'destroy', :format => 'json', :id => ag.id.to_s})
JSON.parse(response.body)['status'].should == 'unauthorized'
end
it 'should delete an appointment group' do
ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course])
json = api_call(:delete, "/api/v1/appointment_groups/#{ag.id}",
{:controller => 'appointment_groups', :action => 'destroy', :format => 'json', :id => ag.id.to_s})
json.keys.sort.should eql expected_fields
@ -297,7 +297,7 @@ describe AppointmentGroupsController, :type => :integration do
types = {
'users' => proc {
@ag = @course.appointment_groups.create(:title => "yay", :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 = AppointmentGroup.create!(:title => "yay", :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"]], :contexts => [@course])
@ag.publish!
student1 = student_in_course(:course => @course, :active_all => true).user
@ag.appointments.first.reserve_for student1, @me
@ -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_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 = AppointmentGroup.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"]], :contexts => [@course])
@ag.publish!
group1 = cat.groups.create(:context => @course)
group1.users << student_in_course(:course => @course, :active_all => true).user

View File

@ -120,7 +120,7 @@ describe CalendarEventsApiController, :type => :integration do
context 'appointments' do
it 'should include appointments for teachers (with participant info)' do
ag1 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
ag1 = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course])
event1 = ag1.appointments.first
student_ids = []
3.times {
@ -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_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
ag2 = AppointmentGroup.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"]], :contexts => [@course])
event2 = ag2.appointments.first
group_ids = []
group_student_ids = []
@ -172,13 +172,13 @@ describe CalendarEventsApiController, :type => :integration do
@teacher = @course.admins.first
student_in_course :course => @course, :user => @me, :active_all => true
group1 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
group1 = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course])
group1.publish!
event1 = group1.appointments.first
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_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
group2 = AppointmentGroup.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"]], :contexts => [@course])
group2.publish!
event2 = group2.appointments.first
g = cat.groups.create(:context => @course)
@ -208,7 +208,7 @@ describe CalendarEventsApiController, :type => :integration do
@teacher = @course.admins.first
student_in_course :course => @course, :user => @me, :active_all => true
group = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :participant_visibility => 'private', :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
group = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :participant_visibility => 'private', :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course])
group.publish!
event = group.appointments.first
event.reserve_for(@me, @teacher)
@ -231,7 +231,7 @@ describe CalendarEventsApiController, :type => :integration do
@teacher = @course.admins.first
student_in_course :course => @course, :user => @me, :active_all => true
group = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :participant_visibility => 'protected', :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
group = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :participant_visibility => 'protected', :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course])
group.publish!
event = group.appointments.first
event.reserve_for(@me, @teacher)
@ -257,8 +257,15 @@ describe CalendarEventsApiController, :type => :integration do
@teacher = @course.admins.first
student_in_course :course => @course, :user => @me, :active_all => true
otherguy = student_in_course(:course => @course, :active_all => true).user
ag1 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
course1 = @course
course_with_teacher(:user => @teacher, :active_all => true)
course2, @course = @course, course1
ag1 = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [course2])
ag1.publish!
ag1.contexts = [course1, course2]
ag1.save!
event1 = ag1.appointments.first
my_personal_appointment = event1.reserve_for(@me, @me)
event1.reserve_for(otherguy, otherguy)
@ -269,7 +276,8 @@ 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_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
ag2 = AppointmentGroup.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"]], :contexts => [course1, course2])
ag2.publish!
event2 = ag2.appointments.first
my_group_appointment = event2.reserve_for(mygroup, @me)
@ -322,7 +330,7 @@ describe CalendarEventsApiController, :type => :integration do
student_in_course(:course => @course, :user => (@other_guy = user), :active_all => true)
@ag1 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00", "2012-01-01 13:00:00", "2012-01-01 14:00:00"]])
@ag1 = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00", "2012-01-01 13:00:00", "2012-01-01 14:00:00"]], :contexts => [@course])
@ag1.publish!
@event1 = @ag1.appointments.first
@event2 = @ag1.appointments.last
@ -333,7 +341,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_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
@ag2 = AppointmentGroup.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"]], :contexts => [@course])
@ag2.publish!
@event3 = @ag2.appointments.first

View File

@ -49,7 +49,7 @@ def appointment_group_model(opts={})
if sub_context = opts.delete(:sub_context)
opts[:sub_context_codes] = [sub_context.asset_string]
end
@appointment_group = @course.appointment_groups.create!(valid_appointment_group_attributes.merge(opts))
@appointment_group = AppointmentGroup.create!(valid_appointment_group_attributes.merge(opts))
@appointment_group
end
@ -61,6 +61,7 @@ end
def valid_appointment_group_attributes
{
:title => "some title"
:title => "some title",
:contexts => [@course]
}
end

View File

@ -21,11 +21,11 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_deleted.email' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
course_with_student(:active_all => true)
appointment_group_model(:contexts => [@course])
@appointment_group.cancel_reason = 'just because'
generate_message(:appointment_group_deleted, :email, @appointment_group)
generate_message(:appointment_group_deleted, :email, @appointment_group, :user => @user)
@message.subject.should include('some title')
@message.body.should include('some title')

View File

@ -21,11 +21,11 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_deleted.facebook' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
student_in_course(:active_all => true)
appointment_group_model(:contexts => [@course])
@appointment_group.cancel_reason = 'just because'
generate_message(:appointment_group_deleted, :facebook, @appointment_group)
generate_message(:appointment_group_deleted, :facebook, @appointment_group, :user => @user)
@message.body.should include('some title')
@message.body.should include('just because')

View File

@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_deleted.sms' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
appointment_group_model(:contexts => [course_model])
@appointment_group.cancel_reason = 'just because'
generate_message(:appointment_group_deleted, :sms, @appointment_group)

View File

@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_deleted.twitter' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
appointment_group_model(:contexts => [course_model])
@appointment_group.cancel_reason = 'just because'
generate_message(:appointment_group_deleted, :twitter, @appointment_group)

View File

@ -21,10 +21,10 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_published.email' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
course_with_student(:active_all => true)
appointment_group_model(:contexts => [@course])
generate_message(:appointment_group_published, :email, @appointment_group)
generate_message(:appointment_group_published, :email, @appointment_group, :user => @user)
@message.subject.should include('some title')
@message.body.should include('some title')
@ -36,7 +36,7 @@ describe 'appointment_group_published.email' do
user = user_model
@course = course_model
cat = @course.group_categories.create(:name => 'teh category')
appointment_group_model(:context => @course, :sub_context => cat)
appointment_group_model(:contexts => [@course], :sub_context => cat)
generate_message(:appointment_group_published, :email, @appointment_group)

View File

@ -21,10 +21,10 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_published.facebook' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
course_with_student(:active_all => true)
appointment_group_model(:contexts => [@course])
generate_message(:appointment_group_published, :facebook, @appointment_group)
generate_message(:appointment_group_published, :facebook, @appointment_group, :user => @user)
@message.body.should include('some title')
@message.body.should include(@course.name)
@ -35,7 +35,7 @@ describe 'appointment_group_published.facebook' do
user = user_model
@course = course_model
cat = @course.group_categories.create(:name => 'teh category')
appointment_group_model(:context => @course, :sub_context => cat)
appointment_group_model(:contexts => [@course], :sub_context => cat)
generate_message(:appointment_group_published, :facebook, @appointment_group)

View File

@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_published.sms' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
appointment_group_model(:contexts => [course_model])
generate_message(:appointment_group_published, :sms, @appointment_group)

View File

@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_published.summary' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
appointment_group_model(:contexts => [course_model])
generate_message(:appointment_group_published, :summary, @appointment_group)

View File

@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_published.twitter' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
appointment_group_model(:contexts => [course_model])
generate_message(:appointment_group_published, :twitter, @appointment_group)

View File

@ -21,10 +21,10 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_updated.email' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
user = course_with_student(:active_all => true)
appointment_group_model(:contexts => [@course])
generate_message(:appointment_group_updated, :email, @appointment_group)
generate_message(:appointment_group_updated, :email, @appointment_group, :user => @user)
@message.subject.should include('some title')
@message.body.should include('some title')
@ -36,7 +36,7 @@ describe 'appointment_group_updated.email' do
user = user_model
@course = course_model
cat = @course.group_categories.create(:name => 'teh category')
appointment_group_model(:context => @course, :sub_context => cat)
appointment_group_model(:contexts => [@course], :sub_context => cat)
generate_message(:appointment_group_updated, :email, @appointment_group)

View File

@ -21,10 +21,10 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_updated.facebook' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
course_with_student(:active_all => true)
appointment_group_model(:contexts => [@course])
generate_message(:appointment_group_updated, :facebook, @appointment_group)
generate_message(:appointment_group_updated, :facebook, @appointment_group, :user => @user)
@message.body.should include('some title')
@message.body.should include(@course.name)
@ -35,7 +35,7 @@ describe 'appointment_group_updated.facebook' do
user = user_model
@course = course_model
cat = @course.group_categories.create(:name => 'teh category')
appointment_group_model(:context => @course, :sub_context => cat)
appointment_group_model(:contexts => [@course], :sub_context => cat)
generate_message(:appointment_group_updated, :facebook, @appointment_group)

View File

@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_updated.sms' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
appointment_group_model(:contexts => [course_model])
generate_message(:appointment_group_updated, :sms, @appointment_group)

View File

@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_updated.email' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
appointment_group_model(:contexts => [course_model])
generate_message(:appointment_group_updated, :summary, @appointment_group)

View File

@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper')
describe 'appointment_group_updated.twitter' do
it "should render" do
user = user_model
appointment_group_model(:context => course_model)
appointment_group_model(:contexts => [course_model])
generate_message(:appointment_group_updated, :twitter, @appointment_group)

View File

@ -27,7 +27,7 @@ describe AppointmentGroup do
it "should ensure the course section matches the course" do
AppointmentGroup.new(
:title => "test",
:context => @course,
:contexts => [@course],
:sub_context_codes => [CourseSection.create.asset_string]
).should_not be_valid
end
@ -35,7 +35,7 @@ describe AppointmentGroup do
it "should ensure the group category matches the course" do
AppointmentGroup.new(
:title => "test",
:context => @course,
:contexts => [@course],
:sub_context_codes => [GroupCategory.create.asset_string]
).should_not be_valid
end
@ -43,18 +43,62 @@ describe AppointmentGroup do
it "should ignore invalid sub context types" do
group = AppointmentGroup.new(
:title => "test",
:context => @course,
:contexts => [@course],
:sub_context_codes => [Account.create.asset_string]
)
group.should be_valid
group.sub_context_codes.should be_empty
end
it "should only add contexts" do
course1 = @course
course_with_student(:active_all => true)
course2 = @course
group = AppointmentGroup.new(
:title => "test",
:contexts => [course1],
:sub_context_codes => [Account.create.asset_string]
)
group.contexts = [course2]
group.save!
group.contexts.should eql [course1, course2]
# also make sure you can't get duplicates
group.contexts = [course1]
group.save!
group.contexts.should eql [course1, course2]
end
end
context "add context" do
it "should update appointments effective_context_code" do
course(:active_all => true)
course1 = @course
course(:active_all => true)
course2 = @course
group = AppointmentGroup.create!(
:title => "test",
:contexts => [course1],
:new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']]
)
group.appointments.map(&:effective_context_code).should eql [course1.asset_string]
group.contexts = [course1, course2]
group.save!
group.reload
group.appointments.map(&:effective_context_code).should eql ["#{course1.asset_string},#{course2.asset_string}"]
end
end
context "add_appointment" do
before do
course_with_student(:active_all => true)
@ag = AppointmentGroup.create!(:title => "test", :context => @course, :new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']])
@ag = AppointmentGroup.create!(:title => "test", :contexts => [@course], :new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']])
@appointment = @ag.appointments.first
@appointment.should_not be_nil
end
@ -117,22 +161,32 @@ describe AppointmentGroup do
@course.enroll_user(@user, 'TaEnrollment', :section => section2, :limit_privileges_to_course_section => true).accept!
@ta = @user
@g1 = AppointmentGroup.create(:title => "test", :context => @course)
@g1 = AppointmentGroup.create(:title => "test", :contexts => [@course])
@g1.publish!
@g2 = AppointmentGroup.create(:title => "test", :context => @course)
@g3 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [@course.default_section.asset_string])
@g2 = AppointmentGroup.create(:title => "test", :contexts => [@course])
@g3 = AppointmentGroup.create(:title => "test", :contexts => [@course], :sub_context_codes => [@course.default_section.asset_string])
@g3.publish!
@g4 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [gc.asset_string])
@g4 = AppointmentGroup.create(:title => "test", :contexts => [@course], :sub_context_codes => [gc.asset_string])
@g4.publish!
@g5 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [section2.asset_string])
@g5 = AppointmentGroup.create(:title => "test", :contexts => [@course], :sub_context_codes => [section2.asset_string])
@g5.publish!
@g6 = AppointmentGroup.create(:title => "test", :context => other_course)
@g6 = AppointmentGroup.create(:title => "test", :contexts => [other_course])
@g6.publish!
# multiple sub_contexts
@g7 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [@course.default_section.asset_string, section2.asset_string])
@g7 = AppointmentGroup.create(:title => "test", :contexts => [@course], :sub_context_codes => [@course.default_section.asset_string, section2.asset_string])
@g7.publish!
# multiple contexts
course_bak, teacher_bak = @course, @teacher
course_with_teacher(:active_all => true)
@course2, @teacher2 = @course, @teacher
course_with_teacher(:user => @teacher2, :active_all => true)
teacher_in_course(:course => @course)
@course3, @teacher3, @course, @teacher = @course, @teacher, course_bak, teacher_bak
@g8 = AppointmentGroup.create(:title => "test", :contexts => [@course2, @course3])
@g8.publish!
@groups = [@g1, @g2, @g3, @g4, @g5, @g7]
end
@ -169,6 +223,16 @@ describe AppointmentGroup do
@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
# multiple contexts
@student_in_course1 = @student
student_in_course(:course => @course2, :active_all => true)
@student_in_course2 = @user
student_in_course(:course => @course3, :active_all => true)
@student_in_course3 = @user
@g8.grants_right?(@student_in_course1, nil, :reserve).should be_false
@g8.grants_right?(@student_in_course2, nil, :reserve).should be_true
@g8.grants_right?(@student_in_course3, nil, :reserve).should be_true
end
@ -199,6 +263,11 @@ describe AppointmentGroup do
visible_groups = AppointmentGroup.manageable_by(@student).sort_by(&:id)
visible_groups.should eql []
@groups.each{ |g| g.grants_right?(@student, nil, :manage).should be_false }
# multiple contexts
@g8.grants_right?(@teacher, nil, :manage).should be_false # not in any courses
@g8.grants_right?(@teacher2, nil, :manage).should be_true
@g8.grants_right?(@teacher3, nil, :manage).should be_false # not in all courses
end
end
@ -216,7 +285,7 @@ describe AppointmentGroup do
channel.confirm
end
@ag = @course.appointment_groups.create(:title => "test", :new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']])
@ag = AppointmentGroup.create!(:title => "test", :contexts => [@course], :new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']])
end
it "should notify all participants when publishing" do
@ -244,8 +313,9 @@ describe AppointmentGroup do
@unpublished_course.enroll_user(@student, 'StudentEnrollment')
@unpublished_course.enroll_user(@teacher, 'TeacherEnrollment')
@ag = @unpublished_course.appointment_groups.create(:title => "test",
:new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']])
@ag = AppointmentGroup.create!(:title => "test",
:contexts => [@unpublished_course],
:new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']])
@ag.publish!
@ag.messages_sent.should be_empty
@ -258,7 +328,7 @@ describe AppointmentGroup do
course_with_teacher(:active_all => true)
@teacher = @user
ag = AppointmentGroup.create(:title => "test", :context => @course, :new_appointments => [['2012-01-01 17:00:00', '2012-01-01 18:00:00']])
ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :new_appointments => [['2012-01-01 17:00:00', '2012-01-01 18:00:00']])
appt = ag.appointments.first
participants = 3.times.map {
student_in_course(:course => @course, :active_all => true)
@ -279,7 +349,7 @@ describe AppointmentGroup do
before do
course_with_teacher(:active_all => true)
@teacher = @user
@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"]])
@ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :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"]])
@appointment = @ag.appointments.first
@ag.reload.available_slots.should eql 4
end
@ -354,7 +424,7 @@ describe AppointmentGroup do
@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"]])
@ag = AppointmentGroup.create!(:title => "test", :contexts => [@course], :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

View File

@ -160,10 +160,13 @@ describe CalendarEvent do
it "should return events implicitly tied to the contexts (via effective_context_string)" do
@teacher = user
@course.enroll_teacher(@teacher).accept!
g1 = @course.appointment_groups.create(:title => "foo")
course1 = @course
course_with_teacher(@teacher)
course2, @course = @course, course1
g1 = AppointmentGroup.create(:title => "foo", :contexts => [course1, course2])
g1.publish!
a1 = g1.appointments.create.reserve_for(@student, @student)
g2 = @course.appointment_groups.create(:title => "foo", :sub_context_codes => [@course.default_section.asset_string])
g2 = AppointmentGroup.create(:title => "foo", :contexts => [@course], :sub_context_codes => [@course.default_section.asset_string])
g2.publish!
a2 = g2.appointments.create.reserve_for(@student, @student)
pe = @course.calendar_events.create!
@ -258,7 +261,7 @@ describe CalendarEvent do
@student1 = @user
@other_section = @course.course_sections.create!
@other_course = Course.create!
@ag = AppointmentGroup.create(:title => "test", :context => @course)
@ag = AppointmentGroup.create(:title => "test", :contexts => [@course])
@ag.publish!
@appointment = @ag.appointments.create(:start_at => '2012-01-01 12:00:00', :end_at => '2012-01-01 13:00:00')
end
@ -280,7 +283,7 @@ describe CalendarEvent do
@group = c1.groups.create(:context => @course)
@group.users << @student1 << @student2
@ag2 = AppointmentGroup.create!(:title => "test", :context => @course, :sub_context_codes => [c1.asset_string])
@ag2 = AppointmentGroup.create!(:title => "test", :contexts => [@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')
@ -337,7 +340,7 @@ describe CalendarEvent do
end
it "should allow multiple participants in an appointment, up to the limit" do
ag = AppointmentGroup.create(:title => "test", :context => @course, :participants_per_appointment => 2,
ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :participants_per_appointment => 2,
:new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']]
)
ag.publish!
@ -356,7 +359,7 @@ describe CalendarEvent do
it "should give preference to the calendar's appointment limit" do
ag = AppointmentGroup.create!(
:title => "testing...",
:context => @course,
:contexts => [@course],
:participants_per_appointment => 2,
:new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']]
)
@ -384,7 +387,7 @@ describe CalendarEvent do
it "should revert to the appointment group's participant_limit when appropriate" do
ag = AppointmentGroup.create!(
:title => "testing...",
:context => @course,
:contexts => [@course],
:participants_per_appointment => 2,
:new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']]
)
@ -403,7 +406,7 @@ describe CalendarEvent do
end
it "should not let participants exceed max_appointments_per_participant" do
ag = AppointmentGroup.create(:title => "test", :context => @course, :max_appointments_per_participant => 1,
ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :max_appointments_per_participant => 1,
:new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00'], ['2012-01-01 13:00:00', '2012-01-01 14:00:00']]
)
ag.publish!
@ -415,7 +418,7 @@ describe CalendarEvent do
end
it "should cancel existing reservations if cancel_existing = true" do
ag = AppointmentGroup.create(:title => "test", :context => @course, :max_appointments_per_participant => 1,
ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :max_appointments_per_participant => 1,
:new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00'], ['2012-01-01 13:00:00', '2012-01-01 14:00:00']]
)
ag.publish!
@ -428,7 +431,7 @@ describe CalendarEvent do
end
it "should enforce the section" do
ag = AppointmentGroup.create(:title => "test", :context => @course.course_sections.create,
ag = AppointmentGroup.create(:title => "test", :contexts => [@course.course_sections.create],
:new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']]
)
ag.publish!
@ -445,7 +448,7 @@ describe CalendarEvent do
c2 = @course.group_categories.create
g2 = c2.groups.create(:context => @course)
ag = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [c1.asset_string],
ag = AppointmentGroup.create(:title => "test", :contexts => [@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
@ -479,7 +482,7 @@ describe CalendarEvent do
end
it "should unlock the appointment when the last reservation is canceled" do
ag = AppointmentGroup.create(:title => "test", :context => @course, :participants_per_appointment => 2,
ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :participants_per_appointment => 2,
:new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']]
)
appointment = ag.appointments.first
@ -497,7 +500,7 @@ describe CalendarEvent do
end
it "should copy the group attributes to the initial appointments" do
ag = AppointmentGroup.create(:title => "test", :context => @course, :description => "hello\nworld",
ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :description => "hello\nworld",
:new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']]
)
e = ag.appointments.first
@ -524,7 +527,7 @@ describe CalendarEvent do
end
it "should copy the group attributes to subsequent appointments" do
ag = AppointmentGroup.create(:title => "test", :context => @course)
ag = AppointmentGroup.create(:title => "test", :contexts => [@course])
ag.update_attributes(
:title => 'haha',
:new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']]
@ -540,7 +543,7 @@ describe CalendarEvent do
end
it "should allow a user to re-reserve a slot after canceling" do
ag = AppointmentGroup.create(:title => "test", :context => @course, :participants_per_appointment => 1,
ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :participants_per_appointment => 1,
:new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']]
)
appointment = ag.appointments.first

View File

@ -319,7 +319,7 @@ describe Course do
event2.updating_user = @teacher
event2.save!
event3 = event2.child_events.first
appointment_group = @course.appointment_groups.create
appointment_group = AppointmentGroup.create! :title => "ag", :contexts => [@course]
appointment_group.publish!
assignment = @course.assignments.create!
@ -2425,7 +2425,7 @@ describe Course, "conclusions" do
context "appointment cancelation" do
before do
course_with_student(:active_all => true)
@ag = @course.appointment_groups.create(:title => "test", :new_appointments => [['2010-01-01 13:00:00', '2010-01-01 14:00:00'], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]])
@ag = AppointmentGroup.create!(:title => "test", :contexts => [@course], :new_appointments => [['2010-01-01 13:00:00', '2010-01-01 14:00:00'], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]])
@ag.appointments.each do |a|
a.reserve_for(@user, @user)
end

View File

@ -1460,7 +1460,7 @@ describe User do
describe "calendar_events_for_calendar" do
it "should include own scheduled appointments" do
course_with_student(:active_all => true)
ag = @course.appointment_groups.create(:title => 'test appointment', :new_appointments => [[Time.now, Time.now + 1.hour], [Time.now + 1.hour, Time.now + 2.hour]])
ag = AppointmentGroup.create!(:title => 'test appointment', :contexts => [@course], :new_appointments => [[Time.now, Time.now + 1.hour], [Time.now + 1.hour, Time.now + 2.hour]])
ag.appointments.first.reserve_for(@user, @user)
events = @user.calendar_events_for_calendar
events.size.should eql 1
@ -1470,7 +1470,7 @@ describe User do
it "should include manageable appointments" do
course(:active_all => true)
@user = @course.instructors.first
ag = @course.appointment_groups.create(:title => 'test appointment', :new_appointments => [[Time.now, Time.now + 1.hour]])
ag = AppointmentGroup.create!(:title => 'test appointment', :contexts => [@course], :new_appointments => [[Time.now, Time.now + 1.hour]])
events = @user.calendar_events_for_calendar
events.size.should eql 1
events.first.title.should eql 'test appointment'
@ -1481,7 +1481,7 @@ describe User do
it "should include manageable appointment groups" do
course(:active_all => true)
@user = @course.instructors.first
ag = @course.appointment_groups.create(:title => 'test appointment', :new_appointments => [[Time.now, Time.now + 1.hour]])
ag = AppointmentGroup.create!(:title => 'test appointment', :contexts => [@course], :new_appointments => [[Time.now, Time.now + 1.hour]])
events = @user.upcoming_events
events.size.should eql 1
events.first.title.should eql 'test appointment'

View File

@ -9,12 +9,12 @@ shared_examples_for "calendar2 selenium tests" do
tomorrow = Date.today.to_s
default_params = {
:title => "new appointment group",
:context => @course,
:contexts => [@course],
:new_appointments => [
[tomorrow + ' 12:00:00', tomorrow + ' 13:00:00'],
]
}
ag = @course.appointment_groups.create!(default_params.merge(params))
ag = AppointmentGroup.create!(default_params.merge(params))
ag.publish!
ag.title
end

View File

@ -331,6 +331,16 @@ describe "dashboard" do
assignment_menu.should include_text(assignment.title)
end
it "should display appointment groups in todo list" do
ag = AppointmentGroup.create! :title => "appointment group",
:contexts => [@course],
:new_appointments => [[Time.now.utc + 2.hour, Time.now.utc + 3.hour]]
student_in_course(:course => @course, :active_all => true)
ag.appointments.first.reserve_for(@student, @student)
get "/"
f('#right-side .events_list').text.should include 'appointment group'
end
it "should show submitted essay quizzes in the todo list" do
quiz_title = 'new quiz'
student_in_course

View File

@ -9,9 +9,11 @@ describe "scheduler" do
def fill_out_appointment_group_form(new_appointment_text)
driver.find_element(:css, '.create_link').click
edit_form = driver.find_element(:id, 'edit_appointment_form')
edit_form = f('#edit_appointment_form')
keep_trying_until { edit_form.should be_displayed }
replace_content(find_with_jquery('input[name="title"]'), new_appointment_text)
f('.ag_contexts_selector').click
f('[name="context_codes[]"]').click
date_field = edit_form.find_element(:css, '.date_field')
date_field.click
wait_for_animations
@ -300,7 +302,7 @@ describe "scheduler" do
end
it "should allow me to override the participant limit on a slot-by-slot basis" do
create_appointment_group :participants_per_appointment => 2, :context => @course
create_appointment_group :participants_per_appointment => 2
get "/calendar2"
wait_for_ajaximations
click_scheduler_link
@ -325,6 +327,36 @@ describe "scheduler" do
ag.appointments.first.participants_per_appointment.should be_nil
end
it "should allow me to create a course with multiple contexts" do
course1 = @course
course_with_teacher(:user => @teacher, :active_all => true)
get "/calendar2"
click_scheduler_link
fill_out_appointment_group_form('multiple contexts')
course_box = f("[value=#{@course.asset_string}]")
course_box.click
ff('.ag_sections_toggle').last.click
# sections should get checked by their parent
section_box = f("[value=#{@course.course_sections.first.asset_string}]")
section_box[:checked].should be_true
# unchecking all sections should uncheck their parent
section_box.click
course_box[:checked].should be_false
# checking all sections should check parent
section_box.click
course_box[:checked].should be_true
driver.find_element(:css, '.ui-dialog-buttonset .ui-button-primary').click
wait_for_ajaximations
ag = AppointmentGroup.first
ag.contexts.should include course1
ag.contexts.should include @course
ag.sub_contexts.should eql []
end
end
context "as a student" do
@ -339,6 +371,22 @@ describe "scheduler" do
wait_for_ajax_requests
end
it "should let me reserve appointment groups for contexts I am in" do
my_course = @course
course_with_student(:active_all => true)
other_course = @course
create_appointment_group(:contexts => [other_course, my_course])
get "/calendar2"
click_scheduler_link
wait_for_ajaximations
click_appointment_link
reserve_appointment_manual(0)
driver.find_element(:css, '.fc-event').should include_text "Reserved"
end
it "should allow me to cancel existing reservation and sign up for the appointment group from the calendar" do
tomorrow = (Date.today + 1).to_s
create_appointment_group(:max_appointments_per_participant => 1,