pact_broker 2.19.1 → 2.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/README.md +0 -1
  4. data/db/migrations/000002_create_versions_table.rb +1 -1
  5. data/db/migrations/20180311_optimise_head_matrix.rb +1 -0
  6. data/db/migrations/20180523_create_latest_verifications_for_consumer_version_tags.rb +50 -0
  7. data/db/migrations/20180524_create_latest_verifications_for_consumer_and_provider.rb +46 -0
  8. data/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb +7 -3
  9. data/lib/pact_broker/configuration.rb +6 -1
  10. data/lib/pact_broker/domain/webhook_request.rb +12 -11
  11. data/lib/pact_broker/index/service.rb +14 -84
  12. data/lib/pact_broker/matrix/aggregated_row.rb +71 -0
  13. data/lib/pact_broker/matrix/head_row.rb +23 -0
  14. data/lib/pact_broker/matrix/repository.rb +0 -1
  15. data/lib/pact_broker/matrix/row.rb +15 -20
  16. data/lib/pact_broker/pacts/content.rb +66 -0
  17. data/lib/pact_broker/pacts/create_formatted_diff.rb +0 -1
  18. data/lib/pact_broker/pacts/diff.rb +3 -2
  19. data/lib/pact_broker/pacts/generate_sha.rb +24 -0
  20. data/lib/pact_broker/pacts/parse.rb +11 -0
  21. data/lib/pact_broker/pacts/repository.rb +4 -4
  22. data/lib/pact_broker/pacts/service.rb +4 -2
  23. data/lib/pact_broker/pacts/sort_content.rb +54 -0
  24. data/lib/pact_broker/verifications/latest_verification_for_consumer_and_provider.rb +26 -0
  25. data/lib/pact_broker/verifications/latest_verification_for_consumer_version_tag.rb +23 -0
  26. data/lib/pact_broker/version.rb +1 -1
  27. data/lib/pact_broker/versions/repository.rb +12 -3
  28. data/script/run-with-ssl.rb +44 -0
  29. data/spec/features/base_equality_only_on_content_that_affects_verification_results_spec.rb +34 -0
  30. data/spec/lib/pact_broker/api/decorators/webhook_execution_result_decorator_spec.rb +6 -2
  31. data/spec/lib/pact_broker/domain/{index_items_spec.rb → index_item_spec.rb} +0 -0
  32. data/spec/lib/pact_broker/domain/webhook_request_spec.rb +16 -5
  33. data/spec/lib/pact_broker/matrix/aggregated_row_spec.rb +79 -0
  34. data/spec/lib/pact_broker/matrix/head_row_spec.rb +55 -0
  35. data/spec/lib/pact_broker/matrix/row_spec.rb +22 -2
  36. data/spec/lib/pact_broker/pacts/content_spec.rb +166 -0
  37. data/spec/lib/pact_broker/pacts/create_formatted_diff_spec.rb +0 -3
  38. data/spec/lib/pact_broker/pacts/generate_sha_spec.rb +92 -0
  39. data/spec/lib/pact_broker/pacts/repository_spec.rb +47 -4
  40. data/spec/lib/pact_broker/pacts/sort_content_spec.rb +44 -0
  41. data/spec/lib/pact_broker/versions/repository_spec.rb +13 -5
  42. data/spec/spec_helper.rb +1 -0
  43. data/spec/support/foo-bar.json +34 -0
  44. data/spec/support/rspec_match_hash.rb +14 -17
  45. data/spec/support/test_data_builder.rb +5 -4
  46. metadata +26 -8
  47. data/lib/pact_broker/matrix/latest_row.rb +0 -10
  48. data/lib/pact_broker/pacts/sort_verifiable_content.rb +0 -41
  49. data/spec/lib/pact_broker/pacts/sort_verifiable_content_spec.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6f1619d6f62ce6aaa00ca73eca6b1cdf76ee0305
4
- data.tar.gz: c8fc20b4a52499d6bc259a4937d463c1a902567e
3
+ metadata.gz: 6596910c63bcfb8363e958de4bb4bb698df0613e
4
+ data.tar.gz: 929b0ac8d06300c3a6226f4afb3af099f97c7476
5
5
  SHA512:
6
- metadata.gz: 1a8ad6a1480b3a288c11e66f4473ba5af7890fe6ccab8f5ff81f69054dd0df7dc47d24ffc3e77b1888a202b5c9d73ef118794a9c8def6acce31e25590962f89c
7
- data.tar.gz: c7270ddcf9932fc5579263dbb0149de7b1e3da15f8e69be217f4b2ddbc47182b409b0fcb0fc3b2d232922b854caae41eb47f2fc08d4ec9ae84d5773b3152751e
6
+ metadata.gz: aaf62c676cffcc46679e3cba55afd4234d74208577869803d76e670805b314d7e8a742525de8d3599aafa5f5769d50b62b76ee8464796766c42c92dc73807b36
7
+ data.tar.gz: 3cb076e57dcb8f23450ddd50e1b8f92f4161f79fd4541799296a5fdc0ed4671e13d1ba409eb0dfeff60c41cc86cc3472a304fb6e1068b6219a2359b3365799e7
@@ -1,3 +1,23 @@
1
+ <a name="v2.19.2"></a>
2
+ ### v2.19.2 (2018-05-29)
3
+
4
+
5
+ #### Features
6
+
7
+ * load latest verification for consumer/provider via relationship rather than repository ([13b7c6e](/../../commit/13b7c6e))
8
+ * include more columns in latest_verifications_for_consumer_version_tags to avoid having to do extra queries for pact_versions and provider_versions ([e366af4](/../../commit/e366af4))
9
+ * optimise queries for index page with tags ([524e08d](/../../commit/524e08d))
10
+ * create view for latest verifications for consumer version tags ([eb67511](/../../commit/eb67511))
11
+ * log pact content as debug instead of info when publishing ([d116157](/../../commit/d116157))
12
+ * allow pact equality to be based only on the content that affects verification results ([bf8130f](/../../commit/bf8130f))
13
+
14
+
15
+ #### Bug Fixes
16
+
17
+ * temporarily redact webhook response body from UI for security purposes ([becf20c](/../../commit/becf20c))
18
+ * handle race condition causing unique constraint violation when creating pacticipant versions ([6c75ebd](/../../commit/6c75ebd))
19
+
20
+
1
21
  <a name="v2.19.1"></a>
2
22
  ### v2.19.1 (2018-05-18)
3
23
 
data/README.md CHANGED
@@ -4,7 +4,6 @@
4
4
  [![Code Climate](https://codeclimate.com/github/pact-foundation/pact_broker/badges/gpa.svg)](https://codeclimate.com/github/pact-foundation/pact_broker)
5
5
  [![Test Coverage](https://codeclimate.com/github/pact-foundation/pact_broker/badges/coverage.svg)](https://codeclimate.com/github/pact-foundation/pact_broker/coverage)
6
6
  [![Issue Count](https://codeclimate.com/github/pact-foundation/pact_broker/badges/issue_count.svg)](https://codeclimate.com/github/pact-foundation/pact_broker)
7
- [![Dependency Status](https://gemnasium.com/badges/github.com/pact-foundation/pact_broker.svg)](https://gemnasium.com/github.com/pact-foundation/pact_broker)
8
7
 
9
8
  The Pact Broker is an application for sharing for consumer driven contracts and verification results. It is optimised for use with "pacts" (contracts created by the [Pact][pact-docs] framework), but can be used for any type of contract that can be serialized to JSON.
10
9
 
@@ -6,7 +6,7 @@ Sequel.migration do
6
6
  String :number
7
7
  String :repository_ref
8
8
  foreign_key :pacticipant_id, :pacticipants, :null=>false
9
- index [:pacticipant_id, :number], :unique=>true
9
+ index [:pacticipant_id, :number], :unique => true
10
10
  end
11
11
  end
12
12
 
@@ -16,6 +16,7 @@ Sequel.migration do
16
16
  )
17
17
 
18
18
  # Add provider_version_order to original definition
19
+ # The most recent verification for each pact version
19
20
  v = :verifications
20
21
  create_or_replace_view(:latest_verifications,
21
22
  from(v)
@@ -0,0 +1,50 @@
1
+ Sequel.migration do
2
+ up do
3
+ # The latest verification id for each consumer version tag
4
+ create_view(:latest_verification_ids_for_consumer_version_tags,
5
+ "select
6
+ pv.pacticipant_id as provider_id,
7
+ lpp.consumer_id,
8
+ t.name as consumer_version_tag_name,
9
+ max(v.id) as latest_verification_id
10
+ from verifications v
11
+ join latest_pact_publications_by_consumer_versions lpp
12
+ on v.pact_version_id = lpp.pact_version_id
13
+ join tags t
14
+ on lpp.consumer_version_id = t.version_id
15
+ join versions pv
16
+ on v.provider_version_id = pv.id
17
+ group by pv.pacticipant_id, lpp.consumer_id, t.name")
18
+
19
+ # The most recent verification for each consumer/consumer version tag/provider
20
+ latest_verifications = from(:verifications)
21
+ .select(
22
+ Sequel[:lv][:consumer_id],
23
+ Sequel[:lv][:provider_id],
24
+ Sequel[:lv][:consumer_version_tag_name],
25
+ Sequel[:pv][:sha].as(:pact_version_sha),
26
+ Sequel[:prv][:number].as(:provider_version_number),
27
+ Sequel[:prv][:order].as(:provider_version_order),
28
+ )
29
+ .select_append{ verifications.* }
30
+ .join(:latest_verification_ids_for_consumer_version_tags,
31
+ {
32
+ Sequel[:verifications][:id] => Sequel[:lv][:latest_verification_id],
33
+ }, { table_alias: :lv })
34
+ .join(:versions,
35
+ {
36
+ Sequel[:verifications][:provider_version_id] => Sequel[:prv][:id]
37
+ }, { table_alias: :prv })
38
+ .join(:pact_versions,
39
+ {
40
+ Sequel[:verifications][:pact_version_id] => Sequel[:pv][:id]
41
+ }, { table_alias: :pv })
42
+
43
+ create_or_replace_view(:latest_verifications_for_consumer_version_tags, latest_verifications)
44
+ end
45
+
46
+ down do
47
+ drop_view(:latest_verifications_for_consumer_version_tags)
48
+ drop_view(:latest_verification_ids_for_consumer_version_tags)
49
+ end
50
+ end
@@ -0,0 +1,46 @@
1
+ Sequel.migration do
2
+ up do
3
+ # The latest verification id for each consumer version tag
4
+ create_view(:latest_verification_ids_for_consumer_and_provider,
5
+ "select
6
+ pv.pacticipant_id as provider_id,
7
+ lpp.consumer_id,
8
+ max(v.id) as latest_verification_id
9
+ from verifications v
10
+ join latest_pact_publications_by_consumer_versions lpp
11
+ on v.pact_version_id = lpp.pact_version_id
12
+ join versions pv
13
+ on v.provider_version_id = pv.id
14
+ group by pv.pacticipant_id, lpp.consumer_id")
15
+
16
+ # The most recent verification for each consumer/consumer version tag/provider
17
+ latest_verifications = from(:verifications)
18
+ .select(
19
+ Sequel[:lv][:consumer_id],
20
+ Sequel[:lv][:provider_id],
21
+ Sequel[:pv][:sha].as(:pact_version_sha),
22
+ Sequel[:prv][:number].as(:provider_version_number),
23
+ Sequel[:prv][:order].as(:provider_version_order),
24
+ )
25
+ .select_append{ verifications.* }
26
+ .join(:latest_verification_ids_for_consumer_and_provider,
27
+ {
28
+ Sequel[:verifications][:id] => Sequel[:lv][:latest_verification_id],
29
+ }, { table_alias: :lv })
30
+ .join(:versions,
31
+ {
32
+ Sequel[:verifications][:provider_version_id] => Sequel[:prv][:id]
33
+ }, { table_alias: :prv })
34
+ .join(:pact_versions,
35
+ {
36
+ Sequel[:verifications][:pact_version_id] => Sequel[:pv][:id]
37
+ }, { table_alias: :pv })
38
+
39
+ create_or_replace_view(:latest_verifications_for_consumer_and_provider, latest_verifications)
40
+ end
41
+
42
+ down do
43
+ drop_view(:latest_verifications_for_consumer_and_provider)
44
+ drop_view(:latest_verification_ids_for_consumer_and_provider)
45
+ end
46
+ end
@@ -34,11 +34,15 @@ module PactBroker
34
34
  represented.body
35
35
  end
36
36
  end
37
-
38
37
  end
39
38
 
40
- property :error, :extend => ErrorDecorator
41
- property :response, :extend => HTTPResponseDecorator
39
+ property :message, exec_context: :decorator
40
+ # property :error, :extend => ErrorDecorator
41
+ # property :response, :extend => HTTPResponseDecorator
42
+
43
+ def message
44
+ "Webhook response has been redacted temporarily for security purposes. Please see the Pact Broker application logs for the response body."
45
+ end
42
46
 
43
47
  link :webhook do | options |
44
48
  {
@@ -27,13 +27,14 @@ module PactBroker
27
27
  ]
28
28
 
29
29
  attr_accessor :log_dir, :database_connection, :auto_migrate_db, :use_hal_browser, :html_pact_renderer
30
- attr_accessor :validate_database_connection_config, :enable_diagnostic_endpoints, :version_parser
30
+ attr_accessor :validate_database_connection_config, :enable_diagnostic_endpoints, :version_parser, :sha_generator
31
31
  attr_accessor :use_case_sensitive_resource_names, :order_versions_by_date
32
32
  attr_accessor :check_for_potential_duplicate_pacticipant_names
33
33
  attr_accessor :semver_formats
34
34
  attr_accessor :enable_public_badge_access, :shields_io_base_url
35
35
  attr_accessor :webhook_retry_schedule
36
36
  attr_accessor :disable_ssl_verification
37
+ attr_accessor :base_equality_only_on_content_that_affects_verification_results
37
38
  attr_reader :api_error_reporters
38
39
  attr_writer :logger
39
40
 
@@ -51,6 +52,8 @@ module PactBroker
51
52
 
52
53
  def self.default_configuration
53
54
  require 'pact_broker/versions/parse_semantic_version'
55
+ require 'pact_broker/pacts/generate_sha'
56
+
54
57
  config = Configuration.new
55
58
  config.log_dir = File.expand_path("./log")
56
59
  config.auto_migrate_db = true
@@ -62,6 +65,8 @@ module PactBroker
62
65
  config.use_case_sensitive_resource_names = true
63
66
  config.html_pact_renderer = default_html_pact_render
64
67
  config.version_parser = PactBroker::Versions::ParseSemanticVersion
68
+ config.sha_generator = PactBroker::Pacts::GenerateSha
69
+ config.base_equality_only_on_content_that_affects_verification_results = false
65
70
  # Not recommended to set this to true unless there is no way to
66
71
  # consistently extract an orderable object from the consumer application version number.
67
72
  config.order_versions_by_date = false
@@ -114,18 +114,19 @@ module PactBroker
114
114
  execution_logger.info(" ")
115
115
  logger.info "Received response for webhook #{uuid} status=#{response.code}"
116
116
  execution_logger.info "HTTP/#{response.http_version} #{response.code} #{response.message}"
117
- response.each_header do | header |
118
- execution_logger.info "#{header.split("-").collect(&:capitalize).join('-')}: #{response[header]}"
119
- end
117
+ #response.each_header do | header |
118
+ # execution_logger.info "#{header.split("-").collect(&:capitalize).join('-')}: #{response[header]}"
119
+ #end
120
120
  logger.debug "body=#{response.body}"
121
- safe_body = nil
122
- if response.body
123
- safe_body = response.body.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
124
- if response.body != safe_body
125
- execution_logger.debug "Note that invalid UTF-8 byte sequences were removed from response body before saving the logs"
126
- end
127
- end
128
- execution_logger.info safe_body
121
+ # safe_body = nil
122
+ # if response.body
123
+ # safe_body = response.body.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
124
+ # if response.body != safe_body
125
+ # execution_logger.debug "Note that invalid UTF-8 byte sequences were removed from response body before saving the logs"
126
+ # end
127
+ # end
128
+ #execution_logger.info safe_body
129
+ execution_logger.info "Webhook response has been redacted temporarily for security purposes. Please see the Pact Broker application logs for the response body."
129
130
  end
130
131
 
131
132
  def log_completion_message options, execution_logger, success
@@ -1,8 +1,8 @@
1
1
  require 'pact_broker/repositories'
2
2
  require 'pact_broker/logging'
3
3
  require 'pact_broker/domain/index_item'
4
- require 'pact_broker/matrix/latest_row'
5
4
  require 'pact_broker/matrix/head_row'
5
+ require 'pact_broker/matrix/aggregated_row'
6
6
 
7
7
  module PactBroker
8
8
 
@@ -14,107 +14,37 @@ module PactBroker
14
14
  extend PactBroker::Logging
15
15
 
16
16
  def self.find_index_items options = {}
17
- rows = []
18
- overall_latest_publication_ids = nil
19
-
20
17
  rows = PactBroker::Matrix::HeadRow
21
18
  .select_all_qualified
22
19
  .eager(:latest_triggered_webhooks)
23
20
  .eager(:webhooks)
24
- .order(:consumer_name, :provider_name)
25
- .eager(:consumer_version_tags)
26
- .eager(:provider_version_tags)
27
21
 
28
22
  if !options[:tags]
29
- rows = rows.where(consumer_version_tag_name: nil).all
30
- overall_latest_publication_ids = rows.collect(&:pact_publication_id)
31
- end
32
-
33
- if options[:tags]
23
+ rows = rows.where(consumer_version_tag_name: nil)
24
+ else
34
25
  if options[:tags].is_a?(Array)
35
26
  rows = rows.where(consumer_version_tag_name: options[:tags]).or(consumer_version_tag_name: nil)
36
27
  end
37
-
38
- rows = rows.all
39
- overall_latest_publication_ids = rows.select{|r| !r[:consumer_version_tag_name] }.collect(&:pact_publication_id).uniq
40
-
41
- # Smoosh all the rows with matching pact publications together
42
- # and collect their consumer_head_tag_names
43
- rows = rows
44
- .group_by(&:pact_publication_id)
45
- .values
46
- .collect{|group| [group.last, group.collect{|r| r[:consumer_version_tag_name]}.compact] }
47
- .collect{ |(row, tag_names)| row.consumer_head_tag_names = tag_names; row }
28
+ rows = rows.eager(:consumer_version_tags)
29
+ .eager(:provider_version_tags)
30
+ .eager(:latest_verification_for_consumer_version_tag)
48
31
  end
32
+ rows = rows.all.group_by(&:pact_publication_id).values.collect{ | rows| Matrix::AggregatedRow.new(rows) }
49
33
 
50
- index_items = []
51
- rows.sort.each do | row |
52
- tag_names = []
53
- if options[:tags]
54
- tag_names = row.consumer_version_tags.collect(&:name)
55
- end
56
-
57
- overall_latest = overall_latest_publication_ids.include?(row.pact_publication_id)
58
- latest_verification = if overall_latest
59
- verification_repository.find_latest_verification_for row.consumer_name, row.provider_name
60
- else
61
- tag_names.collect do | tag_name |
62
- verification_repository.find_latest_verification_for row.consumer_name, row.provider_name, tag_name
63
- end.compact.sort do | v1, v2 |
64
- # Some provider versions have nil orders, not sure why
65
- # Sort by execution_date instead of order
66
- v1.execution_date <=> v2.execution_date
67
- end.last
68
- end
69
-
70
- index_items << PactBroker::Domain::IndexItem.create(
34
+ rows.sort.collect do | row |
35
+ # TODO simplify. Do we really need 3 layers of abstraction?
36
+ PactBroker::Domain::IndexItem.create(
71
37
  row.consumer,
72
38
  row.provider,
73
39
  row.pact,
74
- overall_latest,
75
- latest_verification,
40
+ row.overall_latest?,
41
+ row.latest_verification,
76
42
  row.webhooks,
77
43
  row.latest_triggered_webhooks,
78
- row.consumer_head_tag_names,
79
- row.provider_version_tags.select(&:latest?)
44
+ options[:tags] ? row.consumer_head_tag_names : [],
45
+ options[:tags] ? row.provider_version_tags.select(&:latest?) : []
80
46
  )
81
47
  end
82
-
83
- index_items
84
- end
85
-
86
- def self.tags_for(pact, options)
87
- if options[:tags] == true
88
- tag_service.find_all_tag_names_for_pacticipant(pact.consumer_name)
89
- elsif options[:tags].is_a?(Array)
90
- options[:tags]
91
- else
92
- []
93
- end
94
- end
95
-
96
- def self.build_index_item_rows(pact, tags)
97
- index_items = [build_latest_pact_index_item(pact, tags)]
98
- tags.each do | tag |
99
- index_items << build_index_item_for_tagged_pact(pact, tag)
100
- end
101
- index_items.compact
102
- end
103
-
104
- def self.build_latest_pact_index_item pact, tags
105
- latest_verification = verification_service.find_latest_verification_for(pact.consumer, pact.provider)
106
- webhooks = webhook_service.find_by_consumer_and_provider pact.consumer, pact.provider
107
- triggered_webhooks = webhook_service.find_latest_triggered_webhooks pact.consumer, pact.provider
108
- tag_names = pact.consumer_version_tag_names.select{ |name| tags.include?(name) }
109
- PactBroker::Domain::IndexItem.create pact.consumer, pact.provider, pact, true, latest_verification, webhooks, triggered_webhooks, tag_names
110
- end
111
-
112
- def self.build_index_item_for_tagged_pact latest_pact, tag
113
- pact = pact_service.find_latest_pact consumer_name: latest_pact.consumer_name, provider_name: latest_pact.provider_name, tag: tag
114
- return nil unless pact
115
- return nil if pact.id == latest_pact.id
116
- verification = verification_repository.find_latest_verification_for pact.consumer_name, pact.provider_name, tag
117
- PactBroker::Domain::IndexItem.create pact.consumer, pact.provider, pact, false, verification, [], [], [tag]
118
48
  end
119
49
  end
120
50
  end
@@ -0,0 +1,71 @@
1
+ require 'pact_broker/verifications/repository'
2
+
3
+ # A collection of matrix rows with the same pact publication id
4
+ # It's basically a normalised view of a denormalised view :(
5
+ # A pact publication may be the overall latest, and/or the latest for a tag
6
+ module PactBroker
7
+ module Matrix
8
+ class AggregatedRow
9
+ extend Forwardable
10
+
11
+ delegate [:consumer, :consumer_name, :consumer_version, :consumer_version_number, :consumer_version_order, :consumer_version_tags] => :first_row
12
+ delegate [:provider, :provider_name, :provider_version, :provider_version_number, :provider_version_order, :provider_version_tags] => :first_row
13
+ delegate [:pact, :pact_version, :pact_revision_number, :webhooks, :latest_triggered_webhooks, :'<=>'] => :first_row
14
+ delegate [:verification_id, :verification] => :first_row
15
+
16
+ def initialize matrix_rows
17
+ @matrix_rows = matrix_rows
18
+ @first_row = matrix_rows.first
19
+ end
20
+
21
+ def overall_latest?
22
+ !!matrix_rows.find{ | row| row.consumer_version_tag_name.nil? }
23
+ end
24
+
25
+ # If this comes back nil, it won't be "cached", but it's a reasonably
26
+ # quick query, so it's probably ok.
27
+ def latest_verification
28
+ @latest_verification ||= begin
29
+ verification = matrix_rows.collect do | row|
30
+ row.verification || latest_verification_for_consumer_version_tag(row)
31
+ end.compact.sort{ |v1, v2| v1.id <=> v2.id }.last
32
+
33
+ if !verification && overall_latest?
34
+ overall_latest_verification
35
+ else
36
+ verification
37
+ end
38
+ end
39
+ end
40
+
41
+ # The list of tag names for which this pact publication is the most recent with that tag
42
+ # There could, however, be a later consumer version that does't have a pact (perhaps because it was deleted)
43
+ # that has the same tag.
44
+ # TODO show a warning when the data is "corrupted" as above.
45
+ def consumer_head_tag_names
46
+ matrix_rows.collect(&:consumer_version_tag_name).compact
47
+ end
48
+
49
+ private
50
+
51
+ attr_reader :matrix_rows
52
+
53
+ def latest_verification_for_consumer_version_tag row
54
+ row.latest_verification_for_consumer_version_tag if row.consumer_version_tag_name
55
+ end
56
+
57
+ def overall_latest_verification
58
+ # not eager loaded because it shouldn't be called that often
59
+ first_row.latest_verification_for_consumer_and_provider
60
+ end
61
+
62
+ def first_row
63
+ @first_row
64
+ end
65
+
66
+ def consumer_version_tag_names
67
+ matrix_rows.collect(&:consumer_version_tag_name)
68
+ end
69
+ end
70
+ end
71
+ end