From 1ec8fc8209ca5b2f32d640f7b9deec035769f64e Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Sat, 11 Sep 2021 17:55:14 +1000 Subject: [PATCH] feat: allow dashboard pacts to be viewed by branch, tag, environment or all --- lib/pact_broker/index/service.rb | 58 ++++--- .../pacts/pact_publication_dataset_module.rb | 21 +++ lib/pact_broker/ui/controllers/dashboard.rb | 7 +- lib/pact_broker/ui/view_models/index_item.rb | 4 + lib/pact_broker/ui/views/dashboard/show.haml | 86 +++++++---- lib/pact_broker/ui/views/index/show.haml | 8 +- public/javascripts/index.js | 65 ++++---- spec/lib/pact_broker/index/service_spec.rb | 6 +- .../pact_broker/index/service_view_spec.rb | 144 ++++++++++++++++++ 9 files changed, 310 insertions(+), 89 deletions(-) create mode 100644 spec/lib/pact_broker/index/service_view_spec.rb diff --git a/lib/pact_broker/index/service.rb b/lib/pact_broker/index/service.rb index 9736121ba..e947a00fd 100644 --- a/lib/pact_broker/index/service.rb +++ b/lib/pact_broker/index/service.rb @@ -157,25 +157,27 @@ def self.head_pact_publications(options = {}) return base.paginate(options[:page_number] || DEFAULT_PAGE_NUMBER, options[:page_size] || DEFAULT_PAGE_SIZE) if pacticipant_ids.blank? end - ids_query = query_pact_publication_ids_by_tags(base, options[:tags]) - query = PactBroker::Pacts::PactPublication - .select_all_qualified - .where(Sequel[:pact_publications][:id] => ids_query) - .join_consumers(:consumers) - .join_providers(:providers) - .join(:versions, - { Sequel[:pact_publications][:consumer_version_id] => Sequel[:cv][:id] }, - { table_alias: :cv } - ) + ids_query = if options[:view] + pact_publications_by_view(base, options) + else + query_pact_publication_ids_by_tags(base, options[:tags]) + end - order_columns = [ - Sequel.asc(Sequel.function(:lower, Sequel[:consumers][:name])), - Sequel.desc(Sequel[:cv][:order]), - Sequel.asc(Sequel.function(:lower, Sequel[:providers][:name])) - ] + select_columns_and_order(ids_query, options) + end - query.order(*order_columns) - .paginate(options[:page_number] || DEFAULT_PAGE_NUMBER, options[:page_size] || DEFAULT_PAGE_SIZE) + def self.pact_publications_by_view(query, options) + case options[:view] + when "branch" then query.latest_by_consumer_branch + when "tag" then query.latest_by_consumer_tag + when "environment" then query.in_environments + else + query + .overall_latest + .union(query.latest_by_consumer_branch) + .union(query.latest_by_consumer_tag) + .union(query.in_environments) + end end # eager loading the tag stuff doesn't seem to make it quicker @@ -223,7 +225,27 @@ def self.pact_pacticipant_ids_by_name(pacticipant_name) pacticipant_repository.search_by_name(pacticipant_name).collect(&:id) end - private_class_method :base_query, :query_pact_publication_ids_by_tags, :pact_pacticipant_ids_by_name + def self.select_columns_and_order(ids_query, options) + query = PactBroker::Pacts::PactPublication + .select_all_qualified + .where(Sequel[:pact_publications][:id] => ids_query) + .join_consumers(:consumers) + .join_providers(:providers) + + + order_columns = [ + Sequel.asc(Sequel.function(:lower, Sequel[:consumers][:name])), + Sequel.desc(Sequel[:pact_publications][:consumer_version_order]), + Sequel.asc(Sequel.function(:lower, Sequel[:providers][:name])) + ] + + pact_number = options[:page_number] || DEFAULT_PAGE_NUMBER + page_size = options[:page_size] || DEFAULT_PAGE_SIZE + + query.order(*order_columns).paginate(pact_number, page_size) + end + + private_class_method :base_query, :query_pact_publication_ids_by_tags, :pact_pacticipant_ids_by_name, :select_columns_and_order end end end diff --git a/lib/pact_broker/pacts/pact_publication_dataset_module.rb b/lib/pact_broker/pacts/pact_publication_dataset_module.rb index 6fa3cb835..030f552c1 100644 --- a/lib/pact_broker/pacts/pact_publication_dataset_module.rb +++ b/lib/pact_broker/pacts/pact_publication_dataset_module.rb @@ -216,6 +216,27 @@ def for_latest_consumer_versions_with_tag(tag_name) end end + def in_environments + currently_deployed_join = { + Sequel[:pact_publications][:consumer_version_id] => Sequel[:currently_deployed_version_ids][:version_id] + } + + released_join = { + Sequel[:pact_publications][:consumer_version_id] => Sequel[:released_versions][:version_id], + Sequel[:released_versions][:support_ended_at] => nil + } + + base_query = self + if no_columns_selected? + base_query = base_query.select_all_qualified + end + + deployed = base_query.join(:currently_deployed_version_ids, currently_deployed_join) + released = base_query.join(:released_versions, released_join) + + deployed.union(released) + end + def verified_before_date(date) where { Sequel[:verifications][:execution_date] < date } end diff --git a/lib/pact_broker/ui/controllers/dashboard.rb b/lib/pact_broker/ui/controllers/dashboard.rb index 27dedb4bb..aa60bfedc 100644 --- a/lib/pact_broker/ui/controllers/dashboard.rb +++ b/lib/pact_broker/ui/controllers/dashboard.rb @@ -14,12 +14,14 @@ class Dashboard < Base page_number = params[:page]&.to_i || 1 # Make page size smaller for data intensive query page_size = params[:pageSize]&.to_i || 30 + view = params[:view] || "branch" options = { tags: tags, page_number: page_number, page_size: page_size, consumer_name: params[:consumer_name], - provider_name: params[:provider_name] + provider_name: params[:provider_name], + view: view }.compact error_messages = [] @@ -44,7 +46,8 @@ class Dashboard < Base current_page_size: view_index_items.size, base_url: base_url, errors: error_messages, - tags: tags + tags: tags, + view: view } haml page, { locals: locals, layout: :'layouts/main', escape_html: true } diff --git a/lib/pact_broker/ui/view_models/index_item.rb b/lib/pact_broker/ui/view_models/index_item.rb index 4820427fc..64e40f18a 100644 --- a/lib/pact_broker/ui/view_models/index_item.rb +++ b/lib/pact_broker/ui/view_models/index_item.rb @@ -57,6 +57,10 @@ def display_provider_version_number PactBroker::Versions::AbbreviateNumber.call(provider_version_number) end + def display_latest_label? + consumer_version_latest_tag_names.empty? && @relationship.tag_names.empty? + end + def latest? @relationship.latest? end diff --git a/lib/pact_broker/ui/views/dashboard/show.haml b/lib/pact_broker/ui/views/dashboard/show.haml index d3b4ad8cf..092da0d0b 100644 --- a/lib/pact_broker/ui/views/dashboard/show.haml +++ b/lib/pact_broker/ui/views/dashboard/show.haml @@ -10,16 +10,35 @@ %div.alert.alert-danger = error + %form + %div.my-3 + %label.mr-2 + View pacts by: + %div.form-check.form-check-inline + %input.form-check-input{ type: "radio", name: "view", id: "by_branch", value: "branch", checked: view == "branch" } + %label.form-check-label{ for:"by_branch"} branches + %div.form-check.form-check-inline + %input.form-check-input{ type: "radio", name: "view", id: "by_tag", value: "tag", checked: view == "tag"} + %label.form-check-label{ for:"by_tag"} tags + %div.form-check.form-check-inline + %input.form-check-input{ type: "radio", name: "view", id: "by_environment", value: "environment", checked: view == "environment"} + %label.form-check-label{ for:"by_environment"} environments + %div.form-check.form-check-inline + %input.form-check-input{ type: "radio", name: "view", id: "by_all", value: "all", checked: view == "all"} + %label.form-check-label{ for:"by_all"} no filter + %input{ type: "hidden", name: "page", value: page_number } + %input{ type: "hidden", name: "pageSize", value: page_size } + %table.table.table-bordered.table-striped{ id: 'relationships' } %thead %tr %th.consumer-version-number Consumer
Version %span.sort-icon.relationships-sort - %th.pact{ style: 'width: 40px' } %th.provider-version-number Provider
Version %span.sort-icon.relationships-sort + %th.pact{ style: 'width: 40px' } %th Published %span.sort-icon.relationships-sort @@ -37,7 +56,8 @@ 'data-provider-name': index_item.provider_name, 'data-integration-url': index_item.integration_url, 'data-pact-tags': index_item.pact_tags, - 'data-pact-branches': index_item.pact_branches + 'data-pact-branches': index_item.pact_branches, + 'data-view': view } %td.consumer-version-number{"data-text": index_item.consumer_version_order} %div.clippable{"data-clippable": index_item.consumer_version_number} @@ -45,38 +65,44 @@ - if index_item.display_consumer_version_number %button.clippy.invisible{ title: "Copy to clipboard" } %span.copy-icon - - index_item.consumer_version_branches.each do | branch_name | - %div{"class": "tag badge badge-dark"} - = "branch: " + branch_name - - index_item.consumer_version_latest_tag_names.each do | tag_name | - .tag.badge.badge-primary - = "tag: " + tag_name - - index_item.consumer_version_environment_names.each do | environment_name | - .tag.badge.badge-success - = "env: " + environment_name - - if index_item.latest? + - if view == "branch" || view == "all" + - index_item.consumer_version_branches.each do | branch_name | + %div{"class": "tag badge badge-dark"} + = "branch: " + branch_name + - if view == "tag" || view == "all" + - index_item.consumer_version_latest_tag_names.each do | tag_name | + .tag.badge.badge-primary + = "tag: " + tag_name + - if view == "environment" || view == "all" + - index_item.consumer_version_environment_names.each do | environment_name | + .tag.badge.badge-success + = "env: " + environment_name + - if view == "all" && index_item.display_latest_label? && index_item.latest? .tag.badge.bg-light latest - %td.pact - %span.pact - %a{ href: index_item.pact_url, title: "View pact" } - %span.pact-matrix - %a{ href: index_item.pact_matrix_url, title: "View pact matrix" } %td.provider-version-number %div.clippable{"data-clippable": index_item.provider_version_number} = index_item.display_provider_version_number - if index_item.display_provider_version_number %button.clippy.invisible{ title: "Copy to clipboard" } %span.copy-icon - - index_item.provider_version_branches.each do | branch_name | - %div{"class": "tag badge badge-dark"} - = "branch: " + branch_name - - index_item.provider_version_latest_tag_names.each do | tag_name | - .tag.badge.badge-primary - = "tag: " + tag_name - - index_item.provider_version_environment_names.each do | environment_name | - .tag.badge.badge-success - = "env: " + environment_name + - if view == "branch" || view == "all" + - index_item.provider_version_branches.each do | branch_name | + %div{"class": "tag badge badge-dark"} + = "branch: " + branch_name + - if view == "tag" || view == "all" + - index_item.provider_version_latest_tag_names.each do | tag_name | + .tag.badge.badge-primary + = "tag: " + tag_name + - if view == "environment" || view == "all" + - index_item.provider_version_environment_names.each do | environment_name | + .tag.badge.badge-success + = "env: " + environment_name + %td.pact + %span.pact + %a{ href: index_item.pact_url, title: "View pact" } + %span.pact-matrix + %a{ href: index_item.pact_matrix_url, title: "View pact matrix" } %td{"data-text": index_item.publication_date_of_latest_pact_order} = index_item.publication_date_of_latest_pact.gsub("about ", "") %td{ class: "table-#{index_item.webhook_status}" } @@ -90,7 +116,8 @@ - if index_item.warning? %span.warning-icon{ 'aria-hidden': true } %td - %span.integration-settings.kebab-horizontal{ 'aria-hidden': true } + - if view != "environment" + %span.integration-settings.kebab-horizontal{ 'aria-hidden': true } %div.pagination.text-center @@ -139,3 +166,8 @@ window.location = url.toString(); } }) + + $("[name*='view']").change(function(event){ + $("[name='page']").attr('disabled','disabled'); + $(this.form).submit() + }) diff --git a/lib/pact_broker/ui/views/index/show.haml b/lib/pact_broker/ui/views/index/show.haml index 390133c21..60de74a14 100644 --- a/lib/pact_broker/ui/views/index/show.haml +++ b/lib/pact_broker/ui/views/index/show.haml @@ -27,11 +27,11 @@ %th.consumer Consumer %span.sort-icon.relationships-sort - %th.pact %th.provider Provider %span.sort-icon.relationships-sort + %th.pact %th %th Latest pact
published @@ -55,14 +55,14 @@ %td.consumer{'role':"button"} %a{ href: index_item.consumer_group_url } = index_item.consumer_name + %td.provider{'role':"button"} + %a{ href: index_item.provider_group_url } + = index_item.provider_name %td.pact{'role':"button"} %span.pact %a{ href: index_item.latest_pact_url, :title => "View pact" } %span.pact-matrix %a{ href: index_item.pact_matrix_url, title: "View pact matrix" } - %td.provider{'role':"button"} - %a{ href: index_item.provider_group_url } - = index_item.provider_name %td{'role':"button"} %td{"data-text": index_item.publication_date_of_latest_pact_order, 'role':"button"} = index_item.publication_date_of_latest_pact diff --git a/public/javascripts/index.js b/public/javascripts/index.js index 875f4e899..219f09811 100644 --- a/public/javascripts/index.js +++ b/public/javascripts/index.js @@ -210,38 +210,11 @@ function deleteResources(url, successCallback, errorCallback) { } function buildMaterialMenuItems(clickedElementData) { - const baseOptions = [ - { - type: "normal", - text: "Delete pacts ...", - click: handleDeletePactsSelected - }, - { - type: "normal", - text: "Delete integration...", - click: handleDeleteIntegrationsSelected - } - ]; - const providerName = clickedElementData.providerName; const consumerName = clickedElementData.consumerName; - const taggedPactsOptions = (clickedElementData.pactTags || []).map(tag => { - const refName = tag.name; - const deletionUrl = tag.deletionUrl; - return { - type: "normal", - text: `Delete pacts for tag ${refName}...`, - click: handleDeleteTagOrBranchSelected({ - providerName, - consumerName, - refName, - deletionUrl, - scope: "with tag" - }) - }; - }); - const branchOptions = (clickedElementData.pactBranches || []).map(branch => { + if (clickedElementData.view === "branch" || clickedElementData.view === "all") { + return (clickedElementData.pactBranches || []).map(branch => { const refName = branch.name; const deletionUrl = branch.deletionUrl; return { @@ -256,10 +229,36 @@ function buildMaterialMenuItems(clickedElementData) { }) }; }); - - if (clickedElementData.index) { - return baseOptions; + } else if (clickedElementData.view === "tag" || clickedElementData.view === "all") { + return (clickedElementData.pactTags || []).map(tag => { + const refName = tag.name; + const deletionUrl = tag.deletionUrl; + return { + type: "normal", + text: `Delete pacts for tag ${refName}...`, + click: handleDeleteTagOrBranchSelected({ + providerName, + consumerName, + refName, + deletionUrl, + scope: "with tag" + }) + }; + }); + } else if (clickedElementData.index) { + return [ + { + type: "normal", + text: "Delete pacts ...", + click: handleDeletePactsSelected + }, + { + type: "normal", + text: "Delete integration...", + click: handleDeleteIntegrationsSelected + } + ]; } else { - return [...branchOptions, ...taggedPactsOptions]; + return [] } } diff --git a/spec/lib/pact_broker/index/service_spec.rb b/spec/lib/pact_broker/index/service_spec.rb index c953bd1ef..cbf7c0ede 100644 --- a/spec/lib/pact_broker/index/service_spec.rb +++ b/spec/lib/pact_broker/index/service_spec.rb @@ -1,12 +1,8 @@ -require "spec_helper" require "pact_broker/index/service" -require "pact_broker/domain/tag" -require "pact_broker/domain/pact" module PactBroker module Index describe Service do - let(:td) { TestDataBuilder.new } let(:tags) { ["prod", "production"] } let(:options) { { tags: tags, page_size: page_size, page_number: page_number } } let(:page_number) { nil } @@ -19,7 +15,7 @@ module Index subject { Service } - describe "find_relationships integration test" do + describe "find_index_items integration test" do context "when a prod pact exists and is not the latest version" do before do td.create_pact_with_hierarchy("Foo", "1.2.3", "Bar") diff --git a/spec/lib/pact_broker/index/service_view_spec.rb b/spec/lib/pact_broker/index/service_view_spec.rb new file mode 100644 index 000000000..c5c52adcd --- /dev/null +++ b/spec/lib/pact_broker/index/service_view_spec.rb @@ -0,0 +1,144 @@ +require "pact_broker/index/service" + +module PactBroker + module Index + describe Service do + let(:tags) { ["prod", "production"] } + let(:options) do + { + page_size: page_size, + page_number: page_number, + view: view, + consumer_name: consumer_name, + provider_name: provider_name + } + end + let(:page_number) { nil } + let(:page_size) { nil } + let(:consumer_name) { "Foo"} + let(:provider_name) { "Bar"} + + subject { Service.find_index_items(options) } + + describe "find_index_items" do + context "when view == branch" do + before do + td.create_consumer("Foo") + .create_provider("Bar") + .create_consumer_version("1", branch: "main", tag_names: ["dev"]) + .create_pact + .create_consumer_version("2", tag_names: ["dev"]) + .create_pact + .create_consumer_version("3", branch: "feat/x", tag_names: ["dev-1"]) + .create_pact + .create_consumer_version("4", branch: "main", tag_names: ["dev-2"]) + .create_pact + .create_consumer_version("4", branch: "feat/y") + .create_consumer("NotFoo") + .create_consumer_version("10", branch: "main") + .create_pact + end + + let(:view) { "branch" } + + let(:consumer_version_numbers) { subject.collect(&:consumer_version_number) } + + it "returns the latest pacts for each branch" do + expect(consumer_version_numbers).to eq ["4", "3"] + end + end + + context "when view == tag" do + before do + td.create_consumer("Foo") + .create_provider("Bar") + .create_consumer_version("1", branch: "main", tag_names: ["dev"]) + .create_pact + .create_consumer_version("2", tag_names: ["dev"]) + .create_pact + .create_consumer_version("3", branch: "feat/x", tag_names: ["dev-2"]) + .create_pact + .create_consumer_version("4", branch: "main", tag_names: ["dev-3"]) + .create_pact + .create_consumer_version("4", branch: "feat/y") + .create_consumer("NotFoo") + .create_consumer_version("10", branch: "main") + .create_pact + end + + let(:view) { "tag" } + + let(:consumer_version_numbers) { subject.collect(&:consumer_version_number) } + + it "returns the latest pacts for each tag" do + expect(consumer_version_numbers).to eq ["4", "3", "2"] + end + end + + context "when view == environment" do + before do + td.create_environment("test") + .create_consumer("Foo") + .create_provider("Bar") + .create_consumer_version("1") + .create_pact + .create_deployed_version_for_consumer_version(currently_deployed: false) + .create_consumer_version("2") + .create_pact + .create_deployed_version_for_consumer_version + .create_consumer_version("3") + .create_pact + .create_released_version_for_consumer_version(currently_supported: false) + .create_consumer_version("4") + .create_pact + .create_released_version_for_consumer_version + .create_consumer_version("5") + .create_pact + end + + let(:view) { "environment" } + + let(:consumer_version_numbers) { subject.collect(&:consumer_version_number) } + + it "returns the currently deployed and released+supported pacts" do + expect(consumer_version_numbers).to eq ["4", "2"] + end + end + + context "when view == all" do + before do + td.create_environment("test") + .create_consumer("Foo") + .create_provider("Bar") + .create_consumer_version("1") + .create_pact + .create_deployed_version_for_consumer_version + .create_consumer_version("2") + .create_pact + .create_released_version_for_consumer_version + .create_consumer_version("3", branch: "main") + .create_pact + .create_consumer_version("4", branch: "main") + .create_pact + .create_consumer_version("5", tag_names: "dev") + .create_pact + .create_consumer_version("6", tag_names: "dev") + .create_pact + .create_consumer_version("7") + .create_pact + .create_consumer_version("8") + .create_pact + end + + let(:view) { "all" } + + let(:consumer_version_numbers) { subject.collect(&:consumer_version_number) } + + it "returns the latest for each branch, tag, deployed, released + overall latest" do + expect(consumer_version_numbers).to eq ["8", "6", "4", "2", "1"] + end + end + end + end + end +end