return user display info for collection items

This standardizes the "user display" sub-object returned by the
discussions api, and returns that same data for each collection item.

test plan: make api calls to return collection items, verify the user
sub-object is present and contains the expected user data.

Change-Id: Ie5b1468816ffbf27a005044effbc49082bdf679b
Reviewed-on: https://gerrit.instructure.com/11276
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
This commit is contained in:
Brian Palmer 2012-06-04 08:56:39 -06:00
parent 9be4a8edea
commit 06b10f6a34
15 changed files with 122 additions and 100 deletions

View File

@ -35,9 +35,8 @@
#
# /api/v1/collection_items/<id>/discussion_topics/self/view
#
# A Collection Item object looks like:
# @object Collection Item
#
# !!!javascript
# {
# // The ID of the collection item.
# id: 7,
@ -45,9 +44,6 @@
# // The ID of the collection that this item belongs to.
# collection_id: 2,
#
# // The ID of the user that created the collection item.
# user_id: 37,
#
# // The type of the item.
# // Currently defined types are: "url", "image", "audio", and "video".
# //
@ -115,6 +111,21 @@
#
# // The timestamp of when the item was posted by the user
# created_at: "2012-05-30T17:45:25Z",
#
# // Information on the user that created the collection item.
# user : {
# // The ID of the user.
# id: 37,
#
# // The display name of the user.
# display_name: "John Doe",
#
# // The URL of the user's avatar image, or a fallback image if the user has not given one.
# avatar_image_url: "http://...",
#
# // The URL to the HTML page in Canvas of this user's public profile.
# html_url: "http://<canvas>/users/37"
# },
# }
class CollectionItemsController < ApplicationController
before_filter :require_collection, :only => [:index, :create]
@ -132,29 +143,11 @@ class CollectionItemsController < ApplicationController
# curl https://<canvas>/api/v1/collections/<collection_id>/items \
# -H 'Authorization: Bearer <token>'
#
# @example_response
# [
# {
# id: 7,
# collection_id: 2,
# item_type: "url",
# link_url: "https://example.com/some/path",
# post_count: 2,
# upvote_count: 3,
# upvoted_by_user: false,
# root_item_id: 3,
# image_url: "https://<canvas>/files/item_image.png",
# title: "my title",
# description: "some block of plain text",
# user_comment: nil,
# url: "https://<canvas>/api/v1/collections/items/7"
# created_at: "2012-05-30T17:45:25Z",
# }
# ]
# @returns [Collection Item]
def index
pagination_route = api_v1_collection_items_list_url(@collection)
if authorized_action(@collection, @current_user, :read)
@items = Api.paginate(@collection.collection_items.active.newest_first, self, pagination_route)
@items = Api.paginate(@collection.collection_items.active.newest_first.scoped(:include => :user), self, pagination_route)
render :json => collection_items_json(@items, @current_user, session)
end
end
@ -169,23 +162,7 @@ class CollectionItemsController < ApplicationController
# curl https://<canvas>/api/v1/collections/items/<item_id> \
# -H 'Authorization: Bearer <token>'
#
# @example_response
# {
# id: 7,
# collection_id: 2,
# item_type: "url",
# link_url: "https://example.com/some/path",
# post_count: 2,
# upvote_count: 3,
# upvoted_by_user: false,
# root_item_id: 3,
# image_url: "https://<canvas>/files/item_image.png",
# title: "my title",
# description: "some block of plain text",
# user_comment: nil,
# url: "https://<canvas>/api/v1/collections/items/7"
# created_at: "2012-05-30T17:45:25Z",
# }
# @returns Collection Item
def show
find_item_and_collection
if authorized_action(@item, @current_user, :read)
@ -237,6 +214,7 @@ class CollectionItemsController < ApplicationController
# -F user_comment="clone of some other item" \
# -H 'Authorization: Bearer <token>'
#
# @returns Collection Item
def create
@item = @collection.collection_items.new(:user => @current_user)
if authorized_action(@item, @current_user, :create)
@ -271,6 +249,7 @@ class CollectionItemsController < ApplicationController
# -F user_comment='edited comment' \
# -H 'Authorization: Bearer <token>'
#
# @returns Collection Item
def update
find_item_and_collection
if authorized_action(@item, @current_user, :update)
@ -293,6 +272,8 @@ class CollectionItemsController < ApplicationController
# curl https://<canvas>/api/v1/collections/items/<item_id> \
# -X DELETE \
# -H 'Authorization: Bearer <token>'
#
# @returns Collection Item
def destroy
find_item_and_collection
if authorized_action(@item, @current_user, :delete)
@ -384,7 +365,7 @@ class CollectionItemsController < ApplicationController
end
def find_item_and_collection
@item = CollectionItem.active.find(params[:item_id])
@item = CollectionItem.active.find(params[:item_id], :include => :user)
@collection = @item.active_collection
end

View File

@ -32,9 +32,7 @@
# Group collections can only be created, updated, or deleted by group
# moderators.
#
# A Collection object looks like:
#
# !!!javascript
# @object Collection
# {
# // The ID of the collection.
# id: 5,
@ -69,21 +67,7 @@ class CollectionsController < ApplicationController
# curl -H 'Authorization: Bearer <token>' \
# https://<canvas>/api/v1/users/self/collections
#
# @example_response
# [
# {
# id: 1,
# name: "My Collection",
# visibility: "public",
# followed_by_user: false
# },
# {
# id: 2,
# name: "My Personal Collection",
# visibility: "private",
# followed_by_user: true
# }
# ]
# @returns [Collection]
def index
collection_route = polymorphic_url([:api_v1, @context, :collections])
scope = @context.collections.active.newest_first
@ -111,13 +95,7 @@ class CollectionsController < ApplicationController
# curl -H 'Authorization: Bearer <token>' \
# https://<canvas>/api/v1/collections/<collection_id>
#
# @example_response
# {
# id: 1,
# name: "My Collection",
# visibility: "public",
# followed_by_user: false
# }
# @returns Collection
def show
@collection = find_collection
if authorized_action(@collection, @current_user, :read)
@ -139,13 +117,7 @@ class CollectionsController < ApplicationController
# -F visibility=public \
# https://<canvas>/api/v1/users/self/collections
#
# @example_response
# {
# id: 1,
# name: "My Collection",
# visibility: "public",
# followed_by_user: false
# }
# @returns Collection
def create
@collection = @context.collections.new(params.slice(*SETTABLE_ATTRIBUTES))
if authorized_action(@collection, @current_user, :create)
@ -174,13 +146,7 @@ class CollectionsController < ApplicationController
# -F name='My Edited Collection' \
# https://<canvas>/api/v1/collections/<collection_id>
#
# @example_response
# {
# id: 1,
# name: "My Edited Collection",
# visibility: "public",
# followed_by_user: false
# }
# @returns Collection
def update
@collection = find_collection
if authorized_action(@collection, @current_user, :update)
@ -202,13 +168,7 @@ class CollectionsController < ApplicationController
# -X DELETE \
# https://<canvas>/api/v1/collections/<collection_id>
#
# @example_response
# {
# id: 1,
# name: "My Collection",
# visibility: "public",
# followed_by_user: false
# }
# @returns Collection
def destroy
@collection = find_collection
if authorized_action(@collection, @current_user, :delete)

View File

@ -21,6 +21,7 @@
# API for accessing and participating in discussion topics in groups and courses.
class DiscussionTopicsApiController < ApplicationController
include Api::V1::DiscussionTopics
include Api::V1::User
before_filter :require_context
before_filter :require_topic
@ -75,8 +76,8 @@ class DiscussionTopicsApiController < ApplicationController
# {
# "unread_entries": [1,3,4],
# "participants": [
# { "id": 10, "display_name": "user 1", "avatar_url": "https://..." },
# { "id": 11, "display_name": "user 2", "avatar_url": "https://..." }
# { "id": 10, "display_name": "user 1", "avatar_image_url": "https://...", "html_url": "https://..." },
# { "id": 11, "display_name": "user 2", "avatar_image_url": "https://...", "html_url": "https://..." }
# ],
# "view": [
# { "id": 1, "user_id": 10, "parent_id": null, "message": "...html text...", "replies": [
@ -92,8 +93,7 @@ class DiscussionTopicsApiController < ApplicationController
if structure
participant_info = User.find(participant_ids).map do |user|
participant_url = @context.is_a?(CollectionItem) ? user_url(user) : polymorphic_url([@context, user])
{ :id => user.id, :display_name => user.short_name, :avatar_image_url => avatar_image_url(User.avatar_key(user.id)), :html_url => participant_url }
user_display_json(user, @context.is_a_context? && @context)
end
unread_entries = entry_ids - DiscussionEntryParticipant.read_entry_ids(entry_ids, @current_user)
# as an optimization, the view structure is pre-serialized as a json

View File

@ -53,6 +53,7 @@ def init
options[:page_title] = "Canvas LMS REST API Documentation"
build_json_objects_map
generate_assets
serialize_index
serialize_static_pages
@ -136,3 +137,20 @@ def serialize_static_pages
options.delete(:file)
end
end
def build_json_objects_map
obj_map = {}
resource_obj_list = {}
options[:resources].each do |r,cs|
cs.each do |controller|
controller.tags(:object).each do |obj|
name, json = obj.text.split(%r{\n+}, 2).map(&:strip)
obj_map[name] = topicize r
resource_obj_list[r] ||= []
resource_obj_list[r] << [name, json]
end
end
end
options[:json_objects_map] = obj_map
options[:json_objects] = resource_obj_list
end

View File

@ -18,7 +18,6 @@
SyntaxHighlighter.defaults['gutter'] = false;
SyntaxHighlighter.defaults['toolbar'] = false;
SyntaxHighlighter.defaults['auto-links'] = false;
$('.example_response pre.example.code').addClass('brush: js');
$('#content pre.code.javascript code').each(function(i, el) {
$(el).parent().addClass('brush: js');
$(el).replaceWith(el.innerHTML);

View File

@ -3,7 +3,7 @@
<h4>Example Response:</h4>
<% object.tags(:example_response).each do |tag| %>
<h4><%= htmlify_line(tag.name) %></h4>
<pre class="example code"><%= html_syntax_highlight(tag.text, :plain) %></pre>
<pre class="example code brush:js"><%= html_syntax_highlight(tag.text, :plain) %></pre>
<% end %>
</div>
<% end %>

View File

@ -0,0 +1,6 @@
<% if @is_list %>
Returns a list of
<% else %>
Returns a
<% end %>
<a href='<%= @resource_name %>.html#<%= @object_name %>'><%= @is_list ? @object_name.pluralize : @object_name %></a>

View File

@ -18,7 +18,7 @@
def init
super
sections :argument, :request_field, :response_field, :example_request, :example_response
sections :argument, :request_field, :response_field, :example_request, :example_response, :returns
end
def request_field
@ -33,6 +33,22 @@ def argument
generic_tag :argument, :no_types => false, :label => "Request Parameters"
end
def returns
return unless object.has_tag?(:returns)
response_info = object.tag(:returns)
case response_info.text
when %r{\[(.*)\]}
@object_name = $1.strip
@is_list = true
else
@object_name = response_info.text.strip
@is_list = false
end
@resource_name = options[:json_objects_map][@object_name]
return unless @resource_name
erb(:returns)
end
def generic_tag(name, opts = {})
return unless object.has_tag?(name)
@no_names = true if opts[:no_names]

View File

@ -36,5 +36,6 @@ end
def topic_doc
@docstring = options[:controllers].map { |c| c.docstring }.join("\n\n")
def @object.source_type; nil; end
htmlify(@docstring.strip, :markdown)
@json_objects = options[:json_objects][@resource] || []
erb(:topic_doc)
end

View File

@ -0,0 +1,11 @@
<%= htmlify(@docstring.strip, :markdown) %>
<% @json_objects.each do |name, json| %>
<div class='object_definition'>
<h3><a name="<%= name %>">A <%= name %> object looks like:</a></h3>
<pre class="example code brush:js">
<%= html_syntax_highlight(json, :plain) %>
</pre>
</div>
<% end %>

View File

@ -18,13 +18,14 @@
module Api::V1::Collection
include Api::V1::Json
include Api::V1::User
API_COLLECTION_JSON_OPTS = {
:only => %w(id name visibility),
}
API_COLLECTION_ITEM_JSON_OPTS = {
:only => %w(id collection_id user_id user_comment created_at),
:only => %w(id collection_id user_comment created_at),
}
API_COLLECTION_ITEM_DATA_JSON_OPTS = {
@ -54,6 +55,7 @@ module Api::V1::Collection
items.map do |item|
hash = api_json(item, current_user, session, API_COLLECTION_ITEM_JSON_OPTS)
hash['user'] = user_display_json(item.user)
hash['url'] = api_v1_collection_item_url(item)
item_data = item.collection_item_data
hash['image_pending'] = item_data.image_pending

View File

@ -52,6 +52,22 @@ module Api::V1::User
end
end
# this mini-object is used for secondary user responses, when we just want to
# provide enough information to display a user.
# for instance, discussion entries return this json as a sub-object.
#
# if parent_context is given, the html_url will be scoped to that context, so:
# /courses/X/users/Y
# otherwise it'll just be:
# /users/Y
# keep in mind the latter form is only accessible if the user has a public profile
# (or if the api caller is an admin)
def user_display_json(user, parent_context = nil)
return {} unless user
participant_url = parent_context ? polymorphic_url([parent_context, user]) : user_url(user)
{ :id => user.id, :display_name => user.short_name, :avatar_image_url => avatar_image_url(User.avatar_key(user.id)), :html_url => participant_url }
end
# optimization hint, currently user only needs to pull pseudonyms from the db
# if a site admin is making the request or they can manage_students
def user_json_is_admin?(context = @context, current_user = @current_user)

View File

@ -36,6 +36,8 @@ YARD::Tags::Library.define_tag("API example request", :example_request)
YARD::Tags::Library.define_tag("API example response", :example_response)
YARD::Tags::Library.define_tag("API subtopic", :subtopic)
YARD::Tags::Library.define_tag("API resource is Beta", :beta)
YARD::Tags::Library.define_tag("API Object Definition", :object)
YARD::Tags::Library.define_tag("API Return Type", :returns)
module YARD::Templates::Helpers
module BaseHelper

View File

@ -169,7 +169,12 @@ describe "Collections API", :type => :integration do
{
'id' => item.id,
'collection_id' => item.collection_id,
'user_id' => item.user_id,
'user' => {
'id' => item.user.id,
'display_name' => item.user.short_name,
'avatar_image_url' => "http://www.example.com/images/users/#{User.avatar_key(item.user.id)}",
'html_url' => (item.user == @user) ? "http://www.example.com/profile" : "http://www.example.com/users/#{item.user.id}",
},
'item_type' => item.collection_item_data.item_type,
'link_url' => item.collection_item_data.link_url,
'post_count' => item.collection_item_data.post_count,

View File

@ -374,7 +374,12 @@ describe UsersController, :type => :integration do
'collection_item' => {
'id' => @item.id,
'collection_id' => @item.collection_id,
'user_id' => @item.user_id,
'user' => {
'id' => @item.user.id,
'display_name' => @item.user.short_name,
'avatar_image_url' => "http://www.example.com/images/users/#{User.avatar_key(@item.user.id)}",
'html_url' => (@item.user == @user) ? "http://www.example.com/profile" : "http://www.example.com/users/#{@item.user.id}",
},
'item_type' => @item.collection_item_data.item_type,
'link_url' => @item.collection_item_data.link_url,
'post_count' => @item.collection_item_data.post_count,