Skip to content

Commit

Permalink
Merge pull request #1690 from projectblacklight/json-api
Browse files Browse the repository at this point in the history
Improve the catalog JSON API by adopting JSON-API practices
  • Loading branch information
Michael Tribone committed Sep 26, 2017
2 parents b88b93a + 8015896 commit cd875b4
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 37 deletions.
4 changes: 2 additions & 2 deletions app/controllers/concerns/blacklight/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def search_action_url options = {}

def search_action_path *args
if args.first.is_a? Hash
args.first[:only_path] = true
args.first[:only_path] = true if args.first[:only_path].nil?
end

search_action_url(*args)
Expand All @@ -94,7 +94,7 @@ def search_facet_url options = {}

def search_facet_path(options = {})
Deprecation.silence(Blacklight::Controller) do
search_facet_url(options.merge(only_path: true))
search_facet_url(options.reverse_merge(only_path: true))
end
end

Expand Down
4 changes: 2 additions & 2 deletions app/helpers/blacklight/facets_helper_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ def render_facet_value(facet_field, item, options = {})
# @param [Blacklight::Solr::Response::Facets::FacetField] facet_field
# @param [String] item
# @return [String]
def path_for_facet(facet_field, item)
def path_for_facet(facet_field, item, path_options = {})
facet_config = facet_configuration_for_field(facet_field)
if facet_config.url_method
send(facet_config.url_method, facet_field, item)
else
search_action_path(search_state.add_facet_params_and_redirect(facet_field, item))
search_action_path(search_state.add_facet_params_and_redirect(facet_field, item).merge(path_options))
end
end

Expand Down
1 change: 1 addition & 0 deletions app/presenters/blacklight/json_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def documents

def search_facets_as_json
@facets.as_json.each do |f|
f.stringify_keys!
f.delete "options"
f["label"] = facet_configuration_for_field(f["name"]).label
f["items"] = f["items"].as_json.each do |i|
Expand Down
68 changes: 65 additions & 3 deletions app/views/catalog/index.json.jbuilder
Original file line number Diff line number Diff line change
@@ -1,5 +1,67 @@
json.response do
json.docs @presenter.documents
json.facets @presenter.search_facets_as_json
json.links do
json.self url_for(search_state.to_h.merge(only_path: false))
json.prev url_for(search_state.to_h.merge(only_path: false, page: @response.prev_page.to_s)) if @response.prev_page
json.next url_for(search_state.to_h.merge(only_path: false, page: @response.next_page.to_s)) if @response.next_page
json.last url_for(search_state.to_h.merge(only_path: false, page: @response.total_pages.to_s))
end

json.meta do
json.pages @presenter.pagination_info
end

json.data do
json.array! @presenter.documents do |document|
json.id document.id
json.attributes document
json.links do
json.self polymorphic_url(url_for_document(document))
end
end
end

json.included do
json.array! @presenter.search_facets_as_json do |facet|
json.type 'facet'
json.id facet['name']
json.attributes do
json.items do
json.array! facet['items'] do |item|
json.id
json.attributes do
json.label item['label']
json.value item['value']
json.hits item['hits']
end
json.links do
json.self path_for_facet(facet['name'], item['value'], only_path: false)
end
end
end
end
json.links do
json.self search_facet_path(id: facet['name'], only_path: false)
end
end

json.array! search_fields do |(label, key)|
json.type 'search_field'
json.id key
json.attributes do
json.label label
end
json.links do
json.self url_for(search_state.to_h.merge(search_field: key, only_path: false))
end
end

json.array! active_sort_fields do |key, field|
json.type 'sort'
json.id key
json.attributes do
json.label field.label
end
json.links do
json.self url_for(search_state.to_h.merge(sort: key, only_path: false))
end
end
end
31 changes: 21 additions & 10 deletions spec/controllers/catalog_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,11 @@
get :index, params: { format: 'json' }
expect(response).to be_success
end
let(:json) { JSON.parse(response.body)['response'] }
let(:pages) { json["pages"] }
let(:docs) { json["docs"] }
let(:facets) { json["facets"] }
let(:json) { JSON.parse(response.body) }
let(:pages) { json['meta']['pages'] }
let(:docs) { json['data'] }
let(:facets) { json['included'].select { |x| x['type'] == 'facet' } }
let(:search_fields) { json['included'].select { |x| x['type'] == 'search_field' } }

it "gets the pages" do
expect(pages["total_count"]).to eq 30
Expand All @@ -140,22 +141,32 @@

it "gets the documents" do
expect(docs).to have(10).documents
expect(docs.first.keys).to match_array(["published_display", "author_display", "lc_callnum_display", "pub_date", "subtitle_display", "format", "material_type_display", "title_display", "id", "subject_topic_facet", "language_facet", "marc_display", "score"])
expect(docs.first['attributes'].keys).to match_array(["published_display", "author_display", "lc_callnum_display", "pub_date", "subtitle_display", "format", "material_type_display", "title_display", "id", "subject_topic_facet", "language_facet", "marc_display", "score"])
expect(docs.first['links']['self']).to eq solr_document_url(id: docs.first['id'])
end

it "gets the facets" do
expect(facets).to have(9).facets
expect(facets.first).to eq({"name"=>"format", "label" => "Format", "items"=>[{"value"=>"Book", "hits"=>30, "label"=>"Book"}]})

format = facets.find { |x| x['id'] == 'format' }

expect(format['attributes']['items'].map { |x| x['attributes'] }).to match_array([{"value"=>"Book", "hits"=>30, "label"=>"Book"}])
expect(format['links']['self']).to eq facet_catalog_url(format: :json, id: 'format')
expect(format['attributes']['items'].first['links']['self']).to eq search_catalog_url(format: :json, f: { format: ['Book']})
end

it "gets the search fields" do
expect(search_fields).to have(4).fields
expect(search_fields.map { |x| x['id']}).to match_array ['all_fields', 'author', 'subject', 'title']
expect(search_fields.first['links']['self']).to eq search_catalog_url(format: :json, search_field: 'all_fields')
end

describe "facets" do
let(:query_facet_items) { facets.last['items'] }
let(:regular_facet_items) { facets.first['items'] }
let(:query_facet) { facets.find { |x| x['id'] == 'example_query_facet_field' } }
let(:query_facet_items) { query_facet['attributes']['items'].map { |x| x['attributes'] } }
it "has items with labels and values" do
expect(query_facet_items.first['label']).to eq 'within 10 Years'
expect(query_facet_items.first['value']).to eq 'years_10'
expect(regular_facet_items.first['label']).to eq "Book"
expect(regular_facet_items.first['value']).to eq "Book"
end
end
end
Expand Down
1 change: 0 additions & 1 deletion spec/features/search_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,3 @@
expect(page).to have_content "No results found for your search"
end
end

83 changes: 64 additions & 19 deletions spec/views/catalog/index.json.jbuilder_spec.rb
Original file line number Diff line number Diff line change
@@ -1,30 +1,75 @@
# frozen_string_literal: true
RSpec.describe "catalog/index.json" do
let(:response) { instance_double(Blacklight::Solr::Response, documents: docs) }
let(:docs) { [{ id: '123', title_t: 'Book1' }, { id: '456', title_t: 'Book2' }] }
let(:response) { instance_double(Blacklight::Solr::Response, documents: docs, prev_page: nil, next_page: 2, total_pages: 3) }
let(:docs) { [SolrDocument.new(id: '123', title_t: 'Book1'), SolrDocument.new(id: '456', title_t: 'Book2')] }
let(:facets) { double("facets") }
let(:config) { instance_double(Blacklight::Configuration) }
let(:config) { Blacklight::Configuration.new }
let(:presenter) { Blacklight::JsonPresenter.new(response, facets, config) }

it "renders index json" do
let(:hash) do
render template: "catalog/index.json", format: :json
JSON.parse(rendered).with_indifferent_access
end

before do
allow(view).to receive(:blacklight_config).and_return(config)
allow(view).to receive(:search_action_path).and_return('http://test.host/some/search/url')
allow(view).to receive(:search_facet_path).and_return('http://test.host/some/facet/url')
allow(presenter).to receive(:pagination_info).and_return({ current_page: 1, next_page: 2,
prev_page: nil })
allow(presenter).to receive(:search_facets_as_json).and_return(
[{ name: "format", label: "Format",
items: [{ value: 'Book', hits: 30, label: 'Book' }] }])
[{ 'name' => "format", 'label' => "Format",
'items' => [{ 'value' => 'Book', 'hits' => 30, 'label' => 'Book' }] }])
assign :presenter, presenter
render template: "catalog/index.json", format: :json
hash = JSON.parse(rendered)
expect(hash).to eq('response' => { 'docs' => [{ 'id' => '123', 'title_t' => 'Book1' },
{ 'id' => '456', 'title_t' => 'Book2' }],
'facets' => [{ 'name' => "format", 'label' => "Format",
'items' => [
{ 'value' => 'Book',
'hits' => 30,
'label' => 'Book' }] }],
'pages' => { 'current_page' => 1,
'next_page' => 2,
'prev_page' => nil } }
)
assign :response, response
end

it "has pagination links" do
expect(hash).to include(links: hash_including(
self: 'http://test.host/',
next: 'http://test.host/?page=2',
last: 'http://test.host/?page=3'))
end

it "has pagination information" do
expect(hash).to include(meta: hash_including(pages:
{
'current_page' => 1,
'next_page' => 2,
'prev_page' => nil
}))
end

it "includes documents, links, and their attributes" do
expect(hash).to include(data: [
{
id: '123',
attributes: { 'id' => '123', 'title_t' => 'Book1' },
links: { self: 'http://test.host/catalog/123' }
},
{
id: '456',
attributes: { 'id' => '456', 'title_t' => 'Book2' },
links: { self: 'http://test.host/catalog/456' }
},
])
end

it "has facet information and links" do
expect(hash).to include(:included)

facets = hash[:included].select { |x| x['type'] == 'facet' }
expect(facets).to be_present

expect(facets.map { |x| x['id'] }).to include 'format'

format = facets.find { |x| x['id'] == 'format' }

expect(format['links']).to include self: 'http://test.host/some/facet/url'
expect(format['attributes']).to include :items

format_items = format['attributes']['items'].map { |x| x['attributes'] }

expect(format_items).to match_array [{value: 'Book', hits: 30, label: 'Book'}]
end
end

0 comments on commit cd875b4

Please sign in to comment.