Skip to content

Commit

Permalink
feat(matrix): improve reasons in response when pacticipant cannot be …
Browse files Browse the repository at this point in the history
…deployed
  • Loading branch information
bethesque committed Mar 15, 2019
1 parent 475a401 commit e96544f
Show file tree
Hide file tree
Showing 15 changed files with 578 additions and 273 deletions.
4 changes: 4 additions & 0 deletions MATRIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Interpreting the Matrix results

* 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).
* If there is no row, it's because it has been verified, but not by the provider version you've specified.
96 changes: 74 additions & 22 deletions lib/pact_broker/matrix/deployment_status_summary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,52 @@ def counts
end

def deployable?
# return nil if rows.empty?
return false if specified_selectors_that_do_not_exist.any?
return nil if rows.any?{ |row| row.success.nil? }
return nil if required_integrations_without_a_row.any?
rows.all?{ |row| row.success }
rows.all?{ |row| row.success } # true if rows is empty
end

def reasons
@reasons ||= begin
reasons = []
reasons.concat(missing_reasons)
reasons.concat(failure_messages)
reasons.concat(unverified_messages)
reasons.concat(success_messages)
reasons
error_messages.any? ? error_messages : success_messages
end

def error_messages
@error_messages ||= begin
messages = []
messages.concat(specified_selectors_do_not_exist_messages)
if messages.empty?
messages.concat(missing_reasons)
messages.concat(failure_messages)
messages.concat(unverified_messages)
end
messages.uniq
end
end

def specified_selectors_that_do_not_exist
resolved_selectors.select(&:specified_version_that_does_not_exist?)
end

def specified_selectors_do_not_exist_messages
specified_selectors_that_do_not_exist.collect(&:version_does_not_exist_description)
end

def unverified_messages
if rows.any?{ |row| row.success.nil? }
["Missing one or more verification results"]
rows.collect do | row |
missing_verified_pact_reason(row)

# selectors = selectors_without_a_version_for(row)
# if selectors.any?
# selectors.collect(&:version_does_not_exist_description)
# else
# ["Missing one or more required verification results"]
# end
end
else
[]
end
end.flatten.uniq
end

def failure_messages
Expand All @@ -57,10 +80,14 @@ def failure_messages

def success_messages
if rows.all?{ |row| row.success } && required_integrations_without_a_row.empty?
["All verification results are published and successful"]
if rows.any?
["All required verification results are published and successful"]
else
["There are no missing dependencies"]
end
else
[]
end
end.flatten.uniq
end

# For deployment, the consumer requires the provider,
Expand All @@ -77,20 +104,45 @@ def required_integrations_without_a_row
end

def missing_reasons
required_integrations_without_a_row.collect do | missing_relationship|
consumer_version_desc = "#{missing_relationship.consumer_name} (#{resolved_version_for(missing_relationship.consumer_id)})"
provider_version_desc = "#{missing_relationship.provider_name} (#{resolved_version_for(missing_relationship.provider_id)})"
"There is no verified pact between #{consumer_version_desc} and #{provider_version_desc}"
required_integrations_without_a_row.collect do | integration |
relevant_selectors_without_a_version = selectors_without_a_version_for(integration)
# if relevant_selectors_without_a_version.any?
# missing_specified_version_reasons(relevant_selectors_without_a_version)
# else
missing_verified_pact_reason(integration)
# end
end.flatten
end

def selectors_without_a_version_for(integration)
selectors_with_non_existing_versions.select do | selector |
integration.involves_pacticipant_with_name?(selector.pacticipant_name)
end
end

def resolved_version_for(pacticipant_id)
resolved_selector = resolved_selectors.find{ | s| s[:pacticipant_id] == pacticipant_id }
def selectors_with_non_existing_versions
@selectors_with_non_existing_versions ||= resolved_selectors.select(&:latest_tagged_version_that_does_not_exist?)
end

def missing_specified_version_reasons(selectors)
selectors.collect(&:version_does_not_exist_description)
end

def missing_verified_pact_reason(integration)
"There is no verified pact between #{description_for_selector(integration.consumer_name)} and #{description_for_selector(integration.provider_name)}"
# "There is no verification by #{description_for_selector(integration.provider_name)} for the pact for #{description_for_selector(integration.consumer_name)}"
end

def description_for_selector(pacticipant_name)
resolved_selector = resolved_selectors.find{ | s| s.pacticipant_name == pacticipant_name }
if resolved_selector
resolved_selector[:pacticipant_version_number]
resolved_selector.description
else
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}"
"unresolved version"
# This happens when the user has not specified a version of the provider (eg no 'latest' and/or 'tag')
# so the "add inferred selectors" code has not run
# AND no versions of the provider exist (ie. it has never verified the pact).
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}"
"#{pacticipant_name} (unresolved version)"
end
end
end
Expand Down
10 changes: 9 additions & 1 deletion lib/pact_broker/matrix/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def pacticipant_names
end

def to_s
"Relationship between #{consumer_name} (id=#{consumer_id}) and #{provider_name} (id=#{provider_id})"
"Integration between #{consumer_name} (id=#{consumer_id}) and #{provider_name} (id=#{provider_id})"
end

def involves_consumer_with_id?(consumer_id)
Expand All @@ -75,6 +75,14 @@ def involves_provider_with_name?(provider_name)
def involves_consumer_with_name?(consumer_name)
self.consumer_name == consumer_name
end

def pacticipant_names
[consumer_name, provider_name]
end

def involves_pacticipant_with_name?(pacticipant_name)
pacticipant_names.include?(pacticipant_name)
end
end
end
end
5 changes: 3 additions & 2 deletions lib/pact_broker/matrix/query_results.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
module PactBroker
module Matrix
class QueryResults < Array
attr_reader :selectors, :options, :resolved_selectors
attr_reader :selectors, :options, :resolved_selectors, :integrations

def initialize rows, selectors, options, resolved_selectors
def initialize rows, selectors, options, resolved_selectors, integrations
super(rows)
@selectors = selectors
@resolved_selectors = resolved_selectors
@options = options
@integrations = integrations
end

def rows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ module Matrix
class QueryResultsWithDeploymentStatusSummary < QueryResults
attr_reader :deployment_status_summary

def initialize rows, selectors, options, resolved_selectors, deployment_status_summary
super(rows, selectors, options, resolved_selectors)
def initialize rows, selectors, options, resolved_selectors, integrations, deployment_status_summary
super(rows, selectors, options, resolved_selectors, integrations)
@deployment_status_summary = deployment_status_summary
end
end
Expand Down
98 changes: 49 additions & 49 deletions lib/pact_broker/matrix/repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ def find specified_selectors, options = {}
lines = lines.select{ |l| options[:success].include?(l.success) }
end

QueryResults.new(lines.sort, specified_selectors, options, resolved_selectors)
integrations = find_integrations_for_specified_selectors(resolved_selectors.select(&:specified?))

QueryResults.new(lines.sort, specified_selectors, options, resolved_selectors, integrations)
end

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

def find_integrations(pacticipant_names)
selectors = pacticipant_names.collect{ | pacticipant_name | add_ids_to_selector(pacticipant_name: pacticipant_name) }
# If one pacticipant is specified, find all the integrations that involve that pacticipant
# If two or more are specified, just return the integrations that involve the specified pacticipants
def find_integrations_for_specified_selectors(resolved_specified_selectors)
specified_pacticipant_names = resolved_specified_selectors.collect(&:pacticipant_name)
Row
.select(:consumer_name, :consumer_id, :provider_name, :provider_id)
.matching_selectors(selectors)
.matching_selectors(resolved_specified_selectors)
.distinct
.all
.collect do |row |
row.to_hash
end
.uniq
.collect do | hash |
Integration.from_hash(hash.merge(required: pacticipant_names.include?(hash[:consumer_name])))
required = is_a_row_for_this_integration_required?(specified_pacticipant_names, hash[:consumer_name])
Integration.from_hash(hash.merge(required: required))
end
end

private

# If one of the specified pacticipants is a consumer, then that provider is required to be deployed
# to the same environment before the consumer can be deployed.
# If one of the specified pacticipants is a provider, then the provider may be deployed
# without the consumer being present.
def is_a_row_for_this_integration_required?(specified_pacticipant_names, consumer_name)
specified_pacticipant_names.include?(consumer_name)
end

def apply_latestby options, selectors, lines
return lines unless options[:latestby]
group_by_columns = case options[:latestby]
Expand Down Expand Up @@ -121,7 +134,7 @@ def remove_overwritten_revisions lines
end

def query_matrix selectors, options
query = view_for(options).select_all.matching_selectors(selectors)
query = Row.select_all.matching_selectors(selectors)
query = query.limit(options[:limit]) if options[:limit]
query
.order_by_names_ascending_most_recent_first
Expand All @@ -130,12 +143,8 @@ def query_matrix selectors, options
.all
end

def view_for(options)
Row
end

def resolve_selectors(specified_selectors, options)
resolved_specified_selectors = resolve_versions_and_add_ids(specified_selectors, options)
resolved_specified_selectors = resolve_versions_and_add_ids(specified_selectors, :specified)
if options[:latest] || options[:tag]
add_inferred_selectors(resolved_specified_selectors, options)
else
Expand All @@ -144,42 +153,40 @@ def resolve_selectors(specified_selectors, options)
end

# Find the version number for selectors with the latest and/or tag specified
def resolve_versions_and_add_ids(selectors, options, required = true)
def resolve_versions_and_add_ids(selectors, selector_type)
selectors.collect do | selector |
pacticipant = PactBroker::Domain::Pacticipant.find(name: selector[:pacticipant_name])
versions = find_versions_for_selector(selector)
build_selectors_for_pacticipant_and_versions(pacticipant, versions, selector, selector_type)
end.flatten
end

versions = find_versions_for_selector(selector, required)

if versions
versions.collect do | version |
if version
selector_for_version(pacticipant, version)
else
selector_for_non_existing_version(pacticipant)
end
def build_selectors_for_pacticipant_and_versions(pacticipant, versions, original_selector, selector_type)
if versions
versions.collect do | version |
if version
selector_for_version(pacticipant, version, original_selector, selector_type)
else
selector_for_non_existing_version(pacticipant, original_selector, selector_type)
end
else
selector_without_version(pacticipant)
end
end.flatten
else
selector_without_version(pacticipant, selector_type)
end
end

def find_versions_for_selector(selector, required)
def find_versions_for_selector(selector)
if selector[:tag] && selector[:latest]
version = version_repository.find_by_pacticipant_name_and_latest_tag(selector[:pacticipant_name], selector[:tag])
# raise Error.new("No version of #{selector[:pacticipant_name]} found with tag #{selector[:tag]}") if required && !version
[version]
elsif selector[:latest]
version = version_repository.find_latest_by_pacticpant_name(selector[:pacticipant_name])
# raise Error.new("No version of #{selector[:pacticipant_name]} found") if required && !version
[version]
elsif selector[:tag]
versions = version_repository.find_by_pacticipant_name_and_tag(selector[:pacticipant_name], selector[:tag])
# raise Error.new("No version of #{selector[:pacticipant_name]} found with tag #{selector[:tag]}") if required && versions.empty?
versions.any? ? versions : [nil]
elsif selector[:pacticipant_version_number]
version = version_repository.find_by_pacticipant_name_and_number(selector[:pacticipant_name], selector[:pacticipant_version_number])
# raise Error.new("No version #{selector[:pacticipant_version_number]} of #{selector[:pacticipant_name]} found") if required && !version
[version]
else
nil
Expand All @@ -203,24 +210,17 @@ def add_ids_to_selector(selector)
selector
end

# eg. when checking to see if Foo version 2 can be deployed to prod,
# need to look up all the 'partner' pacticipants, and determine their latest prod versions
# When only one selector is specified, (eg. checking to see if Foo version 2 can be deployed to prod),
# need to look up all integrated pacticipants, and determine their relevant (eg. latest prod) versions
def add_inferred_selectors(resolved_specified_selectors, options)
integrations = find_integrations(resolved_specified_selectors.collect{|s| s[:pacticipant_name]})
integrations = find_integrations_for_specified_selectors(resolved_specified_selectors)
all_pacticipant_names = integrations.collect(&:pacticipant_names).flatten.uniq
specified_names = resolved_specified_selectors.collect{ |s| s[:pacticipant_name] }
inferred_pacticipant_names = all_pacticipant_names - specified_names
# Inferred providers are required for a consumer to be deployed
required_inferred_pacticipant_names = inferred_pacticipant_names.select{ | n | integrations.any?{ |i| i.involves_provider_with_name?(n) } }
# Inferred consumers are NOT required for a provider to be deployed
optional_inferred_pacticipant_names = inferred_pacticipant_names - required_inferred_pacticipant_names

resolved_specified_selectors +
build_inferred_selectors(required_inferred_pacticipant_names, options, true) +
build_inferred_selectors(optional_inferred_pacticipant_names, options, false)
resolved_specified_selectors + build_inferred_selectors(inferred_pacticipant_names, options)
end

def build_inferred_selectors(inferred_pacticipant_names, options, required)
def build_inferred_selectors(inferred_pacticipant_names, options)
selectors = inferred_pacticipant_names.collect do | pacticipant_name |
selector = {
pacticipant_name: pacticipant_name
Expand All @@ -229,26 +229,26 @@ def build_inferred_selectors(inferred_pacticipant_names, options, required)
selector[:latest] = options[:latest] if options[:latest]
selector
end
resolve_versions_and_add_ids(selectors, options, required)
resolve_versions_and_add_ids(selectors, :inferred)
end

def all_pacticipant_names_in_specified_matrix(selectors)
find_integrations(selectors.collect{|s| s[:pacticipant_name]})
find_integrations_for_specified_selectors(selectors)
.collect(&:pacticipant_names)
.flatten
.uniq
end

def selector_for_non_existing_version(pacticipant)
ResolvedSelector.for_pacticipant_and_non_existing_version(pacticipant)
def selector_for_non_existing_version(pacticipant, original_selector, selector_type)
ResolvedSelector.for_pacticipant_and_non_existing_version(pacticipant, original_selector, selector_type)
end

def selector_for_version(pacticipant, version)
ResolvedSelector.for_pacticipant_and_version(pacticipant, version)
def selector_for_version(pacticipant, version, original_selector, selector_type)
ResolvedSelector.for_pacticipant_and_version(pacticipant, version, original_selector, selector_type)
end

def selector_without_version(pacticipant)
ResolvedSelector.for_pacticipant(pacticipant)
def selector_without_version(pacticipant, selector_type)
ResolvedSelector.for_pacticipant(pacticipant, selector_type)
end
end
end
Expand Down
Loading

0 comments on commit e96544f

Please sign in to comment.