add observer alert threshold CRUD

closes MBL-10122

test plan:
- do CRUD operations on the observer_alert_threshold endpoints
  /users/<id>/observer_alert_threshold[?student_id=<id>]
  and
  /users/<id>/observer_alert_threshold/<id>

Change-Id: I2c5c7700adaedd4a2068a61568217219b763339e
Reviewed-on: https://gerrit.instructure.com/149147
Reviewed-by: Matthew Sessions <msessions@instructure.com>
Product-Review: Matthew Sessions <msessions@instructure.com>
QA-Review: Matthew Sessions <msessions@instructure.com>
Tested-by: Jenkins
This commit is contained in:
Cameron Sutter 2018-05-03 14:18:23 -06:00
parent 2de0f345b2
commit a3f3ec6e81
9 changed files with 451 additions and 5 deletions

View File

@ -0,0 +1,85 @@
#
# Copyright (C) 2018 - present 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 'atom'
class ObserverAlertThresholdsApiController < ApplicationController
include Api::V1::ObserverAlertThreshold
before_action :require_user
def index
thresholds = if params[:student_id]
student_id = params[:student_id]
link = @current_user.as_observer_observation_links.active.where(student: student_id).take
return render_unauthorized_action unless link
link.observer_alert_thresholds.active
else
links = @current_user.as_observer_observation_links.active
return render_unauthorized_action unless links.count > 0
links.map { |uol| uol.observer_alert_thresholds.active }.flatten
end
render json: thresholds.map { |threshold| observer_alert_threshold_json(threshold, @current_user, session) }
end
def show
threshold = ObserverAlertThreshold.active.find(params[:observer_alert_threshold_id])
link = @current_user.as_observer_observation_links.select { |uol| uol.id == threshold.user_observation_link.id }
return render_unauthorized_action unless link.count > 0
render json: observer_alert_threshold_json(threshold, @current_user, session)
end
def create
student_id = params[:student_id]
link = UserObservationLink.where(observer_id: @current_user, user_id: student_id).take
return render_unauthorized_action unless link
attrs = create_params.merge(user_observation_link: link)
begin
threshold = link.observer_alert_thresholds.create(attrs)
render json: observer_alert_threshold_json(threshold, @current_user, session)
rescue ActiveRecord::NotNullViolation
render :json => ['missing required parameters'], :status => :bad_request
end
end
def update
threshold = ObserverAlertThreshold.active.find(params[:observer_alert_threshold_id])
link = @current_user.as_observer_observation_links.select { |uol| uol.id == threshold.user_observation_link.id }
return render_unauthorized_action unless link.count > 0
threshold.update(update_params)
render json: observer_alert_threshold_json(threshold.reload, @current_user, session)
end
def destroy
threshold = ObserverAlertThreshold.active.find(params[:observer_alert_threshold_id])
link = @current_user.as_observer_observation_links.select { |uol| uol.id == threshold.user_observation_link.id }
return render_unauthorized_action unless link.count > 0
threshold.destroy
render json: observer_alert_threshold_json(threshold, @current_user, session)
end
def create_params
params.require(:observer_alert_threshold).permit(:alert_type, :threshold)
end
def update_params
params.require(:observer_alert_threshold).permit(:threshold)
end
end

View File

@ -16,7 +16,7 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
class ObserverAlert < ActiveRecord::Base
belongs_to :user_observation_link, :inverse_of => :observer_alert
belongs_to :observer_alert_threshold
belongs_to :user_observation_link, :inverse_of => :observer_alerts
belongs_to :observer_alert_threshold, :inverse_of => :observer_alerts
belongs_to :context, polymorphic: [:announcement, :assignment, :course, :account_notification]
end

View File

@ -16,5 +16,13 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
class ObserverAlertThreshold < ActiveRecord::Base
belongs_to :user_observation_link, :inverse_of => :observer_alert_threshold
belongs_to :user_observation_link, :inverse_of => :observer_alert_thresholds
has_many :observer_alerts, :inverse_of => :observer_alert_threshold
scope :active, -> { where.not(workflow_state: 'deleted') }
def destroy
self.workflow_state = 'deleted'
self.save!
end
end

View File

@ -27,8 +27,8 @@ class UserObservationLink < ActiveRecord::Base
belongs_to :observer, :class_name => 'User', inverse_of: :as_observer_observation_links
belongs_to :root_account, :class_name => 'Account'
has_many :observer_alert_threshold, :inverse_of => :user_observation_link
has_many :observer_alert, :inverse_of => :user_observation_link
has_many :observer_alert_thresholds, :inverse_of => :user_observation_link
has_many :observer_alerts, :inverse_of => :user_observation_link
after_create :create_linked_enrollments

View File

@ -1305,6 +1305,14 @@ CanvasRails::Application.routes.draw do
put 'users/:user_id/observees/:observee_id', action: :update
delete 'users/:user_id/observees/:observee_id', action: :destroy
end
scope(controller: :observer_alert_thresholds_api) do
get 'users/:user_id/observer_alert_thresholds', action: :index
post 'users/:user_id/observer_alert_thresholds', action: :create
get 'users/:user_id/observer_alert_thresholds/:observer_alert_threshold_id', action: :show
put 'users/:user_id/observer_alert_thresholds/:observer_alert_threshold_id', action: :update
delete 'users/:user_id/observer_alert_thresholds/:observer_alert_threshold_id', action: :destroy
end
end
scope(controller: :custom_data) do

View File

@ -0,0 +1,36 @@
#
# Copyright (C) 2018 - present 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::ObserverAlertThreshold
include Api::V1::Json
include ApplicationHelper
API_ALLOWED_OUTPUT_FIELDS = {
:only => %w(
id
user_observation_link_id
alert_type
threshold
workflow_state
).freeze
}.freeze
def observer_alert_threshold_json(threshold, user, session, _opts = {})
api_json(threshold, user, session, API_ALLOWED_OUTPUT_FIELDS)
end
end

View File

@ -0,0 +1,221 @@
#
# Copyright (C) 2018 - present 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_relative '../api_spec_helper'
describe ObserverAlertThresholdsApiController, type: :request do
include Api
include Api::V1::ObserverAlertThreshold
context '#index' do
before :once do
observer_alert_threshold_model(active_all: true, alert_type: 'missing_assignment')
@path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds?student_id=#{@observee.id}"
@params = {user_id: @observer.to_param, student_id: @observee.to_param,
controller: 'observer_alert_thresholds_api', action: 'index', format: 'json'}
end
describe 'with student_id' do
it 'returns the thresholds' do
json = api_call_as_user(@observer, :get, @path, @params)
expect(json.length).to eq 1
expect(json[0]['user_observation_link_id']).to eq @observation_link.id
expect(json[0]['alert_type']).to eq 'missing_assignment'
end
it 'only returns active thresholds' do
to_destroy = @observation_link.observer_alert_thresholds.create(alert_type: 'assignment_grade_low')
to_destroy.destroy!
json = api_call_as_user(@observer, :get, @path, @params)
expect(json.length).to eq 1
thresholds = @observation_link.observer_alert_thresholds.reload
expect(thresholds.count).to eq 2
end
it 'errors without proper user_observation_link' do
user = user_model
path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds?student_id=#{user.id}"
params = {user_id: @observer.to_param, student_id: user.to_param,
controller: 'observer_alert_thresholds_api', action: 'index', format: 'json'}
api_call_as_user(@observer, :get, path, params)
expect(response.code).to eq "401"
end
end
describe 'without student_id' do
it 'returns the thresholds' do
link = UserObservationLink.create(observer_id: @observer, user_id: user_model)
link.observer_alert_thresholds.create(alert_type: 'assignment_grade_high')
path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds"
params = {user_id: @observer.to_param, controller: 'observer_alert_thresholds_api',
action: 'index', format: 'json'}
json = api_call_as_user(@observer, :get, path, params)
expect(json.length).to eq 2
end
it 'errors without proper user_observation_link' do
user = user_model
path = "/api/v1/users/#{user.id}/observer_alert_thresholds"
params = {user_id: user.to_param, controller: 'observer_alert_thresholds_api',
action: 'index', format: 'json'}
api_call_as_user(user, :get, path, params)
expect(response.code).to eq "401"
end
end
end
context '#show' do
before :once do
observer_alert_threshold_model(active_all: true, alert_type: 'missing_assignment')
@path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds/#{@observer_alert_threshold.id}"
@params = {user_id: @observer.to_param, observer_alert_threshold_id: @observer_alert_threshold.to_param,
controller: 'observer_alert_thresholds_api', action: 'show', format: 'json'}
end
it 'returns the threshold' do
json = api_call_as_user(@observer, :get, @path, @params)
expect(json['id']).to eq @observer_alert_threshold.id
expect(json['user_observation_link_id']).to eq @observation_link.id
expect(json['alert_type']).to eq 'missing_assignment'
end
it 'errors without proper user_observation_link' do
user = user_model
path = "/api/v1/users/#{user.id}/observer_alert_thresholds/#{@observer_alert_threshold.id}"
params = {user_id: user.to_param, observer_alert_threshold_id: @observer_alert_threshold.to_param,
controller: 'observer_alert_thresholds_api', action: 'show', format: 'json'}
api_call_as_user(user, :get, path, params)
expect(response.code).to eq "401"
end
end
context '#create' do
before :once do
@observer = user_model
@observee = user_model
@uol = UserObservationLink.create(observer_id: @observer, user_id: @observee)
@path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds"
end
it 'creates the threshold' do
create_params = {alert_type: 'assignment_grade_high', threshold: "88"}
params = {user_id: @observer.to_param, student_id: @uol.user_id, observer_alert_threshold: create_params,
controller: 'observer_alert_thresholds_api', action: 'create', format: 'json'}
json = api_call_as_user(@observer, :post, @path, params)
expect(json['alert_type']).to eq 'assignment_grade_high'
expect(json['user_observation_link_id']).to eq @uol.id
expect(json['threshold']).to eq "88"
end
it 'errors with bad student_id' do
create_params = {alert_type: 'assignment_grade_high', threshold: "88"}
params = {user_id: @observer.to_param, student_id: @uol.user_id + 100, observer_alert_threshold: create_params,
controller: 'observer_alert_thresholds_api', action: 'create', format: 'json'}
api_call_as_user(@observer, :post, @path, params)
expect(response.code).to eq "401"
end
it 'errors if user_observation_link doesnt belong to user' do
user = user_model
path = "/api/v1/users/#{user.id}/observer_alert_thresholds"
create_params = {alert_type: 'assignment_grade_high', threshold: "88"}
params = {user_id: user.to_param, student_id: @uol.user_id, observer_alert_threshold: create_params,
controller: 'observer_alert_thresholds_api', action: 'create', format: 'json'}
api_call_as_user(user, :post, path, params)
expect(response.code).to eq "401"
end
it 'errors without required params' do
create_params = {threshold: "88"}
params = {user_id: @observer.to_param, student_id: @uol.user_id, observer_alert_threshold: create_params,
controller: 'observer_alert_thresholds_api', action: 'create', format: 'json'}
api_call_as_user(@observer, :post, @path, params)
expect(response.code).to eq "400"
end
it 'ignores improper params' do
create_params = {something_sneaky: 'sneaky!', alert_type: 'assignment_grade_high', threshold: "88"}
params = {user_id: @observer.to_param, student_id: @uol.user_id, observer_alert_threshold: create_params,
controller: 'observer_alert_thresholds_api', action: 'create', format: 'json'}
json = api_call_as_user(@observer, :post, @path, params)
expect(response.code).to eq "200"
expect(json['something_sneaky']).to eq nil
end
end
context '#update' do
before :once do
observer_alert_threshold_model(active_all: true, alert_type: 'assignment_grade_low', threshold: "88")
@path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds/#{@observer_alert_threshold.id}"
@params = {user_id: @observer.to_param, observer_alert_threshold_id: @observer_alert_threshold.to_param,
controller: 'observer_alert_thresholds_api', action: 'update', format: 'json'}
end
it 'updates the threshold' do
update_params = {threshold: "50", alert_type: "assignment_missing",
user_observation_link_id: @observation_link.id + 100}
params = @params.merge({observer_alert_threshold: update_params})
json = api_call_as_user(@observer, :put, @path, params)
expect(json['alert_type']).to eq 'assignment_grade_low'
expect(json['threshold']).to eq "50"
expect(json['user_observation_link_id']).to eq @observation_link.id
end
it 'errors without proper user_observation_link' do
user = user_model
path = "/api/v1/users/#{user.id}/observer_alert_thresholds/#{@observer_alert_threshold.id}"
params = @params.merge({user_id: user.to_param, observer_alert_threshold: {threshold: "50"}})
api_call_as_user(user, :put, path, params)
expect(response.code).to eq "401"
end
end
context '#destroy' do
before :once do
observer_alert_threshold_model(active_all: true, alert_type: 'assignment_grade_low', threshold: "88")
@path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds/#{@observer_alert_threshold.id}"
@params = {user_id: @observer.to_param, observer_alert_threshold_id: @observer_alert_threshold.to_param,
controller: 'observer_alert_thresholds_api', action: 'destroy', format: 'json'}
end
it 'destroys the threshold' do
json = api_call_as_user(@observer, :delete, @path, @params)
expect(json['id']).to eq @observer_alert_threshold.id
thresholds = @observation_link.observer_alert_thresholds.active.reload
expect(thresholds.count).to eq 0
end
it 'errors without proper user_observation_link' do
user = user_model
path = "/api/v1/users/#{user.id}/observer_alert_thresholds/#{@observer_alert_threshold.id}"
params = @params.merge({user_id: user.to_param})
api_call_as_user(user, :delete, path, params)
expect(response.code).to eq "401"
end
end
end

View File

@ -0,0 +1,40 @@
#
# Copyright (C) 2018 - present 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 Factories
def observer_alert_threshold_model(opts = {})
@observee = opts[:observee] || course_with_student(opts).user
@observer = opts[:observer] || user_model
@observation_link = UserObservationLink.create!(user_id: @observee, observer_id: @observer)
attrs = default_attrs.merge(opts.slice(*valid_attrs_list))
@observer_alert_threshold = @observation_link.observer_alert_thresholds.create(attrs)
end
def default_attrs
{
alert_type: 'value for type',
threshold: nil,
workflow_state: 'active',
}
end
def valid_attrs_list
[:alert_type, :threshold, :workflow_state]
end
end

View File

@ -0,0 +1,48 @@
#
# Copyright (C) 2018 - present 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_relative '../../../spec_helper.rb'
class ObserverAlertThresholdApiHarness
include Api::V1::ObserverAlertThreshold
def value_to_boolean(value)
Canvas::Plugin.value_to_boolean(value)
end
def session
Object.new
end
end
describe "Api::V1::ObserverAlertThreshold" do
subject(:api) { ObserverAlertThresholdApiHarness.new }
let(:observer_alert_threshold) { observer_alert_threshold_model(active_all: true) }
describe "#observer_alert_threshold_json" do
let(:user) { user_model }
let(:session) { Object.new }
it "returns json" do
json = api.observer_alert_threshold_json(observer_alert_threshold, user, session)
expect(json['alert_type']).to eq('value for type')
expect(json['workflow_state']).to eq('active')
expect(json['user_observation_link_id']).to eq @observation_link.id
end
end
end