Skip to content

Commit 9bc12ba

Browse files
authored
Merge pull request #384 from alphagov/models
Refactor Discovery Engine resource handling
2 parents 2da041c + 4d6e809 commit 9bc12ba

File tree

24 files changed

+185
-33
lines changed

24 files changed

+185
-33
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@ jobs:
4545
RAILS_ENV: test
4646
# All Google client library calls are mocked, but the application needs this set to boot
4747
GOOGLE_CLOUD_PROJECT_ID: not-used
48-
DISCOVERY_ENGINE_SERVING_CONFIG: not-used
49-
DISCOVERY_ENGINE_DATASTORE: not-used
50-
DISCOVERY_ENGINE_DATASTORE_BRANCH: not-used
48+
DISCOVERY_ENGINE_DEFAULT_COLLECTION_NAME: not-used
5149
# Redis running through govuk-infrastructure action
5250
REDIS_URL: redis://localhost:6379
5351
steps:

app/models/branch.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Represents a branch on a Discovery Engine data store.
2+
#
3+
# Currently, every data store on Discovery Engine has exactly *one* branch (the default branch), and
4+
# we are not able to make any changes to that resource. However, we still need to model it here
5+
# because documents are children of the branch, not the datastore itself.
6+
#
7+
# see https://cloud.google.com/ruby/docs/reference/google-cloud-discovery_engine-v1/latest/Google-Cloud-DiscoveryEngine-V1-DocumentService-Client
8+
# (there is no documentation specific to branches)
9+
Branch = Data.define(:remote_resource_id) do
10+
include DiscoveryEngineNameable
11+
12+
# The default branch automatically available on a data store
13+
def self.default
14+
new("default_branch")
15+
end
16+
17+
def parent
18+
# We only use a single data store in our architecture, so we can hardcode it here.
19+
DataStore.default
20+
end
21+
end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Enhances models with a `#name` method returning their fully qualified Google Cloud Platform
2+
# resource name (like a path).
3+
#
4+
# For example, for a `Control`, this would be:
5+
# projects/{project}/locations/{location}/collections/{collection_id}/engines/
6+
# {engine_id}/controls/{control_id}
7+
#
8+
# Requires the including class to implement `#remote_resource_id`, and optionally `#parent` if the
9+
# parent resource is not the default collection.
10+
module DiscoveryEngineNameable
11+
# The name (fully qualified path) of this Discovery Engine resource on GCP
12+
def name
13+
[parent_name, resource_path_fragment, remote_resource_id].join("/")
14+
end
15+
16+
private
17+
18+
def resource_path_fragment
19+
# For example: `DataStore` -> `dataStores`
20+
self.class.name.downcase_first.pluralize
21+
end
22+
23+
def parent_name
24+
if respond_to?(:parent)
25+
parent.name
26+
else
27+
# The default collection is the parent of all root-level resources
28+
Rails.configuration.discovery_engine_default_collection_name
29+
end
30+
end
31+
end

app/models/data_store.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Represents a data store on Discovery Engine.
2+
#
3+
# A data store contains the indexed documents that can be searched through an engine.
4+
#
5+
# Our architecture currently only has a single data store, so we do not need the ability to manage
6+
# data stores through Search Admin.
7+
#
8+
# see https://cloud.google.com/ruby/docs/reference/google-cloud-discovery_engine-v1/latest/Google-Cloud-DiscoveryEngine-V1-DataStore
9+
DataStore = Data.define(:remote_resource_id) do
10+
include DiscoveryEngineNameable
11+
12+
def self.default
13+
new("govuk_content")
14+
end
15+
end

app/models/engine.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Represents an engine on Discovery Engine.
2+
#
3+
# An engine (called "app" in the Google Cloud UI) is an abstraction over the data stores that
4+
# contain our searchable documents, and is used for querying. It is the parent resource of several
5+
# other resources such as controls and serving configs.
6+
#
7+
# Our architecture currently only has a single engine, so we do not need the ability to manage
8+
# engines through Search Admin.
9+
#
10+
# see https://cloud.google.com/ruby/docs/reference/google-cloud-discovery_engine-v1/latest/Google-Cloud-DiscoveryEngine-V1-Engine
11+
Engine = Data.define(:remote_resource_id) do
12+
include DiscoveryEngineNameable
13+
14+
# The default engine created through Terraform in `govuk-infrastructure`
15+
def self.default
16+
new("govuk")
17+
end
18+
end

app/models/serving_config.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Represents a serving config on Discovery Engine.
2+
#
3+
# A serving config is an endpoint on an engine that can be used for
4+
# querying. Each serving config can have different configuration (in particular, different sets of
5+
# active controls), which allows us to test out new configuration changes outside of the default
6+
# serving config.
7+
#
8+
# see https://cloud.google.com/ruby/docs/reference/google-cloud-discovery_engine-v1beta/latest/Google-Cloud-DiscoveryEngine-V1beta-ServingConfig
9+
ServingConfig = Data.define(:remote_resource_id) do
10+
include DiscoveryEngineNameable
11+
12+
# The default serving config automatically available on an engine
13+
def self.default
14+
new("default_search")
15+
end
16+
17+
def parent
18+
# We only use a single engine in our architecture, so we can hardcode it here.
19+
Engine.default
20+
end
21+
end

app/services/discovery_engine/autocomplete/complete.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,10 @@ def suggestions
3232

3333
def complete_query_request
3434
{
35-
data_store:,
35+
data_store: DataStore.default.name,
3636
query:,
3737
query_model: QUERY_MODEL,
3838
}
3939
end
40-
41-
def data_store
42-
Rails.configuration.discovery_engine_datastore
43-
end
4440
end
4541
end

app/services/discovery_engine/autocomplete/update_denylist.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def bucket_name
5454
end
5555

5656
def parent
57-
Rails.configuration.discovery_engine_datastore
57+
DataStore.default.name
5858
end
5959
end
6060
end

app/services/discovery_engine/query/search.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def query
5656
end
5757

5858
def serving_config
59-
Rails.configuration.discovery_engine_serving_config
59+
ServingConfig.default.name
6060
end
6161

6262
def page_size

app/services/discovery_engine/sync/operation.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def version_cache
6060
end
6161

6262
def document_name
63-
"#{Rails.configuration.discovery_engine_datastore_branch}/documents/#{content_id}"
63+
[Branch.default.name, "documents", content_id].join("/")
6464
end
6565

6666
def log(level, message)

app/services/discovery_engine/user_events/import.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def call
5353
table_id:,
5454
partition_date:,
5555
},
56-
parent: Rails.configuration.discovery_engine_datastore,
56+
parent: DataStore.default.name,
5757
)
5858

5959
logger.info("Waiting for import_user_events operation to finish remotely")

config/application.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ class Application < Rails::Application
1717
config.api_only = true
1818

1919
# Google Discovery Engine configuration
20-
config.discovery_engine_serving_config = ENV.fetch("DISCOVERY_ENGINE_SERVING_CONFIG")
21-
config.discovery_engine_datastore = ENV.fetch("DISCOVERY_ENGINE_DATASTORE")
22-
config.discovery_engine_datastore_branch = ENV.fetch("DISCOVERY_ENGINE_DATASTORE_BRANCH")
20+
config.discovery_engine_default_collection_name = ENV.fetch("DISCOVERY_ENGINE_DEFAULT_COLLECTION_NAME")
2321
config.google_cloud_project_id = ENV.fetch("GOOGLE_CLOUD_PROJECT_ID")
2422

2523
# Document sync configuration

config/environments/test.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939

4040
# Raise error when a before_action's only/except options reference missing actions.
4141
config.action_controller.raise_on_missing_callback_actions = true
42+
43+
# Google Discovery Engine configuration
44+
config.discovery_engine_default_collection_name = "[collection]"
4245
end
4346

4447
# TODO: remove this workaround once GovukPrometheusExporter initialisation is fixed in govuk_app_config.

spec/models/branch_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
RSpec.describe Branch do
2+
subject(:branch) { described_class.new("my-branch") }
3+
4+
describe ".default" do
5+
it "returns the default branch" do
6+
expect(described_class.default).to eq(described_class.new("default_branch"))
7+
end
8+
end
9+
10+
describe "#name" do
11+
it "returns the fully qualified name of the branch" do
12+
expect(branch.name).to eq("[collection]/dataStores/govuk_content/branches/my-branch")
13+
end
14+
end
15+
end

spec/models/data_store_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
RSpec.describe DataStore do
2+
subject(:data_store) { described_class.new("my-data-store") }
3+
4+
describe ".default" do
5+
it "returns the default data store" do
6+
expect(described_class.default).to eq(described_class.new("govuk_content"))
7+
end
8+
end
9+
10+
describe "#name" do
11+
it "returns the fully qualified name of the data store" do
12+
expect(data_store.name).to eq("[collection]/dataStores/my-data-store")
13+
end
14+
end
15+
end

spec/models/engine_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
RSpec.describe Engine do
2+
subject(:engine) { described_class.new("my-engine") }
3+
4+
describe ".default" do
5+
it "returns the default engine" do
6+
expect(described_class.default).to eq(described_class.new("govuk"))
7+
end
8+
end
9+
10+
describe "#name" do
11+
it "returns the fully qualified name of the engine" do
12+
expect(engine.name).to eq("[collection]/engines/my-engine")
13+
end
14+
end
15+
end

spec/models/serving_config_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
RSpec.describe ServingConfig do
2+
subject(:serving_config) { described_class.new("my-serving-config") }
3+
4+
describe ".default" do
5+
it "returns the default serving config" do
6+
expect(described_class.default).to eq(described_class.new("default_search"))
7+
end
8+
end
9+
10+
describe "#name" do
11+
it "returns the fully qualified name of the serving config" do
12+
expect(serving_config.name).to eq("[collection]/engines/govuk/servingConfigs/my-serving-config")
13+
end
14+
end
15+
end

spec/services/discovery_engine/autocomplete/complete_spec.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@
33

44
let(:client) { double("completion service", complete_query:) }
55

6-
before do
7-
allow(Rails.configuration).to receive(:discovery_engine_datastore).and_return("/the/datastore")
8-
end
9-
106
describe "#completion_result" do
117
subject(:completion_result) { completion.completion_result }
128

@@ -22,7 +18,7 @@
2218
completion_result
2319

2420
expect(client).to have_received(:complete_query).with(
25-
data_store: "/the/datastore",
21+
data_store: DataStore.default.name,
2622
query:,
2723
query_model: "user-event",
2824
)

spec/services/discovery_engine/autocomplete/update_denylist_spec.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
before do
1717
allow(Rails.configuration).to receive_messages(
18-
discovery_engine_datastore: "data/store",
1918
google_cloud_project_id: "my-fancy-project",
2019
)
2120
end
@@ -25,7 +24,7 @@
2524
update_denylist.call
2625

2726
expect(client).to have_received(:purge_suggestion_deny_list_entries)
28-
.with(parent: "data/store")
27+
.with(parent: DataStore.default.name)
2928
expect(purge_operation).to have_received(:wait_until_done!)
3029
end
3130

@@ -37,7 +36,7 @@
3736
data_schema: "suggestion_deny_list",
3837
input_uris: ["gs://my-fancy-project_vais_artifacts/denylist.jsonl"],
3938
},
40-
parent: "data/store",
39+
parent: DataStore.default.name,
4140
)
4241
expect(import_operation).to have_received(:wait_until_done!)
4342
end

spec/services/discovery_engine/query/search_spec.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
end
1717

1818
before do
19-
allow(Rails.configuration).to receive(:discovery_engine_serving_config)
20-
.and_return("serving-config-path")
2119
allow(DiscoveryEngine::Query::Filters).to receive(:new).and_return(filters)
2220
end
2321

@@ -52,7 +50,7 @@
5250

5351
it "calls the client with the expected parameters" do
5452
expect(client).to have_received(:search).with(
55-
serving_config: "serving-config-path",
53+
serving_config: ServingConfig.default.name,
5654
query: "garden centres",
5755
offset: 0,
5856
page_size: 10,
@@ -76,7 +74,7 @@
7674

7775
it "calls the client with the expected parameters" do
7876
expect(client).to have_received(:search).with(
79-
serving_config: "serving-config-path",
77+
serving_config: ServingConfig.default.name,
8078
query: "garden centres",
8179
offset: 11,
8280
page_size: 22,

spec/services/discovery_engine/sync/delete_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
it "deletes the document" do
1616
expect(client).to have_received(:delete_document)
17-
.with(name: "branch/documents/some_content_id")
17+
.with(name: "#{Branch.default.name}/documents/some_content_id")
1818
end
1919

2020
it_behaves_like "a successful sync operation", "delete"

spec/services/discovery_engine/sync/put_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
expect(client).to have_received(:update_document).with(
2727
document: {
2828
id: "some_content_id",
29-
name: "branch/documents/some_content_id",
29+
name: "#{Branch.default.name}/documents/some_content_id",
3030
json_data: "{\"foo\":\"bar\",\"payload_version\":\"1\"}",
3131
content: {
3232
mime_type: "text/html",

spec/services/discovery_engine/sync/shared_examples.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
before do
99
allow(Kernel).to receive(:sleep).and_return(nil)
1010
allow(Rails).to receive(:logger).and_return(logger)
11-
allow(Rails.configuration).to receive(:discovery_engine_datastore_branch).and_return("branch")
1211
allow(GovukError).to receive(:notify)
1312

1413
allow(Coordination::DocumentLock).to receive(:new).with("some_content_id").and_return(lock)

spec/services/discovery_engine/user_events/import_spec.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
before do
1616
allow(Rails.configuration).to receive_messages(
17-
discovery_engine_datastore: "data/store",
1817
google_cloud_project_id: "my-fancy-project",
1918
)
2019
end
@@ -55,7 +54,7 @@
5554
table_id: "search-event",
5655
partition_date: Google::Type::Date.new(year: 2000, month: 1, day: 1),
5756
},
58-
parent: "data/store",
57+
parent: DataStore.default.name,
5958
)
6059
end
6160
end
@@ -71,7 +70,7 @@
7170
table_id: "search-intraday-event",
7271
partition_date: Google::Type::Date.new(year: 1989, month: 12, day: 13),
7372
},
74-
parent: "data/store",
73+
parent: DataStore.default.name,
7574
)
7675
end
7776
end

0 commit comments

Comments
 (0)