From 10dda8ae0d98325de5aa894b0826aec3a5c85665 Mon Sep 17 00:00:00 2001 From: bangn Date: Sun, 5 Sep 2021 17:48:05 +1000 Subject: [PATCH] feat: Support deleting all pacts for a specific tag in the UI (#480) --- lib/pact_broker/domain/index_item.rb | 12 +- lib/pact_broker/ui/view_models/index_item.rb | 9 + .../ui/views/index/show-with-tags.haml | 9 +- public/javascripts/index.js | 163 ++++++++++++++---- .../ui/view_models/index_item_spec.rb | 10 ++ 5 files changed, 160 insertions(+), 43 deletions(-) diff --git a/lib/pact_broker/domain/index_item.rb b/lib/pact_broker/domain/index_item.rb index 32948a6a7..c08b39739 100644 --- a/lib/pact_broker/domain/index_item.rb +++ b/lib/pact_broker/domain/index_item.rb @@ -5,12 +5,12 @@ module PactBroker module Domain class IndexItem attr_reader :consumer, - :provider, - :latest_pact, - :latest_verification, - :webhooks, - :triggered_webhooks, - :latest_verification_latest_tags + :provider, + :latest_pact, + :latest_verification, + :webhooks, + :triggered_webhooks, + :latest_verification_latest_tags, # rubocop:disable Metrics/ParameterLists def self.create(consumer, provider, latest_pact, latest, latest_verification, webhooks = [], triggered_webhooks = [], tags = [], latest_verification_latest_tags = [], latest_for_branch = nil) diff --git a/lib/pact_broker/ui/view_models/index_item.rb b/lib/pact_broker/ui/view_models/index_item.rb index 98aea5ed2..48f63910f 100644 --- a/lib/pact_broker/ui/view_models/index_item.rb +++ b/lib/pact_broker/ui/view_models/index_item.rb @@ -206,6 +206,15 @@ def base_url @options[:base_url] end + def tagged_pacts + @relationship.tag_names.map do |tag| + { + tag: tag, + deletionUrl: PactBroker::Api::PactBrokerUrls.tagged_pact_versions_url(consumer_name, provider_name, tag, base_url) + }.to_json + end + end + private attr_reader :relationship diff --git a/lib/pact_broker/ui/views/index/show-with-tags.haml b/lib/pact_broker/ui/views/index/show-with-tags.haml index bbcb58d72..918858499 100644 --- a/lib/pact_broker/ui/views/index/show-with-tags.haml +++ b/lib/pact_broker/ui/views/index/show-with-tags.haml @@ -48,7 +48,11 @@ %tbody - index_items.each do | index_item | - %tr{'data-pact-versions-url': index_item.pact_versions_url, 'data-consumer-name': index_item.consumer_name, 'data-provider-name': index_item.provider_name, 'data-integration-url': index_item.integration_url} + %tr{'data-pact-versions-url': index_item.pact_versions_url, + 'data-consumer-name': index_item.consumer_name, + 'data-provider-name': index_item.provider_name, + 'data-integration-url': index_item.integration_url, + 'data-tagged-pacts': index_item.tagged_pacts} %td.consumer %a{:href => index_item.consumer_group_url } = index_item.consumer_name @@ -108,8 +112,7 @@ - if index_item.warning? %span.warning-icon{ 'aria-hidden': true } %td - - if index_item.show_settings? - %span.integration-settings.kebab-horizontal{ 'aria-hidden': true } + %span.integration-settings.kebab-horizontal{ 'aria-hidden': true } %div.pagination.text-center diff --git a/public/javascripts/index.js b/public/javascripts/index.js index 8fa4c0ada..ee06a3421 100644 --- a/public/javascripts/index.js +++ b/public/javascripts/index.js @@ -1,24 +1,13 @@ $(document).ready(function() { - $(".integration-settings") - .materialMenu("init", { + $(".integration-settings").click(function() { + const clickedElementData = $(this).closest("tr").data(); + $(this).materialMenu("init", { position: "overlay", animationSpeed: 1, - items: [ - { - type: "normal", - text: "Delete pacts ...", - click: handleDeletePactsSelected - }, - { - type: "normal", - text: "Delete integration...", - click: handleDeleteIntegrationsSelected - } - ] - }) - .click(function() { - $(this).materialMenu("open"); + items: buildMaterialMenuItems(clickedElementData) }); + $(this).materialMenu("open"); + }); }); function createPactDeletionConfirmationText(rowData) { @@ -35,6 +24,16 @@ function createIntegrationDeletionConfirmationText(rowData) { }, and all associated data (pacts, verifications, application versions, tags and webhooks) that are not associated with other integrations. Do you wish to continue?`; } +function createPactTagDeletionConfirmationText({ + providerName, + consumerName, + pactTagName +}) { + return `This will delete the pacts for provider ${providerName} and all versions of ${ + consumerName + } with tag ${pactTagName}. Do you wish to continue?`; +} + function handleDeletePactsSelected(clickedElement) { const tr = $(clickedElement).closest("tr"); const confirmationText = createPactDeletionConfirmationText(tr.data()); @@ -51,12 +50,47 @@ function handleDeleteIntegrationsSelected(clickedElement) { handleDeleteResourcesSelected(tr, tr.data().integrationUrl, confirmationText); } -function findRowsToBeDeleted(table, consumerName, providerName) { - return table - .children("tbody") - .find( - `[data-consumer-name="${consumerName}"][data-provider-name="${providerName}"]` +function handleDeleteTagSelected({ + providerName, + consumerName, + pactTagName, + deletionUrl +}) { + return function(clickedElement) { + const tr = $(clickedElement).closest("tr"); + const confirmationText = createPactTagDeletionConfirmationText({ + providerName, + consumerName, + pactTagName + }); + handleDeleteResourcesSelected( + tr, + deletionUrl, + confirmationText, + pactTagName ); + }; +} + +function findRowsToBeDeleted(table, consumerName, providerName, tagName) { + if (!tagName) { + return table + .children("tbody") + .find( + `[data-consumer-name="${consumerName}"][data-provider-name="${providerName}"]` + ); + } + + return table + .children("tbody") + .find("tr") + .find("td") + .filter(function() { + return $(this) + .text() + .includes(`tag: ${tagName}`); + }) + .closest("tr"); } function highlightRowsToBeDeleted(rows) { @@ -87,13 +121,20 @@ function confirmDeleteResources( }); } -function handleDeleteResourcesSelected(row, deletionUrl, confirmationText) { +function handleDeleteResourcesSelected( + row, + deletionUrl, + confirmationText, + tagName +) { const rowData = row.data(); const rows = findRowsToBeDeleted( row.closest("table"), rowData.consumerName, - rowData.providerName + rowData.providerName, + tagName ); + const isRefreshingThePage = !!tagName; const cancelled = function() { unHighlightRows(rows); }; @@ -101,14 +142,17 @@ function handleDeleteResourcesSelected(row, deletionUrl, confirmationText) { deleteResources( deletionUrl, function() { - handleDeletionSuccess(rows); + handleDeletionSuccess(rows, isRefreshingThePage); }, function(response) { handleDeletionFailure(rows, response); } ); }; - highlightRowsToBeDeleted(rows); + + if (!isRefreshingThePage) { + highlightRowsToBeDeleted(rows); + } confirmDeleteResources(confirmationText, confirmed, cancelled); } @@ -125,19 +169,34 @@ function hideDeletedRows(rows) { }); } -function handleDeletionSuccess(rows) { +function refreshPage() { + const url = new URL(window.location); + url.searchParams.delete("search"); + window.location = url.toString(); +} + +function handleDeletionSuccess(rows, isRefreshingThePage) { + if (isRefreshingThePage) { + return refreshPage(); + } + hideDeletedRows(rows); } function createErrorMessage(responseBody) { - if (responseBody && responseBody.error && responseBody.error.message && responseBody.error.reference) { - return `

Could not delete resources due to error: ${ - responseBody.error.message - }

Error reference: + if ( + responseBody && + responseBody.error && + responseBody.error.message && + responseBody.error.reference + ) { + return `

Could not delete resources due to error: ${responseBody.error.message}

Error reference: ${responseBody.error.reference}

`; } else if (responseBody) { - return `Could not delete resources due to error: ${JSON.stringify(responseBody)}`; + return `Could not delete resources due to error: ${JSON.stringify( + responseBody + )}`; } return "Could not delete resources."; @@ -159,11 +218,47 @@ function deleteResources(url, successCallback, errorCallback) { accepts: { text: "application/hal+json" }, - success: function(data, textStatus, jQxhr) { + success: function() { successCallback(); }, - error: function(jqXhr, textStatus, errorThrown) { + error: function(jqXhr) { errorCallback(jqXhr.responseJSON); } }); } + +function buildMaterialMenuItems(clickedElementData) { + const baseOptions = [ + { + type: "normal", + text: "Delete pacts ...", + click: handleDeletePactsSelected + }, + { + type: "normal", + text: "Delete integration...", + click: handleDeleteIntegrationsSelected + } + ]; + + const taggedPacts = clickedElementData.taggedPacts || []; + const providerName = clickedElementData.providerName; + const consumerName = clickedElementData.consumerName; + const taggedPactsOptions = taggedPacts.map(taggedPact => { + const taggedPactObject = JSON.parse(taggedPact); + const pactTagName = taggedPactObject.tag; + const taggedPactUrl = taggedPactObject.deletionUrl; + return { + type: "normal", + text: `Delete pacts for ${pactTagName}...`, + click: handleDeleteTagSelected({ + providerName, + consumerName, + pactTagName, + deletionUrl: taggedPactUrl + }) + }; + }); + + return [...baseOptions, ...taggedPactsOptions]; +} diff --git a/spec/lib/pact_broker/ui/view_models/index_item_spec.rb b/spec/lib/pact_broker/ui/view_models/index_item_spec.rb index aeed8f097..88491dbc6 100644 --- a/spec/lib/pact_broker/ui/view_models/index_item_spec.rb +++ b/spec/lib/pact_broker/ui/view_models/index_item_spec.rb @@ -135,6 +135,16 @@ module ViewDomain its(:provider_version_latest_tag_names) { is_expected.to eq ["dev", "prod"] } end + describe "tagged_pacts" do + let(:tags) { ["master", "prod"] } + its(:tagged_pacts) do + is_expected.to eq([ + { tag: "master", deletionUrl: "/pacts/provider/Provider%20Name/consumer/Consumer%20Name/tag/master" }.to_json, + { tag: "prod", deletionUrl: "/pacts/provider/Provider%20Name/consumer/Consumer%20Name/tag/prod" }.to_json + ]) + end + end + describe "<=>" do let(:relationship_model_4) { double("PactBroker::Domain::IndexItem", consumer_name: "A", provider_name: "X") }