Skip to content

Commit ad85db4

Browse files
committed
fix: correct the logic for determining the deployment status in the matrix resource
When there are multiple integrations, and one integration returns a matching matrix row for the can-i-deploy query, while another other doesn't, the deployable status should not be true. Closes: pact-foundation/pact_broker-client#33
1 parent ddec810 commit ad85db4

15 files changed

+460
-91
lines changed

lib/pact_broker/api/decorators/matrix_decorator.rb

+12-17
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ module Decorators
77
class MatrixDecorator
88
include PactBroker::Api::PactBrokerUrls
99

10-
def initialize(lines)
11-
@lines = lines
10+
def initialize(query_results_with_deployment_status_summary)
11+
@query_results_with_deployment_status_summary = query_results_with_deployment_status_summary
1212
end
1313

1414
def to_json(options)
@@ -21,32 +21,27 @@ def to_hash(options)
2121
deployable: deployable,
2222
reason: reason
2323
},
24-
matrix: matrix(lines, options[:user_options][:base_url])
25-
}
24+
matrix: matrix(options[:user_options][:base_url])
25+
}.tap do | hash |
26+
hash[:summary].merge!(query_results_with_deployment_status_summary.deployment_status_summary.counts)
27+
end
28+
2629
end
2730

2831
def deployable
29-
return nil if lines.empty?
30-
return nil if lines.any?{ |line| line.success.nil? }
31-
lines.any? && lines.all?{ |line| line.success }
32+
query_results_with_deployment_status_summary.deployment_status_summary.deployable?
3233
end
3334

3435
def reason
35-
return "No results matched the given query" if lines.empty?
36-
case deployable
37-
when true then "All verification results are published and successful"
38-
when false then "One or more verifications have failed"
39-
else
40-
"Missing one or more verification results"
41-
end
36+
query_results_with_deployment_status_summary.deployment_status_summary.reasons.join("\n")
4237
end
4338

4439
private
4540

46-
attr_reader :lines
41+
attr_reader :query_results_with_deployment_status_summary
4742

48-
def matrix(lines, base_url)
49-
lines.collect do | line |
43+
def matrix(base_url)
44+
query_results_with_deployment_status_summary.rows.collect do | line |
5045
provider = OpenStruct.new(name: line.provider_name)
5146
consumer = OpenStruct.new(name: line.consumer_name)
5247
consumer_version = OpenStruct.new(number: line.consumer_version_number, pacticipant: consumer)

lib/pact_broker/api/resources/relationships.rb

+1-3
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ def to_csv
2222
def pacts
2323
pact_service.find_latest_pacts
2424
end
25-
2625
end
2726
end
28-
2927
end
30-
end
28+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
module PactBroker
2+
module Matrix
3+
class DeploymentStatusSummary
4+
attr_reader :rows, :resolved_selectors, :integrations
5+
6+
def initialize(rows, resolved_selectors, integrations)
7+
@rows = rows
8+
@resolved_selectors = resolved_selectors
9+
@integrations = integrations
10+
end
11+
12+
def counts
13+
{
14+
success: rows.count{ |row| row.success },
15+
failed: rows.count { |row| row.success == false },
16+
unknown: integrations_without_a_row.count + rows.count { |row| row.success.nil? }
17+
}
18+
end
19+
20+
def deployable?
21+
return nil if rows.empty?
22+
return nil if rows.any?{ |row| row.success.nil? }
23+
return nil if integrations_without_a_row.any?
24+
rows.all?{ |row| row.success }
25+
end
26+
27+
def reasons
28+
@reasons ||= begin
29+
reasons = []
30+
if rows.empty?
31+
reasons << "No results matched the given query"
32+
else
33+
reasons.concat(missing_reasons)
34+
reasons.concat(failure_messages)
35+
reasons.concat(unverified_messages)
36+
reasons.concat(success_messages)
37+
end
38+
reasons
39+
end
40+
end
41+
42+
def unverified_messages
43+
if rows.any?{ |row| row.success.nil? }
44+
["Missing one or more verification results"]
45+
else
46+
[]
47+
end
48+
end
49+
50+
def failure_messages
51+
if rows.any?{ |row| row.success == false }
52+
["One or more verifications have failed"]
53+
else
54+
[]
55+
end
56+
end
57+
58+
def success_messages
59+
if rows.all?{ |row| row.success } && integrations_without_a_row.empty?
60+
["All verification results are published and successful"]
61+
else
62+
[]
63+
end
64+
end
65+
66+
def integrations_without_a_row
67+
@integrations_without_a_row ||= begin
68+
integrations.select do | relationship |
69+
!rows.find do | row |
70+
row.consumer_id == relationship.consumer_id && row.provider_id == relationship.provider_id
71+
end
72+
end
73+
end
74+
end
75+
76+
def missing_reasons
77+
integrations_without_a_row.collect do | missing_relationship|
78+
consumer_version_desc = "#{missing_relationship.consumer_name} (#{resolved_version_for(missing_relationship.consumer_id)})"
79+
provider_version_desc = "#{missing_relationship.provider_name} (#{resolved_version_for(missing_relationship.provider_id)})"
80+
"There is no verified pact between #{consumer_version_desc} and #{provider_version_desc}"
81+
end
82+
end
83+
84+
def resolved_version_for(pacticipant_id)
85+
resolved_selectors.find{ | s| s[:pacticipant_id] == pacticipant_id }[:pacticipant_version_number]
86+
end
87+
end
88+
end
89+
end

lib/pact_broker/matrix/integration.rb

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#
2+
# Represents the integration relationship between a consumer and a provider
3+
#
4+
module PactBroker
5+
module Matrix
6+
class Integration
7+
8+
attr_reader :consumer_name, :consumer_id, :provider_name, :provider_id
9+
10+
def initialize consumer_id, consumer_name, provider_id, provider_name
11+
@consumer_id = consumer_id
12+
@consumer_name = consumer_name
13+
@provider_id = provider_id
14+
@provider_name = provider_name
15+
end
16+
17+
def self.from_hash hash
18+
new(
19+
hash.fetch(:consumer_id),
20+
hash.fetch(:consumer_name),
21+
hash.fetch(:provider_id),
22+
hash.fetch(:provider_name)
23+
)
24+
end
25+
26+
def == other
27+
consumer_id == other.consumer_id && provider_id == other.provider_id
28+
end
29+
30+
def <=> other
31+
comparison = consumer_name <=> other.consumer_name
32+
return comparison if comparison != 0
33+
provider_name <=> other.provider_name
34+
end
35+
36+
def to_hash
37+
{
38+
consumer_name: consumer_name,
39+
consumer_id: consumer_id,
40+
provider_name: provider_name,
41+
provider_id: provider_id,
42+
}
43+
end
44+
45+
def pacticipant_names
46+
[consumer_name, provider_name]
47+
end
48+
49+
def to_s
50+
"Relationship between #{consumer_name} (id=#{consumer_id}) and #{provider_name} (id=#{provider_id})"
51+
end
52+
end
53+
end
54+
end

lib/pact_broker/matrix/parse_query.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def self.call query
2929
options[:limit] = params['limit']
3030
end
3131
if params.key?('latest') && params['latest'] != ''
32-
options[:latest] = params['latest']
32+
options[:latest] = params['latest'] == 'true'
3333
end
3434
if params.key?('tag') && params['tag'] != ''
3535
options[:tag] = params['tag']
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module PactBroker
2+
module Matrix
3+
class QueryResults < Array
4+
attr_reader :selectors, :options, :resolved_selectors
5+
6+
def initialize rows, selectors, options, resolved_selectors
7+
super(rows)
8+
@selectors = selectors
9+
@resolved_selectors = resolved_selectors
10+
@options = options
11+
end
12+
13+
def rows
14+
to_a
15+
end
16+
end
17+
end
18+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
require 'pact_broker/matrix/query_results'
2+
3+
module PactBroker
4+
module Matrix
5+
class QueryResultsWithDeploymentStatusSummary < QueryResults
6+
attr_reader :deployment_status_summary
7+
8+
def initialize rows, selectors, options, resolved_selectors, deployment_status_summary
9+
super(rows, selectors, options, resolved_selectors)
10+
@deployment_status_summary = deployment_status_summary
11+
end
12+
end
13+
end
14+
end

lib/pact_broker/matrix/repository.rb

+32-20
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
require 'pact_broker/matrix/row'
33
require 'pact_broker/matrix/head_row'
44
require 'pact_broker/error'
5-
5+
require 'pact_broker/matrix/query_results'
6+
require 'pact_broker/matrix/integration'
7+
require 'pact_broker/matrix/query_results_with_deployment_status_summary'
68

79
module PactBroker
810
module Matrix
@@ -65,7 +67,8 @@ def find_ids_for_pacticipant_names params
6567

6668
# Return the latest matrix row (pact/verification) for each consumer_version_number/provider_version_number
6769
def find selectors, options = {}
68-
lines = query_matrix(resolve_selectors(selectors, options), options)
70+
resolved_selectors = resolve_selectors(selectors, options)
71+
lines = query_matrix(resolved_selectors, options)
6972
lines = apply_latestby(options, selectors, lines)
7073

7174
# This needs to be done after the latestby, so can't be done in the db unless
@@ -74,9 +77,31 @@ def find selectors, options = {}
7477
lines = lines.select{ |l| options[:success].include?(l.success) }
7578
end
7679

77-
lines.sort
80+
QueryResults.new(lines.sort, selectors, options, resolved_selectors)
81+
end
82+
83+
def find_for_consumer_and_provider pacticipant_1_name, pacticipant_2_name
84+
selectors = [{ pacticipant_name: pacticipant_1_name }, { pacticipant_name: pacticipant_2_name }]
85+
options = { latestby: 'cvpv' }
86+
find(selectors, options)
87+
end
88+
89+
def find_compatible_pacticipant_versions selectors
90+
find(selectors, latestby: 'cvpv').select{|line| line.success }
91+
end
92+
93+
def find_integrations(pacticipant_names)
94+
selectors = pacticipant_names.collect{ | pacticipant_name | add_ids(pacticipant_name: pacticipant_name) }
95+
Row
96+
.select(:consumer_name, :consumer_id, :provider_name, :provider_id)
97+
.matching_selectors(selectors)
98+
.distinct
99+
.all
100+
.collect{ |row | Integration.from_hash(row.to_hash) }.uniq
78101
end
79102

103+
private
104+
80105
def apply_latestby options, selectors, lines
81106
return lines unless options[:latestby]
82107
group_by_columns = case options[:latestby]
@@ -106,16 +131,6 @@ def remove_overwritten_revisions lines
106131
latest_revisions
107132
end
108133

109-
def find_for_consumer_and_provider pacticipant_1_name, pacticipant_2_name
110-
selectors = [{ pacticipant_name: pacticipant_1_name }, { pacticipant_name: pacticipant_2_name }]
111-
options = { latestby: 'cvpv' }
112-
find(selectors, options)
113-
end
114-
115-
def find_compatible_pacticipant_versions selectors
116-
find(selectors, latestby: 'cvpv').select{|line| line.success }
117-
end
118-
119134
def query_matrix selectors, options
120135
query = view_for(options).select_all.matching_selectors(selectors)
121136
query = query.limit(options[:limit]) if options[:limit]
@@ -195,7 +210,7 @@ def add_ids(selector)
195210
# eg. when checking to see if Foo version 2 can be deployed to prod,
196211
# need to look up all the 'partner' pacticipants, and determine their latest prod versions
197212
def apply_latest_and_tag_to_inferred_selectors(selectors, options)
198-
all_pacticipant_names = all_pacticipant_names_in_specified_matrix(selectors, options)
213+
all_pacticipant_names = all_pacticipant_names_in_specified_matrix(selectors)
199214
specified_names = selectors.collect{ |s| s[:pacticipant_name] }
200215
inferred_names = all_pacticipant_names - specified_names
201216

@@ -211,12 +226,9 @@ def apply_latest_and_tag_to_inferred_selectors(selectors, options)
211226
selectors + look_up_version_numbers(inferred_selectors, options)
212227
end
213228

214-
def all_pacticipant_names_in_specified_matrix(selectors, options)
215-
query = view_for(options).select(:consumer_name, :provider_name)
216-
query = query.matching_selectors(selectors)
217-
query
218-
.all
219-
.collect{ | row | [row.consumer_name, row.provider_name] }
229+
def all_pacticipant_names_in_specified_matrix(selectors)
230+
find_integrations(selectors.collect{|s| s[:pacticipant_name]})
231+
.collect(&:pacticipant_names)
220232
.flatten
221233
.uniq
222234
end

lib/pact_broker/matrix/service.rb

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'pact_broker/repositories'
22
require 'pact_broker/matrix/row'
3+
require 'pact_broker/matrix/deployment_status_summary'
34

45
module PactBroker
56
module Matrix
@@ -17,12 +18,18 @@ def refresh_tags params, &block
1718
matrix_repository.refresh_tags(params, &block)
1819
end
1920

20-
def find criteria, options = {}
21-
matrix_repository.find criteria, options
21+
def find selectors, options = {}
22+
query_results = matrix_repository.find selectors, options
23+
pacticipant_names = selectors.collect{ | s| s[:pacticipant_name] }
24+
integrations = matrix_repository.find_integrations(pacticipant_names)
25+
deployment_status_summary = DeploymentStatusSummary.new(query_results.rows, query_results.resolved_selectors, integrations)
26+
QueryResultsWithDeploymentStatusSummary.new(query_results.rows, query_results.selectors, query_results.options, query_results.resolved_selectors, deployment_status_summary)
2227
end
2328

2429
def find_for_consumer_and_provider params
25-
matrix_repository.find_for_consumer_and_provider params[:consumer_name], params[:provider_name]
30+
selectors = [{ pacticipant_name: params[:consumer_name] }, { pacticipant_name: params[:provider_name] }]
31+
options = { latestby: 'cvpv' }
32+
find(selectors, options)
2633
end
2734

2835
def find_for_consumer_and_provider_with_tags params

0 commit comments

Comments
 (0)