2012-08-07 04:43:30 +08:00
#
2017-04-28 03:39:07 +08:00
# Copyright (C) 2012 - present Instructure, Inc.
2012-08-07 04:43:30 +08:00
#
# 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 Outcome Groups
#
# API for accessing learning outcome group information.
2013-01-01 04:16:05 +08:00
#
# Learning outcome groups organize outcomes within a context (or in the global
# "context" for global outcomes). Every outcome is created in a particular
# context (that context then becomes its "owning context") but may be linked
# multiple times in one or more related contexts. This allows different
# accounts or courses to organize commonly defined outcomes in ways appropriate
# to their pedagogy, including having the same outcome discoverable at
# different locations in the organizational hierarchy.
#
# While an outcome can be linked into a context (such as a course) multiple
# times, it may only be linked into a particular group once.
#
2014-02-12 07:22:21 +08:00
# @model OutcomeGroup
2013-01-01 04:16:05 +08:00
# {
2014-02-12 07:22:21 +08:00
# "id": "OutcomeGroup",
# "description": "",
# "properties": {
# "id": {
# "description": "the ID of the outcome group",
# "example": 1,
# "type": "integer"
# },
# "url": {
# "description": "the URL for fetching/updating the outcome group. should be treated as opaque",
# "example": "/api/v1/accounts/1/outcome_groups/1",
# "type": "string"
# },
# "parent_outcome_group": {
# "description": "an abbreviated OutcomeGroup object representing the parent group of this outcome group, if any. omitted in the abbreviated form.",
# "$ref": "OutcomeGroup"
# },
# "context_id": {
# "description": "the context owning the outcome group. may be null for global outcome groups. omitted in the abbreviated form.",
# "example": 1,
# "type": "integer"
# },
# "context_type": {
# "example": "Account",
# "type": "string"
# },
# "title": {
# "description": "title of the outcome group",
# "example": "Outcome group title",
# "type": "string"
# },
# "description": {
# "description": "description of the outcome group. omitted in the abbreviated form.",
# "example": "Outcome group description",
# "type": "string"
# },
# "vendor_guid": {
# "description": "A custom GUID for the learning standard.",
# "example": "customid9000",
# "type": "string"
# },
# "subgroups_url": {
# "description": "the URL for listing/creating subgroups under the outcome group. should be treated as opaque",
# "example": "/api/v1/accounts/1/outcome_groups/1/subgroups",
# "type": "string"
# },
# "outcomes_url": {
# "description": "the URL for listing/creating outcome links under the outcome group. should be treated as opaque",
# "example": "/api/v1/accounts/1/outcome_groups/1/outcomes",
# "type": "string"
# },
# "import_url": {
# "description": "the URL for importing another group into this outcome group. should be treated as opaque. omitted in the abbreviated form.",
# "example": "/api/v1/accounts/1/outcome_groups/1/import",
# "type": "string"
# },
# "can_edit": {
# "description": "whether the current user can update the outcome group",
# "example": true,
# "type": "boolean"
# }
# }
2013-01-01 04:16:05 +08:00
# }
#
2014-02-12 07:22:21 +08:00
# @model OutcomeLink
2013-01-01 04:16:05 +08:00
# {
2014-02-12 07:22:21 +08:00
# "id": "OutcomeLink",
# "description": "",
# "properties": {
# "url": {
# "description": "the URL for fetching/updating the outcome link. should be treated as opaque",
2014-06-19 00:16:10 +08:00
# "example": "/api/v1/accounts/1/outcome_groups/1/outcomes/1",
2014-02-12 07:22:21 +08:00
# "type": "string"
# },
# "context_id": {
# "description": "the context owning the outcome link. will match the context owning the outcome group containing the outcome link; included for convenience. may be null for links in global outcome groups.",
# "example": 1,
# "type": "integer"
# },
# "context_type": {
# "example": "Account",
# "type": "string"
# },
# "outcome_group": {
# "description": "an abbreviated OutcomeGroup object representing the group containing the outcome link.",
# "$ref": "OutcomeGroup"
# },
# "outcome": {
# "description": "an abbreviated Outcome object representing the outcome linked into the containing outcome group.",
2014-05-28 15:54:28 +08:00
# "$ref": "Outcome"
OutcomeLink API objects: Add 'assessed' trait
This provides a way to query the learning outcome object to see if it is
assessed in a particular course.
Previously it was only possible to query to see if it had been assessed
anywhere. This is problematic because we sometimes do care whether the
assessing was done for a particular course (such as deciding whether to
allow the outcome to be removed from a course)
Add course_ids_where_assessed and others to learning outcome model
We have need now to know if a particular outcome has been assessed in
a given course (or context), that is not necessarily the context stored
on the outcome object itself.
For example, when an Account level outcome is imported into a course, it
maintains the account and account ID as its context. The problem then,
is how do we know whether this outcome has been assessed in the course
in which it has been imported, and in which we are now viewing the
object?
The answer, is to add an 'assessed' trait on the outcome link object
returned by the API. This outcome link contains information about the
context that we are interested in. In this example, it is the course in
which it was imported.
Using this new trait, clients (including our UI) can tell if a student
has been assessed with this outcome in the particular context, rather
than just globally.
Refs CNVS-19806
Test Plan:
- Make an account level outcome
- import that outcome into two courses
- assess the outcome in one of the courses
- from a rails console, call the assessed? method and pass in each
course object. For the course that was assessed it should return
true, and for the other it should return false.
- Get a learning outcome object
- LearningOutcome.find(<id)
- LearningOutcome.where(context_id: <course_id>)
- Call the assessed? method and pass in the course or course id
- learning_outcome.assessed?(course.id)
- To get an array of all courses:
- Course.all
- from the API, request the outcome links for the course.
- ensure that the link for the course in which the outcome was
assessed has the 'assessed' field set to 'true', and the other has
it set to 'false'
Change-Id: If9a0260289704944145cc3a0929e1183656dfb68
Reviewed-on: https://gerrit.instructure.com/52509
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Reviewed-by: John Corrigan <jcorrigan@instructure.com>
Product-Review: Benjamin Porter <bporter@instructure.com>
2015-04-18 10:06:36 +08:00
# },
# "assessed": {
# "description": "whether this outcome has been used to assess a student in the context of this outcome link. In other words, this will be set to true if the context is a course, and a student has been assessed with this outcome in that course.",
# "example": true,
# "type": "boolean"
2015-10-28 01:18:49 +08:00
# },
# "can_unlink": {
# "description": "whether this outcome link is manageable and is not the last link to an aligned outcome",
# "type": "boolean"
2014-02-12 07:22:21 +08:00
# }
2013-01-01 04:16:05 +08:00
# }
# }
#
2012-08-07 04:43:30 +08:00
class OutcomeGroupsApiController < ApplicationController
include Api :: V1 :: Outcome
2017-02-09 01:08:10 +08:00
before_action :require_user
before_action :get_context
before_action :require_context , :only = > [ :link_index ]
2012-08-07 04:43:30 +08:00
2013-01-01 04:16:05 +08:00
# @API Redirect to root outcome group for context
#
# Convenience redirect to find the root outcome group for a particular
# context. Will redirect to the appropriate outcome group's URL.
#
2012-08-07 04:43:30 +08:00
def redirect
2014-12-03 05:52:02 +08:00
return unless can_read_outcomes
@outcome_group = @context ?
@context . root_outcome_group :
LearningOutcomeGroup . global_root_outcome_group
redirect_to polymorphic_path [ :api_v1 , @context || :global , :outcome_group ] , :id = > @outcome_group . id
2012-08-07 04:43:30 +08:00
end
2014-01-15 08:15:38 +08:00
# @API Get all outcome groups for context
# @beta
#
# @returns [OutcomeGroup]
def index
return unless can_read_outcomes
url = polymorphic_url [ :api_v1 , @context || :global , :outcome_groups ]
groups = Api . paginate ( context_outcome_groups , self , url )
render json : groups . map { | group | outcome_group_json ( group , @current_user , session ) }
end
# @API Get all outcome links for context
# @beta
#
2014-05-28 15:54:28 +08:00
# @argument outcome_style [Optional, String]
# The detail level of the outcomes. Defaults to "abbrev".
# Specify "full" for more information.
#
# @argument outcome_group_style [Optional, String]
# The detail level of the outcome groups. Defaults to "abbrev".
# Specify "full" for more information.
#
2014-01-15 08:15:38 +08:00
# @returns [OutcomeLink]
def link_index
return unless can_read_outcomes
2014-11-13 03:06:00 +08:00
url = polymorphic_url [ :api_v1 , @context , :outcome_group_links ]
2015-02-19 10:54:16 +08:00
2016-12-16 03:25:16 +08:00
links = @context . learning_outcome_links . preload ( :learning_outcome_content ) . order ( :id )
2014-11-13 03:06:00 +08:00
links = Api . paginate ( links , self , url )
2015-02-19 10:54:16 +08:00
outcome_params = params . slice ( :outcome_style , :outcome_group_style )
unless params [ " outcome_style " ] == " abbrev "
outcome_ids = links . map ( & :content_id )
2017-03-15 01:48:55 +08:00
ret = LearningOutcomeResult . distinct . where ( learning_outcome_id : outcome_ids ) . pluck ( :learning_outcome_id )
2015-02-19 10:54:16 +08:00
# ret is now a list of outcomes that have been assessed
outcome_params [ :assessed_outcomes ] = ret
end
OutcomeLink API objects: Add 'assessed' trait
This provides a way to query the learning outcome object to see if it is
assessed in a particular course.
Previously it was only possible to query to see if it had been assessed
anywhere. This is problematic because we sometimes do care whether the
assessing was done for a particular course (such as deciding whether to
allow the outcome to be removed from a course)
Add course_ids_where_assessed and others to learning outcome model
We have need now to know if a particular outcome has been assessed in
a given course (or context), that is not necessarily the context stored
on the outcome object itself.
For example, when an Account level outcome is imported into a course, it
maintains the account and account ID as its context. The problem then,
is how do we know whether this outcome has been assessed in the course
in which it has been imported, and in which we are now viewing the
object?
The answer, is to add an 'assessed' trait on the outcome link object
returned by the API. This outcome link contains information about the
context that we are interested in. In this example, it is the course in
which it was imported.
Using this new trait, clients (including our UI) can tell if a student
has been assessed with this outcome in the particular context, rather
than just globally.
Refs CNVS-19806
Test Plan:
- Make an account level outcome
- import that outcome into two courses
- assess the outcome in one of the courses
- from a rails console, call the assessed? method and pass in each
course object. For the course that was assessed it should return
true, and for the other it should return false.
- Get a learning outcome object
- LearningOutcome.find(<id)
- LearningOutcome.where(context_id: <course_id>)
- Call the assessed? method and pass in the course or course id
- learning_outcome.assessed?(course.id)
- To get an array of all courses:
- Course.all
- from the API, request the outcome links for the course.
- ensure that the link for the course in which the outcome was
assessed has the 'assessed' field set to 'true', and the other has
it set to 'false'
Change-Id: If9a0260289704944145cc3a0929e1183656dfb68
Reviewed-on: https://gerrit.instructure.com/52509
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Reviewed-by: John Corrigan <jcorrigan@instructure.com>
Product-Review: Benjamin Porter <bporter@instructure.com>
2015-04-18 10:06:36 +08:00
render json : outcome_links_json ( links , @current_user , session , outcome_params )
2014-01-15 08:15:38 +08:00
end
2013-01-01 04:16:05 +08:00
# @API Show an outcome group
#
# @returns OutcomeGroup
#
2012-08-07 04:43:30 +08:00
def show
2014-12-03 05:52:02 +08:00
return unless can_read_outcomes
@outcome_group = context_outcome_groups . find ( params [ :id ] )
render :json = > outcome_group_json ( @outcome_group , @current_user , session )
2012-08-07 04:43:30 +08:00
end
2013-01-01 04:16:05 +08:00
# @API Update an outcome group
#
# Modify an existing outcome group. Fields not provided are left as is;
# unrecognized fields are ignored.
#
# When changing the parent outcome group, the new parent group must belong to
# the same context as this outcome group, and must not be a descendant of
# this outcome group (i.e. no cycles allowed).
#
2014-08-15 01:26:45 +08:00
# @argument title [String]
2013-08-14 23:22:32 +08:00
# The new outcome group title.
#
2014-08-15 01:26:45 +08:00
# @argument description [String]
2013-08-14 23:22:32 +08:00
# The new outcome group description.
#
2014-08-15 01:26:45 +08:00
# @argument vendor_guid [String]
2013-08-14 23:22:32 +08:00
# A custom GUID for the learning standard.
#
2014-08-15 01:26:45 +08:00
# @argument parent_outcome_group_id [Integer]
2013-08-14 23:22:32 +08:00
# The id of the new parent outcome group.
2013-01-01 04:16:05 +08:00
#
# @returns OutcomeGroup
#
# @example_request
#
2013-10-26 01:51:23 +08:00
# curl 'https://<canvas>/api/v1/accounts/1/outcome_groups/2.json' \
2015-01-03 03:27:53 +08:00
# -X PUT \
# -F 'title=Outcome Group Title' \
2013-06-22 00:32:24 +08:00
# -F 'description=Outcome group description' \
# -F 'vendor_guid=customid9000' \
2015-01-03 03:27:53 +08:00
# -F 'parent_outcome_group_id=1' \
2013-01-01 04:16:05 +08:00
# -H "Authorization: Bearer <token>"
#
# @example_request
#
2013-10-26 01:51:23 +08:00
# curl 'https://<canvas>/api/v1/accounts/1/outcome_groups/2.json' \
2015-01-03 03:27:53 +08:00
# -X PUT \
2013-01-01 04:16:05 +08:00
# --data-binary '{
# "title": "Outcome Group Title",
# "description": "Outcome group description",
2013-06-22 00:32:24 +08:00
# "vendor_guid": "customid9000",
2013-01-01 04:16:05 +08:00
# "parent_outcome_group_id": 1
2015-01-03 03:27:53 +08:00
# }' \
# -H "Content-Type: application/json" \
2013-01-01 04:16:05 +08:00
# -H "Authorization: Bearer <token>"
#
2012-08-07 04:43:30 +08:00
def update
2014-12-03 05:52:02 +08:00
return unless can_manage_outcomes
@outcome_group = context_outcome_groups . find ( params [ :id ] )
if @outcome_group . learning_outcome_group_id . nil?
render :json = > 'error' . to_json , :status = > :bad_request
return
end
2017-01-13 04:24:07 +08:00
@outcome_group . update_attributes ( params . permit ( :title , :description , :vendor_guid ) )
2014-12-03 05:52:02 +08:00
if params [ :parent_outcome_group_id ] && params [ :parent_outcome_group_id ] != @outcome_group . learning_outcome_group_id
new_parent = context_outcome_groups . find ( params [ :parent_outcome_group_id ] )
unless new_parent . adopt_outcome_group ( @outcome_group )
2012-08-07 04:43:30 +08:00
render :json = > 'error' . to_json , :status = > :bad_request
return
end
2014-12-03 05:52:02 +08:00
end
if @outcome_group . save
render :json = > outcome_group_json ( @outcome_group , @current_user , session )
else
render :json = > @outcome_group . errors , :status = > :bad_request
2012-08-07 04:43:30 +08:00
end
end
2013-01-01 04:16:05 +08:00
# @API Delete an outcome group
#
# Deleting an outcome group deletes descendant outcome groups and outcome
# links. The linked outcomes themselves are only deleted if all links to the
# outcome were deleted.
#
# Aligned outcomes cannot be deleted; as such, if all remaining links to an
# aligned outcome are included in this group's descendants, the group
# deletion will fail.
#
# @returns OutcomeGroup
#
# @example_request
#
2013-10-26 01:51:23 +08:00
# curl 'https://<canvas>/api/v1/accounts/1/outcome_groups/2.json' \
2015-01-03 03:27:53 +08:00
# -X DELETE \
2013-01-01 04:16:05 +08:00
# -H "Authorization: Bearer <token>"
#
2012-08-07 04:43:30 +08:00
def destroy
2014-12-03 05:52:02 +08:00
return unless can_manage_outcomes
@outcome_group = context_outcome_groups . find ( params [ :id ] )
if @outcome_group . learning_outcome_group_id . nil?
render :json = > 'error' . to_json , :status = > :bad_request
return
end
begin
@outcome_group . skip_tag_touch = true
@outcome_group . destroy
@context . try ( :touch )
render :json = > outcome_group_json ( @outcome_group , @current_user , session )
rescue ActiveRecord :: RecordNotSaved
render :json = > 'error' . to_json , :status = > :bad_request
2012-08-07 04:43:30 +08:00
end
end
2013-01-01 04:16:05 +08:00
# @API List linked outcomes
#
2017-09-14 04:52:58 +08:00
# A paginated list of the immediate OutcomeLink children of the outcome group.
2013-01-01 04:16:05 +08:00
#
2016-03-11 05:40:42 +08:00
# @argument outcome_style [Optional, String]
# The detail level of the outcomes. Defaults to "abbrev".
# Specify "full" for more information.
#
2013-01-01 04:16:05 +08:00
# @returns [OutcomeLink]
#
2012-08-07 04:43:30 +08:00
def outcomes
2014-12-03 05:52:02 +08:00
return unless can_read_outcomes
@outcome_group = context_outcome_groups . find ( params [ :id ] )
2012-08-07 04:43:30 +08:00
2014-12-03 05:52:02 +08:00
# get and paginate links from group
2016-12-16 03:25:16 +08:00
link_scope = @outcome_group . child_outcome_links . active . order_by_outcome_title
2014-12-03 05:52:02 +08:00
url = polymorphic_url [ :api_v1 , @context || :global , :outcome_group_outcomes ] , :id = > @outcome_group . id
@links = Api . paginate ( link_scope , self , url )
2012-08-07 04:43:30 +08:00
2014-12-03 05:52:02 +08:00
# pre-populate the links' groups and contexts to prevent
# extraneous loads
@links . each do | link |
link . associated_asset = @outcome_group
2016-12-16 03:25:16 +08:00
link . context = @outcome_group . context
2012-08-07 04:43:30 +08:00
end
2014-12-03 05:52:02 +08:00
2016-03-11 05:40:42 +08:00
outcome_params = params . slice ( :outcome_style )
2014-12-03 05:52:02 +08:00
# preload the links' outcomes' contexts.
2016-12-16 03:25:16 +08:00
ActiveRecord :: Associations :: Preloader . new . preload ( @links , :learning_outcome_content = > :context )
2014-12-03 05:52:02 +08:00
# render to json and serve
2016-03-11 05:40:42 +08:00
render :json = > outcome_links_json ( @links , @current_user , session , outcome_params )
2012-08-07 04:43:30 +08:00
end
2013-01-01 04:16:05 +08:00
# Intentionally undocumented in the API. Used by the UI to show a list of
# accounts' root outcome groups for the account(s) above the context.
2012-09-18 01:20:13 +08:00
def account_chain
2014-12-03 05:52:02 +08:00
return unless authorized_action ( @context , @current_user , :manage_outcomes )
account_chain =
if @context . is_a? ( Account )
@context . account_chain - [ @context ]
else
@context . account . account_chain
end
account_chain . map! { | a | {
:id = > a . root_outcome_group . id ,
:title = > a . name ,
:description = > t ( 'account_group_description' , 'Account level outcomes group.' ) ,
:dontImport = > true ,
:url = > polymorphic_path ( [ :api_v1 , a , :outcome_group ] , :id = > a . root_outcome_group . id ) ,
:subgroups_url = > polymorphic_path ( [ :api_v1 , a , :outcome_group_subgroups ] , :id = > a . root_outcome_group . id ) ,
:outcomes_url = > polymorphic_path ( [ :api_v1 , a , :outcome_group_outcomes ] , :id = > a . root_outcome_group . id )
} }
path = polymorphic_path [ :api_v1 , @context , :account_chain ]
account_chain = Api . paginate ( account_chain , self , path )
render :json = > account_chain
2012-09-18 01:20:13 +08:00
end
2013-01-01 04:16:05 +08:00
# @API Create/link an outcome
#
# Link an outcome into the outcome group. The outcome to link can either be
# specified by a PUT to the link URL for a specific outcome (the outcome_id
# in the PUT URLs) or by supplying the information for a new outcome (title,
# description, ratings, mastery_points) in a POST to the collection.
#
# If linking an existing outcome, the outcome_id must identify an outcome
# available to this context; i.e. an outcome owned by this group's context,
# an outcome owned by an associated account, or a global outcome. With
# outcome_id present, any other parameters are ignored.
#
# If defining a new outcome, the outcome is created in the outcome group's
# context using the provided title, description, ratings, and mastery points;
2015-03-24 04:14:48 +08:00
# the title is required but all other fields are optional. The new outcome
# is then linked into the outcome group.
2013-01-01 04:16:05 +08:00
#
# If ratings are provided when creating a new outcome, an embedded rubric
# criterion is included in the new outcome. This criterion's mastery_points
# default to the maximum points in the highest rating if not specified in the
# mastery_points parameter. Any ratings lacking a description are given a
# default of "No description". Any ratings lacking a point value are given a
# default of 0. If no ratings are provided, the mastery_points parameter is
# ignored.
#
2014-08-15 01:26:45 +08:00
# @argument outcome_id [Integer]
2013-08-14 23:22:32 +08:00
# The ID of the existing outcome to link.
#
2014-08-15 01:26:45 +08:00
# @argument title [String]
2013-08-14 23:22:32 +08:00
# The title of the new outcome. Required if outcome_id is absent.
#
2014-08-15 01:26:45 +08:00
# @argument display_name [String]
2014-06-05 04:39:03 +08:00
# A friendly name shown in reports for outcomes with cryptic titles,
# such as common core standards names.
#
2014-08-15 01:26:45 +08:00
# @argument description [String]
2013-08-14 23:22:32 +08:00
# The description of the new outcome.
#
2014-08-15 01:26:45 +08:00
# @argument vendor_guid [String]
2013-08-14 23:22:32 +08:00
# A custom GUID for the learning standard.
#
2014-08-15 01:26:45 +08:00
# @argument mastery_points [Integer]
2013-08-14 23:22:32 +08:00
# The mastery threshold for the embedded rubric criterion.
#
2014-08-15 01:26:45 +08:00
# @argument ratings[][description] [String]
2013-08-14 23:22:32 +08:00
# The description of a rating level for the embedded rubric criterion.
#
2014-08-15 01:26:45 +08:00
# @argument ratings[][points] [Integer]
2013-08-14 23:22:32 +08:00
# The points corresponding to a rating level for the embedded rubric criterion.
2013-01-01 04:16:05 +08:00
#
2014-12-27 12:53:54 +08:00
# @argument calculation_method [String, "decaying_average"|"n_mastery"|"latest"|"highest"]
2015-03-24 04:14:48 +08:00
# The new calculation method. Defaults to "highest"
2014-12-27 12:53:54 +08:00
#
# @argument calculation_int [Integer]
# The new calculation int. Only applies if the calculation_method is "decaying_average" or "n_mastery"
#
2013-01-01 04:16:05 +08:00
# @returns OutcomeLink
#
# @example_request
#
2013-10-26 01:51:23 +08:00
# curl 'https://<canvas>/api/v1/accounts/1/outcome_groups/1/outcomes/1.json' \
2015-01-03 03:27:53 +08:00
# -X PUT \
2013-01-01 04:16:05 +08:00
# -H "Authorization: Bearer <token>"
#
# @example_request
#
2013-10-26 01:51:23 +08:00
# curl 'https://<canvas>/api/v1/accounts/1/outcome_groups/1/outcomes.json' \
2015-01-03 03:27:53 +08:00
# -X POST \
# -F 'title=Outcome Title' \
2014-06-05 04:39:03 +08:00
# -F 'display_name=Title for reporting' \
2013-06-22 00:32:24 +08:00
# -F 'description=Outcome description' \
# -F 'vendor_guid=customid9000' \
2014-12-27 12:53:54 +08:00
# -F 'mastery_points=3' \
# -F 'calculation_method=decaying_average' \
# -F 'calculation_int=75' \
# -F 'ratings[][description]=Exceeds Expectations' \
# -F 'ratings[][points]=5' \
# -F 'ratings[][description]=Meets Expectations' \
# -F 'ratings[][points]=3' \
# -F 'ratings[][description]=Does Not Meet Expectations' \
# -F 'ratings[][points]=0' \
2013-01-01 04:16:05 +08:00
# -H "Authorization: Bearer <token>"
#
# @example_request
#
2013-10-26 01:51:23 +08:00
# curl 'https://<canvas>/api/v1/accounts/1/outcome_groups/1/outcomes.json' \
2015-01-03 03:27:53 +08:00
# -X POST \
2013-01-01 04:16:05 +08:00
# --data-binary '{
# "title": "Outcome Title",
2014-06-05 04:39:03 +08:00
# "display_name": "Title for reporting",
2013-01-01 04:16:05 +08:00
# "description": "Outcome description",
2013-06-22 00:32:24 +08:00
# "vendor_guid": "customid9000",
2013-01-01 04:16:05 +08:00
# "mastery_points": 3,
# "ratings": [
# { "description": "Exceeds Expectations", "points": 5 },
# { "description": "Meets Expectations", "points": 3 },
# { "description": "Does Not Meet Expectations", "points": 0 }
# ]
2015-01-03 03:27:53 +08:00
# }' \
# -H "Content-Type: application/json" \
2013-01-01 04:16:05 +08:00
# -H "Authorization: Bearer <token>"
#
2012-08-07 04:43:30 +08:00
def link
2014-12-03 05:52:02 +08:00
return unless can_manage_outcomes
@outcome_group = context_outcome_groups . find ( params [ :id ] )
if params [ :outcome_id ]
@outcome = context_available_outcome ( params [ :outcome_id ] )
unless @outcome
render :json = > 'error' . to_json , :status = > :bad_request
return
end
else
2017-01-13 04:24:07 +08:00
outcome_params = params . permit ( :title , :description , :mastery_points , :vendor_guid ,
2016-11-08 22:38:30 +08:00
:display_name , :calculation_method , :calculation_int , :ratings = > strong_anything )
@outcome = context_create_outcome ( outcome_params )
2014-12-03 05:52:02 +08:00
unless @outcome . valid?
render :json = > @outcome . errors , :status = > :bad_request
return
2012-08-07 04:43:30 +08:00
end
end
2014-12-03 05:52:02 +08:00
@outcome_link = @outcome_group . add_outcome ( @outcome )
render :json = > outcome_link_json ( @outcome_link , @current_user , session )
2012-08-07 04:43:30 +08:00
end
2013-01-01 04:16:05 +08:00
# @API Unlink an outcome
#
# Unlinking an outcome only deletes the outcome itself if this was the last
# link to the outcome in any group in any context. Aligned outcomes cannot be
# deleted; as such, if this is the last link to an aligned outcome, the
# unlinking will fail.
#
# @returns OutcomeLink
#
# @example_request
#
2013-10-26 01:51:23 +08:00
# curl 'https://<canvas>/api/v1/accounts/1/outcome_groups/1/outcomes/1.json' \
2015-01-03 03:27:53 +08:00
# -X DELETE \
2013-01-01 04:16:05 +08:00
# -H "Authorization: Bearer <token>"
#
2012-08-07 04:43:30 +08:00
def unlink
2014-12-03 05:52:02 +08:00
return unless can_manage_outcomes
@outcome_group = context_outcome_groups . find ( params [ :id ] )
2016-12-16 03:25:16 +08:00
@outcome_link = @outcome_group . child_outcome_links . active . where ( content_id : params [ :outcome_id ] ) . first
2016-09-23 07:15:44 +08:00
2014-12-03 05:52:02 +08:00
raise ActiveRecord :: RecordNotFound unless @outcome_link
begin
@outcome_link . destroy
render :json = > outcome_link_json ( @outcome_link , @current_user , session )
2016-12-16 03:25:16 +08:00
rescue ContentTag :: LastLinkToOutcomeNotDestroyed = > error
2014-12-03 05:52:02 +08:00
render :json = > { 'message' = > error . message } , :status = > :bad_request
rescue ActiveRecord :: RecordNotSaved
render :json = > 'error' . to_json , :status = > :bad_request
2012-08-07 04:43:30 +08:00
end
end
2013-01-01 04:16:05 +08:00
# @API List subgroups
#
2017-09-14 04:52:58 +08:00
# A paginated list of the immediate OutcomeGroup children of the outcome group.
2013-01-01 04:16:05 +08:00
#
# @returns [OutcomeGroup]
#
2012-08-07 04:43:30 +08:00
def subgroups
2014-12-03 05:52:02 +08:00
return unless can_read_outcomes
2012-08-07 04:43:30 +08:00
2014-12-03 05:52:02 +08:00
@outcome_group = context_outcome_groups . find ( params [ :id ] )
2012-08-07 04:43:30 +08:00
2014-12-03 05:52:02 +08:00
# get and paginate subgroups from group
subgroup_scope = @outcome_group . child_outcome_groups . active . order_by_title
url = polymorphic_url [ :api_v1 , @context || :global , :outcome_group_subgroups ] , :id = > @outcome_group . id
@subgroups = Api . paginate ( subgroup_scope , self , url )
2012-08-07 04:43:30 +08:00
2014-12-03 05:52:02 +08:00
# pre-populate the subgroups' parent groups to prevent extraneous
# loads
@subgroups . each { | group | group . context = @outcome_group . context }
# render to json and serve
render :json = > @subgroups . map { | group | outcome_group_json ( group , @current_user , session , :abbrev ) }
2012-08-07 04:43:30 +08:00
end
2013-01-01 04:16:05 +08:00
# @API Create a subgroup
#
# Creates a new empty subgroup under the outcome group with the given title
# and description.
#
2014-08-15 01:26:45 +08:00
# @argument title [Required, String]
2013-08-14 23:22:32 +08:00
# The title of the new outcome group.
#
2014-08-15 01:26:45 +08:00
# @argument description [String]
2013-08-14 23:22:32 +08:00
# The description of the new outcome group.
#
2014-08-15 01:26:45 +08:00
# @argument vendor_guid [String]
2013-08-14 23:22:32 +08:00
# A custom GUID for the learning standard
2013-01-01 04:16:05 +08:00
#
# @returns OutcomeGroup
#
# @example_request
#
2013-10-26 01:51:23 +08:00
# curl 'https://<canvas>/api/v1/accounts/1/outcome_groups/1/subgroups.json' \
2015-01-03 03:27:53 +08:00
# -X POST \
# -F 'title=Outcome Group Title' \
2013-06-22 00:32:24 +08:00
# -F 'description=Outcome group description' \
# -F 'vendor_guid=customid9000' \
2013-01-01 04:16:05 +08:00
# -H "Authorization: Bearer <token>"
#
# @example_request
#
2013-10-26 01:51:23 +08:00
# curl 'https://<canvas>/api/v1/accounts/1/outcome_groups/1/subgroups.json' \
2015-01-03 03:27:53 +08:00
# -X POST \
2013-01-01 04:16:05 +08:00
# --data-binary '{
# "title": "Outcome Group Title",
2013-06-22 00:32:24 +08:00
# "description": "Outcome group description",
# "vendor_guid": "customid9000"
2015-01-03 03:27:53 +08:00
# }' \
# -H "Content-Type: application/json" \
2013-01-01 04:16:05 +08:00
# -H "Authorization: Bearer <token>"
#
2012-08-07 04:43:30 +08:00
def create
2014-12-03 05:52:02 +08:00
return unless can_manage_outcomes
@outcome_group = context_outcome_groups . find ( params [ :id ] )
2017-01-13 04:24:07 +08:00
@child_outcome_group = @outcome_group . child_outcome_groups . build ( params . permit ( :title , :description , :vendor_guid ) )
2014-12-03 05:52:02 +08:00
if @child_outcome_group . save
render :json = > outcome_group_json ( @child_outcome_group , @current_user , session )
else
render :json = > 'error' . to_json , :status = > :bad_request
2012-08-07 04:43:30 +08:00
end
end
2013-01-01 04:16:05 +08:00
# @API Import an outcome group
#
# Creates a new subgroup of the outcome group with the same title and
# description as the source group, then creates links in that new subgroup to
# the same outcomes that are linked in the source group. Recurses on the
# subgroups of the source group, importing them each in turn into the new
# subgroup.
#
# Allows you to copy organizational structure, but does not create copies of
# the outcomes themselves, only new links.
#
# The source group must be either global, from the same context as this
# outcome group, or from an associated account. The source group cannot be
# the root outcome group of its context.
#
2014-08-15 01:26:45 +08:00
# @argument source_outcome_group_id [Required, Integer]
2013-08-14 23:22:32 +08:00
# The ID of the source outcome group.
2013-01-01 04:16:05 +08:00
#
# @returns OutcomeGroup
#
# @example_request
#
2013-10-26 01:51:23 +08:00
# curl 'https://<canvas>/api/v1/accounts/2/outcome_groups/3/import.json' \
2015-01-03 03:27:53 +08:00
# -X POST \
# -F 'source_outcome_group_id=2' \
2013-01-01 04:16:05 +08:00
# -H "Authorization: Bearer <token>"
#
2012-08-07 04:43:30 +08:00
def import
2014-12-03 05:52:02 +08:00
return unless can_manage_outcomes
2012-08-07 04:43:30 +08:00
2014-12-03 05:52:02 +08:00
@outcome_group = context_outcome_groups . find ( params [ :id ] )
2012-08-07 04:43:30 +08:00
2014-12-03 05:52:02 +08:00
# source has to exist
@source_outcome_group = LearningOutcomeGroup . active . where ( id : params [ :source_outcome_group_id ] ) . first
unless @source_outcome_group
render :json = > 'error' . to_json , :status = > :bad_request
return
end
2012-08-07 04:43:30 +08:00
2014-12-03 05:52:02 +08:00
# source has to be global, in same context, or in an associated
# account
source_context = @source_outcome_group . context
unless ! source_context || source_context == @context || @context . associated_accounts . include? ( source_context )
render :json = > 'error' . to_json , :status = > :bad_request
return
end
2012-08-07 04:43:30 +08:00
2014-12-03 05:52:02 +08:00
# source can't be a root group
unless @source_outcome_group . learning_outcome_group_id
render :json = > 'error' . to_json , :status = > :bad_request
return
2012-08-07 04:43:30 +08:00
end
2014-12-03 05:52:02 +08:00
# import the validated source
@child_outcome_group = @outcome_group . add_outcome_group ( @source_outcome_group )
render :json = > outcome_group_json ( @child_outcome_group , @current_user , session )
2012-08-07 04:43:30 +08:00
end
protected
def can_read_outcomes
if @context
2012-11-01 04:28:33 +08:00
authorized_action ( @context , @current_user , :read_outcomes )
2012-08-07 04:43:30 +08:00
else
2012-11-01 04:28:33 +08:00
authorized_action ( Account . site_admin , @current_user , :read_global_outcomes )
2012-08-07 04:43:30 +08:00
end
end
def can_manage_outcomes
if @context
authorized_action ( @context , @current_user , :manage_outcomes )
else
authorized_action ( Account . site_admin , @current_user , :manage_global_outcomes )
end
end
# get the active outcome groups in the context/global
def context_outcome_groups
LearningOutcomeGroup . for_context ( @context ) . active
end
# verify the outcome is eligible to be linked into the context,
# returning the outcome if so
def context_available_outcome ( outcome_id )
if @context
@context . available_outcome ( outcome_id , :allow_global = > true )
else
2014-08-21 06:35:07 +08:00
LearningOutcome . global . where ( id : outcome_id ) . first
2012-08-07 04:43:30 +08:00
end
end
def context_create_outcome ( data )
scope = @context ? @context . created_learning_outcomes : LearningOutcome . global
2014-06-05 04:39:03 +08:00
outcome = scope . build ( data . slice ( :title , :display_name , :description , :vendor_guid ) )
2014-12-27 12:53:54 +08:00
outcome . rubric_criterion = data . slice ( :ratings , :mastery_points ) if data [ :ratings ]
if data [ :calculation_method ]
outcome . calculation_method = data [ :calculation_method ]
outcome . calculation_int = data [ :calculation_int ]
2012-08-07 04:43:30 +08:00
end
outcome . save
outcome
end
end