Skip to content

Commit

Permalink
Merge pull request #1373 from alphagov/sconf
Browse files Browse the repository at this point in the history
Add serving configs to Search Admin
  • Loading branch information
csutter authored Feb 12, 2025
2 parents c75d318 + 2717204 commit 1f5c17e
Show file tree
Hide file tree
Showing 14 changed files with 346 additions and 1 deletion.
19 changes: 19 additions & 0 deletions app/controllers/serving_configs_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class ServingConfigsController < ApplicationController
before_action :set_serving_config, only: %i[show]

self.primary_navigation_area = :search
self.secondary_navigation_area = :serving_configs
layout "search"

def index
@serving_configs = ServingConfig.order(:use_case, :display_name)
end

def show; end

private

def set_serving_config
@serving_config = ServingConfig.find(params[:id])
end
end
17 changes: 17 additions & 0 deletions app/helpers/serving_configs_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module ServingConfigsHelper
SERVING_CONFIG_USE_CASE_TAG_COLOURS = {
live: "green",
preview: "yellow",
system: "grey",
}.freeze

def serving_config_use_case_tag(serving_config)
scope = "activerecord.attributes.serving_config.use_case_values"
colour = SERVING_CONFIG_USE_CASE_TAG_COLOURS[serving_config.use_case.to_sym]

tag.span(
t(serving_config.use_case, scope:),
class: "govuk-tag govuk-tag--#{colour}",
)
end
end
37 changes: 37 additions & 0 deletions app/models/serving_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Represents a serving config(uration) on Discovery Engine.
#
# A serving config is a specific endpoint on an engine which is used for querying.
#
# Having more than one serving config on an engine is helpful because each one can have different
# sets of controls attached to it.
#
# see https://cloud.google.com/ruby/docs/reference/google-cloud-discovery_engine-v1beta/latest/Google-Cloud-DiscoveryEngine-V1beta-ServingConfig
class ServingConfig < ApplicationRecord
include DiscoveryEngineNameable

# Tracks what this serving config is used for
enum :use_case, {
# Used for actual public search on GOV.UK, for example the default serving config or any
# additional serving configs used for AB tests
live: 0,
# Used by search administrators for testing purposes, for example to try out a new control
# before adding it to a live serving config
preview: 1,
# Used internally, can only be modified programatically (not through the Search Admin UI)
system: 2,
}, validate: true

# Don't allow changing remote_resource_id after creation
attr_readonly :remote_resource_id

validates :display_name, presence: true
validates :remote_resource_id, presence: true, uniqueness: true

# A URL to preview this serving config on Finder Frontend
def preview_url
FinderFrontendSearch.new(
keywords: "example search",
debug_serving_config: remote_resource_id,
).url
end
end
5 changes: 5 additions & 0 deletions app/views/layouts/search.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
href: controls_path,
current: secondary_navigation_area == :controls,
},
{
label: t("serving_configs.index.page_title"),
href: serving_configs_path,
current: secondary_navigation_area == :serving_configs,
},
]
} %>
<% end %>
Expand Down
18 changes: 18 additions & 0 deletions app/views/serving_configs/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<%= render "page_title", title: t(".page_title") %>

<div class="govuk-!-margin-top-6 app-table__container">
<%= render "govuk_publishing_components/components/table", {
head: [
{ text: t_model_attr(:display_name) },
{ text: t_model_attr(:use_case) },
{ text: t_model_attr(:description) },
],
rows: @serving_configs.map do |serving_config|
[
{ text: link_to(serving_config.display_name, serving_config, class: "govuk-link") },
{ text: serving_config_use_case_tag(serving_config) },
{ text: serving_config.description },
]
end
} %>
</div>
52 changes: 52 additions & 0 deletions app/views/serving_configs/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<% content_for(:breadcrumbs) do %>
<%= render "govuk_publishing_components/components/breadcrumbs", {
breadcrumbs: [
{
title: t("serving_configs.index.page_title"),
url: serving_configs_path
},
{
title: @serving_config.display_name
}
]
} %>
<% end %>

<%= render "page_title", {
title: @serving_config.display_name,
} %>

<div class="actions">
<%= render "govuk_publishing_components/components/button", {
text: t(".buttons.preview"),
href: @serving_config.preview_url,
target: "_blank",
secondary_quiet: true,
inline_layout: true
} %>
</div>

<%= render "govuk_publishing_components/components/summary_list", {
items: [
{
field: t_model_attr(:display_name),
value: @serving_config.display_name,
},
{
field: t_model_attr(:use_case),
value: serving_config_use_case_tag(@serving_config),
},
{
field: t_model_attr(:description),
value: @serving_config.description,
},
{
field: t_model_attr(:remote_resource_id),
value: tag.code(@serving_config.remote_resource_id),
},
{
field: t_model_attr(:name),
value: tag.code(@serving_config.name),
}
]
} %>
20 changes: 20 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ en:
recommended_link:
one: external link
other: external links
serving_config:
one: serving configuration
other: serving configurations
attributes:
control:
comment: Comment
Expand All @@ -94,6 +97,16 @@ en:
description: Description
keywords: Keywords
comment: Comments
serving_config:
display_name: Name
use_case: Kind
use_case_values:
live: Live
preview: Preview
system: System
name: Google Cloud resource name
remote_resource_id: Google Cloud ID
description: Description

errors:
models:
Expand Down Expand Up @@ -153,3 +166,10 @@ en:
destroy:
success: The external link was successfully deleted.
failure: The external link could not be deleted.

serving_configs:
index:
page_title: Serving configs
show:
buttons:
preview: Preview on GOV.UK
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
resources :with_boost_actions, controller: :controls, action_type: Control::BoostAction
resources :with_filter_actions, controller: :controls, action_type: Control::FilterAction
end
resources :serving_configs

resources :recommended_links, path: "/recommended-links"

Expand Down
22 changes: 22 additions & 0 deletions db/migrate/20250212115000_create_serving_configs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class CreateServingConfigs < ActiveRecord::Migration[8.0]
def change
create_table :serving_configs do |t|
t.integer :use_case,
null: false,
comment: "An enum declaring what use case this serving config is for"
t.string :display_name,
null: false,
comment: "A human-readable name"
t.string :description,
null: false,
comment: "A description of this serving config's purpose"
t.string :remote_resource_id,
null: false,
comment: "The ID of this serving config on Discovery Engine"

t.timestamps

t.index :remote_resource_id, unique: true
end
end
end
12 changes: 11 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[8.0].define(version: 2025_02_10_143408) do
ActiveRecord::Schema[8.0].define(version: 2025_02_12_115000) do
create_table "control_boost_actions", charset: "utf8mb3", force: :cascade do |t|
t.string "filter_expression", null: false
t.float "boost_factor", null: false
Expand Down Expand Up @@ -47,6 +47,16 @@
t.index ["link"], name: "index_recommended_links_on_link", unique: true
end

create_table "serving_configs", charset: "utf8mb3", force: :cascade do |t|
t.integer "use_case", null: false, comment: "An enum declaring what use case this serving config is for"
t.string "display_name", null: false, comment: "A human-readable name"
t.string "description", null: false, comment: "A description of this serving config's purpose"
t.string "remote_resource_id", null: false, comment: "The ID of this serving config on Discovery Engine"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["remote_resource_id"], name: "index_serving_configs_on_remote_resource_id", unique: true
end

create_table "users", charset: "utf8mb3", force: :cascade do |t|
t.string "name"
t.string "email"
Expand Down
30 changes: 30 additions & 0 deletions lib/tasks/bootstrap.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Tasks to seed the app's production environments, for example creating records for pre-existing
# resources.
namespace :bootstrap do
# As of Feb 2025, the Ruby client for Discovery Engine does not allow full lifecycle management
# of serving config resources (only updating existing ones) - so we created the following serving
# configs in Terraform in `govuk-infrastructure`.
#
# see https://github.com/alphagov/govuk-infrastructure/pull/1680
desc "Creates ServingConfig records for resources already created in Terraform"
task seed_serving_configs: :environment do
ServingConfig.create!(
use_case: :live,
display_name: "Default",
description: "Used by live GOV.UK site search",
remote_resource_id: "govuk_default",
)
ServingConfig.create!(
use_case: :preview,
display_name: "Preview",
description: "A preview serving config to test out changes",
remote_resource_id: "govuk_preview",
)
ServingConfig.create!(
use_case: :system,
display_name: "Raw",
description: "A serving config without any controls applied",
remote_resource_id: "govuk_raw",
)
end
end
7 changes: 7 additions & 0 deletions spec/factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@
keywords { "tax, self assessment, hmrc" }
end

factory :serving_config do
use_case { :live }
display_name { "Serving config" }
description { "A serving configuration" }
remote_resource_id { "serving-config" }
end

factory :user do
factory :admin_user do
permissions { %w[admin] }
Expand Down
69 changes: 69 additions & 0 deletions spec/models/serving_config_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
RSpec.describe ServingConfig, type: :model do
describe "validations" do
subject(:serving_config) { build(:serving_config) }

it { is_expected.to be_valid }

context "without a display name" do
before do
serving_config.display_name = nil
end

it "is invalid" do
expect(serving_config).to be_invalid
expect(serving_config.errors).to be_of_kind(:display_name, :blank)
end
end

context "without a remote resource ID" do
before do
serving_config.remote_resource_id = nil
end

it "is invalid" do
expect(serving_config).to be_invalid
expect(serving_config.errors).to be_of_kind(:remote_resource_id, :blank)
end
end

context "with a duplicate remote resource ID" do
before do
create(:serving_config, remote_resource_id: "dupe")
serving_config.remote_resource_id = "dupe"
end

it "is invalid" do
expect(serving_config).to be_invalid
expect(serving_config.errors).to be_of_kind(:remote_resource_id, :taken)
end
end
end

describe "#remote_resource_id" do
subject(:serving_config) { create(:serving_config, remote_resource_id: "hello") }

it "is immutable after initial creation" do
expect { serving_config.update!(remote_resource_id: "goodbye") }.to raise_error(
ActiveRecord::ReadonlyAttributeError,
)
end
end

describe "#preview_url" do
subject(:serving_config) { create(:serving_config, remote_resource_id: "hello") }

let(:finder_frontend_search) do
instance_double(FinderFrontendSearch, url: "https://example.org")
end

before do
allow(FinderFrontendSearch).to receive(:new)
.with(keywords: "example search", debug_serving_config: "hello")
.and_return(finder_frontend_search)
end

it "returns the preview URL" do
expect(serving_config.preview_url).to eq("https://example.org")
end
end
end
Loading

0 comments on commit 1f5c17e

Please sign in to comment.