diff --git a/db/migrations/000050_create_latest_matrix.rb b/db/migrations/000050_create_latest_matrix.rb index b07739a82..366839b80 100644 --- a/db/migrations/000050_create_latest_matrix.rb +++ b/db/migrations/000050_create_latest_matrix.rb @@ -5,6 +5,7 @@ # latest verification for each provider version) # Must include lines where verification_id is null so that we don't # lose the unverified pacts. + # In this view there will be one row for each consumer version/provider version create_view(:latest_matrix, "SELECT matrix.* FROM matrix INNER JOIN latest_verification_id_for_consumer_version_and_provider_version AS lv diff --git a/db/migrations/20180123_create_latest_verification_tags.rb b/db/migrations/20180123_create_latest_verification_tags.rb new file mode 100644 index 000000000..1d6628c40 --- /dev/null +++ b/db/migrations/20180123_create_latest_verification_tags.rb @@ -0,0 +1,43 @@ +Sequel.migration do + change do + # the provider version order of the latest verification for each consumer/provider/tag + create_view(:latest_tagged_verification_provider_version_orders, + " + select m.consumer_id, m.provider_id, t.name as tag_name, max(m.provider_version_order) as latest_provider_version_order + from latest_matrix m + inner join tags t + on m.provider_version_id = t.version_id + where m.provider_version_order is not null + group by m.consumer_id, m.provider_id, t.name + " + ) + +=begin + The tags for which the given verification is the latest of that tag + Imagine that: + + provider v1 has verification + has tag dev + has tag prod <- latest + provider v2 has verification + has tag dev <-latest + provider v3 has tag dev + + This table would contain the prod tag row for the v1 verification + This table would contain the dev tag row for the v2 verification +=end + create_view(:latest_verification_tags, + " + select t.*, m.verification_id + from latest_matrix m + inner join latest_tagged_verification_provider_version_orders l + on m.consumer_id = l.consumer_id + and m.provider_id = l.provider_id + and m.provider_version_order = l.latest_provider_version_order + inner join tags t + on l.tag_name = t.name + and m.provider_version_id = t.version_id + " + ) + end +end \ No newline at end of file diff --git a/lib/pact_broker/api/decorators/dashboard_decorator.rb b/lib/pact_broker/api/decorators/dashboard_decorator.rb index db94b3d8f..1d16f9510 100644 --- a/lib/pact_broker/api/decorators/dashboard_decorator.rb +++ b/lib/pact_broker/api/decorators/dashboard_decorator.rb @@ -38,6 +38,7 @@ def index_item_hash(consumer, provider, consumer_version, index_item, base_url) pact: pact_hash(index_item, base_url), pactTags: pact_tags(index_item, base_url), latestVerificationResult: verification_hash(index_item, base_url), + latestVerificationResultTags: verification_tags(index_item, base_url), verificationStatus: index_item.verification_status.to_s, webhookStatus: index_item.webhook_status.to_s, latestWebhookExecution: latest_webhook_execution(index_item, base_url), @@ -125,6 +126,21 @@ def pact_tags(index_item, base_url) end end + def verification_tags(index_item, base_url) + index_item.latest_verification_latest_tags.collect do | tag | + fake_tag = OpenStruct.new(name: tag.name, version: index_item.provider_version) + { + name: tag.name, + latest: true, + _links: { + self: { + href: tag_url(base_url, fake_tag) + } + } + } + end + end + def latest_webhook_execution(index_item, base_url) if index_item.last_webhook_execution_date { diff --git a/lib/pact_broker/domain/index_item.rb b/lib/pact_broker/domain/index_item.rb index 173af29e4..078a38e8a 100644 --- a/lib/pact_broker/domain/index_item.rb +++ b/lib/pact_broker/domain/index_item.rb @@ -5,9 +5,9 @@ module PactBroker module Domain class IndexItem - attr_reader :consumer, :provider, :latest_pact, :latest_verification, :webhooks, :triggered_webhooks + attr_reader :consumer, :provider, :latest_pact, :latest_verification, :webhooks, :triggered_webhooks, :latest_verification_latest_tags - def initialize consumer, provider, latest_pact = nil, latest = true, latest_verification = nil, webhooks = [], triggered_webhooks = [], tags = [] + def initialize consumer, provider, latest_pact = nil, latest = true, latest_verification = nil, webhooks = [], triggered_webhooks = [], tags = [], latest_verification_latest_tags = [] @consumer = consumer @provider = provider @latest_pact = latest_pact @@ -16,10 +16,11 @@ def initialize consumer, provider, latest_pact = nil, latest = true, latest_veri @webhooks = webhooks @triggered_webhooks = triggered_webhooks @tags = tags + @latest_verification_latest_tags = latest_verification_latest_tags end - def self.create consumer, provider, latest_pact, latest, latest_verification, webhooks = [], triggered_webhooks = [], tags = [] - new consumer, provider, latest_pact, latest, latest_verification, webhooks, triggered_webhooks, tags + def self.create consumer, provider, latest_pact, latest, latest_verification, webhooks = [], triggered_webhooks = [], tags = [], latest_verification_latest_tags = [] + new consumer, provider, latest_pact, latest, latest_verification, webhooks, triggered_webhooks, tags, latest_verification_latest_tags end def eq? other diff --git a/lib/pact_broker/index/service.rb b/lib/pact_broker/index/service.rb index 5aca4adc2..bb04420df 100644 --- a/lib/pact_broker/index/service.rb +++ b/lib/pact_broker/index/service.rb @@ -29,12 +29,13 @@ def self.find_index_items options = {} if options[:tags] tagged_rows = PactBroker::Matrix::Row .select_all_qualified - .select_append(:tag_name) - .join(:head_pact_publications, {id: :pact_publication_id}) + .select_append(Sequel[:head_pact_publications][:tag_name]) + .join(:head_pact_publications, {consumer_id: :consumer_id, provider_id: :provider_id, consumer_version_order: :consumer_version_order}) .eager(:latest_triggered_webhooks) .eager(:webhooks) .order(:consumer_name, :provider_name) .eager(:consumer_version_tags) + .eager(:latest_verification_tags) if options[:tags].is_a?(Array) tagged_rows = tagged_rows.where(Sequel[:head_pact_publications][:tag_name] => options[:tags]).or(Sequel[:head_pact_publications][:tag_name] => nil) @@ -43,7 +44,7 @@ def self.find_index_items options = {} tagged_rows = tagged_rows.all .group_by(&:pact_publication_id) .values - .collect{|group| [group.first, group.collect{|r| r[:tag_name]}.compact] } + .collect{|group| [group.last, group.collect{|r| r[:tag_name]}.compact] } .collect{ |(row, tag_names)| row.consumer_head_tag_names = tag_names; row } rows = tagged_rows @@ -59,7 +60,15 @@ def self.find_index_items options = {} end end previous_index_item_for_same_consumer_and_provider = index_items.last && index_items.last.consumer_name == row.consumer_name && index_items.last.provider_name == row.provider_name - index_items << PactBroker::Domain::IndexItem.create(row.consumer, row.provider, row.pact, !previous_index_item_for_same_consumer_and_provider, row.latest_verification, row.webhooks, row.latest_triggered_webhooks, tag_names) + index_items << PactBroker::Domain::IndexItem.create(row.consumer, row.provider, + row.pact, + !previous_index_item_for_same_consumer_and_provider, + row.latest_verification, + row.webhooks, + row.latest_triggered_webhooks, + tag_names, + row.latest_verification_tags + ) end index_items diff --git a/lib/pact_broker/matrix/row.rb b/lib/pact_broker/matrix/row.rb index 30a08d93c..939e7a364 100644 --- a/lib/pact_broker/matrix/row.rb +++ b/lib/pact_broker/matrix/row.rb @@ -1,5 +1,6 @@ require 'pact_broker/repositories/helpers' require 'pact_broker/webhooks/latest_triggered_webhook' +require 'pact_broker/tags/latest_verification_tag' module PactBroker module Matrix @@ -13,6 +14,7 @@ class Row < Sequel::Model(:matrix) # TODO modify this to work with single pacticipant webhooks associate(:one_to_many, :webhooks, :class => "PactBroker::Webhooks::Webhook", primary_key: [:consumer_id, :provider_id], key: [:consumer_id, :provider_id]) associate(:one_to_many, :consumer_version_tags, :class => "PactBroker::Domain::Tag", primary_key: :consumer_version_id, key: :version_id) + associate(:one_to_many, :latest_verification_tags, :class => "PactBroker::Tags::LatestVerificationTag", primary_key: :verification_id, key: :verification_id) dataset_module do include PactBroker::Repositories::Helpers diff --git a/lib/pact_broker/tags/latest_verification_tag.rb b/lib/pact_broker/tags/latest_verification_tag.rb new file mode 100644 index 000000000..4eaf9fd81 --- /dev/null +++ b/lib/pact_broker/tags/latest_verification_tag.rb @@ -0,0 +1,14 @@ +require 'pact_broker/db' +require 'pact_broker/repositories/helpers' + +module PactBroker + module Tags + # The tag associated with the latest verification for a given tag + class LatestVerificationTag < Sequel::Model + + dataset_module do + include PactBroker::Repositories::Helpers + end + end + end +end diff --git a/spec/fixtures/dashboard.json b/spec/fixtures/dashboard.json index 0922d4b8a..0ce6f1d11 100644 --- a/spec/fixtures/dashboard.json +++ b/spec/fixtures/dashboard.json @@ -46,6 +46,17 @@ } } ], + "latestVerificationResultTags": [ + { + "name": "dev", + "latest": true, + "_links": { + "self": { + "href": "verification_dev_tag_url" + } + } + } + ], "pact": { "_links": { "self": { diff --git a/spec/lib/pact_broker/api/decorators/dashboard_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/dashboard_decorator_spec.rb index e8deec820..5dd969fc8 100644 --- a/spec/lib/pact_broker/api/decorators/dashboard_decorator_spec.rb +++ b/spec/lib/pact_broker/api/decorators/dashboard_decorator_spec.rb @@ -20,7 +20,8 @@ module Decorators verification_status: 'wiffle', provider_version_number: provider_version.number, consumer_version_number: consumer_version.number, - tag_names: ['prod'] + tag_names: ['prod'], + latest_verification_latest_tags: [double('tag', name: 'dev')] ) end let(:consumer) { instance_double('PactBroker::Domain::Pacticipant', name: 'Foo') } @@ -42,10 +43,17 @@ module Decorators allow_any_instance_of(DashboardDecorator).to receive(:version_url).with(base_url, consumer_version).and_return('consumer_version_url') allow_any_instance_of(DashboardDecorator).to receive(:webhooks_status_url).with(consumer, provider, base_url).and_return('webhooks_status_url') allow_any_instance_of(DashboardDecorator).to receive(:tag_url) do | instance, base_url, tag | - expect(tag.name).to eq 'prod' - expect(tag.version).to be consumer_version - expect(base_url).to eq base_url - 'pact_prod_tag_url' + if tag.version == consumer_version + expect(tag.name).to eq 'prod' + expect(tag.version).to be consumer_version + expect(base_url).to eq base_url + 'pact_prod_tag_url' + else + expect(tag.name).to eq 'dev' + expect(tag.version).to be provider_version + expect(base_url).to eq base_url + 'verification_dev_tag_url' + end end end diff --git a/spec/lib/pact_broker/index/service_spec.rb b/spec/lib/pact_broker/index/service_spec.rb index cf3c67ef4..b0f1bbf80 100644 --- a/spec/lib/pact_broker/index/service_spec.rb +++ b/spec/lib/pact_broker/index/service_spec.rb @@ -24,6 +24,7 @@ module Index .create_consumer_version_tag("also-ignored") .create_pact .create_verification(provider_version: "2.1.0") + .use_provider_version("2.1.0") end let(:rows) { subject.find_index_items(options) } @@ -93,6 +94,22 @@ module Index expect(rows.first.latest_verification.provider_version_number).to eq '2.0.0' end end + + context "when the verification is the latest for a given tag" do + before do + td.create_pact_with_hierarchy("Foo", "1", "Bar") + .create_verification(provider_version: "1.0.0", tag_names: ['dev', 'prod']) + .create_verification(provider_version: "2.0.0", number: 2, tag_names: ['dev']) + end + + let(:rows) { subject.find_index_items(options) } + let(:options) { { tags: true } } + + it "includes the names of the tags for which the verification is the latest of that tag" do + expect(rows.first.provider_version_number).to eq "2.0.0" + expect(rows.first.latest_verification_latest_tags.collect(&:name)).to eq ['dev'] + end + end end end end diff --git a/spec/migrations/20180123_create_latest_verification_tags_spec.rb b/spec/migrations/20180123_create_latest_verification_tags_spec.rb new file mode 100644 index 000000000..b7a9ea83f --- /dev/null +++ b/spec/migrations/20180123_create_latest_verification_tags_spec.rb @@ -0,0 +1,85 @@ +describe 'latest tagged verifications', migration: true do + before do + PactBroker::Database.migrate(20180123) + end + + let(:now) { DateTime.new(2018, 2, 2) } + let!(:consumer) { create(:pacticipants, {name: 'C', created_at: now, updated_at: now}) } + let!(:provider) { create(:pacticipants, {name: 'P', created_at: now, updated_at: now}) } + let!(:consumer_version_1) { create(:versions, {number: '1', order: 1, pacticipant_id: consumer[:id], created_at: now, updated_at: now}) } + let!(:consumer_version_2) { create(:versions, {number: '2', order: 2, pacticipant_id: consumer[:id], created_at: now, updated_at: now}) } + + let!(:provider_version_1) { create(:versions, {number: '1', order: 1, pacticipant_id: provider[:id], created_at: now, updated_at: now}) } + let!(:provider_version_2) { create(:versions, {number: '2', order: 2, pacticipant_id: provider[:id], created_at: now, updated_at: now}) } + let!(:provider_version_3) { create(:versions, {number: '3', order: 3, pacticipant_id: provider[:id], created_at: now, updated_at: now}) } + + let!(:provider_version_1_prod_tag) { create(:tags, {version_id: provider_version_1[:id], name: 'prod', created_at: now, updated_at: now}, nil) } + let!(:provider_version_1_dev_tag) { create(:tags, {version_id: provider_version_1[:id], name: 'dev', created_at: now, updated_at: now}, nil) } + let!(:provider_version_2_dev_tag) { create(:tags, {version_id: provider_version_2[:id], name: 'dev', created_at: now, updated_at: now}, nil) } + + let!(:pact_version_1) { create(:pact_versions, {content: {some: 'json'}.to_json, sha: '1', consumer_id: consumer[:id], provider_id: provider[:id], created_at: now}) } + let!(:pact_version_2) { create(:pact_versions, {content: {some: 'json other'}.to_json, sha: '2', consumer_id: consumer[:id], provider_id: provider[:id], created_at: now}) } + #let!(:pact_version_3) { create(:pact_versions, {content: {some: 'json more'}.to_json, sha: '3', consumer_id: consumer[:id], provider_id: provider[:id], created_at: now}) } + let!(:pact_publication_1) do + create(:pact_publications, { + consumer_version_id: consumer_version_1[:id], + provider_id: provider[:id], + revision_number: 1, + pact_version_id: pact_version_1[:id], + created_at: now + }) + end + + let!(:pact_publication_2) do + create(:pact_publications, { + consumer_version_id: consumer_version_2[:id], + provider_id: provider[:id], + revision_number: 1, + pact_version_id: pact_version_2[:id], + created_at: now + }) + end + + # provider v1 + let!(:verification_1) do + create(:verifications, { + number: 1, + success: true, + provider_version_id: provider_version_1[:id], + pact_version_id: pact_version_1[:id], + execution_date: now, + created_at: now + }) + end + + # provider v2 + let!(:verification_2) do + create(:verifications, { + number: 2, + success: true, + provider_version_id: provider_version_2[:id], + pact_version_id: pact_version_1[:id], + execution_date: now, + created_at: now + }) + end + + # provider v2 + let!(:verification_3) do + create(:verifications, { + number: 3, + success: true, + provider_version_id: provider_version_2[:id], + pact_version_id: pact_version_1[:id], + execution_date: now, + created_at: now + }) + end + + it "includes the tag rows for which the related verification is the latest of that tag" do + rows = database[:latest_verification_tags].all + expect(rows).to contain_hash(verification_id: verification_1[:id], name: 'prod') + expect(rows).to contain_hash(verification_id: verification_3[:id], name: 'dev') + expect(rows.size).to eq 2 + end +end diff --git a/spec/support/migration_helpers.rb b/spec/support/migration_helpers.rb index c6ef6bde7..99ec667ae 100644 --- a/spec/support/migration_helpers.rb +++ b/spec/support/migration_helpers.rb @@ -3,7 +3,7 @@ module MigrationHelpers def create table_name, params, id_column_name = :id database[table_name].insert(params); - database[table_name].order(id_column_name).last + database[table_name].order(id_column_name).last if id_column_name end def clean table_name diff --git a/spec/support/rspec_match_hash.rb b/spec/support/rspec_match_hash.rb index 70a3473c5..c5a7046d8 100644 --- a/spec/support/rspec_match_hash.rb +++ b/spec/support/rspec_match_hash.rb @@ -8,8 +8,12 @@ def contains_hash?(expected, actual) - expected.all? do |key, value| - unordered_match(actual[key], value) + if actual.is_a?(Array) + actual.any? && actual.any?{|actual_item| contains_hash?(expected, actual_item)} + else + expected.all? do |key, value| + unordered_match(actual[key], value) + end end end diff --git a/spec/support/test_data_builder.rb b/spec/support/test_data_builder.rb index 106035cfa..035cd4118 100644 --- a/spec/support/test_data_builder.rb +++ b/spec/support/test_data_builder.rb @@ -240,12 +240,19 @@ def create_deprecated_webhook_execution params = {} end def create_verification parameters = {} + tag_names = [parameters.delete(:tag_names), parameters.delete(:tag_name)].flatten.compact provider_version_number = parameters[:provider_version] || '4.5.6' default_parameters = {success: true, number: 1, test_results: {some: 'results'}} parameters = default_parameters.merge(parameters) parameters.delete(:provider_version) verification = PactBroker::Domain::Verification.new(parameters) @verification = PactBroker::Verifications::Repository.new.create(verification, provider_version_number, @pact) + if tag_names.any? + provider_version = PactBroker::Domain::Version.where(pacticipant_id: @provider.id, number: provider_version_number).single_record + tag_names.each do | tag_name | + PactBroker::Domain::Tag.create(name: tag_name, version: provider_version) + end + end self end