Skip to content

Commit e238749

Browse files
committed
feat(index): optimise query, feature toggled
1 parent 54156df commit e238749

File tree

9 files changed

+105
-63
lines changed

9 files changed

+105
-63
lines changed

DEVELOPER_DOCUMENTATION.md

+11
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,19 @@ pact publication resource will be created, but it will reuse the existing pact v
106106
-> versions
107107
-> latest_tagged_pact_consumer_version_orders
108108
-> latest_pact_publications_by_consumer_versions
109+
110+
= head_pact_publications
111+
-> latest_pact_publications
112+
-> latest_pact_publication_ids_for_consumer_versions
113+
-> latest_tagged_pact_publications
114+
-> latest_pact_publications_by_consumer_versions (optimised for pp_ids)
115+
-> latest_tagged_pact_consumer_version_orders (optimised for pp_ids)
116+
109117
```
110118

119+
120+
121+
111122
### Useful to know stuff
112123

113124
* The supported database types are Postgres (recommended), MySQL (sigh) and Sqlite (just for testing, not recommended for production). Check the travis.yml file for the supported database versions.

lib/pact_broker/domain/index_item.rb

+4
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ def consumer_version_number
6060
@latest_pact.consumer_version_number
6161
end
6262

63+
def consumer_version_order
64+
consumer_version.order
65+
end
66+
6367
def consumer_version
6468
@latest_pact.consumer_version
6569
end

lib/pact_broker/index/service.rb

+72-49
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,24 @@
33
require 'pact_broker/domain/index_item'
44
require 'pact_broker/matrix/head_row'
55
require 'pact_broker/matrix/aggregated_row'
6+
require 'pact_broker/repositories/helpers'
67

78
module PactBroker
8-
99
module Index
1010
class Service
11-
1211
extend PactBroker::Repositories
1312
extend PactBroker::Services
1413
extend PactBroker::Logging
1514

15+
COLS = [:id, :consumer_name, :provider_name, :consumer_version_order]
16+
LATEST_PPS = Sequel::Model.db[:latest_pact_publications].select(*COLS)
17+
LATEST_TAGGED_PPS = Sequel::Model.db[:latest_tagged_pact_publications].select(*COLS)
18+
HEAD_PP_ORDER_COLUMNS = [
19+
Sequel.asc(Sequel.function(:lower, :consumer_name)),
20+
Sequel.desc(:consumer_version_order),
21+
Sequel.asc(Sequel.function(:lower, :provider_name))
22+
].freeze
23+
1624
# This method provides data for both the OSS server side rendered index (with and without tags)
1725
# and the Pactflow UI. It really needs to be broken into to separate methods, as it's getting too messy
1826
# supporting both
@@ -46,10 +54,7 @@ def self.find_index_items_original options = {}
4654
end
4755
rows = rows.all.group_by(&:pact_publication_id).values.collect{ | rows| Matrix::AggregatedRow.new(rows) }
4856

49-
50-
5157
rows.sort.collect do | row |
52-
# TODO simplify. Do we really need 3 layers of abstraction?
5358
PactBroker::Domain::IndexItem.create(
5459
row.consumer,
5560
row.provider,
@@ -65,31 +70,14 @@ def self.find_index_items_original options = {}
6570
end
6671

6772
def self.find_index_items_optimised options = {}
68-
pact_publication_ids = nil
69-
latest_verifications_for_cv_tags = nil
70-
71-
if !options[:tags]
72-
# server side rendered index page without tags
73-
pact_publication_ids = latest_pact_publications.select(:id)
74-
else
75-
# server side rendered index page with tags=true or tags[]=a&tags=[]b
76-
if options[:tags].is_a?(Array)
77-
# TODO test for this
78-
pact_publication_ids = head_pact_publications_ids_for_tags(options[:tags])
79-
latest_verifications_for_cv_tags = PactBroker::Verifications::LatestVerificationForConsumerVersionTag
80-
.eager(:provider_version)
81-
.where(consumer_version_tag_name: options[:tags]).all
82-
else
83-
pact_publication_ids = head_pact_publications_ids
84-
latest_verifications_for_cv_tags = PactBroker::Verifications::LatestVerificationForConsumerVersionTag.eager(:provider_version).all
85-
end
86-
end
87-
73+
latest_verifications_for_cv_tags = latest_verifications_for_consumer_version_tags(options)
8874
latest_pact_publication_ids = latest_pact_publications.select(:id).all.collect{ |h| h[:id] }
8975

9076
# We only need to know if a webhook exists for an integration, not what its properties are
9177
webhooks = PactBroker::Webhooks::Webhook.select(:consumer_id, :provider_id).distinct.all
9278

79+
pact_publication_ids = head_pact_publication_ids(options)
80+
9381
pact_publications = PactBroker::Pacts::PactPublication
9482
.where(id: pact_publication_ids)
9583
.select_all_qualified
@@ -102,7 +90,6 @@ def self.find_index_items_optimised options = {}
10290
.eager(:head_pact_tags)
10391

10492
pact_publications.all.collect do | pact_publication |
105-
10693
is_overall_latest_for_integration = latest_pact_publication_ids.include?(pact_publication.id)
10794
latest_verification = latest_verification_for_pseudo_branch(pact_publication, is_overall_latest_for_integration, latest_verifications_for_cv_tags, options[:tags])
10895
webhook = webhooks.find{ |webhook| webhook.is_for?(pact_publication.integration) }
@@ -148,47 +135,83 @@ def self.consumer_version_tags(pact_publication, tags_option)
148135
end
149136

150137
def self.find_index_items_for_api(consumer_name: nil, provider_name: nil, **ignored)
151-
rows = PactBroker::Matrix::HeadRow
152-
.eager(:consumer_version_tags)
153-
.eager(:provider_version_tags)
138+
latest_pact_publication_ids = latest_pact_publications.select(:id).all.collect{ |h| h[:id] }
139+
pact_publication_ids = head_pact_publication_ids(consumer_name: consumer_name, provider_name: provider_name, tags: true)
140+
141+
pact_publications = PactBroker::Pacts::PactPublication
142+
.where(id: pact_publication_ids)
154143
.select_all_qualified
144+
.eager(:consumer)
145+
.eager(:provider)
146+
.eager(:pact_version)
147+
.eager(:consumer_version)
148+
.eager(latest_verification: { provider_version: :tags_with_latest_flag })
149+
.eager(:head_pact_tags)
155150

156-
rows = rows.consumer(consumer_name) if consumer_name
157-
rows = rows.provider(provider_name) if provider_name
158151

159-
rows = rows.all.group_by(&:pact_publication_id).values.collect{ | rows| Matrix::AggregatedRow.new(rows) }
152+
pact_publications.all.collect do | pact_publication |
153+
154+
is_overall_latest_for_integration = latest_pact_publication_ids.include?(pact_publication.id)
160155

161-
rows.sort.collect do | row |
162-
# TODO separate this model from IndexItem
163-
# webhook status not currently displayed in Pactflow UI, so don't query for it.
164156
PactBroker::Domain::IndexItem.create(
165-
row.consumer,
166-
row.provider,
167-
row.pact,
168-
row.overall_latest?,
169-
row.latest_verification_for_pact_version,
157+
pact_publication.consumer,
158+
pact_publication.provider,
159+
pact_publication.to_domain_lightweight,
160+
is_overall_latest_for_integration,
161+
pact_publication.latest_verification,
170162
[],
171163
[],
172-
row.consumer_head_tag_names,
173-
row.provider_version_tags.select(&:latest?)
164+
pact_publication.head_pact_tags.collect(&:name),
165+
pact_publication.latest_verification ? pact_publication.latest_verification.provider_version.tags_with_latest_flag.select(&:latest?) : []
174166
)
175-
end
167+
end.sort
176168
end
177169

178170
def self.latest_pact_publications
179171
db[:latest_pact_publications]
180172
end
181173

182-
def self.head_pact_publications_ids
183-
db[:head_pact_tags].select(Sequel[:pact_publication_id].as(:id)).union(db[:latest_pact_publications].select(:id)).limit(500)
174+
def self.db
175+
PactBroker::Pacts::PactPublication.db
184176
end
185177

186-
def self.head_pact_publications_ids_for_tags(tag_names)
187-
db[:head_pact_tags].select(Sequel[:pact_publication_id].as(:id)).where(name: tag_names).union(db[:latest_pact_publications].select(:id)).limit(500)
178+
def self.head_pact_publication_ids(options = {})
179+
query = if options[:tags].is_a?(Array)
180+
LATEST_PPS.union(LATEST_TAGGED_PPS.where(tag_name: options[:tags]))
181+
elsif options[:tags]
182+
LATEST_PPS.union(LATEST_TAGGED_PPS)
183+
else
184+
LATEST_PPS
185+
end
186+
187+
if options[:consumer_name]
188+
query = query.where(PactBroker::Repositories::Helpers.name_like(:consumer_name, options[:consumer_name]))
189+
end
190+
191+
if options[:provider_name]
192+
query = query.where(PactBroker::Repositories::Helpers.name_like(:provider_name, options[:provider_name]))
193+
end
194+
195+
query.order(*HEAD_PP_ORDER_COLUMNS)
196+
.limit(options[:limit] || 50)
197+
.offset(options[:offset] || 0)
198+
.select(:id)
188199
end
189200

190-
def self.db
191-
PactBroker::Pacts::PactPublication.db
201+
def self.latest_verifications_for_consumer_version_tags(options)
202+
# server side rendered index page with tags[]=a&tags=[]b
203+
if options[:tags].is_a?(Array)
204+
PactBroker::Verifications::LatestVerificationForConsumerVersionTag
205+
.eager(:provider_version)
206+
.where(consumer_version_tag_name: options[:tags])
207+
.all
208+
elsif options[:tags] # server side rendered index page with tags=true
209+
PactBroker::Verifications::LatestVerificationForConsumerVersionTag
210+
.eager(:provider_version)
211+
.all
212+
else
213+
nil # should not be used
214+
end
192215
end
193216
end
194217
end

lib/pact_broker/ui/controllers/index.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Index < Base
1414
if params[:tags]
1515
tags = params[:tags] == 'true' ? true : [*params[:tags]].compact
1616
end
17-
options = { tags: tags }
17+
options = { tags: tags, limit: params[:limit]&.to_i, offset: params[:offset]&.to_i }
1818
options[:optimised] = true if params[:optimised] == 'true'
1919
view_model = ViewDomain::IndexItems.new(index_service.find_index_items(options))
2020
page = tags ? :'index/show-with-tags' : :'index/show'

lib/pact_broker/ui/view_models/index_item.rb

+7-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ def consumer_version_number
2727
PactBroker::Versions::AbbreviateNumber.call(@relationship.consumer_version_number)
2828
end
2929

30+
def consumer_version_order
31+
@relationship.consumer_version_order
32+
end
33+
3034
def provider_version_number
3135
PactBroker::Versions::AbbreviateNumber.call(@relationship.provider_version_number)
3236
end
@@ -164,7 +168,9 @@ def verification_tooltip
164168
def <=> other
165169
comp = consumer_name.downcase <=> other.consumer_name.downcase
166170
return comp unless comp == 0
167-
provider_name.downcase <=> other.provider_name.downcase
171+
comp = provider_name.downcase <=> other.provider_name.downcase
172+
return comp unless comp == 0
173+
other.consumer_version_order <=> consumer_version_order
168174
end
169175

170176
def short_version_number version_number

lib/pact_broker/ui/views/index/show-with-tags.haml

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
%td.consumer
3939
%a{:href => index_item.consumer_group_url }
4040
= escape_html(index_item.consumer_name)
41-
%td.consumer-version-number
41+
%td.consumer-version-number{"data-text": index_item.consumer_version_order}
4242
%div.clippable
4343
= escape_html(index_item.consumer_version_number)
4444
- if index_item.consumer_version_number

spec/lib/pact_broker/index/service_spec.rb

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
require 'pact_broker/domain/pact'
55

66
module PactBroker
7-
87
module Index
98
describe Service do
109
let(:td) { TestDataBuilder.new }

spec/lib/pact_broker/ui/controllers/index_spec.rb

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
require 'spec_helper'
2-
require 'pact_broker/ui/controllers/index'
3-
41
require 'rack/test'
2+
require 'pact_broker/ui/controllers/index'
3+
require 'pact_broker/index/service'
54

65
module PactBroker
76
module UI
@@ -37,7 +36,7 @@ module Controllers
3736
end
3837

3938
it "passes tags: true to the IndexService" do
40-
expect(PactBroker::Index::Service).to receive(:find_index_items).with(tags: true)
39+
expect(PactBroker::Index::Service).to receive(:find_index_items).with(tags: true, limit: nil, offset: nil)
4140
get "/", { tags: 'true' }
4241
end
4342
end
@@ -48,7 +47,7 @@ module Controllers
4847
end
4948

5049
it "passes tags: ['prod'] to the IndexService" do
51-
expect(PactBroker::Index::Service).to receive(:find_index_items).with(tags: ["prod"])
50+
expect(PactBroker::Index::Service).to receive(:find_index_items).with(tags: ["prod"], limit: nil, offset: nil)
5251
get "/", { tags: ["prod"] }
5352
end
5453
end
@@ -59,7 +58,7 @@ module Controllers
5958
end
6059

6160
it "passes tags: ['prod'] to the IndexService" do
62-
expect(PactBroker::Index::Service).to receive(:find_index_items).with(tags: ["prod"])
61+
expect(PactBroker::Index::Service).to receive(:find_index_items).with(tags: ["prod"], limit: nil, offset: nil)
6362
get "/", { tags: "prod" }
6463
end
6564
end

spec/lib/pact_broker/ui/view_models/index_items_spec.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ module UI
66
module ViewDomain
77
describe IndexItems do
88

9-
let(:relationship_model_4) { double("PactBroker::Domain::IndexItem", consumer_name: "A", provider_name: "X") }
10-
let(:relationship_model_2) { double("PactBroker::Domain::IndexItem", consumer_name: "a", provider_name: "y") }
11-
let(:relationship_model_3) { double("PactBroker::Domain::IndexItem", consumer_name: "A", provider_name: "Z") }
12-
let(:relationship_model_1) { double("PactBroker::Domain::IndexItem", consumer_name: "C", provider_name: "A") }
9+
let(:relationship_model_4) { double("PactBroker::Domain::IndexItem", consumer_name: "A", provider_name: "X", consumer_version_order: 1) }
10+
let(:relationship_model_2) { double("PactBroker::Domain::IndexItem", consumer_name: "a", provider_name: "y", consumer_version_order: 2) }
11+
let(:relationship_model_3) { double("PactBroker::Domain::IndexItem", consumer_name: "A", provider_name: "Z", consumer_version_order: 3) }
12+
let(:relationship_model_1) { double("PactBroker::Domain::IndexItem", consumer_name: "C", provider_name: "A", consumer_version_order: 4) }
1313

1414
subject { IndexItems.new([relationship_model_1, relationship_model_3, relationship_model_4, relationship_model_2]) }
1515

0 commit comments

Comments
 (0)