Skip to content

Commit

Permalink
Merge pull request #10302 from DFE-Digital/cv/vendor-tokens-filter
Browse files Browse the repository at this point in the history
Filter and export support vendor api page
  • Loading branch information
elceebee authored Jan 27, 2025
2 parents 02cc3d8 + b121463 commit cd49641
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 33 deletions.
26 changes: 22 additions & 4 deletions app/controllers/support_interface/api_tokens_controller.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions app/models/support_interface/vendor_api_token_filter.rb
Original file line number Diff line number Diff line change
@@ -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
43 changes: 43 additions & 0 deletions app/services/support_interface/vendor_api_tokens_csv_export.rb
Original file line number Diff line number Diff line change
@@ -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
56 changes: 30 additions & 26 deletions app/views/support_interface/api_tokens/index.html.erb
Original file line number Diff line number Diff line change
@@ -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) %>

<div class="govuk-grid-row">
<div class="govuk-grid-column-one-half">
Expand All @@ -19,30 +20,33 @@
</div>
</div>

<table class='govuk-table'>
<thead class='govuk-table__head'>
<tr class='govuk-table__row'>
<th scope='col' class='govuk-table__header'>ID</th>
<th scope='col' class='govuk-table__header govuk-!-width-one-third'>Provider</th>
<th scope='col' class='govuk-table__header govuk-!-width-one-quarter'>Vendor</th>
<th scope='col' class='govuk-table__header govuk-!-width-one-quarter'>Last used at</th>
<th scope='col' class='govuk-table__header govuk-!-width-one-quarter'>Created at</th>
<th scope='col' class='govuk-table__header'>Actions</th>
</tr>
</thead>
<%= render PaginatedFilterComponent.new(filter: @filter, collection: @api_tokens) do %>
<table class='govuk-table'>
<thead class='govuk-table__head'>
<tr class='govuk-table__row'>
<th scope='col' class='govuk-table__header'>ID</th>
<th scope='col' class='govuk-table__header govuk-!-width-one-third'>Provider</th>
<th scope='col' class='govuk-table__header govuk-!-width-one-quarter'>Vendor</th>
<th scope='col' class='govuk-table__header govuk-!-width-one-quarter'>Last used at</th>
<th scope='col' class='govuk-table__header govuk-!-width-one-quarter'>Created at</th>
<th scope='col' class='govuk-table__header'>Actions</th>
</tr>
</thead>

<tbody class='govuk-table__body'>
<% @api_tokens.each do |token| %>
<tr class='govuk-table__row'>
<td class='govuk-table__cell'>#<%= token.id %></td>
<td class='govuk-table__cell'><%= token.provider.name %></td>
<td class='govuk-table__cell'><%= token.provider.vendor_name %></td>
<td class='govuk-table__cell'><%= token.last_used_at ? token.last_used_at.to_fs(:govuk_date_and_time) : 'Never' %></td>
<td class='govuk-table__cell'><%= token.created_at.to_fs(:govuk_date_and_time) %></td>
<td class='govuk-table__cell'>
<%= govuk_button_link_to 'Revoke', confirm_revocation_support_interface_api_token_path(token), warning: true %>
</td>
</tr>
<% end %>
</tbody>
</table>
<tbody class='govuk-table__body'>
<% @api_tokens.each do |token| %>
<tr class='govuk-table__row'>
<td class='govuk-table__cell'>#<%= token.id %></td>
<td class='govuk-table__cell'><%= token.provider.name %></td>
<td class='govuk-table__cell'><%= token.provider.vendor_name %></td>
<td class='govuk-table__cell'><%= token.last_used_at ? token.last_used_at.to_fs(:govuk_date_and_time) : 'Never' %></td>
<td class='govuk-table__cell'><%= token.created_at.to_fs(:govuk_date_and_time) %></td>
<td class='govuk-table__cell'>
<%= govuk_button_link_to 'Revoke', confirm_revocation_support_interface_api_token_path(token), warning: true %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= govuk_pagination(pagy: @pagy) %>
<% end %>
63 changes: 63 additions & 0 deletions spec/models/support_interface/vendor_api_token_filter_spec.rb
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
42 changes: 39 additions & 3 deletions spec/system/support_interface/api_tokens/view_tokens_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,25 @@
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
sign_in_as_support_user
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)
Expand All @@ -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

0 comments on commit cd49641

Please sign in to comment.