Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Solr and basic search page #1313

Merged
merged 3 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ gem "plek", "~> 4.1" # TODO: unconstrain once govuk_pub_components up-to-date.
gem "puma"
gem "redcarpet"
gem "rest-client"
gem "rsolr"
gem "sass-rails"
gem "secure_headers"
gem "sentry-raven"
Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,9 @@ GEM
rexml (3.2.8)
strscan (>= 3.0.9)
rouge (4.2.0)
rsolr (2.6.0)
builder (>= 2.1.2)
faraday (>= 0.9, < 3, != 2.0.0)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
Expand Down Expand Up @@ -697,6 +700,7 @@ DEPENDENCIES
rails (= 7.0.5.1)
redcarpet
rest-client
rsolr
rspec
rspec-rails
rubocop-govuk
Expand Down
14 changes: 14 additions & 0 deletions app/controllers/solr_search_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class SolrSearchController < ApplicationController
before_action :search_for_dataset, only: [:search]

def search; end

private

def search_for_dataset
query = Search::Solr.search(params)

@datasets = query["response"]["docs"]
@num_results = query["response"]["numFound"]
end
end
37 changes: 37 additions & 0 deletions app/services/search/solr.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module Search
class Solr
def self.search(params)
query_param = params.fetch("q", "").squish
page = params["page"]
page && page.to_i.positive? ? page.to_i : 1

solr_client = client

query = "*:*" if query_param.empty?

solr_client.get "select", params: {
q: query,
start: page,
rows: 20,
fl: field_list,
}
end

def self.field_list
%w[
id
name
title
organization
notes
metadata_modified
extras_theme-primary
validated_data_dict
].freeze
end

def self.client
@client ||= RSolr.connect(url: ENV["SOLR_URL"])
end
end
end
55 changes: 55 additions & 0 deletions app/views/solr_search/search.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<% content_for :page_title do %><%= search_term.present? ?
t('.results_page_title', query: search_term) :
t('.search_results') %>
<% end %>

<% content_for :head do %>
<meta name="robots" content="noindex">
<% end %>
<form action="/search/solr" method="GET">
<main role="main" id="main-content" class="govuk-main-wrapper">
<div class="govuk-width-container">
<%= render "govuk_publishing_components/components/heading", {
text: t(".search_results_heading"),
margin_bottom: 6,
heading_level: 1,
font_size: "l",
} %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds" id="search-box">
<%= render "govuk_publishing_components/components/search", {
label_text: t('.search_data_gov_uk'),
} %>
</div>
</div>

<div class="govuk-grid-row dgu-filters">
<div class="govuk-grid-column-two-thirds dgu-results">
<span class="dgu-results__summary">
<span class="govuk-body-s govuk-!-font-weight-bold"><%= number_with_delimiter(@num_results) %></span>
<%= t('.result').pluralize(@num_results) %> <%= t('.found') %>
</span>

<% @datasets.each do |dataset| %>
<div class="dgu-results__result">
<h2 class="govuk-heading-m">
<%= dataset["title"] %>
</h2>
<dl class="dgu-metadata__box">
<dt><%= t('.meta_data_box.published_by') %>:</dt>
<dd class="published_by"><%= JSON.parse(dataset["validated_data_dict"])["organization"]["title"] %></dd>
<dt><%= t('.meta_data_box.last_updated') %>:</dt>
<% if dataset["metadata_modified"].present? %>
<dd><%= format_timestamp(dataset["metadata_modified"]) %></dd>
<% else %>
<dd class="dgu-secondary-text"> <%= t('.meta_data_box.not_applicable') %></dd>
<% end %>
</dl>
<p><%= truncate(strip_tags(dataset["notes"]), length: 200, separator: ' ') %></p>
</div>
<% end %>
</div>
</div>
</div>
</main>
</form>
13 changes: 13 additions & 0 deletions config/locales/views/search/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,16 @@ en:
sort_by: "Sort by"
accessibility:
submit_button: "Apply sorting"
solr_search:
search:
results_page_title: 'Results for "%{query}"'
search_results: 'Search Results'
search_results_heading: "Search results"
search_data_gov_uk: "Search data.gov.uk"
result: "result"
found: "found"
meta_data_box:
availability: "Availability"
published_by: "Published by"
last_updated: "Last updated"
not_applicable: "Not applicable"
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
get "dataset/:uuid/:name", to: "datasets#show", as: "dataset"
get "dataset/:dataset_uuid/:name/datafile/:datafile_uuid/preview", to: "previews#show", as: "datafile_preview"

# /solr feature flag
get "search/solr", to: "solr_search#search"

get "acknowledge", to: "messages#acknowledge"

# Route everything else to CKAN
Expand Down
45 changes: 45 additions & 0 deletions spec/features/solr_search_page_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require "rails_helper"

RSpec.describe "Solr Search page", type: :feature do
let(:results) { File.read(Rails.root.join("spec/fixtures/solr_response.json").to_s) }

before do
allow(Search::Solr).to receive(:search).and_return(JSON.parse(results))
end

scenario "Displays search heading" do
visit "/search/solr"
expect(page).to have_css("h1", text: "Search results")
end

scenario "Displays search box" do
visit "/search/solr"
expect(page).to have_content("Search data.gov.uk")
end

scenario "Displays search results" do
visit "/search/solr"
expect(page).to have_content("2 results found")
end

scenario "Displays the title for each search result" do
visit "/search/solr"
expect(page).to have_css("h2", text: "A very interesting dataset")
expect(page).to have_css("h2", text: "A dataset with additional inspire metadata")
end

scenario "Displays the publisher for each search result" do
visit "/search/solr"
results = all(".published_by")
expect(results.length).to be(2)
expect(results[0]).to have_content "Ministry of Housing, Communities and Local Government"
expect(results[1]).to have_content "Mole Valley District Council"
end

scenario "Displays the last updated for each search result" do
visit "/search/solr"
expect(page).to have_content("Last updated", count: 2)
expect(page).to have_content("30 June 2017")
expect(page).to have_content("17 August 2018")
end
end
29 changes: 29 additions & 0 deletions spec/fixtures/solr_response.json

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions spec/services/search/solr_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
require "rails_helper"

RSpec.describe Search::Solr do
before do
ENV["SOLR_URL"] = "http://localhost:8983/solr"
end

describe "#client" do
let(:client) { described_class.client }

it "is an instance of the RSolr client" do
expect(client).to be_an_instance_of(RSolr::Client)
end

it "connects to Solr with a valid URL" do
expect(client.options[:url]).to eq(ENV["SOLR_URL"])
end

it "only sets up the connection once" do
client1 = described_class.client
expect(client1).to eq(client)
end
end

describe "#search" do
let(:response) { File.read(Rails.root.join("spec/fixtures/solr_response.json").to_s) }
let(:results) { described_class.search("q" => "") }

before do
allow_any_instance_of(RSolr::Client).to receive(:get).and_return(JSON.parse(response))
end

it "returns a JSON response" do
expect(results).to be_a(Hash)
end

it "includes a count of the results" do
expect(results["response"]["numFound"]).to eq(2)
end

it "includes the datasets" do
datasets = results["response"]["docs"]
expect(datasets.length).to eq(2)
end

context "datasets" do
let(:dataset) { results["response"]["docs"].first }

it "includes the id" do
expect(dataset["id"]).to eq("420932c7-e6f8-43ea-adc5-3141f757b5cb")
end

it "includes the name" do
expect(dataset["name"]).to eq("a-very-interesting-dataset")
end

it "includes the title" do
expect(dataset["title"]).to eq("A very interesting dataset")
end

it "includes the notes" do
expect(dataset["notes"]).to eq("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec euismod mauris in augue laoreet congue. Phasellus bibendum leo vel magna lacinia eleifend. Nam vitae lectus quis nulla varius faucibus id quis nibh. Nullam auctor ipsum non nunc efficitur bibendum Sed vitae ex nisi. Suspendisse posuere purus ac dui posuere, in interdum risus ornare. \n\nThe following files can be found on the website here:\n\nhttps://www.gov.uk/")
end

it "includes the last updated date" do
expect(dataset["metadata_modified"]).to eq("2017-06-30T09:08:37.040Z")
end

it "includes the organization slug" do
expect(dataset["organization"]).to eq("department-for-communities-and-local-government")
end

it "includes the organization name" do
data_dict = JSON.parse(dataset["validated_data_dict"])
expect(data_dict["organization"]["title"]).to eq("Ministry of Housing, Communities and Local Government")
end
end
end
end
Loading