Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter and export support vendor api page #10302

Merged
merged 1 commit into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading