Add reporting name for learning outcomes
fixes CNVS-13242 This adds a special field to learning outcomes to use for friendly reporting in case the outcome's actual name is quite complex or cryptic (like common core standard outcomes). TEST PLAN: - login as an instructor - create or edit an outcome and validate that you can add a "friendly" name and that it persists - use that outcome for an assignment and let a student complete it - navigate to the student mastery report for that student - verify that by hovering over that outcome name on the student mastery report you can see both the real title and the friendly name Change-Id: I89d1a5de590666ddf6cbc82617e4475d1f7a5226 Reviewed-on: https://gerrit.instructure.com/35919 Reviewed-by: Drew Bowman <dbowman@instructure.com> QA-Review: Steven Shepherd <sshepherd@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Drew Bowman <dbowman@instructure.com>
This commit is contained in:
parent
ee1db6929c
commit
a0c96d6a41
|
@ -5,7 +5,7 @@ define [
|
||||||
], (_, {Model, Collection}, natcompare) ->
|
], (_, {Model, Collection}, natcompare) ->
|
||||||
class Group extends Model
|
class Group extends Model
|
||||||
initialize: ->
|
initialize: ->
|
||||||
@set('outcomes', new Collection([], comparator: natcompare.byGet('title')))
|
@set('outcomes', new Collection([], comparator: natcompare.byGet('friendly_name')))
|
||||||
|
|
||||||
count: -> @get('outcomes').length
|
count: -> @get('outcomes').length
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,11 @@ define [
|
||||||
'Backbone'
|
'Backbone'
|
||||||
], (_, {Model, Collection}) ->
|
], (_, {Model, Collection}) ->
|
||||||
class Outcome extends Model
|
class Outcome extends Model
|
||||||
|
initialize: ->
|
||||||
|
super
|
||||||
|
@set 'friendly_name', @get('display_name') || @get('title')
|
||||||
|
@set 'hover_name', (@get('title') if @get('display_name'))
|
||||||
|
|
||||||
status: ->
|
status: ->
|
||||||
if @scoreDefined()
|
if @scoreDefined()
|
||||||
score = @get('score')
|
score = @get('score')
|
||||||
|
|
|
@ -384,6 +384,10 @@ class OutcomeGroupsApiController < ApplicationController
|
||||||
# @argument title [Optional, String]
|
# @argument title [Optional, String]
|
||||||
# The title of the new outcome. Required if outcome_id is absent.
|
# The title of the new outcome. Required if outcome_id is absent.
|
||||||
#
|
#
|
||||||
|
# @argument display_name [Optional, String]
|
||||||
|
# A friendly name shown in reports for outcomes with cryptic titles,
|
||||||
|
# such as common core standards names.
|
||||||
|
#
|
||||||
# @argument description [Optional, String]
|
# @argument description [Optional, String]
|
||||||
# The description of the new outcome.
|
# The description of the new outcome.
|
||||||
#
|
#
|
||||||
|
@ -412,6 +416,7 @@ class OutcomeGroupsApiController < ApplicationController
|
||||||
# curl 'https://<canvas>/api/v1/accounts/1/outcome_groups/1/outcomes.json' \
|
# curl 'https://<canvas>/api/v1/accounts/1/outcome_groups/1/outcomes.json' \
|
||||||
# -X POST \
|
# -X POST \
|
||||||
# -F 'title=Outcome Title' \
|
# -F 'title=Outcome Title' \
|
||||||
|
# -F 'display_name=Title for reporting' \
|
||||||
# -F 'description=Outcome description' \
|
# -F 'description=Outcome description' \
|
||||||
# -F 'vendor_guid=customid9000' \
|
# -F 'vendor_guid=customid9000' \
|
||||||
# -F 'mastery_points=3' \
|
# -F 'mastery_points=3' \
|
||||||
|
@ -429,6 +434,7 @@ class OutcomeGroupsApiController < ApplicationController
|
||||||
# -X POST \
|
# -X POST \
|
||||||
# --data-binary '{
|
# --data-binary '{
|
||||||
# "title": "Outcome Title",
|
# "title": "Outcome Title",
|
||||||
|
# "display_name": "Title for reporting",
|
||||||
# "description": "Outcome description",
|
# "description": "Outcome description",
|
||||||
# "vendor_guid": "customid9000",
|
# "vendor_guid": "customid9000",
|
||||||
# "mastery_points": 3,
|
# "mastery_points": 3,
|
||||||
|
@ -451,7 +457,7 @@ class OutcomeGroupsApiController < ApplicationController
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@outcome = context_create_outcome(params.slice(:title, :description, :ratings, :mastery_points, :vendor_guid))
|
@outcome = context_create_outcome(params.slice(:title, :description, :ratings, :mastery_points, :vendor_guid, :display_name))
|
||||||
unless @outcome.valid?
|
unless @outcome.valid?
|
||||||
render :json => @outcome.errors, :status => :bad_request
|
render :json => @outcome.errors, :status => :bad_request
|
||||||
return
|
return
|
||||||
|
@ -666,7 +672,7 @@ class OutcomeGroupsApiController < ApplicationController
|
||||||
|
|
||||||
def context_create_outcome(data)
|
def context_create_outcome(data)
|
||||||
scope = @context ? @context.created_learning_outcomes : LearningOutcome.global
|
scope = @context ? @context.created_learning_outcomes : LearningOutcome.global
|
||||||
outcome = scope.build(data.slice(:title, :description, :vendor_guid))
|
outcome = scope.build(data.slice(:title, :display_name, :description, :vendor_guid))
|
||||||
if data[:ratings]
|
if data[:ratings]
|
||||||
outcome.rubric_criterion = data.slice(:ratings, :mastery_points)
|
outcome.rubric_criterion = data.slice(:ratings, :mastery_points)
|
||||||
end
|
end
|
||||||
|
|
|
@ -49,6 +49,11 @@
|
||||||
# "example": "Outcome title",
|
# "example": "Outcome title",
|
||||||
# "type": "string"
|
# "type": "string"
|
||||||
# },
|
# },
|
||||||
|
# "display_name": {
|
||||||
|
# "description": "Optional friendly name for reporting",
|
||||||
|
# "example": "My Favorite Outocome",
|
||||||
|
# "type": "string"
|
||||||
|
# },
|
||||||
# "description": {
|
# "description": {
|
||||||
# "description": "description of the outcome. omitted in the abbreviated form.",
|
# "description": "description of the outcome. omitted in the abbreviated form.",
|
||||||
# "example": "Outcome description",
|
# "example": "Outcome description",
|
||||||
|
@ -118,6 +123,10 @@ class OutcomesApiController < ApplicationController
|
||||||
# @argument title [Optional, String]
|
# @argument title [Optional, String]
|
||||||
# The new outcome title.
|
# The new outcome title.
|
||||||
#
|
#
|
||||||
|
# @argument display_name [Optional, String]
|
||||||
|
# A friendly name shown in reports for outcomes with cryptic titles,
|
||||||
|
# such as common core standards names.
|
||||||
|
#
|
||||||
# @argument description [Optional, String]
|
# @argument description [Optional, String]
|
||||||
# The new outcome description.
|
# The new outcome description.
|
||||||
#
|
#
|
||||||
|
@ -141,6 +150,7 @@ class OutcomesApiController < ApplicationController
|
||||||
# curl 'https://<canvas>/api/v1/outcomes/1.json' \
|
# curl 'https://<canvas>/api/v1/outcomes/1.json' \
|
||||||
# -X PUT \
|
# -X PUT \
|
||||||
# -F 'title=Outcome Title' \
|
# -F 'title=Outcome Title' \
|
||||||
|
# -F 'display_name=Title for reporting' \
|
||||||
# -F 'description=Outcome description' \
|
# -F 'description=Outcome description' \
|
||||||
# -F 'vendor_guid=customid9001' \
|
# -F 'vendor_guid=customid9001' \
|
||||||
# -F 'mastery_points=3' \
|
# -F 'mastery_points=3' \
|
||||||
|
@ -158,6 +168,7 @@ class OutcomesApiController < ApplicationController
|
||||||
# -X PUT \
|
# -X PUT \
|
||||||
# --data-binary '{
|
# --data-binary '{
|
||||||
# "title": "Outcome Title",
|
# "title": "Outcome Title",
|
||||||
|
# "display_name": "Title for reporting",
|
||||||
# "description": "Outcome description",
|
# "description": "Outcome description",
|
||||||
# "vendor_guid": "customid9001",
|
# "vendor_guid": "customid9001",
|
||||||
# "mastery_points": 3,
|
# "mastery_points": 3,
|
||||||
|
@ -172,7 +183,7 @@ class OutcomesApiController < ApplicationController
|
||||||
#
|
#
|
||||||
def update
|
def update
|
||||||
if authorized_action(@outcome, @current_user, :update)
|
if authorized_action(@outcome, @current_user, :update)
|
||||||
@outcome.update_attributes(params.slice(:title, :description, :vendor_guid))
|
@outcome.update_attributes(params.slice(:title, :display_name, :description, :vendor_guid))
|
||||||
if params[:mastery_points] || params[:ratings]
|
if params[:mastery_points] || params[:ratings]
|
||||||
criterion = @outcome.data && @outcome.data[:rubric_criterion]
|
criterion = @outcome.data && @outcome.data[:rubric_criterion]
|
||||||
criterion ||= {}
|
criterion ||= {}
|
||||||
|
|
|
@ -18,7 +18,9 @@
|
||||||
|
|
||||||
class LearningOutcome < ActiveRecord::Base
|
class LearningOutcome < ActiveRecord::Base
|
||||||
include Workflow
|
include Workflow
|
||||||
attr_accessible :context, :description, :short_description, :title, :rubric_criterion, :vendor_guid
|
attr_accessible :context, :description, :short_description, :title, :display_name
|
||||||
|
attr_accessible :rubric_criterion, :vendor_guid
|
||||||
|
|
||||||
belongs_to :context, :polymorphic => true
|
belongs_to :context, :polymorphic => true
|
||||||
validates_inclusion_of :context_type, :allow_nil => true, :in => ['Account', 'Course']
|
validates_inclusion_of :context_type, :allow_nil => true, :in => ['Account', 'Course']
|
||||||
has_many :learning_outcome_results
|
has_many :learning_outcome_results
|
||||||
|
|
|
@ -361,6 +361,9 @@ $outcome-border: 1px solid #BCC2CA
|
||||||
color: #2a333b
|
color: #2a333b
|
||||||
p
|
p
|
||||||
margin: 0
|
margin: 0
|
||||||
|
white-space: nowrap
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
||||||
|
|
||||||
.title
|
.title
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
|
@ -445,3 +448,6 @@ $outcome-border: 1px solid #BCC2CA
|
||||||
top: 13px
|
top: 13px
|
||||||
.score
|
.score
|
||||||
font-size: 14px
|
font-size: 14px
|
||||||
|
|
||||||
|
.ui-widget.ui-tooltip
|
||||||
|
max-width: 500px
|
||||||
|
|
|
@ -33,6 +33,9 @@
|
||||||
height: 500px
|
height: 500px
|
||||||
border-left: 1px solid $borderColor
|
border-left: 1px solid $borderColor
|
||||||
overflow: scroll
|
overflow: scroll
|
||||||
|
.learning_outcome
|
||||||
|
label.span3
|
||||||
|
margin-left: 0px
|
||||||
.wrapper
|
.wrapper
|
||||||
padding: 15px
|
padding: 15px
|
||||||
width: 600px
|
width: 600px
|
||||||
|
|
|
@ -14,7 +14,9 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="outcome-properties">
|
<div class="outcome-properties">
|
||||||
<div class="title" data-tooltip title="{{description}}">{{title}}</div>
|
<div class="title" data-tooltip title="{{#if hover_name}}{{hover_name}}: {{/if}}{{description}}">
|
||||||
|
{{friendly_name}}
|
||||||
|
</div>
|
||||||
<div class="description">{{{description}}}</div>
|
<div class="description">{{{description}}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="outcome-score outcome-bar-wrapper">
|
<div class="outcome-score outcome-bar-wrapper">
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<div class="outcome-modal">
|
<div class="outcome-modal">
|
||||||
<div class="title">{{title}}</div>
|
<div class="title"{{#if hover_name}} data-tooltip title="{{hover_name}}"{{/if}}>
|
||||||
|
{{friendly_name}}
|
||||||
|
</div>
|
||||||
<div class="outcome-bar-wrapper">
|
<div class="outcome-bar-wrapper">
|
||||||
<div class="score"><strong>{{#if scoreDefined}}{{score}}{{else}}-{{/if}}</strong>/{{mastery_points}}</div>
|
<div class="score"><strong>{{#if scoreDefined}}{{score}}{{else}}-{{/if}}</strong>/{{mastery_points}}</div>
|
||||||
{{view progress}}
|
{{view progress}}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
<form action="{{url}}" class="learning_outcome" method="post">
|
<form action="{{url}}" class="learning_outcome" method="post">
|
||||||
<label for="title">{{#t "title"}}Name this outcome{{/t}}:</label>
|
|
||||||
<input class="outcome_title" name="title" id=title size="50" type="text" value="{{title}}">
|
<label class="span3" for="title">{{#t "title"}}Name this outcome{{/t}}:</label>
|
||||||
|
<input class="outcome_title span3" name="title" id=title size="50" type="text" value="{{title}}">
|
||||||
|
|
||||||
|
<label class="span3" for="display_name">{{#t "display_name"}}Friendly name (optional){{/t}}:</label>
|
||||||
|
<input class="outcome_display_name span3" name="display_name" id=display_name size="50" type="text" value="{{display_name}}">
|
||||||
|
|
||||||
|
|
||||||
<label for="description">{{#t "description"}}Describe this outcome{{/t}}:</label>
|
<label for="description">{{#t "description"}}Describe this outcome{{/t}}:</label>
|
||||||
<textarea cols="40" name="description" id=description rows="20" style="width: 100%; height: 150px;">{{description}}</textarea>
|
<textarea cols="40" name="description" id=description rows="20" style="width: 100%; height: 150px;">{{description}}</textarea>
|
||||||
<div id="outcome_criterion_dialog" style="display: none;">
|
<div id="outcome_criterion_dialog" style="display: none;">
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
class AddDisplayNameToLearningOutcomes < ActiveRecord::Migration
|
||||||
|
tag :predeploy
|
||||||
|
|
||||||
|
def self.up
|
||||||
|
add_column :learning_outcomes, :display_name, :string
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
remove_column :learning_outcomes, :display_name
|
||||||
|
end
|
||||||
|
end
|
|
@ -32,7 +32,8 @@ module Api::V1::Outcome
|
||||||
# can_edit. full expands on that by adding description and criterion values
|
# can_edit. full expands on that by adding description and criterion values
|
||||||
# (if any).
|
# (if any).
|
||||||
def outcome_json(outcome, user, session, style=:full)
|
def outcome_json(outcome, user, session, style=:full)
|
||||||
api_json(outcome, user, session, :only => %w(id context_type context_id vendor_guid), :methods => [:title]).tap do |hash|
|
json_attributes = %w(id context_type context_id vendor_guid display_name)
|
||||||
|
api_json(outcome, user, session, :only => json_attributes, :methods => [:title]).tap do |hash|
|
||||||
hash['url'] = api_v1_outcome_path :id => outcome.id
|
hash['url'] = api_v1_outcome_path :id => outcome.id
|
||||||
hash['can_edit'] = outcome.context_id ?
|
hash['can_edit'] = outcome.context_id ?
|
||||||
outcome.context.grants_right?(user, session, :manage_outcomes) :
|
outcome.context.grants_right?(user, session, :manage_outcomes) :
|
||||||
|
|
|
@ -623,6 +623,7 @@ describe "Outcome Groups API", type: :request do
|
||||||
"context_type" => "Account",
|
"context_type" => "Account",
|
||||||
"context_id" => @account.id,
|
"context_id" => @account.id,
|
||||||
"title" => outcome.title,
|
"title" => outcome.title,
|
||||||
|
"display_name" => nil,
|
||||||
"url" => api_v1_outcome_path(:id => outcome.id),
|
"url" => api_v1_outcome_path(:id => outcome.id),
|
||||||
"can_edit" => true
|
"can_edit" => true
|
||||||
}
|
}
|
||||||
|
@ -772,6 +773,7 @@ describe "Outcome Groups API", type: :request do
|
||||||
"context_type" => nil,
|
"context_type" => nil,
|
||||||
"context_id" => nil,
|
"context_id" => nil,
|
||||||
"title" => @outcome.title,
|
"title" => @outcome.title,
|
||||||
|
"display_name" => nil,
|
||||||
"url" => api_v1_outcome_path(:id => @outcome.id),
|
"url" => api_v1_outcome_path(:id => @outcome.id),
|
||||||
"can_edit" => false
|
"can_edit" => false
|
||||||
}
|
}
|
||||||
|
@ -815,6 +817,7 @@ describe "Outcome Groups API", type: :request do
|
||||||
:id => @group.id.to_s,
|
:id => @group.id.to_s,
|
||||||
:format => 'json' },
|
:format => 'json' },
|
||||||
{ :title => "My Outcome",
|
{ :title => "My Outcome",
|
||||||
|
:display_name => "Friendly Name",
|
||||||
:description => "Description of my outcome",
|
:description => "Description of my outcome",
|
||||||
:mastery_points => 5,
|
:mastery_points => 5,
|
||||||
:ratings => [
|
:ratings => [
|
||||||
|
@ -826,6 +829,7 @@ describe "Outcome Groups API", type: :request do
|
||||||
LearningOutcome.active.count.should == 1
|
LearningOutcome.active.count.should == 1
|
||||||
@outcome = LearningOutcome.active.first
|
@outcome = LearningOutcome.active.first
|
||||||
@outcome.title.should == "My Outcome"
|
@outcome.title.should == "My Outcome"
|
||||||
|
@outcome.display_name.should == "Friendly Name"
|
||||||
@outcome.description.should == "Description of my outcome"
|
@outcome.description.should == "Description of my outcome"
|
||||||
@outcome.data[:rubric_criterion].should == {
|
@outcome.data[:rubric_criterion].should == {
|
||||||
:description => 'My Outcome',
|
:description => 'My Outcome',
|
||||||
|
@ -955,6 +959,7 @@ describe "Outcome Groups API", type: :request do
|
||||||
"vendor_guid" => @outcome.vendor_guid,
|
"vendor_guid" => @outcome.vendor_guid,
|
||||||
"context_type" => nil,
|
"context_type" => nil,
|
||||||
"context_id" => nil,
|
"context_id" => nil,
|
||||||
|
"display_name" => nil,
|
||||||
"title" => @outcome.title,
|
"title" => @outcome.title,
|
||||||
"url" => api_v1_outcome_path(:id => @outcome.id),
|
"url" => api_v1_outcome_path(:id => @outcome.id),
|
||||||
"can_edit" => false
|
"can_edit" => false
|
||||||
|
|
|
@ -104,6 +104,7 @@ describe "Outcomes API", type: :request do
|
||||||
"context_id" => @account.id,
|
"context_id" => @account.id,
|
||||||
"context_type" => "Account",
|
"context_type" => "Account",
|
||||||
"title" => @outcome.title,
|
"title" => @outcome.title,
|
||||||
|
"display_name" => nil,
|
||||||
"url" => api_v1_outcome_path(:id => @outcome.id),
|
"url" => api_v1_outcome_path(:id => @outcome.id),
|
||||||
"vendor_guid" => "vendorguid9000",
|
"vendor_guid" => "vendorguid9000",
|
||||||
"can_edit" => true,
|
"can_edit" => true,
|
||||||
|
@ -134,6 +135,7 @@ describe "Outcomes API", type: :request do
|
||||||
"context_id" => @account.id,
|
"context_id" => @account.id,
|
||||||
"context_type" => "Account",
|
"context_type" => "Account",
|
||||||
"title" => @outcome.title,
|
"title" => @outcome.title,
|
||||||
|
"display_name" => nil,
|
||||||
"url" => api_v1_outcome_path(:id => @outcome.id),
|
"url" => api_v1_outcome_path(:id => @outcome.id),
|
||||||
"vendor_guid" => "vendorguid9000",
|
"vendor_guid" => "vendorguid9000",
|
||||||
"can_edit" => true,
|
"can_edit" => true,
|
||||||
|
@ -250,6 +252,7 @@ describe "Outcomes API", type: :request do
|
||||||
"context_type" => "Account",
|
"context_type" => "Account",
|
||||||
"vendor_guid" => "vendorguid9000",
|
"vendor_guid" => "vendorguid9000",
|
||||||
"title" => "New Title",
|
"title" => "New Title",
|
||||||
|
"display_name" => nil,
|
||||||
"url" => api_v1_outcome_path(:id => @outcome.id),
|
"url" => api_v1_outcome_path(:id => @outcome.id),
|
||||||
"can_edit" => true,
|
"can_edit" => true,
|
||||||
"description" => "New Description"
|
"description" => "New Description"
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
#
|
||||||
|
# Copyright (C) 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/>.
|
||||||
|
|
||||||
|
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper.rb')
|
||||||
|
|
||||||
|
class Subject
|
||||||
|
include Api::V1::Outcome
|
||||||
|
|
||||||
|
def api_v1_outcome_path(opts)
|
||||||
|
"/api/v1/outcome/#{opts.fetch(:id)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Api::V1::Outcome" do
|
||||||
|
describe "#outcome_json" do
|
||||||
|
it "includes the display name from the outcome" do
|
||||||
|
outcome = LearningOutcome.new(display_name: "MyFavoriteOutcome")
|
||||||
|
subj = Subject.new
|
||||||
|
result = subj.outcome_json(outcome, nil, nil)
|
||||||
|
result['display_name'].should == "MyFavoriteOutcome"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -153,10 +153,8 @@ def should_create_a_learning_outcome_nested
|
||||||
replace_content(f('.outcomes-content input[name=title]'), outcome_name)
|
replace_content(f('.outcomes-content input[name=title]'), outcome_name)
|
||||||
|
|
||||||
# submit
|
# submit
|
||||||
driver.execute_script("$('.submit_button').click()")
|
f('.submit_button').click
|
||||||
if !f('.submit_button').nil?
|
wait_for_ajaximations
|
||||||
driver.execute_script("$('.submit_button').click()")
|
|
||||||
end
|
|
||||||
refresh_page
|
refresh_page
|
||||||
|
|
||||||
#select group
|
#select group
|
||||||
|
|
Loading…
Reference in New Issue