Skip to content

Commit e96544f

Browse files
committed
feat(matrix): improve reasons in response when pacticipant cannot be deployed
1 parent 475a401 commit e96544f

15 files changed

+578
-273
lines changed

MATRIX.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Interpreting the Matrix results
2+
3+
* If there is a row with a blank provider version, it's because the pact for that consumer version hasn't been verified by that provider (the result of a left outer join).
4+
* If there is no row, it's because it has been verified, but not by the provider version you've specified.

lib/pact_broker/matrix/deployment_status_summary.rb

+74-22
Original file line numberDiff line numberDiff line change
@@ -22,29 +22,52 @@ def counts
2222
end
2323

2424
def deployable?
25-
# return nil if rows.empty?
25+
return false if specified_selectors_that_do_not_exist.any?
2626
return nil if rows.any?{ |row| row.success.nil? }
2727
return nil if required_integrations_without_a_row.any?
28-
rows.all?{ |row| row.success }
28+
rows.all?{ |row| row.success } # true if rows is empty
2929
end
3030

3131
def reasons
32-
@reasons ||= begin
33-
reasons = []
34-
reasons.concat(missing_reasons)
35-
reasons.concat(failure_messages)
36-
reasons.concat(unverified_messages)
37-
reasons.concat(success_messages)
38-
reasons
32+
error_messages.any? ? error_messages : success_messages
33+
end
34+
35+
def error_messages
36+
@error_messages ||= begin
37+
messages = []
38+
messages.concat(specified_selectors_do_not_exist_messages)
39+
if messages.empty?
40+
messages.concat(missing_reasons)
41+
messages.concat(failure_messages)
42+
messages.concat(unverified_messages)
43+
end
44+
messages.uniq
3945
end
4046
end
4147

48+
def specified_selectors_that_do_not_exist
49+
resolved_selectors.select(&:specified_version_that_does_not_exist?)
50+
end
51+
52+
def specified_selectors_do_not_exist_messages
53+
specified_selectors_that_do_not_exist.collect(&:version_does_not_exist_description)
54+
end
55+
4256
def unverified_messages
4357
if rows.any?{ |row| row.success.nil? }
44-
["Missing one or more verification results"]
58+
rows.collect do | row |
59+
missing_verified_pact_reason(row)
60+
61+
# selectors = selectors_without_a_version_for(row)
62+
# if selectors.any?
63+
# selectors.collect(&:version_does_not_exist_description)
64+
# else
65+
# ["Missing one or more required verification results"]
66+
# end
67+
end
4568
else
4669
[]
47-
end
70+
end.flatten.uniq
4871
end
4972

5073
def failure_messages
@@ -57,10 +80,14 @@ def failure_messages
5780

5881
def success_messages
5982
if rows.all?{ |row| row.success } && required_integrations_without_a_row.empty?
60-
["All verification results are published and successful"]
83+
if rows.any?
84+
["All required verification results are published and successful"]
85+
else
86+
["There are no missing dependencies"]
87+
end
6188
else
6289
[]
63-
end
90+
end.flatten.uniq
6491
end
6592

6693
# For deployment, the consumer requires the provider,
@@ -77,20 +104,45 @@ def required_integrations_without_a_row
77104
end
78105

79106
def missing_reasons
80-
required_integrations_without_a_row.collect do | missing_relationship|
81-
consumer_version_desc = "#{missing_relationship.consumer_name} (#{resolved_version_for(missing_relationship.consumer_id)})"
82-
provider_version_desc = "#{missing_relationship.provider_name} (#{resolved_version_for(missing_relationship.provider_id)})"
83-
"There is no verified pact between #{consumer_version_desc} and #{provider_version_desc}"
107+
required_integrations_without_a_row.collect do | integration |
108+
relevant_selectors_without_a_version = selectors_without_a_version_for(integration)
109+
# if relevant_selectors_without_a_version.any?
110+
# missing_specified_version_reasons(relevant_selectors_without_a_version)
111+
# else
112+
missing_verified_pact_reason(integration)
113+
# end
114+
end.flatten
115+
end
116+
117+
def selectors_without_a_version_for(integration)
118+
selectors_with_non_existing_versions.select do | selector |
119+
integration.involves_pacticipant_with_name?(selector.pacticipant_name)
84120
end
85121
end
86122

87-
def resolved_version_for(pacticipant_id)
88-
resolved_selector = resolved_selectors.find{ | s| s[:pacticipant_id] == pacticipant_id }
123+
def selectors_with_non_existing_versions
124+
@selectors_with_non_existing_versions ||= resolved_selectors.select(&:latest_tagged_version_that_does_not_exist?)
125+
end
126+
127+
def missing_specified_version_reasons(selectors)
128+
selectors.collect(&:version_does_not_exist_description)
129+
end
130+
131+
def missing_verified_pact_reason(integration)
132+
"There is no verified pact between #{description_for_selector(integration.consumer_name)} and #{description_for_selector(integration.provider_name)}"
133+
# "There is no verification by #{description_for_selector(integration.provider_name)} for the pact for #{description_for_selector(integration.consumer_name)}"
134+
end
135+
136+
def description_for_selector(pacticipant_name)
137+
resolved_selector = resolved_selectors.find{ | s| s.pacticipant_name == pacticipant_name }
89138
if resolved_selector
90-
resolved_selector[:pacticipant_version_number]
139+
resolved_selector.description
91140
else
92-
logger.warn "Could not find the resolved version for pacticipant_id #{pacticipant_id} from integrations #{integrations.collect(&:to_s).join(", ")} in resolved selectors #{resolved_selectors.inspect}"
93-
"unresolved version"
141+
# This happens when the user has not specified a version of the provider (eg no 'latest' and/or 'tag')
142+
# so the "add inferred selectors" code has not run
143+
# AND no versions of the provider exist (ie. it has never verified the pact).
144+
logger.warn "Could not find the resolved version for pacticipant_name #{pacticipant_name} from integrations #{integrations.collect(&:to_s).join(", ")} in resolved selectors #{resolved_selectors.inspect}"
145+
"#{pacticipant_name} (unresolved version)"
94146
end
95147
end
96148
end

lib/pact_broker/matrix/integration.rb

+9-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def pacticipant_names
5757
end
5858

5959
def to_s
60-
"Relationship between #{consumer_name} (id=#{consumer_id}) and #{provider_name} (id=#{provider_id})"
60+
"Integration between #{consumer_name} (id=#{consumer_id}) and #{provider_name} (id=#{provider_id})"
6161
end
6262

6363
def involves_consumer_with_id?(consumer_id)
@@ -75,6 +75,14 @@ def involves_provider_with_name?(provider_name)
7575
def involves_consumer_with_name?(consumer_name)
7676
self.consumer_name == consumer_name
7777
end
78+
79+
def pacticipant_names
80+
[consumer_name, provider_name]
81+
end
82+
83+
def involves_pacticipant_with_name?(pacticipant_name)
84+
pacticipant_names.include?(pacticipant_name)
85+
end
7886
end
7987
end
8088
end

lib/pact_broker/matrix/query_results.rb

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
module PactBroker
22
module Matrix
33
class QueryResults < Array
4-
attr_reader :selectors, :options, :resolved_selectors
4+
attr_reader :selectors, :options, :resolved_selectors, :integrations
55

6-
def initialize rows, selectors, options, resolved_selectors
6+
def initialize rows, selectors, options, resolved_selectors, integrations
77
super(rows)
88
@selectors = selectors
99
@resolved_selectors = resolved_selectors
1010
@options = options
11+
@integrations = integrations
1112
end
1213

1314
def rows

lib/pact_broker/matrix/query_results_with_deployment_status_summary.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ module Matrix
55
class QueryResultsWithDeploymentStatusSummary < QueryResults
66
attr_reader :deployment_status_summary
77

8-
def initialize rows, selectors, options, resolved_selectors, deployment_status_summary
9-
super(rows, selectors, options, resolved_selectors)
8+
def initialize rows, selectors, options, resolved_selectors, integrations, deployment_status_summary
9+
super(rows, selectors, options, resolved_selectors, integrations)
1010
@deployment_status_summary = deployment_status_summary
1111
end
1212
end

lib/pact_broker/matrix/repository.rb

+49-49
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ def find specified_selectors, options = {}
6060
lines = lines.select{ |l| options[:success].include?(l.success) }
6161
end
6262

63-
QueryResults.new(lines.sort, specified_selectors, options, resolved_selectors)
63+
integrations = find_integrations_for_specified_selectors(resolved_selectors.select(&:specified?))
64+
65+
QueryResults.new(lines.sort, specified_selectors, options, resolved_selectors, integrations)
6466
end
6567

6668
def find_for_consumer_and_provider pacticipant_1_name, pacticipant_2_name
@@ -73,24 +75,35 @@ def find_compatible_pacticipant_versions selectors
7375
find(selectors, latestby: 'cvpv').select{|line| line.success }
7476
end
7577

76-
def find_integrations(pacticipant_names)
77-
selectors = pacticipant_names.collect{ | pacticipant_name | add_ids_to_selector(pacticipant_name: pacticipant_name) }
78+
# If one pacticipant is specified, find all the integrations that involve that pacticipant
79+
# If two or more are specified, just return the integrations that involve the specified pacticipants
80+
def find_integrations_for_specified_selectors(resolved_specified_selectors)
81+
specified_pacticipant_names = resolved_specified_selectors.collect(&:pacticipant_name)
7882
Row
7983
.select(:consumer_name, :consumer_id, :provider_name, :provider_id)
80-
.matching_selectors(selectors)
84+
.matching_selectors(resolved_specified_selectors)
8185
.distinct
8286
.all
8387
.collect do |row |
8488
row.to_hash
8589
end
8690
.uniq
8791
.collect do | hash |
88-
Integration.from_hash(hash.merge(required: pacticipant_names.include?(hash[:consumer_name])))
92+
required = is_a_row_for_this_integration_required?(specified_pacticipant_names, hash[:consumer_name])
93+
Integration.from_hash(hash.merge(required: required))
8994
end
9095
end
9196

9297
private
9398

99+
# If one of the specified pacticipants is a consumer, then that provider is required to be deployed
100+
# to the same environment before the consumer can be deployed.
101+
# If one of the specified pacticipants is a provider, then the provider may be deployed
102+
# without the consumer being present.
103+
def is_a_row_for_this_integration_required?(specified_pacticipant_names, consumer_name)
104+
specified_pacticipant_names.include?(consumer_name)
105+
end
106+
94107
def apply_latestby options, selectors, lines
95108
return lines unless options[:latestby]
96109
group_by_columns = case options[:latestby]
@@ -121,7 +134,7 @@ def remove_overwritten_revisions lines
121134
end
122135

123136
def query_matrix selectors, options
124-
query = view_for(options).select_all.matching_selectors(selectors)
137+
query = Row.select_all.matching_selectors(selectors)
125138
query = query.limit(options[:limit]) if options[:limit]
126139
query
127140
.order_by_names_ascending_most_recent_first
@@ -130,12 +143,8 @@ def query_matrix selectors, options
130143
.all
131144
end
132145

133-
def view_for(options)
134-
Row
135-
end
136-
137146
def resolve_selectors(specified_selectors, options)
138-
resolved_specified_selectors = resolve_versions_and_add_ids(specified_selectors, options)
147+
resolved_specified_selectors = resolve_versions_and_add_ids(specified_selectors, :specified)
139148
if options[:latest] || options[:tag]
140149
add_inferred_selectors(resolved_specified_selectors, options)
141150
else
@@ -144,42 +153,40 @@ def resolve_selectors(specified_selectors, options)
144153
end
145154

146155
# Find the version number for selectors with the latest and/or tag specified
147-
def resolve_versions_and_add_ids(selectors, options, required = true)
156+
def resolve_versions_and_add_ids(selectors, selector_type)
148157
selectors.collect do | selector |
149158
pacticipant = PactBroker::Domain::Pacticipant.find(name: selector[:pacticipant_name])
159+
versions = find_versions_for_selector(selector)
160+
build_selectors_for_pacticipant_and_versions(pacticipant, versions, selector, selector_type)
161+
end.flatten
162+
end
150163

151-
versions = find_versions_for_selector(selector, required)
152-
153-
if versions
154-
versions.collect do | version |
155-
if version
156-
selector_for_version(pacticipant, version)
157-
else
158-
selector_for_non_existing_version(pacticipant)
159-
end
164+
def build_selectors_for_pacticipant_and_versions(pacticipant, versions, original_selector, selector_type)
165+
if versions
166+
versions.collect do | version |
167+
if version
168+
selector_for_version(pacticipant, version, original_selector, selector_type)
169+
else
170+
selector_for_non_existing_version(pacticipant, original_selector, selector_type)
160171
end
161-
else
162-
selector_without_version(pacticipant)
163172
end
164-
end.flatten
173+
else
174+
selector_without_version(pacticipant, selector_type)
175+
end
165176
end
166177

167-
def find_versions_for_selector(selector, required)
178+
def find_versions_for_selector(selector)
168179
if selector[:tag] && selector[:latest]
169180
version = version_repository.find_by_pacticipant_name_and_latest_tag(selector[:pacticipant_name], selector[:tag])
170-
# raise Error.new("No version of #{selector[:pacticipant_name]} found with tag #{selector[:tag]}") if required && !version
171181
[version]
172182
elsif selector[:latest]
173183
version = version_repository.find_latest_by_pacticpant_name(selector[:pacticipant_name])
174-
# raise Error.new("No version of #{selector[:pacticipant_name]} found") if required && !version
175184
[version]
176185
elsif selector[:tag]
177186
versions = version_repository.find_by_pacticipant_name_and_tag(selector[:pacticipant_name], selector[:tag])
178-
# raise Error.new("No version of #{selector[:pacticipant_name]} found with tag #{selector[:tag]}") if required && versions.empty?
179187
versions.any? ? versions : [nil]
180188
elsif selector[:pacticipant_version_number]
181189
version = version_repository.find_by_pacticipant_name_and_number(selector[:pacticipant_name], selector[:pacticipant_version_number])
182-
# raise Error.new("No version #{selector[:pacticipant_version_number]} of #{selector[:pacticipant_name]} found") if required && !version
183190
[version]
184191
else
185192
nil
@@ -203,24 +210,17 @@ def add_ids_to_selector(selector)
203210
selector
204211
end
205212

206-
# eg. when checking to see if Foo version 2 can be deployed to prod,
207-
# need to look up all the 'partner' pacticipants, and determine their latest prod versions
213+
# When only one selector is specified, (eg. checking to see if Foo version 2 can be deployed to prod),
214+
# need to look up all integrated pacticipants, and determine their relevant (eg. latest prod) versions
208215
def add_inferred_selectors(resolved_specified_selectors, options)
209-
integrations = find_integrations(resolved_specified_selectors.collect{|s| s[:pacticipant_name]})
216+
integrations = find_integrations_for_specified_selectors(resolved_specified_selectors)
210217
all_pacticipant_names = integrations.collect(&:pacticipant_names).flatten.uniq
211218
specified_names = resolved_specified_selectors.collect{ |s| s[:pacticipant_name] }
212219
inferred_pacticipant_names = all_pacticipant_names - specified_names
213-
# Inferred providers are required for a consumer to be deployed
214-
required_inferred_pacticipant_names = inferred_pacticipant_names.select{ | n | integrations.any?{ |i| i.involves_provider_with_name?(n) } }
215-
# Inferred consumers are NOT required for a provider to be deployed
216-
optional_inferred_pacticipant_names = inferred_pacticipant_names - required_inferred_pacticipant_names
217-
218-
resolved_specified_selectors +
219-
build_inferred_selectors(required_inferred_pacticipant_names, options, true) +
220-
build_inferred_selectors(optional_inferred_pacticipant_names, options, false)
220+
resolved_specified_selectors + build_inferred_selectors(inferred_pacticipant_names, options)
221221
end
222222

223-
def build_inferred_selectors(inferred_pacticipant_names, options, required)
223+
def build_inferred_selectors(inferred_pacticipant_names, options)
224224
selectors = inferred_pacticipant_names.collect do | pacticipant_name |
225225
selector = {
226226
pacticipant_name: pacticipant_name
@@ -229,26 +229,26 @@ def build_inferred_selectors(inferred_pacticipant_names, options, required)
229229
selector[:latest] = options[:latest] if options[:latest]
230230
selector
231231
end
232-
resolve_versions_and_add_ids(selectors, options, required)
232+
resolve_versions_and_add_ids(selectors, :inferred)
233233
end
234234

235235
def all_pacticipant_names_in_specified_matrix(selectors)
236-
find_integrations(selectors.collect{|s| s[:pacticipant_name]})
236+
find_integrations_for_specified_selectors(selectors)
237237
.collect(&:pacticipant_names)
238238
.flatten
239239
.uniq
240240
end
241241

242-
def selector_for_non_existing_version(pacticipant)
243-
ResolvedSelector.for_pacticipant_and_non_existing_version(pacticipant)
242+
def selector_for_non_existing_version(pacticipant, original_selector, selector_type)
243+
ResolvedSelector.for_pacticipant_and_non_existing_version(pacticipant, original_selector, selector_type)
244244
end
245245

246-
def selector_for_version(pacticipant, version)
247-
ResolvedSelector.for_pacticipant_and_version(pacticipant, version)
246+
def selector_for_version(pacticipant, version, original_selector, selector_type)
247+
ResolvedSelector.for_pacticipant_and_version(pacticipant, version, original_selector, selector_type)
248248
end
249249

250-
def selector_without_version(pacticipant)
251-
ResolvedSelector.for_pacticipant(pacticipant)
250+
def selector_without_version(pacticipant, selector_type)
251+
ResolvedSelector.for_pacticipant(pacticipant, selector_type)
252252
end
253253
end
254254
end

0 commit comments

Comments
 (0)