adding additional api support for the app center
Reworked how the calls were passed through to take advantage of the app center pagination. Also added an endpoint for retrieving app reviews. The following canvas api calls should work, after configuring the app center plugin - /api/v1/accounts/:account_id/app_center/apps - /api/v1/accounts/:account_id/app_center/apps/:app_id/reviews - /api/v1/courses/:course_id/app_center/apps - /api/v1/courses/:course_id/app_center/apps/:app_id/reviews These should also accept a page and per_page params Change-Id: I15fe1225c01d04bc2ad6efa1736013b88e541c51 Reviewed-on: https://gerrit.instructure.com/20455 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jeremy Stanley <jeremy@instructure.com> Product-Review: Brian Whitmer <brian@instructure.com> QA-Review: Clare Strong <clare@instructure.com>
This commit is contained in:
parent
4735e82b6e
commit
be800d1e1f
|
@ -19,17 +19,30 @@
|
|||
class AppCenterController < ApplicationController
|
||||
before_filter :require_context
|
||||
|
||||
def index
|
||||
collection = PaginatedCollection.build do |pager|
|
||||
current_page = pager.current_page ? pager.current_page.to_i - 1 : 0
|
||||
apps = AppCenter::AppApi.new.get_apps(current_page * pager.per_page, pager.per_page) || []
|
||||
pager.replace(apps)
|
||||
pager.next_page = current_page + 2 if apps.size > 0
|
||||
def generate_app_api_collection(base_url)
|
||||
PaginatedCollection.build do |pager|
|
||||
page = (params['page'] || 1).to_i
|
||||
response = yield AppCenter::AppApi.new, page
|
||||
response ||= {}
|
||||
pager.replace(response['objects'] || [])
|
||||
pager.next_page = response['meta']['next_page'] if response['meta']
|
||||
pager
|
||||
end
|
||||
end
|
||||
|
||||
def index
|
||||
per_page = params['per_page'] || 72
|
||||
endpoint_scope = (@context.is_a?(Account) ? 'account' : 'course')
|
||||
render :json => Api.paginate(collection, self,
|
||||
send("api_v1_#{endpoint_scope}_app_center_apps_url"))
|
||||
base_url = send("api_v1_#{endpoint_scope}_app_center_apps_url")
|
||||
collection = generate_app_api_collection(base_url) {|app_api, page| app_api.get_apps(page, per_page)}
|
||||
render :json => Api.paginate(collection, self, base_url, :per_page => per_page.to_i)
|
||||
end
|
||||
|
||||
def reviews
|
||||
per_page = params['per_page'] || 15
|
||||
endpoint_scope = (@context.is_a?(Account) ? 'account' : 'course')
|
||||
base_url = send("api_v1_#{endpoint_scope}_app_center_app_reviews_url")
|
||||
collection = generate_app_api_collection(base_url) {|app_api, page| app_api.get_app_reviews(params[:app_id], page, per_page)}
|
||||
render :json => Api.paginate(collection, self, base_url, :per_page => per_page.to_i)
|
||||
end
|
||||
end
|
|
@ -25,9 +25,15 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= f.blabel :apps_endpoint, :en => "Apps Endpoint" %></td>
|
||||
<td><%= f.blabel :apps_index_endpoint, :en => "Apps Index Endpoint" %></td>
|
||||
<td>
|
||||
<%= f.text_field :apps_endpoint %>
|
||||
<%= f.text_field :apps_index_endpoint %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= f.blabel :app_reviews_endpoint, :en => "App Show Endpoint" %></td>
|
||||
<td>
|
||||
<%= f.text_field :app_reviews_endpoint %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -1137,8 +1137,11 @@ ActionController::Routing::Routes.draw do |map|
|
|||
end
|
||||
|
||||
api.with_options(:controller => :app_center) do |app_center|
|
||||
app_center.get 'accounts/:account_id/app_center/apps', :action => :index, :path_name => 'account_app_center_apps'
|
||||
app_center.get 'courses/:course_id/app_center/apps', :action => :index, :path_name => 'course_app_center_apps'
|
||||
['course', 'account'].each do |context|
|
||||
prefix = "#{context}s/:#{context}_id/app_center"
|
||||
app_center.get "#{prefix}/apps", :action => :index, :path_name => "#{context}_app_center_apps"
|
||||
app_center.get "#{prefix}/apps/:app_id/reviews", :action => :reviews, :path_name => "#{context}_app_center_app_reviews"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,27 +6,47 @@ module AppCenter
|
|||
@app_center = Canvas::Plugin.find(:app_center)
|
||||
end
|
||||
|
||||
def get_apps(offset = 0, per_page = 10)
|
||||
if @app_center && @app_center.enabled? && !@app_center.settings['base_url'].empty?
|
||||
def valid_app_center?
|
||||
@app_center && @app_center.enabled? && !@app_center.settings['base_url'].empty?
|
||||
end
|
||||
|
||||
base_url = @app_center.settings['base_url']
|
||||
endpoint = @app_center.settings['apps_endpoint']
|
||||
response = Rails.cache.fetch(['app_center', base_url, endpoint, offset, per_page].cache_key, :expires_in => 5.minutes) do
|
||||
path = "#{@app_center.settings['apps_endpoint']}?page=#{offset}&per_page=#{per_page}"
|
||||
response = Canvas::HTTP.get("#{@app_center.settings['base_url']}/#{path}").body
|
||||
end
|
||||
def fetch_app_center_response(endpoint, expires, page, per_page)
|
||||
return {} unless valid_app_center?
|
||||
|
||||
apps = JSON.parse(response)['objects']
|
||||
base_url = @app_center.settings['base_url']
|
||||
page = page.to_i
|
||||
per_page = per_page.to_i
|
||||
offset = ( page - 1 ) * per_page
|
||||
|
||||
#Temporary hack until edu-apps pagination works correctly
|
||||
if apps.size > per_page
|
||||
apps = apps[offset, per_page]
|
||||
end
|
||||
|
||||
return apps
|
||||
else
|
||||
return []
|
||||
cache_key = ['app_center', base_url, endpoint, offset].cache_key
|
||||
response = Rails.cache.fetch(cache_key, :expires_in => expires) do
|
||||
uri = URI.parse("#{base_url}#{endpoint}")
|
||||
uri.query = [uri.query, "offset=#{offset}"].compact.join('&')
|
||||
Canvas::HTTP.get(uri.to_s).body
|
||||
end
|
||||
|
||||
begin
|
||||
json = JSON.parse(response)
|
||||
json['meta']['next_page'] = page + 1 if (json['meta'] && json['meta']['next']) || (json['objects'] && json['objects'].size > per_page)
|
||||
json['objects'] = json['objects'][0, per_page] if json['objects']
|
||||
rescue
|
||||
json = {}
|
||||
Rails.cache.delete cache_key
|
||||
end
|
||||
|
||||
return json
|
||||
end
|
||||
|
||||
def get_apps(page = 1, per_page = 72)
|
||||
return {} unless valid_app_center?
|
||||
|
||||
fetch_app_center_response(@app_center.settings['apps_index_endpoint'], 5.minutes, page, per_page)
|
||||
end
|
||||
|
||||
def get_app_reviews(id, page = 1, per_page = 15)
|
||||
return {} unless valid_app_center?
|
||||
|
||||
fetch_app_center_response(@app_center.settings['app_reviews_endpoint'].gsub(':id', id.to_s), 60.minutes, page, per_page)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -29,7 +29,7 @@ module Canvas::HTTP
|
|||
raise(TooManyRedirectsError) if redirect_limit <= 0
|
||||
|
||||
url, uri = CustomValidations.validate_url(url_str)
|
||||
request = Net::HTTP::Get.new(uri.path, other_headers)
|
||||
request = Net::HTTP::Get.new(uri.request_uri, other_headers)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl = true if uri.scheme == 'https'
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
|
|
|
@ -240,7 +240,8 @@ Canvas::Plugin.register('app_center', nil, {
|
|||
:settings => {
|
||||
:base_url => 'https://www.edu-apps.org',
|
||||
:token => nil,
|
||||
:apps_endpoint => '/api/v1/apps'
|
||||
:apps_index_endpoint => '/api/v1/apps',
|
||||
:app_reviews_endpoint => '/api/v1/apps/:id/reviews'
|
||||
},
|
||||
:validator => 'AppCenterValidator'
|
||||
})
|
||||
|
|
|
@ -30,7 +30,7 @@ module Canvas::Plugins::Validators::AppCenterValidator
|
|||
plugin_setting.errors.add_to_base(I18n.t('canvas.plugins.errors.invalid_url', 'Invalid URL'))
|
||||
false
|
||||
else
|
||||
settings.slice(:base_url, :token)
|
||||
settings.slice(:base_url, :token, :apps_index_endpoint, :app_reviews_endpoint)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
#
|
||||
# Copyright (C) 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__) + '/../spec_helper')
|
||||
|
||||
describe AppCenterController do
|
||||
describe "#generate_app_api_collection" do
|
||||
let(:controller) do
|
||||
controller = AppCenterController.new
|
||||
controller.params = {}
|
||||
controller
|
||||
end
|
||||
|
||||
it "generates a valid paginated collection" do
|
||||
objects = ['object1', 'object2']
|
||||
next_page = 10
|
||||
will_paginate = controller.generate_app_api_collection('www.example.com/endpoint') do |app_api, page|
|
||||
app_api.should be_a AppCenter::AppApi
|
||||
page.should == 1
|
||||
{
|
||||
'objects' => ['object1', 'object2'],
|
||||
'meta' => {"next_page" => next_page}
|
||||
}
|
||||
end
|
||||
will_paginate.should be_a PaginatedCollection::Proxy
|
||||
collection = will_paginate.paginate(:per_page => 72)
|
||||
collection.should == objects
|
||||
collection.next_page.should == next_page
|
||||
end
|
||||
|
||||
it "handles an empty response" do
|
||||
will_paginate = controller.generate_app_api_collection('') {}
|
||||
will_paginate.paginate(:per_page => 50).should == []
|
||||
end
|
||||
|
||||
it "passes the page param as the offset" do
|
||||
controller.params['page'] = 32
|
||||
controller.params['per_page'] = 12
|
||||
will_paginate = controller.generate_app_api_collection('') do |app_api, page, per_page|
|
||||
page.should == controller.params['page']
|
||||
per_page.should == controller.params['per_pages']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -21,31 +21,12 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb')
|
|||
# Manually stubbing the actual API request
|
||||
describe AppCenter::AppApi do
|
||||
let(:api){ AppCenter::AppApi.new }
|
||||
let(:response) do
|
||||
response = mock
|
||||
response.stubs(:body).returns(
|
||||
{
|
||||
'meta' => { "next" => "https://www.example.com/api/v1/apps?offset=72"},
|
||||
'current_offset' => 0,
|
||||
'limit' => 72,
|
||||
'objects' => [
|
||||
{
|
||||
'name' => 'First Tool',
|
||||
'id' => 'first_tool',
|
||||
},
|
||||
{
|
||||
'name' => 'Second Tool',
|
||||
'id' => 'second_tool',
|
||||
}
|
||||
]
|
||||
}.to_json
|
||||
)
|
||||
response
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
default_settings = api.app_center.default_settings
|
||||
default_settings['base_url'] = 'www.example.com'
|
||||
default_settings['apps_index_endpoint'] = '/apps'
|
||||
default_settings['app_reviews_endpoint'] = '/apps/:id'
|
||||
PluginSetting.create(:name => api.app_center.id, :settings => default_settings)
|
||||
end
|
||||
|
||||
|
@ -56,41 +37,130 @@ describe AppCenter::AppApi do
|
|||
api.app_center.settings['base_url'].should_not be_empty
|
||||
end
|
||||
|
||||
describe '#fetch_app_center_response' do
|
||||
let(:response) do
|
||||
response = mock
|
||||
response.stubs(:body).returns(
|
||||
{
|
||||
'meta' => { "next" => "https://www.example.com/api/v1/apps?offset=60"},
|
||||
'current_offset' => 0,
|
||||
'limit' => 50,
|
||||
'objects' => %w(object1 object2 object3 object4)
|
||||
}.to_json
|
||||
)
|
||||
response
|
||||
end
|
||||
|
||||
it "can handle query params in the endpoint" do
|
||||
endpoint = '/?myparam=value'
|
||||
per_page = 11
|
||||
page = 3
|
||||
Canvas::HTTP.expects(:get).with("#{api.app_center.settings['base_url']}#{endpoint}&offset=#{page * per_page - per_page}").returns(response)
|
||||
api.fetch_app_center_response(endpoint, 11.minutes, page, per_page)
|
||||
end
|
||||
|
||||
it "can handle an invalid response" do
|
||||
response.stubs(:body).returns('')
|
||||
Canvas::HTTP.expects(:get).returns(response)
|
||||
api.fetch_app_center_response('', 12.minutes, 8, 3).should == {}
|
||||
end
|
||||
|
||||
it "resets the cache when getting an invalid response" do
|
||||
enable_cache do
|
||||
response.stubs(:body).returns('')
|
||||
Canvas::HTTP.expects(:get).returns(response).twice()
|
||||
api.fetch_app_center_response('', 13.minutes, 7,4).should == {}
|
||||
api.fetch_app_center_response('', 13.minutes, 7,4).should == {}
|
||||
end
|
||||
end
|
||||
|
||||
it "can handle an error response" do
|
||||
message = {"message" => "Tool not found","type" =>"error"}
|
||||
response.stubs(:body).returns(message.to_json)
|
||||
Canvas::HTTP.expects(:get).returns(response)
|
||||
api.fetch_app_center_response('', 13.minutes, 6,9).should == message
|
||||
end
|
||||
|
||||
it "respects per_page param" do
|
||||
endpoint = '/?myparam=value'
|
||||
per_page = 1
|
||||
page = 1
|
||||
offset = page * per_page - per_page
|
||||
Canvas::HTTP.expects(:get).with("#{api.app_center.settings['base_url']}#{endpoint}&offset=#{offset}").returns(response)
|
||||
response = api.fetch_app_center_response(endpoint, 11.minutes, page, per_page)
|
||||
results = response['objects']
|
||||
results.size.should == 1
|
||||
results.first.should == 'object1'
|
||||
response['meta']['next_page'].should == 2
|
||||
end
|
||||
|
||||
it "can omit next page" do
|
||||
message = {"objects" => %w(object1 object2 object3 object4), "meta" => {}}
|
||||
response.stubs(:body).returns(message.to_json)
|
||||
endpoint = '/?myparam=value'
|
||||
per_page = 5
|
||||
page = 1
|
||||
offset = page * per_page - per_page
|
||||
Canvas::HTTP.expects(:get).with("#{api.app_center.settings['base_url']}#{endpoint}&offset=#{offset}").returns(response)
|
||||
response = api.fetch_app_center_response(endpoint, 11.minutes, page, per_page)
|
||||
results = response['objects']
|
||||
results.size.should == 4
|
||||
response['meta']['next_page'].should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_apps' do
|
||||
before(:each) { Canvas::HTTP.stubs(:get).returns(response)}
|
||||
let(:response) do
|
||||
response = mock
|
||||
response.stubs(:body).returns(
|
||||
{
|
||||
'meta' => { "next" => "https://www.example.com/api/v1/apps?offset=72"},
|
||||
'current_offset' => 0,
|
||||
'limit' => 72,
|
||||
'objects' => [
|
||||
{
|
||||
'name' => 'First Tool',
|
||||
'id' => 'first_tool',
|
||||
},
|
||||
{
|
||||
'name' => 'Second Tool',
|
||||
'id' => 'second_tool',
|
||||
}
|
||||
]
|
||||
}.to_json
|
||||
)
|
||||
response
|
||||
end
|
||||
|
||||
it "gets a list of apps" do
|
||||
apps = api.get_apps
|
||||
Canvas::HTTP.stubs(:get).returns(response)
|
||||
apps = api.get_apps()['objects']
|
||||
apps.should be_a Array
|
||||
apps.size.should == 2
|
||||
end
|
||||
|
||||
it "returns an empty array if the app center is disabled" do
|
||||
it "returns an empty hash if the app center is disabled" do
|
||||
Canvas::HTTP.stubs(:get).returns(response)
|
||||
setting = PluginSetting.find_by_name(api.app_center.id)
|
||||
setting.destroy
|
||||
|
||||
api.app_center.should_not be_enabled
|
||||
|
||||
apps = api.get_apps
|
||||
apps.should be_a Array
|
||||
apps.size.should == 0
|
||||
response = api.get_apps()
|
||||
response.should be_a Hash
|
||||
response.size.should == 0
|
||||
end
|
||||
|
||||
it "respects per_page" do
|
||||
apps = api.get_apps(0, 1)
|
||||
apps.size.should == 1
|
||||
apps.first['id'].should == 'first_tool'
|
||||
end
|
||||
|
||||
it "respects offset" do
|
||||
apps = api.get_apps(1,1)
|
||||
apps.size.should == 1
|
||||
apps.first['id'].should == 'second_tool'
|
||||
end
|
||||
|
||||
it "caches results" do
|
||||
it "gets the next page" do
|
||||
enable_cache do
|
||||
Canvas::HTTP.stubs(:get).returns(response)
|
||||
response = api.get_apps
|
||||
response['meta']['next_page'].should == 2
|
||||
end
|
||||
end
|
||||
|
||||
it "caches apps results" do
|
||||
enable_cache do
|
||||
Canvas::HTTP.unstub(:get)
|
||||
Canvas::HTTP.expects(:get).returns(response)
|
||||
api.get_apps()
|
||||
api.get_apps()
|
||||
|
@ -99,12 +169,80 @@ describe AppCenter::AppApi do
|
|||
|
||||
it "caches multiple calls" do
|
||||
enable_cache do
|
||||
Canvas::HTTP.unstub(:get)
|
||||
Canvas::HTTP.expects(:get).returns(response).times(2)
|
||||
api.get_apps(0,1)
|
||||
api.get_apps(1,1)
|
||||
api.get_apps(0,1)
|
||||
api.get_apps(1,1)
|
||||
api.get_apps(0)
|
||||
api.get_apps(1)
|
||||
api.get_apps(0)
|
||||
api.get_apps(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_app_reviews' do
|
||||
let(:response) do
|
||||
response = mock
|
||||
response.stubs(:body).returns(
|
||||
{
|
||||
'meta' => { "next" => "https://www.example.com/api/v1/apps/first_tool/reviews?offset=15"},
|
||||
'current_offset' => 0,
|
||||
'limit' => 15,
|
||||
'objects' => [
|
||||
{
|
||||
'user_name' => 'Iron Man',
|
||||
'user_avatar_url' => 'http://www.example.com/rich.ico',
|
||||
'comments' => 'This tool is so great',
|
||||
},
|
||||
{
|
||||
'user_name' => 'The Hulk',
|
||||
'user_avatar_url' => 'http://www.example.com/beefy.ico',
|
||||
'comments' => 'SMASH!',
|
||||
}
|
||||
]
|
||||
}.to_json
|
||||
)
|
||||
response
|
||||
end
|
||||
|
||||
it "gets an apps reviews" do
|
||||
Canvas::HTTP.stubs(:get).returns(response)
|
||||
reviews = api.get_app_reviews('first_tool')['objects']
|
||||
reviews.should be_a Array
|
||||
reviews.size.should == 2
|
||||
end
|
||||
|
||||
it "returns an empty hash if the app center is disabled" do
|
||||
Canvas::HTTP.stubs(:get).returns(response)
|
||||
setting = PluginSetting.find_by_name(api.app_center.id)
|
||||
setting.destroy
|
||||
|
||||
api.app_center.should_not be_enabled
|
||||
|
||||
response = api.get_app_reviews('first_tool')
|
||||
response.should be_a Hash
|
||||
response.size.should == 0
|
||||
end
|
||||
|
||||
it "gets the next page" do
|
||||
Canvas::HTTP.stubs(:get).returns(response)
|
||||
response = api.get_app_reviews('first_tool')
|
||||
response['meta']['next_page'].should == 2
|
||||
end
|
||||
|
||||
it "caches apps results" do
|
||||
enable_cache do
|
||||
Canvas::HTTP.expects(:get).returns(response)
|
||||
api.get_app_reviews('first_tool')
|
||||
api.get_app_reviews('first_tool')
|
||||
end
|
||||
end
|
||||
|
||||
it "caches multiple calls" do
|
||||
enable_cache do
|
||||
Canvas::HTTP.expects(:get).returns(response).times(2)
|
||||
api.get_app_reviews('first_tool',0)
|
||||
api.get_app_reviews('first_tool',1)
|
||||
api.get_app_reviews('first_tool',0)
|
||||
api.get_app_reviews('first_tool',1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue