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) %>
-
-
-
-
-
-
-
-
-
-
-
+<%= render PaginatedFilterComponent.new(filter: @filter, collection: @api_tokens) do %>
+
+
+
+
+
+
+
+
+
+
+
-
- <% @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 %>
-
-
+
+ <% @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