Skip to content

Commit 1f54ccf

Browse files
committed
feat(index): optimise server side rendered page
1 parent 979dc6b commit 1f54ccf

File tree

14 files changed

+210
-10
lines changed

14 files changed

+210
-10
lines changed

db/ddl_statements/head_pact_tags.rb

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
def head_pact_tags_v1(connection)
2+
connection.from(Sequel.as(:latest_pact_publication_ids_for_consumer_versions, :lp))
3+
.join(:versions,{ Sequel[:lp][:consumer_version_id] => Sequel[:cv][:id]}, { table_alias: :cv })
4+
.join(:latest_tagged_pact_consumer_version_orders, {
5+
Sequel[:lp][:consumer_id] => Sequel[:o][:consumer_id],
6+
Sequel[:lp][:provider_id] => Sequel[:o][:provider_id],
7+
Sequel[:cv][:order] => Sequel[:o][:latest_consumer_version_order]
8+
}, { table_alias: :o})
9+
.select(Sequel[:o][:tag_name].as(:name), Sequel[:lp][:pact_publication_id])
10+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Sequel.migration do
2+
up do
3+
create_view(:head_pact_tags, head_pact_tags_v1(self))
4+
end
5+
6+
down do
7+
drop_view(:head_pact_tags)
8+
end
9+
end

lib/pact_broker/domain/index_item.rb

+26
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,32 @@ def <=> other
127127
provider_name <=> other.provider_name
128128
end
129129

130+
# Add logic for ignoring case
131+
def <=> other
132+
comparisons = [
133+
compare_name_asc(consumer_name, other.consumer_name),
134+
compare_number_desc(consumer_version.order, other.consumer_version.order),
135+
compare_number_desc(latest_pact.revision_number, other.latest_pact.revision_number),
136+
compare_name_asc(provider_name, other.provider_name)
137+
]
138+
139+
comparisons.find{|c| c != 0 } || 0
140+
end
141+
142+
def compare_name_asc name1, name2
143+
name1&.downcase <=> name2&.downcase
144+
end
145+
146+
def compare_number_desc number1, number2
147+
if number1 && number2
148+
number2 <=> number1
149+
elsif number1
150+
1
151+
else
152+
-1
153+
end
154+
end
155+
130156
def to_s
131157
"Pact between #{consumer_name} #{consumer_version_number} and #{provider_name} #{provider_version_number}"
132158
end

lib/pact_broker/domain/version.rb

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'pact_broker/db'
22
require 'pact_broker/domain/order_versions'
33
require 'pact_broker/repositories/helpers'
4+
require 'pact_broker/tags/tag_with_latest_flag'
45

56
module PactBroker
67
module Domain
@@ -9,6 +10,7 @@ class Version < Sequel::Model
910
one_to_many :pact_publications, order: :revision_number, class: "PactBroker::Pacts::PactPublication", key: :consumer_version_id
1011
associate(:many_to_one, :pacticipant, :class => "PactBroker::Domain::Pacticipant", :key => :pacticipant_id, :primary_key => :id)
1112
one_to_many :tags, :reciprocal => :version, order: :created_at
13+
one_to_many :tags_with_latest_flag, class: "PactBroker::Tags::TagWithLatestFlag", key: :version_id, primary_key: :id
1214

1315
dataset_module do
1416
include PactBroker::Repositories::Helpers

lib/pact_broker/index/service.rb

+107
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ class Service
1818
# supporting both
1919

2020
def self.find_index_items options = {}
21+
if options[:optimised]
22+
find_index_items_optimised(options)
23+
else
24+
find_index_items_original(options)
25+
end
26+
end
27+
28+
def self.find_index_items_original options = {}
2129
rows = PactBroker::Matrix::HeadRow
2230
.select_all_qualified
2331
.eager(:latest_triggered_webhooks)
@@ -56,6 +64,89 @@ def self.find_index_items options = {}
5664
end
5765
end
5866

67+
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+
88+
latest_pact_publication_ids = latest_pact_publications.select(:id).all.collect{ |h| h[:id] }
89+
90+
# We only need to know if a webhook exists for an integration, not what its properties are
91+
webhooks = PactBroker::Webhooks::Webhook.select(:consumer_id, :provider_id).distinct.all
92+
93+
pact_publications = PactBroker::Pacts::PactPublication
94+
.where(id: pact_publication_ids)
95+
.select_all_qualified
96+
.eager(:consumer)
97+
.eager(:provider)
98+
.eager(:pact_version)
99+
.eager(integration: [{latest_verification: :provider_version}, :latest_triggered_webhooks])
100+
.eager(:consumer_version)
101+
.eager(latest_verification: { provider_version: :tags_with_latest_flag })
102+
.eager(:head_pact_tags)
103+
104+
pact_publications.all.collect do | pact_publication |
105+
106+
is_overall_latest_for_integration = latest_pact_publication_ids.include?(pact_publication.id)
107+
latest_verification = latest_verification_for_pseudo_branch(pact_publication, is_overall_latest_for_integration, latest_verifications_for_cv_tags, options[:tags])
108+
webhook = webhooks.find{ |webhook| webhook.is_for?(pact_publication.integration) }
109+
110+
PactBroker::Domain::IndexItem.create(
111+
pact_publication.consumer,
112+
pact_publication.provider,
113+
pact_publication.to_domain_lightweight,
114+
is_overall_latest_for_integration,
115+
latest_verification,
116+
webhook ? [webhook]: [],
117+
pact_publication.integration.latest_triggered_webhooks,
118+
consumer_version_tags(pact_publication, options[:tags]),
119+
options[:tags] && latest_verification ? latest_verification.provider_version.tags_with_latest_flag.select(&:latest?) : []
120+
)
121+
end.sort
122+
end
123+
124+
# Worst. Code. Ever.
125+
#
126+
def self.latest_verification_for_pseudo_branch(pact_publication, is_overall_latest, latest_verifications_for_cv_tags, tags_option)
127+
if tags_option == true
128+
latest_verifications_for_cv_tags
129+
.select{ | v | v.consumer_id == pact_publication.consumer_id && v.provider_id == pact_publication.provider_id && pact_publication.head_pact_tags.collect(&:name).include?(v.consumer_version_tag_name) }
130+
.sort{ |v1, v2| v1.id <=> v2.id }.last || (is_overall_latest && pact_publication.integration.latest_verification)
131+
elsif tags_option.is_a?(Array)
132+
latest_verifications_for_cv_tags
133+
.select{ | v | v.consumer_id == pact_publication.consumer_id && v.provider_id == pact_publication.provider_id && pact_publication.head_pact_tags.collect(&:name).include?(v.consumer_version_tag_name) && tags_option.include?(v.consumer_version_tag_name) }
134+
.sort{ |v1, v2| v1.id <=> v2.id }.last || (is_overall_latest && pact_publication.integration.latest_verification)
135+
else
136+
pact_publication.integration.latest_verification
137+
end
138+
end
139+
140+
def self.consumer_version_tags(pact_publication, tags_option)
141+
if tags_option == true
142+
pact_publication.head_pact_tags.collect(&:name)
143+
elsif tags_option.is_a?(Array)
144+
pact_publication.head_pact_tags.collect(&:name) & tags_option
145+
else
146+
[]
147+
end
148+
end
149+
59150
def self.find_index_items_for_api(consumer_name: nil, provider_name: nil, **ignored)
60151
rows = PactBroker::Matrix::HeadRow
61152
.eager(:consumer_version_tags)
@@ -83,6 +174,22 @@ def self.find_index_items_for_api(consumer_name: nil, provider_name: nil, **igno
83174
)
84175
end
85176
end
177+
178+
def self.latest_pact_publications
179+
db[:latest_pact_publications]
180+
end
181+
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)
184+
end
185+
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)
188+
end
189+
190+
def self.db
191+
PactBroker::Pacts::PactPublication.db
192+
end
86193
end
87194
end
88195
end

lib/pact_broker/integrations/integration.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require 'pact_broker/verifications/pseudo_branch_status'
33
require 'pact_broker/domain/verification'
44
require 'pact_broker/webhooks/latest_triggered_webhook'
5+
require 'pact_broker/webhooks/webhook'
56

67
module PactBroker
78
module Integrations
@@ -17,7 +18,7 @@ class Integration < Sequel::Model
1718
# This will only work when using eager loading. The keys are just blanked out to avoid errors.
1819
# I don't understand how they work at all.
1920
# It would be nice to do this declaratively.
20-
many_to_many :webhooks, :left_key => [], left_primary_key: [], :eager_loader=>(proc do |eo_opts|
21+
many_to_many :webhooks, class: "PactBroker::Webhooks::Webhook", :left_key => [], left_primary_key: [], :eager_loader=>(proc do |eo_opts|
2122
eo_opts[:rows].each do |row|
2223
row.associations[:webhooks] = []
2324
end

lib/pact_broker/matrix/head_row.rb

+2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ class HeadRow < Row
1919
# the query down.
2020
# This relation relies on consumer_version_tags already being loaded
2121
one_to_one :latest_verification_for_consumer_version_tag, :class => "PactBroker::Verifications::LatestVerificationForConsumerVersionTag", primary_keys: [], key: [], :eager_loader=>(proc do |eo_opts|
22+
# create an index of provider_id/consumer_id/consumer_version_tag_name => row
2223
tag_to_row = eo_opts[:rows].each_with_object({}) { | row, map | map[[row.provider_id, row.consumer_id, row.consumer_version_tag_name]] = row }
24+
# Initialise the association with nil - not sure why?
2325
eo_opts[:rows].each{|row| row.associations[:latest_verification_for_consumer_version_tag] = nil}
2426

2527
# Need the all then the each to ensure the eager loading works

lib/pact_broker/pacts/pact_publication.rb

+27-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
require 'pact_broker/domain/pact'
22
require 'pact_broker/pacts/pact_version'
33
require 'pact_broker/repositories/helpers'
4+
require 'pact_broker/integrations/integration'
5+
require 'pact_broker/tags/head_pact_tags'
46

57
module PactBroker
68
module Pacts
@@ -16,6 +18,9 @@ class PactPublication < Sequel::Model(:pact_publications)
1618
associate(:many_to_one, :consumer, :class => "PactBroker::Domain::Pacticipant", :key => :consumer_id, :primary_key => :id)
1719
associate(:many_to_one, :consumer_version, :class => "PactBroker::Domain::Version", :key => :consumer_version_id, :primary_key => :id)
1820
associate(:many_to_one, :pact_version, class: "PactBroker::Pacts::PactVersion", :key => :pact_version_id, :primary_key => :id)
21+
associate(:many_to_one, :integration, class: "PactBroker::Integrations::Integration", key: [:consumer_id, :provider_id], primary_key: [:consumer_id, :provider_id])
22+
one_to_one(:latest_verification, class: "PactBroker::Verifications::LatestVerificationForPactVersion", key: :pact_version_id, primary_key: :pact_version_id)
23+
one_to_many(:head_pact_tags, class: "PactBroker::Tags::HeadPactTag", primary_key: :id, key: :pact_publication_id)
1924

2025
dataset_module do
2126
include PactBroker::Repositories::Helpers
@@ -49,21 +54,39 @@ def to_domain
4954
PactBroker::Domain::Pact.new(
5055
id: id,
5156
provider: provider,
52-
consumer: consumer_version.pacticipant,
57+
consumer: consumer,
5358
consumer_version_number: consumer_version.number,
5459
consumer_version: to_version_domain,
5560
revision_number: revision_number,
5661
json_content: pact_version.content,
5762
pact_version_sha: pact_version.sha,
58-
latest_verification: latest_verification,
63+
latest_verification: nil,
64+
created_at: created_at,
65+
head_tag_names: [],
66+
db_model: self
67+
)
68+
end
69+
70+
def to_domain_lightweight
71+
PactBroker::Domain::Pact.new(
72+
id: id,
73+
provider: provider,
74+
consumer: consumer,
75+
consumer_version_number: consumer_version.number,
76+
consumer_version: to_version_domain_lightweight,
77+
revision_number: revision_number,
78+
pact_version_sha: pact_version.sha,
5979
created_at: created_at,
60-
head_tag_names: head_tag_names,
6180
db_model: self
6281
)
6382
end
6483

6584
def to_version_domain
66-
OpenStruct.new(number: consumer_version.number, pacticipant: consumer_version.pacticipant, tags: consumer_version.tags, order: consumer_version.order)
85+
OpenStruct.new(number: consumer_version.number, pacticipant: consumer, tags: consumer_version.tags, order: consumer_version.order)
86+
end
87+
88+
def to_version_domain_lightweight
89+
OpenStruct.new(number: consumer_version.number, pacticipant: consumer, order: consumer_version.order)
6790
end
6891

6992
def upsert
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
require 'pact_broker/db'
2+
require 'pact_broker/repositories/helpers'
3+
4+
module PactBroker
5+
module Tags
6+
class HeadPactTag < Sequel::Model
7+
dataset_module do
8+
include PactBroker::Repositories::Helpers
9+
end
10+
end
11+
end
12+
end

lib/pact_broker/tags/tag_with_latest_flag.rb

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ module PactBroker
55
module Tags
66
# The tag associated with the latest verification for a given tag
77
class TagWithLatestFlag < Sequel::Model(:tags_with_latest_flag)
8-
98
dataset_module do
109
include PactBroker::Repositories::Helpers
1110
end

lib/pact_broker/ui/controllers/index.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ class Index < Base
1414
if params[:tags]
1515
tags = params[:tags] == 'true' ? true : [*params[:tags]].compact
1616
end
17-
view_model = ViewDomain::IndexItems.new(index_service.find_index_items(tags: tags))
17+
options = { tags: tags }
18+
options[:optimised] = true if params[:optimised] == 'true'
19+
view_model = ViewDomain::IndexItems.new(index_service.find_index_items(options))
1820
page = tags ? :'index/show-with-tags' : :'index/show'
1921
haml page, {locals: {index_items: view_model, title: "Pacts"}, layout: :'layouts/main'}
2022
end

lib/pact_broker/webhooks/webhook.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ def parsed_body
6868
end
6969
end
7070

71-
def is_for? relationship
72-
(consumer_id == relationship.consumer_id || !consumer_id) && (provider_id == relationship.provider_id || !provider_id)
71+
def is_for? integration
72+
(consumer_id == integration.consumer_id || !consumer_id) && (provider_id == integration.provider_id || !provider_id)
7373
end
7474

7575
private

spec/lib/pact_broker/index/service_spec.rb

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ module Index
99
describe Service do
1010
let(:td) { TestDataBuilder.new }
1111
let(:tags) { ['prod', 'production'] }
12-
let(:options) { { tags: tags } }
12+
let(:options) { { tags: tags, optimised: optimised } }
1313
let(:rows) { subject.find_index_items(options) }
14+
let(:optimised) { true }
15+
16+
before do
17+
td.create_global_webhook
18+
end
1419

1520
subject{ Service }
1621

spec/lib/pact_broker/pacts/pact_publication_spec.rb

+2
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,14 @@ module Pacts
9999
context "when the pact is the latest for a tag" do
100100
it "returns the relevant tag names" do
101101
expect(pact_publication.head_tag_names).to eq ["yes"]
102+
expect(pact_publication.head_pact_tags.collect(&:name)).to eq ["yes"]
102103
end
103104
end
104105

105106
context "when the pact is not the latest for a tag" do
106107
it "returns the relevant tag names" do
107108
expect(pact_publication.head_tag_names).to eq ["yes"]
109+
expect(pact_publication.head_pact_tags.collect(&:name)).to eq ["yes"]
108110
end
109111
end
110112
end

0 commit comments

Comments
 (0)