canvas-lms/app/controllers/appointment_groups_controll...

379 lines
18 KiB
Ruby

#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
# @API Appointment Groups
#
# API for creating, accessing and updating appointment groups. Appointment groups
# provide a way of creating a bundle of time slots that users can sign up for
# (e.g. "Office Hours" or "Meet with professor about Final Project"). Both time
# slots and reservations of time slots are stored as Calendar Events.
#
# @object Appointment Group
# {
# // The ID of the appointment group
# id: 543,
#
# // The title of the appointment group
# title: "Final Presentation",
#
# // The start of the first time slot in the appointment group
# start_at: "2012-07-20T15:00:00-06:00",
#
# // The end of the last time slot in the appointment group
# end_at: "2012-07-20T17:00:00-06:00",
#
# // The text description of the appointment group
# description: "Es muy importante",
#
# // The location name of the appointment group
# location_name: "El Tigre Chino's office",
#
# // The address of the appointment group's location
# location_address: "Room 234",
#
# // The number of participant who have reserved slots
# // (see include[] argument)
# participant_count: 2,
#
# // The start and end times of slots reserved by the current user as
# // well as the id of the calendar event for the reservation (see
# // include[] argument)
# reserved_times: [{id: 987,
# start_at: "2012-07-20T15:00:00-06:00",
# start_at: "2012-07-20T15:00:00-06:00"}],
#
# // The context codes (i.e. courses) this appointment group belongs to.
# // Only people in these courses will be eligible to sign up.
# context_codes: ["course_123"],
#
# // The sub-context codes (i.e. course sections and group categories)
# // this appointment group is restricted to
# sub_context_codes: ["course_section_234"],
#
# // Current state of the appointment group ("pending", "active" or
# // "deleted"). "pending" indicates that it has not been published yet
# // and is invisible to participants.
# workflow_state: "active",
#
# // Boolean indicating whether the current user needs to sign up for
# // this appointment group (i.e. it's reservable and the
# // min_appointments_per_participant limit has not been met by this
# // user).
# requiring_action: true,
#
# // Number of time slots in this appointment group
# appointments_count: 2,
#
# // Calendar Events representing the time slots (see include[] argument)
# // Refer to the Calendar Events API for more information
# appointments: [ ... ],
#
# // Newly created time slots (same format as appointments above). Only
# // returned in Create/Update responses where new time slots have been
# // added
# new_appointments: [ ... ],
#
# // Maximum number of time slots a user may register for, or null if no
# // limit
# max_appointments_per_participant: 1,
#
# // Minimum number of time slots a user must register for. If not set,
# // users do not need to sign up for any time slots
# min_appointments_per_participant: 1,
#
# // Maximum number of participants that may register for each time slot,
# // or null if no limit
# participants_per_appointment: 1,
#
# // "private" means participants cannot see who has signed up for a
# // particular time slot, "protected" means that they can
# participant_visibility: "private",
#
# // Indicates how participants sign up for the appointment group, either
# // as individuals ("User") or in student groups ("Group"). Related to
# // sub_context_codes (i.e. "Group" signups always have a single group
# // category)
# participant_type: "User",
#
# // URL for this appointment group (to update, delete, etc.)
# url: "https://example.com/api/v1/appointment_groups/543",
#
# // URL for a user to view this appointment group
# html_url: "http://example.com/appointment_groups/1",
#
# // When the appointment group was created
# created_at: "2012-07-13T10:55:20-06:00",
#
# // When the appointment group was last updated
# updated_at: "2012-07-13T10:55:20-06:00"
# }
class AppointmentGroupsController < ApplicationController
include Api::V1::CalendarEvent
before_filter :require_user
before_filter :get_appointment_group, :only => [:show, :update, :destroy, :users, :groups]
def calendar_fragment(opts)
opts.to_json.unpack('H*')
end
private :calendar_fragment
# @API List appointment groups
#
# Retrieve the list of appointment groups that can be reserved or managed by
# the current user.
#
# @argument scope [Optional, "reservable"|"manageable"] Defaults to "reservable"
# @argument context_codes[] [Optional] Array of context codes used to limit returned results.
# @argument include_past_appointments [Optional] Boolean, defaults to false.
# If true, includes past appointment groups
# @argument include[] [Optional] Array of additional information to include.
# Allowable values include "appointments" (i.e. calendar event time slots
# for this appointment group), "child_events" (i.e. reservations of those
# time slots), "participant_count" (i.e. number of reservations), and
# "reserved_times" (i.e. the event id, start time and end time of
# reservations the current user has made)
def index
unless request.format == :json
anchor = calendar_fragment :view_name => :scheduler
return redirect_to calendar2_url(:anchor => anchor)
end
contexts = params[:context_codes] if params.include?(:context_codes)
if params[:scope] == 'manageable'
scope = AppointmentGroup.manageable_by(@current_user, contexts)
scope = scope.current_or_undated unless value_to_boolean(params[:include_past_appointments])
else
scope = AppointmentGroup.reservable_by(@current_user, contexts)
scope = scope.current unless value_to_boolean(params[:include_past_appointments])
end
groups = Api.paginate(
scope.order('id'),
self,
api_v1_appointment_groups_url(:scope => params[:scope])
)
if params[:include]
AppointmentGroup.send(:preload_associations, groups,
[{:appointments =>
[:parent_event,
{:context =>
[{:appointment_group_contexts => :context},
:appointment_group_sub_contexts]},
{:child_events =>
[:parent_event,
:context,
{:child_events =>
[:parent_event,
:context]}]}]},
{:appointment_group_contexts => :context},
:appointment_group_sub_contexts])
end
render :json => groups.map{ |group| appointment_group_json(group, @current_user, session, :include => params[:include]) }
end
# @API Create an appointment group
#
# Create and return a new appointment group. If new_appointments are
# specified, the response will return a new_appointments array (same format
# as appointments array, see "List appointment groups" action)
#
# @argument appointment_group[context_codes][] [Required] Array of context codes (courses, e.g. course_1) this group should be linked to (1 or more). Users in the course(s) with appropriate permissions will be able to sign up for this appointment group.
# @argument appointment_group[sub_context_codes][] [Optional] Array of sub context codes (course sections or a single group category) this group should be linked to. Used to limit the appointment group to particular sections. If a group category is specified, students will sign up in groups and the participant_type will be "Group" instead of "User".
# @argument appointment_group[title] [Optional] Short title for the appointment group.
# @argument appointment_group[description] [Optional] Longer text description of the appointment group.
# @argument appointment_group[location_name] [Optional] Location name of the appointment group.
# @argument appointment_group[location_address] [Optional] Location address.
# @argument appointment_group[publish] [Optional] Boolean, default false. Indicates whether this appointment group should be published (i.e. made available for signup). Once published, an appointment group cannot be unpublished.
# @argument appointment_group[participants_per_appointment] [Optional] Maximum number of participants that may register for each time slot. Defaults to null (no limit).
# @argument appointment_group[min_appointments_per_participant] [Optional] Minimum number of time slots a user must register for. If not set, users do not need to sign up for any time slots.
# @argument appointment_group[max_appointments_per_participant] [Optional] Maximum number of time slots a user may register for.
# @argument appointment_group[new_appointments][X][] [Optional] Nested array of start time/end time pairs indicating time slots for this appointment group. Refer to the example request.
# @argument appointment_group[participant_visibility] [Optional, "private"|"protected"] "private" means participants cannot see who has signed up for a particular time slot, "protected" means that they can. Defaults to "private".
#
# @example_request
#
# curl 'http://<canvas>/api/v1/appointment_groups.json' \
# -X POST \
# -F 'appointment_group[context_codes][]=course_123' \
# -F 'appointment_group[sub_context_codes][]=course_section_234' \
# -F 'appointment_group[title]=Final Presentation' \
# -F 'appointment_group[participants_per_appointment]=1' \
# -F 'appointment_group[min_appointments_per_participant]=1' \
# -F 'appointment_group[max_appointments_per_participant]=1' \
# -F 'appointment_group[new_appointments][0][]=2012-07-19T21:00:00Z' \
# -F 'appointment_group[new_appointments][0][]=2012-07-19T22:00:00Z' \
# -F 'appointment_group[new_appointments][1][]=2012-07-19T22:00:00Z' \
# -F 'appointment_group[new_appointments][1][]=2012-07-19T23:00:00Z' \
# -H "Authorization: Bearer <token>"
def create
contexts = get_contexts
raise ActiveRecord::RecordNotFound unless contexts.present?
publish = value_to_boolean(params[:appointment_group].delete(:publish))
params[:appointment_group][:contexts] = contexts
@group = AppointmentGroup.new(params[:appointment_group])
@group.update_contexts_and_sub_contexts
if authorized_action(@group, @current_user, :manage)
if @group.save
@group.publish! if publish
render :json => appointment_group_json(@group, @current_user, session), :status => :created
else
render :json => @group.errors.to_json, :status => :bad_request
end
end
end
# @API Get a single appointment group
#
# Returns information for a single appointment group
#
# @argument include[] [Optional] Array of additional information to include.
# Allowable values include "child_events" (i.e. reservations of time slots
# time slots). "appointments" will always be returned (see include[]
# argument of "List appointment groups" action).
def show
if authorized_action(@group, @current_user, :read)
unless request.format == :json
anchor = calendar_fragment :view_name => :scheduler, :appointment_group_id => @group.id
return redirect_to calendar2_url(:anchor => anchor)
end
render :json => appointment_group_json(@group, @current_user, session, :include => ((params[:include] || []) | ['appointments']))
end
end
# @API Update an appointment group
#
# Update and return an appointment group. If new_appointments are specified,
# the response will return a new_appointments array (same format as
# appointments array, see "List appointment groups" action).
#
# @argument appointment_group[context_codes][] [Optional] Array of context codes to add to this appointment group (existing ones cannot be removed).
# @argument appointment_group[sub_context_codes][] [Optional] Array of sub context codes to add to this appointment group (existing ones cannot be removed).
# @argument appointment_group[title] [Optional] Short title for the appointment group.
# @argument appointment_group[description] [Optional] Longer text description of the appointment group.
# @argument appointment_group[location_name] [Optional] Location name of the appointment group.
# @argument appointment_group[location_address] [Optional] Location address.
# @argument appointment_group[publish] [Optional] Boolean, default false. Indicates whether this appointment group should be published (i.e. made available for signup). Once published, an appointment group cannot be unpublished.
# @argument appointment_group[participants_per_appointment] [Optional] Maximum number of participants that may register for each time slot. Defaults to null (no limit). Changes will not affect existing reservations.
# @argument appointment_group[min_appointments_per_participant] [Optional] Minimum number of time slots a user must register for. If not set, users do not need to sign up for any time slots. Changes will not affect existing reservations.
# @argument appointment_group[max_appointments_per_participant] [Optional] Maximum number of time slots a user may register for. Changes will not affect existing reservations.
# @argument appointment_group[new_appointments][X][] [Optional] Nested array of new start time/end time pairs indicating time slots for this appointment group. Refer to the example request. To remove existing time slots or reservations, use the Calendar Event API.
# @argument appointment_group[participant_visibility] [Optional, "private"|"protected"] "private" means participants cannot see who has signed up for a particular time slot, "protected" means that they can.
#
# @example_request
#
# curl 'http://<canvas>/api/v1/appointment_groups/543.json' \
# -X PUT \
# -F 'appointment_group[publish]=1' \
# -H "Authorization: Bearer <token>"
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])
@group.publish! if publish
render :json => appointment_group_json(@group, @current_user, session)
else
render :json => @group.errors.to_json, :status => :bad_request
end
end
end
# @API Delete an appointment group
#
# Delete an appointment group (and associated time slots and reservations)
# and return the deleted group
#
# @argument cancel_reason [Optional] Reason for deleting/canceling the
# appointment group.
#
# @example_request
#
# curl 'http://<canvas>/api/v1/appointment_groups/543.json' \
# -X DELETE \
# -F 'cancel_reason=El Tigre Chino got fired' \
# -H "Authorization: Bearer <token>"
def destroy
if authorized_action(@group, @current_user, :delete)
@group.cancel_reason = params[:cancel_reason]
if @group.destroy
render :json => appointment_group_json(@group, @current_user, session)
else
render :json => @group.errors.to_json, :status => :bad_request
end
end
end
# @API List user participants
#
# List users that are (or may be) participating in this appointment group.
# Refer to the Users API for the response fields. Returns no results for
# appointment groups with the "Group" participant_type.
#
# @argument registration_status [Optional, "all"|"registered"|"registered"]
# Limits results to the a given participation status, defaults to "all"
def users
participants('User'){ |u| user_json(u, @current_user, session) }
end
# @API List student group participants
#
# List student groups that are (or may be) participating in this appointment
# group. Refer to the Groups API for the response fields. Returns no results
# for appointment groups with the "User" participant_type.
#
# @argument registration_status [Optional, "all"|"registered"|"registered"]
# Limits results to the a given participation status, defaults to "all"
def groups
participants('Group'){ |g| group_json(g, @current_user, session) }
end
protected
def participants(type, &formatter)
if authorized_action(@group, @current_user, :read)
return render :json => [] unless @group.participant_type == type
render :json => Api.paginate(
@group.possible_participants(params[:registration_status]),
self,
send("api_v1_appointment_group_#{params[:action]}_url", @group)
).map(&formatter)
end
end
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.contexts_for_user(@current_user).first # FIXME?
end
end