add api endpoint to post global annoucement

***test plan
  1.  Make a POST like /accounts/1/account_notifications
  2.  Required Parameters:
      1) account_notification[subject]
      2) account_notification[start_at]
      3) account_notification[end_at]
      4) account_notification[message]
  3.  Optional Parameters:
      1) account_notification[icon]
        'warning' | 'information' | 'question' | 'error' | 'calendar'
         note: defaults to warning
      2) account_notification_roles[]
        'StudentEnrollment', 'TeacherEnrollment' etc...
        this defaults to all roles
  4.  Only account admins should be able to make the api call
  5.  Should not be able to create an account notification with an
  end_at < start_at

closes:  PS-1730
closes: PS-1872

Change-Id: Ide86722598ae4a7ab565422f2996015b48cf8910
Reviewed-on: https://gerrit.instructure.com/37477
Tested-by: Jenkins <jenkins@instructure.com>
QA-Review: Clare Strong <clare@instructure.com>
Product-Review: Matt Fairbourn <mfairbourn@instructure.com>
Reviewed-by: Brandon Broschinsky <brandonbr@instructure.com>
This commit is contained in:
dave 2014-07-09 13:16:18 -06:00 committed by Dave Jungst
parent d7ed7a5911
commit 7e6519d586
8 changed files with 320 additions and 21 deletions

View File

@ -1,6 +1,117 @@
#
# Copyright (C) 2011 - 2014 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 Account Notifications
#
# API for account notifications.
# @model AccountNotificaion
# {
# "id": "AccountNotification",
# "description": "",
# "properties": {
# "subject": {
# "description": "The subject of the notifications",
# "example": "Attention Students",
# "type": "string"
# },
# "message": {
# "description": "The message to be sent in the notification.",
# "example": "This is a test of the notification system.",
# "type": "string"
# },
# "start_at": {
# "description": "When to send out the notification.",
# "example": "2013-08-28T23:59:00-06:00",
# "type": "datetime"
# },
# "end_at": {
# "description": "When to expire the notification.",
# "example": "2013-08-29T23:59:00-06:00",
# "type": "datetime"
# },
# "icon": {
# "description": "The icon to display with the message. Defaults to warning.",
# "example": "[\"information\"]",
# "type": "array",
# "items": {"type": "string"},
# "allowableValues": {
# "values": [
# "warning",
# "information",
# "question",
# "error",
# "calendar"
# ]
# }
# },
# "roles": {
# "description": "The roles to send the notification to. If roles is not passed it defaults to all roles",
# "example": "[\"StudentEnrollment\"]",
# "type": "array",
# "items": {"type": "string"}
# }
# }
# }
class AccountNotificationsController < ApplicationController
include Api::V1::AccountNotifications
before_filter :require_account_admin
# @API Create a global notification
# Create and return a new global notification for an account.
#
# @argument account_notification[subject] [String]
# The subject of the notification.
#
# @argument account_notification[message] [String]
# The message body of the notification.
#
# @argument account_notification[start_at] [DateTime]
# The start date and time of the notification in ISO8601 format.
# e.g. 2014-01-01T01:00Z
#
# @argument account_notification[end_at] [DateTime]
# The end date and time of the notification in ISO8601 format.
# e.g. 2014-01-01T01:00Z
#
# @argument account_notification[icon] [Optional, "warning"|"information"|"question"|"error"|"calendar"]
# The icon to display with the notification.
# Note: Defaults to warning.
#
# @argument account_notification_roles[] [Optional, String]
# The role(s) to send global notification to. Note: ommitting this field will send to everyone
# Example:
# account_notification_roles: ["StudentEnrollment", "TeacherEnrollment"]
#
# @example_request
# curl -X POST -H 'Authorization: Bearer <token>' \
# https://<canvas>/api/v1/accounts/2/account_notifications \
# -d 'account_notification[subject]=New notification' \
# -d 'account_notification[start_at]=2014-01-01T00:00:00Z' \
# -d 'account_notification[end_at]=2014-02-01T00:00:00Z' \
# -d 'account_notification[message]=This is a global notification'
#
# @example_response
# {
# "subject": "New notification",
# "start_at": "2014-01-01T00:00:00Z",
# "end_at": "2014-02-01T00:00:00Z",
# "message": "This is a global notification"
# }
def create
@notification = AccountNotification.new(params[:account_notification])
@notification.account = @account
@ -13,17 +124,21 @@ class AccountNotificationsController < ApplicationController
end
respond_to do |format|
if @notification.save
flash[:notice] = t(:announcement_created_notice, "Announcement successfully created")
format.html { redirect_to account_settings_path(@account, :anchor => 'tab-announcements') }
format.json { render :json => @notification }
if api_request?
format.json { render :json => account_notifications_json(@notification, @current_user, session) }
else
flash[:notice] = t(:announcement_created_notice, "Announcement successfully created")
format.html { redirect_to account_settings_path(@account, :anchor => 'tab-announcements') }
format.json { render :json => @notification }
end
else
flash[:error] = t(:announcement_creation_failed_notice, "Announcement creation failed")
format.html { redirect_to account_settings_path(@account, :anchor => 'tab-announcements') }
format.html { redirect_to account_settings_path(@account, :anchor => 'tab-announcements') } unless api_request?
format.json { render :json => @notification.errors, :status => :bad_request }
end
end
end
def destroy
@notification = @account.announcements.find(params[:id])
@notification.destroy
@ -33,7 +148,7 @@ class AccountNotificationsController < ApplicationController
format.json { render :json => @notification }
end
end
protected
def require_account_admin
get_context
@ -44,4 +159,5 @@ class AccountNotificationsController < ApplicationController
end
return false unless authorized_action(@account, @current_user, :manage_alerts)
end
end

View File

@ -10,8 +10,8 @@ class AccountNotification < ActiveRecord::Base
EXPORTABLE_ASSOCIATIONS = [:account, :user, :account_notification_roles]
validates_presence_of :start_at, :end_at, :account_id
before_validation :infer_defaults
validates_presence_of :start_at, :end_at, :subject, :message, :account_id
validate :validate_dates
belongs_to :account, :touch => true
belongs_to :user
has_many :account_notification_roles, dependent: :destroy
@ -23,10 +23,10 @@ class AccountNotification < ActiveRecord::Base
validates_inclusion_of :months_in_display_cycle, in: 1..48, allow_nil: true
def infer_defaults
self.start_at ||= Time.now.utc
self.end_at ||= self.start_at + 2.weeks
self.end_at = [self.end_at, self.start_at].max
def validate_dates
if self.start_at && self.end_at
errors.add(:end_at, t('errors.invalid_account_notification_end_at', "Account notification end time precedes start time")) if self.end_at < self.start_at
end
end
def self.for_user_and_account(user, account)

View File

@ -846,6 +846,10 @@ routes.draw do
get "courses/:course_id/content_list", :controller => :content_exports_api, :action => :content_list, :path_name => "course_content_list"
end
scope(:controller => :account_notifications) do
post 'accounts/:account_id/account_notifications', :action => :create, :path_name => 'account_notification'
end
scope(:controller => :tabs) do
get "courses/:course_id/tabs", :action => :index, :path_name => 'course_tabs'
get "groups/:group_id/tabs", :action => :index, :path_name => 'group_tabs'

View File

@ -0,0 +1,30 @@
#
# Copyright (C) 2011 - 2014 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/>.
#
module Api::V1::AccountNotifications
include Api::V1::Json
def account_notifications_json(account_notification, user, session)
roles = account_notification.account_notification_roles.map { |r| r.role_type }
json = api_json(account_notification, user, session, :only => %w(subject start_at end_at icon account_notification_roles message))
json['roles'] = roles
json
end
end

View File

@ -0,0 +1,133 @@
#
# Copyright (C) 2012 - 2013 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/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../api_spec_helper')
describe 'Account Notification API', type: :request do
include Api
include Api::V1::AccountNotifications
before do
@admin = account_admin_user
user_with_pseudonym(:user => @admin)
end
describe 'create' do
before :each do
@path = "/api/v1/accounts/#{@admin.account.id}/account_notifications"
@api_params = { :controller => 'account_notifications',
:action => 'create',
:format => 'json',
:account_id => @admin.account.id.to_s }
@start_at = DateTime.now.utc
@end_at = (DateTime.now + 1.day).utc
end
it 'should create an account notification' do
json = api_call(:post, @path, @api_params,
{ :account_notification => {
:subject => 'New global notification',
:start_at => @start_at.iso8601,
:end_at => @end_at.iso8601,
:message => 'This is a notification',
:icon => 'information'}})
json.keys.should include 'start_at'
json.keys.should include 'end_at'
json['subject'].should == 'New global notification'
json['message'].should == 'This is a notification'
json['icon'].should == 'information'
json['roles'].should == []
end
it 'should default icon to warning' do
json = api_call(:post, @path, @api_params,
{ :account_notification => {
:subject => 'New global notification',
:start_at => @start_at.iso8601,
:end_at => @end_at.iso8601,
:message => 'This is a notification'}})
json['icon'].should == 'warning'
end
it 'should create an account notification for specific roles' do
json = api_call(:post, @path, @api_params,
{ :account_notification_roles => ['StudentEnrollment'],
:account_notification => {
:subject => 'New global notification',
:start_at => @start_at.iso8601,
:end_at => @end_at.iso8601,
:message => 'This is a notification'}})
notification = AccountNotification.last
roles = notification.account_notification_roles
roles.count.should == 1
roles.first.role_type.should == 'StudentEnrollment'
json['roles'].should == ["StudentEnrollment"]
end
it 'should return not authorized for non admin user' do
user = user_with_managed_pseudonym
begin
json = api_call_as_user(user, :post, @path, @api_params,
{ :account_notification_roles => ['StudentEnrollment'],
:account_notification => {
:subject => 'New global notification',
:start_at => @start_at.iso8601,
:end_at => @end_at.iso8601,
:message => 'This is a notification'}})
rescue => e
e.message.should include 'unauthorized'
end
end
it 'should return an error for missing required params' do
missing = ['subject', 'message', 'start_at', 'end_at']
raw_api_call(:post, @path, @api_params, { :account_notification => { :icon => 'warning'} })
response.code.should eql '400'
json = JSON.parse(response.body)
errors = json['errors'].keys
(missing - errors).should be_blank
end
it 'should return an error for malformed dates' do
raw_api_call(:post, @path, @api_params,
{ :account_notification => {
:subject => 'New global notification',
:start_at => 'asdrsldkfj',
:end_at => 'invalid_date',
:message => 'This is a notification',
:icon => 'information'}})
response.code.should eql '400'
end
it 'should not allow an end date to be before a start date' do
raw_api_call(:post, @path, @api_params,
{ :account_notification => {
:subject => 'New global notification',
:start_at => @end_at.iso8601,
:end_at => @start_at.iso8601,
:message => 'This is a notification',
:icon => 'information'}})
response.code.should eql '400'
errors = JSON.parse(response.body)
errors['errors'].keys.should include 'end_at'
end
end
end

View File

@ -108,25 +108,37 @@ describe "dashboard" do
end
it "should show account notifications on the dashboard" do
a1 = @course.account.announcements.create!(:message => "hey there")
a2 = @course.account.announcements.create!(:message => "another announcement")
a1 = @course.account.announcements.create!(:subject => 'test',
:message => "hey there",
:start_at => Date.today - 1.day,
:end_at => Date.today + 1.day)
a2 = @course.account.announcements.create!(:subject => 'test 2',
:message => "another annoucement",
:start_at => Date.today - 1.day,
:end_at => Date.today + 1.day)
get "/"
messages = ffj("#dashboard .global-message .message.user_content")
messages.size.should == 2
messages[0].text.should == a2.message
messages[1].text.should == a1.message
messages[0].text.should == a1.message
messages[1].text.should == a2.message
end
it "should interpolate the user's domain in global notifications" do
announcement = @course.account.announcements.create!(:message => "blah blah http://random-survey-startup.ly/?some_GET_parameter_by_which_to_differentiate_results={{ACCOUNT_DOMAIN}}")
announcement = @course.account.announcements.create!(:message => "blah blah http://random-survey-startup.ly/?some_GET_parameter_by_which_to_differentiate_results={{ACCOUNT_DOMAIN}}",
:subject => 'test',
:start_at => Date.today,
:end_at => Date.today + 1.day)
get "/"
fj("#dashboard .global-message .message.user_content").text.should == announcement.message.gsub("{{ACCOUNT_DOMAIN}}", @course.account.domain)
end
it "should interpolate the user's id in global notifications" do
announcement = @course.account.announcements.create!(:message => "blah blah http://random-survey-startup.ly/?surveys_are_not_really_anonymous={{CANVAS_USER_ID}}")
announcement = @course.account.announcements.create!(:message => "blah blah http://random-survey-startup.ly/?surveys_are_not_really_anonymous={{CANVAS_USER_ID}}",
:subject => 'test',
:start_at => Date.today,
:end_at => Date.today + 1.day)
get "/"
fj("#dashboard .global-message .message.user_content").text.should == announcement.message.gsub("{{CANVAS_USER_ID}}", @user.global_id.to_s)
end

View File

@ -762,9 +762,11 @@ end
req_service = opts[:required_account_service] || nil
roles = opts[:roles] || []
message = opts[:message] || "hi there"
subj = opts[:subject] || "this is a subject"
@account = opts[:account] || Account.default
@announcement = @account.announcements.build(message: message, required_account_service: req_service)
@announcement = @account.announcements.build(subject: subj, message: message, required_account_service: req_service)
@announcement.start_at = opts[:start_at] || 5.minutes.ago.utc
@announcement.end_at = opts[:end_at] || 1.day.from_now.utc
@announcement.account_notification_roles.build(roles.map { |r| {account_notification_id: @announcement.id, role_type: r} }) unless roles.empty?
@announcement.save!
end

View File

@ -43,7 +43,9 @@ describe "/users/user_dashboard" do
assigns[:topics] = []
assigns[:upcoming_events] = []
assigns[:stream_items] = []
assigns[:announcements] = [AccountNotification.create(:subject => "My Global Announcement", :account => Account.default)]
assigns[:announcements] = [AccountNotification.create(:message => 'hi', :start_at => Date.today - 1.day,
:end_at => Date.today + 2.days,
:subject => "My Global Announcement", :account => Account.default)]
render "users/user_dashboard"
response.body.should match /My Global Announcement/
end