Manny Dinssa
3 months ago
committed by
GitHub
50 changed files with 1149 additions and 108 deletions
@ -0,0 +1,37 @@
|
||||
<article class="app-log-summary"> |
||||
<div class="govuk-grid-row"> |
||||
<div class="govuk-grid-column-two-thirds"> |
||||
<header class="app-log-summary__header"> |
||||
<h2 class="govuk-heading-m govuk-!-font-weight-regular govuk-!-margin-bottom-0 text-normal-break "> |
||||
<span class="govuk-!-margin-right-1"><%= bulk_upload.filename %></span> |
||||
<span class="app-metadata app-log-summary__details" style="white-space: nowrap;"><%= bulk_upload.year %>/<%= bulk_upload.year + 1 %></span> |
||||
</h2> |
||||
</header> |
||||
<div class="govuk-!-margin-bottom-2"> |
||||
<p class="govuk-hint govuk-!-font-size-16 govuk-!-margin-bottom-1">Uploaded by: <%= bulk_upload.user.name %> (<%= bulk_upload.user.email %>)</p> |
||||
<p class="govuk-hint govuk-!-font-size-16 govuk-!-margin-bottom-1">Uploading organisation: <%= bulk_upload.organisation.name %></p> |
||||
<p class="govuk-hint govuk-!-font-size-16 govuk-!-margin-bottom-1">Time of upload: <%= bulk_upload.created_at.to_formatted_s(:govuk_date_and_time) %></p> |
||||
</div> |
||||
<p class="govuk-body govuk-!-margin-bottom-3"> |
||||
<%= download_file_link(bulk_upload) %> |
||||
<%= view_error_report_link(bulk_upload) %> |
||||
<%= view_logs_link(bulk_upload) %> |
||||
</p> |
||||
</div> |
||||
<footer class="govuk-grid-column-one-third app-log-summary__footer"> |
||||
<p class="govuk-body govuk-!-margin-bottom-3"> |
||||
<%= upload_status %> |
||||
</p> |
||||
<% unless bulk_upload.processing %> |
||||
<div> |
||||
<%= counts( |
||||
[bulk_upload.total_logs_count, "total log"], |
||||
[setup_errors_count, "error on important questions", "errors on important questions"], |
||||
[critical_errors_count, "critical error"], |
||||
[potential_errors_count, "potential error"], |
||||
) %> |
||||
</div> |
||||
<% end %> |
||||
</footer> |
||||
</div> |
||||
</article> |
@ -0,0 +1,72 @@
|
||||
class BulkUploadSummaryComponent < ViewComponent::Base |
||||
attr_reader :bulk_upload |
||||
|
||||
def initialize(bulk_upload:) |
||||
@bulk_upload = bulk_upload |
||||
@bulk_upload_errors = bulk_upload.bulk_upload_errors |
||||
super |
||||
end |
||||
|
||||
def upload_status |
||||
helpers.status_tag(bulk_upload.status, ["app-tag--small govuk-!-font-weight-regular no-max-width"]) |
||||
end |
||||
|
||||
def setup_errors_count |
||||
@bulk_upload_errors.where(category: "setup").count |
||||
end |
||||
|
||||
def critical_errors_count |
||||
@bulk_upload_errors.where(category: [nil, "", "not_answered"]).count |
||||
end |
||||
|
||||
def potential_errors_count |
||||
@bulk_upload_errors.where(category: "soft_validation").count |
||||
end |
||||
|
||||
def formatted_count_text(count, singular_text, plural_text = nil) |
||||
return if count.nil? || count <= 0 |
||||
|
||||
text = count > 1 ? (plural_text || singular_text.pluralize(count)) : singular_text |
||||
content_tag(:p, class: "govuk-!-font-size-16 govuk-!-margin-bottom-1") do |
||||
concat(content_tag(:strong, count)) |
||||
concat(" #{text}") |
||||
end |
||||
end |
||||
|
||||
def counts(*counts_with_texts) |
||||
counts_with_texts.map { |count, singular_text, plural_text| |
||||
formatted_count_text(count, singular_text, plural_text) if count.present? |
||||
}.compact.join("").html_safe |
||||
end |
||||
|
||||
def download_file_link(bulk_upload) |
||||
send("download_#{bulk_upload.log_type}_file_link", bulk_upload) |
||||
end |
||||
|
||||
def download_lettings_file_link(bulk_upload) |
||||
govuk_link_to "Download file", download_lettings_bulk_upload_path(bulk_upload), class: "govuk-link govuk-!-margin-right-2" |
||||
end |
||||
|
||||
def download_sales_file_link(bulk_upload) |
||||
govuk_link_to "Download file", download_sales_bulk_upload_path(bulk_upload), class: "govuk-link govuk-!-margin-right-2" |
||||
end |
||||
|
||||
def view_error_report_link(bulk_upload) |
||||
status = bulk_upload.status.to_s |
||||
return unless %w[important_errors critical_errors potential_errors].include?(status) |
||||
|
||||
path = if status == "important_errors" |
||||
"summary_bulk_upload_#{bulk_upload.log_type}_result_url" |
||||
else |
||||
"bulk_upload_#{bulk_upload.log_type}_result_path" |
||||
end |
||||
|
||||
govuk_link_to "View error report", send(path, bulk_upload), class: "govuk-link" |
||||
end |
||||
|
||||
def view_logs_link(bulk_upload) |
||||
return unless bulk_upload.status.to_s == "logs_uploaded_with_errors" |
||||
|
||||
govuk_link_to "View logs with errors", send("#{bulk_upload.log_type}_logs_path", bulk_upload_id: [bulk_upload.id]), class: "govuk-link" |
||||
end |
||||
end |
@ -1,10 +1,11 @@
|
||||
<% if display_actions? %> |
||||
<div class="govuk-button-group app-filter-toggle govuk-!-margin-bottom-6"> |
||||
<% if create_button_href.present? %> |
||||
<%= govuk_button_to create_button_copy, create_button_href, class: "govuk-!-margin-right-6" %> |
||||
<%= govuk_button_to create_button_copy, create_button_href, class: "govuk-!-margin-right-6" %> |
||||
<% unless user.support? %> |
||||
<%= govuk_button_link_to upload_button_copy, upload_button_href, secondary: true %> |
||||
<% end %> |
||||
<% if upload_button_href.present? && !user.support? %> |
||||
<%= govuk_button_link_to upload_button_copy, upload_button_href, secondary: true %> |
||||
<% if user.support? %> |
||||
<%= govuk_button_link_to view_uploads_button_copy, view_uploads_button_href, secondary: true %> |
||||
<% end %> |
||||
</div> |
||||
<% end %> |
||||
|
@ -0,0 +1,18 @@
|
||||
.grouped-rows td { |
||||
border-top: none; |
||||
border-bottom: none; |
||||
} |
||||
|
||||
.grouped-rows.first-row td { |
||||
border-top: 1px solid #b1b4b6; |
||||
} |
||||
|
||||
.grouped-rows.last-row td, |
||||
.grouped-rows .grouped-multirow-cell { |
||||
border-bottom: 1px solid #b1b4b6; |
||||
} |
||||
|
||||
.text-normal-break { |
||||
white-space: normal; |
||||
word-break: break-all; |
||||
} |
@ -0,0 +1,12 @@
|
||||
module BulkUploadHelper |
||||
def bulk_upload_title(controller_name) |
||||
case controller_name |
||||
when "lettings_logs" |
||||
"Lettings bulk uploads" |
||||
when "sales_logs" |
||||
"Sales bulk uploads" |
||||
else |
||||
"Bulk uploads" |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,78 @@
|
||||
<div class="app-filter-layout__filter"> |
||||
<div class="app-filter"> |
||||
<div class="app-filter__header"> |
||||
<h2 class="govuk-heading-m">Filters</h2> |
||||
</div> |
||||
|
||||
<div class="app-filter__content"> |
||||
<%= form_with html: { method: :get } do |f| %> |
||||
|
||||
<div class="govuk-grid-row" style="white-space: nowrap"> |
||||
<p class="govuk-grid-column-one-half"> |
||||
<%= filters_applied_text(@filter_type) %> |
||||
</p> |
||||
<p class="govuk-!-text-align-right govuk-grid-column-one-half"> |
||||
<%= reset_filters_link(@filter_type, { search: request.params["search"] }.compact) %> |
||||
</p> |
||||
</div> |
||||
|
||||
<%= render partial: "filters/checkbox_filter", |
||||
locals: { |
||||
f:, |
||||
options: collection_year_options, |
||||
label: "Collection year", |
||||
category: "years", |
||||
size: "s", |
||||
} %> |
||||
|
||||
<%= render partial: "filters/radio_filter", |
||||
locals: { |
||||
f:, |
||||
options: { |
||||
"all": { label: "Any user" }, |
||||
"you": { label: "You" }, |
||||
"specific_user": { |
||||
label: "Specific user", |
||||
conditional_filter: { |
||||
type: "text_select", |
||||
label: "User", |
||||
category: "user", |
||||
options: uploaded_by_filter_options, |
||||
caption_text: "User's name or email", |
||||
}, |
||||
}, |
||||
}, |
||||
label: "Uploaded by", |
||||
category: "uploaded_by", |
||||
size: "s", |
||||
} %> |
||||
|
||||
<%= render partial: "filters/radio_filter", locals: { |
||||
f:, |
||||
options: { |
||||
"all": { label: "Any organisation" }, |
||||
"specific_org": { |
||||
label: "Specific organisation", |
||||
conditional_filter: { |
||||
type: "select", |
||||
label: "Uploading Organisation", |
||||
category: "uploading_organisation", |
||||
options: all_owning_organisation_filter_options(current_user), |
||||
caption_text: "Organisation name", |
||||
}, |
||||
}, |
||||
}, |
||||
label: "Uploading organisation", |
||||
category: "uploading_organisation_select", |
||||
size: "s", |
||||
} %> |
||||
|
||||
<% if request.params["search"].present? %> |
||||
<%= f.hidden_field :search, value: request.params["search"] %> |
||||
<% end %> |
||||
|
||||
<%= f.govuk_submit "Apply filters", class: "govuk-!-margin-bottom-0" %> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,15 @@
|
||||
<h2 class="govuk-body"> |
||||
<div class="govuk-grid-row app-search__caption"> |
||||
<div class="govuk-grid-column-three-quarters"> |
||||
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "files uploaded in the last 30 days", filters_count: applied_filters_count(@filter_type))) %> |
||||
</div> |
||||
<div class="govuk-grid-column-one-quarter govuk-!-text-align-right"> |
||||
<% if searched || applied_filters_count(@filter_type).positive? %> |
||||
<br> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</h2> |
||||
<% bulk_uploads.map do |bulk_upload| %> |
||||
<%= render BulkUploadSummaryComponent.new(bulk_upload:) %> |
||||
<% end %> |
@ -0,0 +1,28 @@
|
||||
<% item_label = format_label(@pagy.count, "uploads") %> |
||||
<% title = format_title(@searched, bulk_upload_title(controller.controller_name), current_user, item_label, @pagy.count, nil) %> |
||||
|
||||
<% content_for :title, title %> |
||||
|
||||
<h1 class="govuk-heading-l govuk-!-margin-bottom-7"> |
||||
<%= bulk_upload_title(controller.controller_name) %> |
||||
</h1> |
||||
|
||||
<div class="app-filter-layout" data-controller="filter-layout"> |
||||
<%= render partial: "bulk_upload_shared/upload_filters" %> |
||||
|
||||
<div class="app-filter-layout__content"> |
||||
<%= render SearchComponent.new(current_user:, search_label: "Search by file name, user's name or email, or organisation", value: @searched) %> |
||||
<%= govuk_section_break(visible: true, size: "m") %> |
||||
<%= render partial: "bulk_upload_shared/upload_list", |
||||
locals: { |
||||
bulk_uploads: @bulk_uploads, |
||||
title: "Bulk uploads", |
||||
pagy: @pagy, |
||||
searched: @searched, |
||||
item_label:, |
||||
total_count: @total_count, |
||||
filter_type: @filter_type, |
||||
} %> |
||||
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "bulk uploads" } %> |
||||
</div> |
||||
</div> |
@ -1,12 +1,14 @@
|
||||
<div class="govuk-button-group app-filter-toggle"> |
||||
<% if @organisation.data_protection_confirmed? %> |
||||
<% if current_page?(controller: 'organisations', action: 'lettings_logs') %> |
||||
<%= govuk_button_to "Create a new lettings log for this organisation", lettings_logs_path(lettings_log: { owning_organisation_id: @organisation.id }, method: :post), class: "govuk-!-margin-right-6" %> |
||||
<%= govuk_button_link_to "Upload lettings logs in bulk", bulk_upload_lettings_log_path(id: "start", organisation_id: @organisation.id), secondary: true %> |
||||
<%= govuk_button_to "Create a new lettings log", lettings_logs_path(lettings_log: { owning_organisation_id: @organisation.id }, method: :post), class: "govuk-!-margin-right-6" %> |
||||
<%= govuk_button_link_to "Upload lettings logs in bulk", bulk_upload_lettings_log_path(id: "start", organisation_id: @organisation.id), secondary: true, class: "govuk-!-margin-right-6" %> |
||||
<%= govuk_button_link_to "View lettings bulk uploads", bulk_uploads_lettings_logs_path(organisation_id: @organisation.id, clear_old_filters: true), secondary: true %> |
||||
<% end %> |
||||
<% if current_page?(controller: 'organisations', action: 'sales_logs') %> |
||||
<%= govuk_button_to "Create a new sales log for this organisation", sales_logs_path(sales_log: { owning_organisation_id: @organisation.id }, method: :post), class: "govuk-!-margin-right-6" %> |
||||
<%= govuk_button_link_to "Upload sales logs in bulk", bulk_upload_sales_log_path(id: "start", organisation_id: @organisation.id), secondary: true %> |
||||
<%= govuk_button_to "Create a new sales log", sales_logs_path(sales_log: { owning_organisation_id: @organisation.id }, method: :post), class: "govuk-!-margin-right-6" %> |
||||
<%= govuk_button_link_to "Upload sales logs in bulk", bulk_upload_sales_log_path(id: "start", organisation_id: @organisation.id), secondary: true, class: "govuk-!-margin-right-6" %> |
||||
<%= govuk_button_link_to "View sales bulk uploads", bulk_uploads_sales_logs_path(organisation_id: @organisation.id, clear_old_filters: true), secondary: true %> |
||||
<% end %> |
||||
<% end %> |
||||
</div> |
||||
|
@ -0,0 +1,8 @@
|
||||
class AddFailureReasonAndProcessingToBulkUploads < ActiveRecord::Migration[7.0] |
||||
def change |
||||
change_table :bulk_uploads, bulk: true do |t| |
||||
t.string :failure_reason |
||||
t.boolean :processing |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,148 @@
|
||||
require "rails_helper" |
||||
|
||||
RSpec.describe BulkUploadSummaryComponent, type: :component do |
||||
let(:user) { create(:user) } |
||||
let(:support_user) { create(:user, :support) } |
||||
let(:bulk_upload) { create(:bulk_upload, :lettings, user:, year: 2024, total_logs_count: 10) } |
||||
|
||||
it "shows the file name" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content(bulk_upload.filename) |
||||
end |
||||
|
||||
it "shows the collection year" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content("2024/2025") |
||||
end |
||||
|
||||
it "includes a download file link" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_link("Download file", href: "/lettings-logs/bulk-uploads/#{bulk_upload.id}/download") |
||||
end |
||||
|
||||
it "shows the total log count" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content("10 total logs") |
||||
end |
||||
|
||||
it "shows the uploaded by user" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content("Uploaded by: #{bulk_upload.user.name}") |
||||
end |
||||
|
||||
it "shows the uploading organisation" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content("Uploading organisation: #{bulk_upload.user.organisation.name}") |
||||
end |
||||
|
||||
it "shows the time of upload" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content("Time of upload: #{bulk_upload.created_at.to_formatted_s(:govuk_date_and_time)}") |
||||
end |
||||
|
||||
context "when bulk upload has only critical errors" do |
||||
let(:bulk_upload_errors) { create_list(:bulk_upload_error, 2, category: nil) } |
||||
let(:bulk_upload) { create(:bulk_upload, :lettings, user:, bulk_upload_errors:, total_logs_count: 10) } |
||||
|
||||
it "shows the critical errors status and error count" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content("Critical errors in CSV") |
||||
expect(result).to have_content("2 critical errors") |
||||
expect(result).to have_no_content("errors on important") |
||||
expect(result).to have_no_content("potential") |
||||
end |
||||
|
||||
it "includes a view error report link" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_link("View error report", href: "/lettings-logs/bulk-upload-results/#{bulk_upload.id}") |
||||
end |
||||
end |
||||
|
||||
context "when bulk upload has only potential errors" do |
||||
let(:bulk_upload_errors) { create_list(:bulk_upload_error, 2, category: "soft_validation") } |
||||
let(:bulk_upload) { create(:bulk_upload, :lettings, user:, bulk_upload_errors:, total_logs_count: 16) } |
||||
|
||||
it "shows the potential errors status and error count" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content("Potential errors in CSV") |
||||
expect(result).to have_content("2 potential errors") |
||||
expect(result).to have_content("16 total logs") |
||||
expect(result).to have_no_content("errors on important") |
||||
expect(result).to have_no_content("critical") |
||||
end |
||||
end |
||||
|
||||
context "when bulk upload has only errors on important questions" do |
||||
let(:bulk_upload_errors) { create_list(:bulk_upload_error, 2, category: "setup") } |
||||
let(:bulk_upload) { create(:bulk_upload, :lettings, user:, bulk_upload_errors:, total_logs_count: 16) } |
||||
|
||||
it "shows the errors on important questions status and error count" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content("Errors on important questions in CSV") |
||||
expect(result).to have_content("2 errors on important questions") |
||||
expect(result).to have_content("16 total logs") |
||||
expect(result).to have_no_content("potential") |
||||
expect(result).to have_no_content("critical") |
||||
end |
||||
|
||||
it "includes a view error report link to the summary page" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_link("View error report", href: %r{.*/lettings-logs/bulk-upload-results/#{bulk_upload.id}/summary}) |
||||
end |
||||
end |
||||
|
||||
context "when a bulk upload is uploaded with no errors" do |
||||
let(:bulk_upload) { create(:bulk_upload, :sales, user:, total_logs_count: 1) } |
||||
|
||||
it "shows the logs uploaded with no errors status and no error counts" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content("Logs uploaded with no errors") |
||||
expect(result).to have_content("1 total log") |
||||
expect(result).to have_no_content("important questions") |
||||
expect(result).to have_no_content("potential") |
||||
expect(result).to have_no_content("critical") |
||||
end |
||||
end |
||||
|
||||
context "when a bulk upload is uploaded with errors" do |
||||
let(:bulk_upload_errors) { create_list(:bulk_upload_error, 1) } |
||||
let(:bulk_upload) { create(:bulk_upload, :sales, user:, bulk_upload_errors:, total_logs_count: 21) } |
||||
|
||||
before do |
||||
create_list(:sales_log, 21, bulk_upload:) |
||||
end |
||||
|
||||
it "shows the logs upload with errors status and error count" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content("Logs uploaded with errors") |
||||
expect(result).to have_content("21 total logs") |
||||
expect(result).to have_content("1 critical error") |
||||
expect(result).to have_no_content("important questions") |
||||
expect(result).to have_no_content("potential") |
||||
end |
||||
end |
||||
|
||||
context "when a bulk upload uses the wrong template" do |
||||
let(:bulk_upload) { create(:bulk_upload, :sales, user:, failure_reason: "wrong_template") } |
||||
|
||||
it "shows the wrong template status and no error counts" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content("Wrong template") |
||||
expect(result).to have_no_content("important questions") |
||||
expect(result).to have_no_content("potential") |
||||
expect(result).to have_no_content("critical") |
||||
end |
||||
end |
||||
|
||||
context "when a bulk upload uses a blank template" do |
||||
let(:bulk_upload) { create(:bulk_upload, :sales, user:, failure_reason: "blank_template") } |
||||
|
||||
it "shows the wrong template status and no error counts" do |
||||
result = render_inline(described_class.new(bulk_upload:)) |
||||
expect(result).to have_content("Blank template") |
||||
expect(result).to have_no_content("important questions") |
||||
expect(result).to have_no_content("potential") |
||||
expect(result).to have_no_content("critical") |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue