From b1214631d1a3da621cd9f4fbf0bda2bac27022b7 Mon Sep 17 00:00:00 2001 From: CatalinVoineag <11318084+CatalinVoineag@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:00:30 +0000 Subject: [PATCH] Filter and export support vendor api page We want to make the support vendor api page more usable. This commit adds a vendor filter to the page and a Download CSV button which will take the filtered results and put them into a CSV and adding all the users of each provider. --- .../api_tokens_controller.rb | 26 ++++++-- .../vendor_api_token_filter.rb | 46 ++++++++++++++ .../vendor_api_tokens_csv_export.rb | 43 +++++++++++++ .../api_tokens/index.html.erb | 56 +++++++++-------- .../vendor_api_token_filter_spec.rb | 63 +++++++++++++++++++ .../vendor_api_tokens_csv_export_spec.rb | 17 +++++ .../api_tokens/view_tokens_spec.rb | 42 ++++++++++++- 7 files changed, 260 insertions(+), 33 deletions(-) create mode 100644 app/models/support_interface/vendor_api_token_filter.rb create mode 100644 app/services/support_interface/vendor_api_tokens_csv_export.rb create mode 100644 spec/models/support_interface/vendor_api_token_filter_spec.rb create mode 100644 spec/services/support_interface/vendor_api_tokens_csv_export_spec.rb diff --git a/app/controllers/support_interface/api_tokens_controller.rb b/app/controllers/support_interface/api_tokens_controller.rb index 6c6e26e03a8..ba81263a451 100644 --- a/app/controllers/support_interface/api_tokens_controller.rb +++ b/app/controllers/support_interface/api_tokens_controller.rb @@ -1,11 +1,25 @@ module SupportInterface class APITokensController < SupportInterfaceController def index - @api_tokens = VendorAPIToken.order( - VendorAPIToken.arel_table[:last_used_at].desc.nulls_last, - created_at: :desc, - ) @api_tokens_last_3_months_count = VendorAPIToken.used_in_last_3_months.count + @filter = SupportInterface::VendorAPITokenFilter.new( + filter_params:, + ) + @pagy, @api_tokens = pagy(@filter.filtered_tokens) + + respond_to do |format| + format.csv do + send_data( + SupportInterface::VendorAPITokensCSVExport.call( + vendor_tokens: @filter.filtered_tokens, + ), + format: 'text/csv', + filename: "Providers with api tokens #{Date.current}.csv", + ) + end + + format.html + end end def new @@ -33,6 +47,10 @@ def destroy private + def filter_params + params.permit(vendor_ids: []) + end + def vendor_api_token_params params.expect(vendor_api_token: [:provider_id]) end diff --git a/app/models/support_interface/vendor_api_token_filter.rb b/app/models/support_interface/vendor_api_token_filter.rb new file mode 100644 index 00000000000..7ed972526c7 --- /dev/null +++ b/app/models/support_interface/vendor_api_token_filter.rb @@ -0,0 +1,46 @@ +module SupportInterface + class VendorAPITokenFilter + include FilterParamsHelper + + attr_reader :applied_filters + + def initialize(filter_params:) + @applied_filters = compact_params(filter_params) + end + + def filtered_tokens + scope = VendorAPIToken.order( + VendorAPIToken.arel_table[:last_used_at].desc.nulls_last, + created_at: :desc, + ) + + vendors_condition(scope) + end + + def filters + [ + { + type: :checkboxes, + heading: 'Vendors', + name: 'vendor_ids', + options: Vendor.all.map do |vendor| + { + value: vendor.id, + label: vendor.name, + checked: applied_filters[:vendor_ids]&.include?(vendor.id.to_s), + } + end, + }, + ] + end + + private + + def vendors_condition(scope) + return scope if applied_filters[:vendor_ids].blank? + + scope.left_joins(:provider) + .where(providers: { vendor_id: applied_filters[:vendor_ids] }) + end + end +end diff --git a/app/services/support_interface/vendor_api_tokens_csv_export.rb b/app/services/support_interface/vendor_api_tokens_csv_export.rb new file mode 100644 index 00000000000..d97015eea6c --- /dev/null +++ b/app/services/support_interface/vendor_api_tokens_csv_export.rb @@ -0,0 +1,43 @@ +module SupportInterface + class VendorAPITokensCSVExport + HEADERS = [ + 'Provider', + 'Vendor', + 'Tokens issued', + 'Provider user email addresses', + ].freeze + + attr_reader :vendor_tokens + + def initialize(vendor_tokens:) + @vendor_tokens = vendor_tokens + end + + def self.call(vendor_tokens:) + new(vendor_tokens:).call + end + + def call + providers = Provider.where(id: [vendor_tokens.pluck(:provider_id).uniq]) + + CSV.generate(headers: true) do |rows| + rows << HEADERS + + providers.each do |provider| + rows << generate_row(provider) + end + end + end + + private + + def generate_row(provider) + [ + provider.name, + provider.vendor_name, + provider.vendor_api_tokens.count, + provider.provider_users&.map(&:email_address)&.join(', '), + ] + end + end +end diff --git a/app/views/support_interface/api_tokens/index.html.erb b/app/views/support_interface/api_tokens/index.html.erb index b6e9223f5bd..386cf76a1bf 100644 --- a/app/views/support_interface/api_tokens/index.html.erb +++ b/app/views/support_interface/api_tokens/index.html.erb @@ -1,6 +1,7 @@ <%= render 'support_interface/providers/providers_navigation', title: 'API tokens' %> <%= govuk_button_link_to 'Add a token', new_support_interface_api_token_path %> +<%= govuk_button_link_to 'Download CSV', support_interface_api_tokens_path(params: @filter.applied_filters, format: :csv) %>
@@ -19,30 +20,33 @@
- - - - - - - - - - - +<%= render PaginatedFilterComponent.new(filter: @filter, collection: @api_tokens) do %> +
IDProviderVendorLast used atCreated atActions
+ + + + + + + + + + - - <% @api_tokens.each do |token| %> - - - - - - - - - <% end %> - -
IDProviderVendorLast used atCreated atActions
#<%= token.id %><%= token.provider.name %><%= token.provider.vendor_name %><%= token.last_used_at ? token.last_used_at.to_fs(:govuk_date_and_time) : 'Never' %><%= token.created_at.to_fs(:govuk_date_and_time) %> - <%= govuk_button_link_to 'Revoke', confirm_revocation_support_interface_api_token_path(token), warning: true %> -
+ + <% @api_tokens.each do |token| %> + + #<%= token.id %> + <%= token.provider.name %> + <%= token.provider.vendor_name %> + <%= token.last_used_at ? token.last_used_at.to_fs(:govuk_date_and_time) : 'Never' %> + <%= token.created_at.to_fs(:govuk_date_and_time) %> + + <%= govuk_button_link_to 'Revoke', confirm_revocation_support_interface_api_token_path(token), warning: true %> + + + <% end %> + + + <%= govuk_pagination(pagy: @pagy) %> +<% end %> diff --git a/spec/models/support_interface/vendor_api_token_filter_spec.rb b/spec/models/support_interface/vendor_api_token_filter_spec.rb new file mode 100644 index 00000000000..778fc950cb8 --- /dev/null +++ b/spec/models/support_interface/vendor_api_token_filter_spec.rb @@ -0,0 +1,63 @@ +require 'rails_helper' + +RSpec.describe SupportInterface::VendorAPITokenFilter do + describe '#filtered_tokens' do + it 'returns tokens for vendor 1 when filtering by vendor 1' do + vendor1 = create(:vendor, name: 'vendor1') + vendor2 = create(:vendor, name: 'vendor2') + provider1 = create(:provider, vendor: vendor1) + provider2 = create(:provider, vendor: vendor2) + token1 = create(:vendor_api_token, provider: provider1) + token2 = create(:vendor_api_token, provider: provider2) + + filter = described_class.new(filter_params: { vendor_ids: [vendor1.id] }) + + expect(filter.filtered_tokens).to eq [token1] + expect(filter.filtered_tokens).not_to eq [token2] + end + + it 'returns all tokens if there are no filters' do + vendor1 = create(:vendor, name: 'vendor1') + vendor2 = create(:vendor, name: 'vendor2') + provider1 = create(:provider, vendor: vendor1) + provider2 = create(:provider, vendor: vendor2) + token1 = create(:vendor_api_token, provider: provider1) + token2 = create(:vendor_api_token, provider: provider2) + + filter = described_class.new(filter_params: { vendor_ids: [] }) + + expect(filter.filtered_tokens).to eq [token1, token2] + end + end + + describe '#filters' do + it 'returns the filters hash' do + vendor1 = create(:vendor, name: 'vendor1') + vendor2 = create(:vendor, name: 'vendor2') + + filter = described_class.new(filter_params: {}) + + expect(filter.filters).to eq( + [ + { + type: :checkboxes, + heading: 'Vendors', + name: 'vendor_ids', + options: [ + { + value: vendor1.id, + label: vendor1.name, + checked: nil, + }, + { + value: vendor2.id, + label: vendor2.name, + checked: nil, + }, + ], + }, + ], + ) + end + end +end diff --git a/spec/services/support_interface/vendor_api_tokens_csv_export_spec.rb b/spec/services/support_interface/vendor_api_tokens_csv_export_spec.rb new file mode 100644 index 00000000000..7926a69d54c --- /dev/null +++ b/spec/services/support_interface/vendor_api_tokens_csv_export_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +RSpec.describe SupportInterface::VendorAPITokensCSVExport do + describe '.call' do + it 'generates a CSV for vendor api tokens' do + token = create(:vendor_api_token) + provider = token.provider + + export = described_class.call(vendor_tokens: [token]) + + expect(export).to eq( + "Provider,Vendor,Tokens issued,Provider user email addresses\n" \ + "#{provider.name},#{provider.vendor_name},#{provider.vendor_api_tokens.count},#{provider.provider_users&.map(&:email_address)&.join(', ')}\n", + ) + end + end +end diff --git a/spec/system/support_interface/api_tokens/view_tokens_spec.rb b/spec/system/support_interface/api_tokens/view_tokens_spec.rb index 8786261e17b..4fcecc8c358 100644 --- a/spec/system/support_interface/api_tokens/view_tokens_spec.rb +++ b/spec/system/support_interface/api_tokens/view_tokens_spec.rb @@ -8,6 +8,14 @@ and_api_tokens_exist when_i_visit_the_tokens_page then_i_see_the_count_of_providers_with_api_tokens + + when_i_filter_by_a_vendor + then_i_see_only_the_providers_for_a_specific_vendor + + when_i_filter_by_a_vendor + + when_i_click_download_csv + then_i_receive_a_csv_file end def given_i_am_signed_in @@ -15,9 +23,10 @@ def given_i_am_signed_in end def and_api_tokens_exist - vendor = create(:vendor, name: 'vendor_1') - provider_1 = create(:provider, name: 'Provider 1', vendor:) - provider_2 = create(:provider, name: 'Provider 2', vendor:) + vendor_1 = create(:vendor, name: 'vendor_1') + vendor_2 = create(:vendor, name: 'vendor_2') + provider_1 = create(:provider, name: 'Provider 1', vendor: vendor_1) + provider_2 = create(:provider, name: 'Provider 2', vendor: vendor_2) create(:vendor_api_token, provider: provider_1, last_used_at: 1.month.ago) create(:vendor_api_token, provider: provider_2) @@ -35,6 +44,33 @@ def then_i_see_the_count_of_providers_with_api_tokens expect(page).to have_content 'Provider 1' expect(page).to have_content 'Provider 2' expect(page).to have_content 'vendor_1' + expect(page).to have_content 'vendor_2' + end + end + + def when_i_filter_by_a_vendor + within('.moj-filter__options') do + check('vendor_1') end + + click_link_or_button 'Apply filters' + end + + def then_i_see_only_the_providers_for_a_specific_vendor + expect(page).to have_content '1 API tokens issued' + expect(page).to have_content '1 API tokens used in the last 3 months' + + within '.govuk-table' do + expect(page).to have_content 'Provider 1' + expect(page).to have_content 'vendor_1' + end + end + + def when_i_click_download_csv + click_link_or_button 'Download CSV' + end + + def then_i_receive_a_csv_file + expect(response_headers['Content-Type']).to eq 'text/csv' end end