Skip to content

Commit

Permalink
Filter and export support vendor api page
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
CatalinVoineag committed Jan 24, 2025
1 parent 88e1e15 commit 6aaf9a6
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 6aaf9a6

Please sign in to comment.