Browse Source

CLDC-3816 Update rails admin styling (#2879)

* Update rails admin console layout

* Update dashboard and view tables

* lint

* More lint

* Inline actions

* Re-add filters

* Update buttons

* Update validation documentation generating (#2894)

* Update documentation generator to use relevant translation files

* Add and use collection_year instead of from/to in log validations
ui-demo^2
kosiakkatrina 4 days ago committed by GitHub
parent
commit
d8fa3cf7fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 34
      app/frontend/styles/_custom-rails-admin.scss
  2. 1
      app/frontend/styles/application.scss
  3. 58
      app/services/documentation_generator.rb
  4. 9
      app/views/layouts/rails_admin/_navigation.html.erb
  5. 70
      app/views/layouts/rails_admin/application.html.erb
  6. 25
      app/views/rails_admin/main/_submit_buttons.html.erb
  7. 68
      app/views/rails_admin/main/dashboard.html.erb
  8. 21
      app/views/rails_admin/main/delete.html.erb
  9. 187
      app/views/rails_admin/main/index.html.erb
  10. 2
      config/initializers/assets.rb
  11. 0
      config/locales/validations/lettings/financial.en.yml
  12. 5
      db/migrate/20250110150609_add_collection_year.rb
  13. 3
      db/schema.rb
  14. 1
      lib/tasks/generate_lettings_documentation.rake
  15. 6
      lib/tasks/set_log_validation_collection_year.rake
  16. 29
      spec/lib/tasks/set_log_validation_collection_year_spec.rb
  17. 12
      spec/services/documentation_generator_spec.rb

34
app/frontend/styles/_custom-rails-admin.scss

@ -0,0 +1,34 @@
.rails-admin-sidescroll {
overflow-x: scroll;
}
.rails-admin-description_field {
min-width: 500px;
}
.rails-admin-case_field {
min-width: 500px;
}
.rails-admin-error_message_field {
min-width: 500px;
}
.rails-admin-actions {
min-width: 160px;
ul {
float: right;
}
}
.rails-admin-filters-box {
.filter {
// stylelint-disable-next-line declaration-no-important
display: flex !important;
}
button {
min-width: 20%;
}
}

1
app/frontend/styles/application.scss

@ -48,6 +48,7 @@ $govuk-breakpoints: (
@import "search"; @import "search";
@import "sub-navigation"; @import "sub-navigation";
@import "unread-notification"; @import "unread-notification";
@import "custom-rails-admin";
// App utilities // App utilities
.app-\!-colour-muted { .app-\!-colour-muted {

58
app/services/documentation_generator.rb

@ -18,14 +18,16 @@ class DocumentationGenerator
next next
end end
validation_source = method(meth).source validation = method(meth)
validation_source = validation.source
file_path = validation.source_location[0]
helper_methods_source = all_helper_methods.map { |helper_method| helper_methods_source = all_helper_methods.map { |helper_method|
if validation_source.include?(helper_method.to_s) if validation_source.include?(helper_method.to_s)
method(helper_method).source method(helper_method).source
end end
}.compact.join("\n") }.compact.join("\n")
response = describe_hard_validation(client, meth, validation_source, helper_methods_source, form) response = describe_hard_validation(client, meth, validation_source, helper_methods_source, form, file_path)
next unless response next unless response
begin begin
@ -41,19 +43,19 @@ class DocumentationGenerator
def describe_bu_validations(client, form, row_parser_class, all_validation_methods, all_helper_methods, field_mapping_for_errors, log_type) def describe_bu_validations(client, form, row_parser_class, all_validation_methods, all_helper_methods, field_mapping_for_errors, log_type)
all_validation_methods.each do |meth| all_validation_methods.each do |meth|
if LogValidation.where(validation_name: meth.to_s, bulk_upload_specific: true, from: form.start_date, log_type:).exists? if LogValidation.where(validation_name: meth.to_s, bulk_upload_specific: true, collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}", log_type:).exists?
Rails.logger.info("Validation #{meth} already exists for #{form.start_date.year}") Rails.logger.info("Validation #{meth} already exists for #{form.start_date.year}")
next next
end end
validation = row_parser_class.instance_method(meth)
validation_source = row_parser_class.instance_method(meth).source validation_source = validation.source
helper_methods_source = all_helper_methods.map { |helper_method| helper_methods_source = all_helper_methods.map { |helper_method|
if validation_source.include?(helper_method.to_s) if validation_source.include?(helper_method.to_s)
row_parser_class.instance_method(helper_method).source row_parser_class.instance_method(helper_method).source
end end
}.compact.join("\n") }.compact.join("\n")
response = describe_hard_validation(client, meth, validation_source, helper_methods_source, form) response = describe_hard_validation(client, meth, validation_source, helper_methods_source, form, validation.source_location[0])
next unless response next unless response
begin begin
@ -69,7 +71,7 @@ class DocumentationGenerator
def describe_soft_validations(client, all_validation_methods, all_helper_methods, log_type) def describe_soft_validations(client, all_validation_methods, all_helper_methods, log_type)
validation_descriptions = {} validation_descriptions = {}
all_validation_methods.each do |meth| all_validation_methods[0..5].each do |meth|
validation_source = method(meth).source validation_source = method(meth).source
helper_methods_source = all_helper_methods.map { |helper_method| helper_methods_source = all_helper_methods.map { |helper_method|
if validation_source.include?(helper_method.to_s) if validation_source.include?(helper_method.to_s)
@ -101,8 +103,8 @@ class DocumentationGenerator
private private
def describe_hard_validation(client, meth, validation_source, helper_methods_source, form) def describe_hard_validation(client, meth, validation_source, helper_methods_source, form, file_path)
en_yml = File.read("./config/locales/en.yml") en_yml = File.read(translation_file_path(form, file_path))
begin begin
client.chat( client.chat(
@ -166,14 +168,6 @@ private
required: %w[error_message field], required: %w[error_message field],
}, },
}, },
from: {
type: :number,
description: "the year from which the validation starts. If this validation runs for logs with a startdate after a certain year, specify that year here, only if it is not specified in the validation method, leave this field blank",
},
to: {
type: :number,
description: "the year in which the validation ends. If this validation runs for logs with a startdate before a certain year, specify that year here, only if it is not specified in the validation method, leave this field blank",
},
validation_type: { validation_type: {
type: :string, type: :string,
enum: %w[presence format minimum maximum range inclusion length other], enum: %w[presence format minimum maximum range inclusion length other],
@ -271,8 +265,7 @@ Look at these helper methods where needed to understand what is being checked in
error_message: error["error_message"], error_message: error["error_message"],
case: case_info["case_description"], case: case_info["case_description"],
section: form.get_question(error["field"], nil)&.subsection&.id, section: form.get_question(error["field"], nil)&.subsection&.id,
from: case_info["from"] || "", collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}",
to: case_info["to"] || "",
validation_type: case_info["validation_type"], validation_type: case_info["validation_type"],
hard_soft: "hard", hard_soft: "hard",
other_validated_models: case_info["other_validated_models"]) other_validated_models: case_info["other_validated_models"])
@ -295,8 +288,7 @@ Look at these helper methods where needed to understand what is being checked in
error_message: error["error_message"], error_message: error["error_message"],
case: case_info["case_description"], case: case_info["case_description"],
section: form.get_question(error_field, nil)&.subsection&.id, section: form.get_question(error_field, nil)&.subsection&.id,
from: form.start_date, collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}",
to: form.start_date + 1.year,
validation_type: case_info["validation_type"], validation_type: case_info["validation_type"],
hard_soft: "hard", hard_soft: "hard",
other_validated_models: case_info["other_validated_models"], other_validated_models: case_info["other_validated_models"],
@ -333,7 +325,7 @@ Look at these helper methods where needed to understand what is being checked in
return return
end end
if LogValidation.where(validation_name: validation_depends_on_hash.keys.first, field: page_the_validation_applied_to.questions.first.id, from: form.start_date, log_type:).exists? if LogValidation.where(validation_name: validation_depends_on_hash.keys.first, field: page_the_validation_applied_to.questions.first.id, collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}", log_type:).exists?
Rails.logger.info("Validation #{validation_depends_on_hash.keys.first} already exists for #{page_the_validation_applied_to.questions.first.id} for start year #{form.start_date.year}") Rails.logger.info("Validation #{validation_depends_on_hash.keys.first} already exists for #{page_the_validation_applied_to.questions.first.id} for start year #{form.start_date.year}")
return return
end end
@ -360,12 +352,30 @@ Look at these helper methods where needed to understand what is being checked in
error_message:, error_message:,
case: case_info, case: case_info,
section: form.get_question(page_the_validation_applied_to.questions.first.id, nil)&.subsection&.id, section: form.get_question(page_the_validation_applied_to.questions.first.id, nil)&.subsection&.id,
from: form.start_date, collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}",
to: form.start_date + 1.year,
validation_type: result["validation_type"], validation_type: result["validation_type"],
hard_soft: "soft", hard_soft: "soft",
other_validated_models: result["other_validated_models"]) other_validated_models: result["other_validated_models"])
Rails.logger.info("******** described #{validation_depends_on_hash.keys.first} for #{page_the_validation_applied_to.questions.first.id} ********") Rails.logger.info("******** described #{validation_depends_on_hash.keys.first} for #{page_the_validation_applied_to.questions.first.id} ********")
end end
TRANSLATION_FILE_MAPPINGS = {
"property" => "property_information",
}.freeze
def translation_file_path(form, file_path)
return "./config/locales/validations/#{form.type}/#{form.start_date.year}/bulk_upload.en.yml" if file_path.include?("bulk_upload")
file_name = file_path.split("/").last.gsub("_validations.rb", "")
translation_file_name = TRANSLATION_FILE_MAPPINGS[file_name] || file_name
file_path = "./config/locales/validations/#{form.type}/#{translation_file_name}.en.yml"
return file_path if File.exist?(file_path)
shared_file_path = "./config/locales/validations/#{translation_file_name}.en.yml"
return shared_file_path if File.exist?(shared_file_path)
"./config/locales/en.yml"
end
end end

9
app/views/layouts/rails_admin/_navigation.html.erb

@ -0,0 +1,9 @@
<%= govuk_header(
classes: "app-header app-header--orange",
homepage_url: Rails.application.routes.url_helpers.root_path,
navigation_classes: "govuk-header__navigation--end",
) do |component|
component.with_product_name(name: t("service_name"))
component.with_navigation_item(text: "Your account", href: Rails.application.routes.url_helpers.account_path)
component.with_navigation_item(text: "Sign out", href: Rails.application.routes.url_helpers.destroy_user_session_path)
end %>

70
app/views/layouts/rails_admin/application.html.erb

@ -0,0 +1,70 @@
</html><!DOCTYPE html>
<html lang="en" class="govuk-template">
<head>
<title>Admin</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= tag.meta name: "viewport", content: "width=device-width, initial-scale=1" %>
<%= tag.meta property: "og:image", content: asset_path("images/govuk-opengraph-image.png") %>
<%= tag.meta name: "theme-color", content: "#0b0c0c" %>
<%= favicon_link_tag asset_path("images/favicon.ico"), type: nil, sizes: "48x48" %>
<%= favicon_link_tag asset_path("images/favicon.svg"), type: "image/svg+xml", sizes: "any" %>
<%= favicon_link_tag asset_path("images/govuk-icon-mask.svg"), rel: "mask-icon", color: "#0b0c0c", type: nil %>
<%= favicon_link_tag asset_path("images/govuk-icon-180.png"), rel: "apple-touch-icon", type: nil %>
<%= stylesheet_link_tag "application" %>
<%= javascript_include_tag "vendor/html5shiv.min.js" %>
<script>
window.html5.elements = "output";
html5.shivDocument(document);
</script>
<%= javascript_include_tag "vendor/polyfill-output-value.js" %>
<%= javascript_include_tag "vendor/outerHTML.js" %>
<%= javascript_include_tag "application", defer: true %>
<% if content_for?(:head) %>
<%= yield(:head) %>
<% end %>
<%= capybara_lockstep if defined?(Capybara::Lockstep) %>
<% if Rails.env.development? %>
<script>
console.log(<%= session.to_json.html_safe %>)
</script>
<% end %>
<%= render "layouts/rails_admin/head" %>
</head>
<body class="govuk-template__body app-template--wide">
<script>
document.body.className += " js-enabled" + ("noModule" in HTMLScriptElement.prototype ? " govuk-frontend-supported" : "");
</script>
<div data-i18n-options="<%= I18n.t("admin.js").to_json %>" id="admin-js"></div>
<div class="badge bg-warning" id="loading" style="display:none; position:fixed; right:20px; bottom:20px; z-index:100000">
<%= t("admin.loading") %>
</div>
<%= govuk_skip_link %>
<%= render "layouts/rails_admin/navigation" %>
<% feedback_link = govuk_link_to "giving us your feedback (opens in a new tab)", t("feedback_form"), rel: "noreferrer noopener", target: "_blank" %>
<%= govuk_phase_banner(
classes: "#{current_user.present? ? 'no-bottom-border ' : ''}govuk-width-container",
tag: { colour: "orange", text: "Support beta" },
text: "This is a new service – help us improve it by #{feedback_link}".html_safe,
) %>
<div class="govuk-width-container">
<main class="govuk-main-wrapper govuk-main-wrapper--auto-spacing" id="main-content" role="main">
<%= render template: "layouts/rails_admin/content" %>
</main>
</div>
<%= render partial: "layouts/feedback" %>
<%= render partial: "layouts/footer", locals: {
accessibility_statement_path: Rails.application.routes.url_helpers.accessibility_statement_path,
privacy_notice_path: Rails.application.routes.url_helpers.privacy_notice_path,
cookies_path: Rails.application.routes.url_helpers.cookies_path,
} %>
</body>
</html>

25
app/views/rails_admin/main/_submit_buttons.html.erb

@ -0,0 +1,25 @@
<div class="form-actions row justify-content-end my-3">
<div class="col-sm-10">
<input name="return_to" type="<%= :hidden %>" value="<%= (params[:return_to].presence || request.referer) %>">
<button class="govuk-button" data-disable-with="<%= t("admin.form.save") %>" name="_save" type="submit"<%= " disabled" unless @action.enabled? %>>
<i class="fas fa-check"></i>
<%= t("admin.form.save") %>
</button>
<span class="extra_buttons">
<% if @action.enabled? && authorized?(:new, @abstract_model) %>
<button class="govuk-button govuk-button--secondary" data-disable-with="<%= t("admin.form.save_and_add_another") %>" name="_add_another" type="submit">
<%= t("admin.form.save_and_add_another") %>
</button>
<% end %>
<% if @action.enabled? && authorized?(:edit, @abstract_model) %>
<button class="govuk-button govuk-button--secondary" data-disable-with="<%= t("admin.form.save_and_edit") %>" name="_add_edit" type="submit"<%= " disabled" unless @action.enabled? %>>
<%= t("admin.form.save_and_edit") %>
</button>
<% end %>
<button class="govuk-button govuk-button--secondary" data-disable-with="<%= t("admin.form.cancel") %>" formnovalidate="<%= true %>" name="_continue" type="submit">
<i class="fas fa-times"></i>
<%= t("admin.form.cancel") %>
</button>
</span>
</div>
</div>

68
app/views/rails_admin/main/dashboard.html.erb

@ -0,0 +1,68 @@
<% if @abstract_models %>
<table class="table table-condensed table-striped table-hover">
<thead>
<tr>
<th class="shrink model-name">
<%= t "admin.table_headers.model_name" %>
</th>
<th class="shrink last-created">
<%= t "admin.table_headers.last_created" %>
</th>
<th class="records">
<%= t "admin.table_headers.records" %>
</th>
<th class="shrink controls"></th>
</tr>
</thead>
<tbody class="table-group-divider">
<% @abstract_models.each do |abstract_model| %>
<% if authorized? :index, abstract_model %>
<% index_path = index_path(model_name: abstract_model.to_param) %>
<% row_class = "#{cycle('odd', 'even')}#{' link' if index_path} #{abstract_model.param_key}_links" %>
<tr class="<%= row_class %>" data-link="<%= index_path %>">
<% last_created = @most_recent_created[abstract_model.model.name] %>
<% active = last_created.try(:today?) %>
<td>
<span class="show">
<%= link_to abstract_model.config.label_plural, index_path %>
</span>
</td>
<td>
<% if last_created %>
<%= t "admin.misc.time_ago", time: time_ago_in_words(last_created), default: "#{time_ago_in_words(last_created)} #{t('admin.misc.ago')}" %>
<% end %>
</td>
<td>
<% count = @count[abstract_model.model.name] %>
<% percent = if count.positive?
@max <= 1 ? count : ((Math.log(count + 1) * 100.0) / Math.log(@max + 1)).to_i
else
-1
end %>
<div class="<%= active ? "active progress-bar-striped " : "" %>progress" style="margin-bottom:0px">
<div class="bg-<%= get_indicator(percent) %> progress-bar animate-width-to" data-animate-length="<%= ([1.0, percent].max.to_i * 20) %>" data-animate-width-to="<%= [2.0, percent].max.to_i %>%" style="width:2%">
<%= @count[abstract_model.model.name] %>
</div>
</div>
</td>
<td class="last links rails-admin-actions">
<ul class="nav list-inline">
<%= menu_for :collection, abstract_model, nil, true %>
</ul>
</td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
<% end %>
<% if @history && authorized?(:history_index) %>
<div class="block" id="block-tables">
<div class="content">
<h2>
<%= t("admin.actions.history_index.menu") %>
</h2>
<%= render partial: "rails_admin/main/dashboard_history" %>
</div>
</div>
<% end %>

21
app/views/rails_admin/main/delete.html.erb

@ -0,0 +1,21 @@
<h4>
<%= t("admin.form.are_you_sure_you_want_to_delete_the_object", model_name: @abstract_model.pretty_name.downcase) %>
<q><strong><%= @model_config.with(object: @object).object_label %></strong></q>
<%= t("admin.form.all_of_the_following_related_items_will_be_deleted") %>
</h4>
<ul>
<%= render partial: "delete_notice", object: @object %>
</ul>
<%= form_for(@object, url: delete_path(model_name: @abstract_model.to_param, id: @object.id), html: { method: "delete" }) do %>
<input name="return_to" type="<%= :hidden %>" value="<%= (params[:return_to].presence || request.referer) %>">
<div class="form-actions">
<button class="govuk-button govuk-button--warning" data-disable-with="<%= t("admin.form.confirmation") %>" type="submit">
<i class="fas fa-check"></i>
<%= t("admin.form.confirmation") %>
</button>
<button class="govuk-button" data-disable-with="<%= t("admin.form.cancel") %>" name="_continue" type="submit">
<i class="fas fa-times"></i>
<%= t("admin.form.cancel") %>
</button>
</div>
<% end %>

187
app/views/rails_admin/main/index.html.erb

@ -0,0 +1,187 @@
<% query = params[:query] %>
<% params = request.params.except(:authenticity_token, :action, :controller, :utf8, :bulk_export) %>
<% params.delete(:query) if params[:query].blank? %>
<% params.delete(:sort_reverse) unless params[:sort_reverse] == "true" %>
<% sort_reverse = params[:sort_reverse] %>
<% sort = params[:sort] %>
<% params.delete(:sort) if params[:sort] == @model_config.list.sort_by.to_s %>
<% export_action = RailsAdmin::Config::Actions.find(:export, { controller:, abstract_model: @abstract_model }) %>
<% export_action = nil unless export_action && authorized?(export_action.authorization_key, @abstract_model) %>
<% description = RailsAdmin.config(@abstract_model.model_name).description %>
<% properties = @model_config.list.with(controller:, view: self, object: @abstract_model.model.new).fields_for_table %>
<% checkboxes = @model_config.list.checkboxes? %>
<% table_table_header_count = begin
count = checkboxes ? 1 : 0
count += properties.count
end %>
<% content_for :contextual_tabs do %>
<% if filterable_fields.present? %>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">
<%= t("admin.misc.add_filter") %>
<b class="caret"></b>
</a>
<ul class="dropdown-menu dropdown-menu-end" id="filters">
<% filterable_fields.each do |field| %>
<li>
<a
href="#"
class="dropdown-item"
data-options="<%= field.with(view: self).filter_options.to_json %>">
<%= field.label %>
</a>
</li>
<% end %>
</ul>
</li>
<% end %>
<% if checkboxes %>
<%= bulk_menu %>
<% end %>
<% end %>
<style>
<% properties.select { |p| p.column_width.present? }.each do |property| %>
<%= "#list th.#{property.css_class} { width: #{property.column_width}px; min-width: #{property.column_width}px; }" %>
<%= "#list td.#{property.css_class} { max-width: #{property.column_width}px;}" %>
<% end %>
</style>
<div id="list">
<%= form_tag(index_path(params.except(*%w[page f query])), method: :get) do %>
<div class="card mb-3 p-3 bg-light">
<div class="row rails-admin-filters-box" data-options="<%= ordered_filter_options.to_json %>" id="filters_box"></div>
<hr class="filters_box" style="display:<%= ordered_filters.empty? ? "none" : "block" %>">
<div class="row">
<div class="col-sm-8">
<div class="input-group">
<input class="govuk-input govuk-input--width-20" name="query" placeholder="<%= t("admin.misc.filter") %>" type="search" value="<%= query %>" autocomplete="off">
<div class="govuk-!-margin-left-2">
<button class="govuk-button govuk-!-margin-bottom-0" data-disable-with="<%= "<i class=\"fas fa-sync\"></i>#{t('admin.misc.refresh')}" %>" type="submit">
<i class="fas fa-sync"></i>
<%= t("admin.misc.refresh") %>
</button>
</div>
<div id="remove_filter" title="<%= t("admin.misc.reset_filters") %>">
<button class="govuk-button govuk-button--secondary govuk-!-margin-bottom-0">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<% if @model_config.list.search_help.present? %>
<div class="form-text"><%= @model_config.list.search_help %></div>
<% end %>
</div>
<div class="col-sm-4 text-end">
<% if export_action %>
<%= govuk_button_link_to wording_for(:link, export_action), export_path(params.except("page")), class: "govuk-!-margin-bottom-0" %>
<% end %>
</div>
</div>
</div>
<% end %>
<% unless @model_config.list.scopes.empty? %>
<ul class="nav nav-tabs" id="scope_selector">
<% @model_config.list.scopes.each_with_index do |scope, index| %>
<% scope = "_all" if scope.nil? %>
<li class="nav-item">
<a href="<%= index_path(params.merge(scope:, page: nil)) %>" class="nav-link <%= "active" if scope.to_s == params[:scope] || (params[:scope].blank? && index.zero?) %>">
<%= I18n.t("admin.scopes.#{@abstract_model.to_param}.#{scope}", default: I18n.t("admin.scopes.#{scope}", default: scope.to_s.titleize)) %>
</a>
</li>
<% end %>
</ul>
<% end %>
<%= form_tag bulk_action_path(model_name: @abstract_model.to_param), method: :post, id: "bulk_form", class: %w[form mb-3] do %>
<%= hidden_field_tag :bulk_action %>
<% if description.present? %>
<p>
<strong>
<%= description %>
</strong>
</p>
<% end %>
<div id="sidescroll" class="rails-admin-sidescroll">
<table class="table table-condensed table-striped table-hover">
<thead>
<tr>
<% if checkboxes %>
<th class="shrink sticky">
<input class="toggle" type="checkbox">
</th>
<% end %>
<% properties.each do |property| %>
<% selected = (sort == property.name.to_s) %>
<% if property.sortable %>
<% sort_location = index_path params.except("sort_reverse").except("page").merge(sort: property.name).merge(selected && sort_reverse != "true" ? { sort_reverse: "true" } : {}) %>
<% sort_direction = (if selected
sort_reverse == "true" ? "headerSortUp" : "headerSortDown"
end) %>
<% end %>
<th class="<%= [property.sortable && "header", property.sortable && sort_direction, property.sticky? && "sticky", property.css_class, property.type_css_class].select(&:present?).join(" ") %>" data-href="<%= property.sortable && sort_location %>" rel="tooltip" title="<%= property.hint %>">
<%= property.label %>
</th>
<% end %>
<th class="last shrink"></th>
</tr>
</thead>
<tbody class="table-group-divider">
<% @objects.each do |object| %>
<tr class="<%= @abstract_model.param_key %>_row <%= @model_config.list.with(object:).row_css_class %>">
<% if checkboxes %>
<td class="sticky">
<%= check_box_tag "bulk_ids[]", object.id.to_s, false %>
</td>
<% end %>
<% properties.map { |property| property.bind(:object, object) }.each do |property| %>
<% value = property.pretty_value %>
<%= content_tag(:td, class: [property.sticky? && "sticky", property.css_class, property.type_css_class].select(&:present?).map { |x| "rails-admin-#{x}" }, title: strip_tags(value.to_s)) do %>
<%= value %>
<% end %>
<% end %>
<td class="last links ra-sidescroll-frozen rails-admin-actions">
<ul class="nav list-inline">
<%= menu_for :member, @abstract_model, object, true %>
</ul>
</td>
</tr>
<% end %>
<% if @objects.empty? %>
<tr class="empty_row">
<td colspan="<%= table_table_header_count %>">
<%= I18n.t("admin.actions.index.no_records") %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% if @model_config.list.limited_pagination %>
<div class="row">
<div class="col-md-6">
<%= paginate(@objects, theme: "ra-twitter-bootstrap/without_count", total_pages: Float::INFINITY) %>
</div>
</div>
<% elsif @objects.respond_to?(:total_count) %>
<% total_count = @objects.total_count.to_i %>
<div class="row">
<div class="col-md-6">
<%= paginate(@objects, theme: "ra-twitter-bootstrap") %>
</div>
</div>
<div class="row">
<div class="col-md-6">
<%= link_to(t("admin.misc.show_all"), index_path(params.merge(all: true)), class: "govuk-button govuk-button--secondary") unless total_count > 100 || total_count <= @objects.to_a.size %>
</div>
</div>
<div class="clearfix total-count">
<%= "#{total_count} #{@model_config.pluralize(total_count).downcase}" %>
</div>
<% else %>
<div class="clearfix total-count">
<%= "#{@objects.size} #{@model_config.pluralize(@objects.size).downcase}" %>
</div>
<% end %>
<% end %>
</div>

2
config/initializers/assets.rb

@ -5,3 +5,5 @@ Rails.application.config.assets.version = "1.0"
# Add additional assets to the asset load path. # Add additional assets to the asset load path.
Rails.application.config.assets.paths << Rails.root.join("node_modules/@fortawesome/fontawesome-free/webfonts") Rails.application.config.assets.paths << Rails.root.join("node_modules/@fortawesome/fontawesome-free/webfonts")
Rails.application.config.assets.precompile += %w[rails_admin/rails_admin.css rails_admin/rails_admin.js]

0
config/locales/validations/lettings/financial.yml → config/locales/validations/lettings/financial.en.yml

5
db/migrate/20250110150609_add_collection_year.rb

@ -0,0 +1,5 @@
class AddCollectionYear < ActiveRecord::Migration[7.2]
def change
add_column :log_validations, :collection_year, :string
end
end

3
db/schema.rb

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2024_12_06_142944) do ActiveRecord::Schema[7.2].define(version: 2025_01_10_150609) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -450,6 +450,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_12_06_142944) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.boolean "checked" t.boolean "checked"
t.string "collection_year"
end end
create_table "merge_request_organisations", force: :cascade do |t| create_table "merge_request_organisations", force: :cascade do |t|

1
lib/tasks/generate_lettings_documentation.rake

@ -83,6 +83,7 @@ namespace :generate_lettings_documentation do
error_message:, error_message:,
case: validation_description, case: validation_description,
section: form.get_question(field, nil)&.subsection&.id, section: form.get_question(field, nil)&.subsection&.id,
collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}",
validation_type: validation_name, validation_type: validation_name,
hard_soft: "hard") hard_soft: "hard")
end end

6
lib/tasks/set_log_validation_collection_year.rake

@ -0,0 +1,6 @@
desc "Sets value for collection_year log validations depending on the from value"
task set_log_validation_collection_year: :environment do
LogValidation.all.each do |log_validation|
log_validation.update(collection_year: "#{log_validation.from.year}/#{log_validation.from.year + 1}")
end
end

29
spec/lib/tasks/set_log_validation_collection_year_spec.rb

@ -0,0 +1,29 @@
require "rails_helper"
require "rake"
RSpec.describe "set_log_validation_collection_year" do
describe ":set_log_validation_collection_year", type: :task do
subject(:task) { Rake::Task["set_log_validation_collection_year"] }
before do
Rake.application.rake_require("tasks/set_log_validation_collection_year")
Rake::Task.define_task(:environment)
task.reenable
end
context "when the rake task is run" do
let(:user) { create(:user) }
context "and version whodunnit exists for create" do
let!(:log_validation_2023) { LogValidation.create(from: Time.zone.local(2023, 4, 1), to: Time.zone.local(2024, 4, 1)) }
let!(:log_validation_2024) { LogValidation.create(from: Time.zone.local(2024, 4, 1), to: Time.zone.local(2025, 4, 1)) }
it "sets collection_year" do
task.invoke
expect(log_validation_2023.reload.collection_year).to eq("2023/2024")
expect(log_validation_2024.reload.collection_year).to eq("2024/2025")
end
end
end
end
end

12
spec/services/documentation_generator_spec.rb

@ -106,8 +106,7 @@ describe DocumentationGenerator do
expect(any_validation.field).not_to be_empty expect(any_validation.field).not_to be_empty
expect(any_validation.error_message).not_to be_empty expect(any_validation.error_message).not_to be_empty
expect(any_validation.case).to eq("Provided values fulfill the description") expect(any_validation.case).to eq("Provided values fulfill the description")
expect(any_validation.from).not_to be_nil expect(any_validation.collection_year).not_to be_nil
expect(any_validation.to).not_to be_nil
expect(any_validation.validation_type).to eq("format") expect(any_validation.validation_type).to eq("format")
expect(any_validation.hard_soft).to eq("soft") expect(any_validation.hard_soft).to eq("soft")
expect(any_validation.other_validated_models).to eq("User") expect(any_validation.other_validated_models).to eq("User")
@ -137,8 +136,7 @@ describe DocumentationGenerator do
expect(any_validation.field).not_to be_empty expect(any_validation.field).not_to be_empty
expect(any_validation.error_message).not_to be_empty expect(any_validation.error_message).not_to be_empty
expect(any_validation.case).to eq("Provided values fulfill the description") expect(any_validation.case).to eq("Provided values fulfill the description")
expect(any_validation.from).not_to be_nil expect(any_validation.collection_year).not_to be_nil
expect(any_validation.to).not_to be_nil
expect(any_validation.validation_type).to eq("format") expect(any_validation.validation_type).to eq("format")
expect(any_validation.hard_soft).to eq("soft") expect(any_validation.hard_soft).to eq("soft")
expect(any_validation.other_validated_models).to eq("User") expect(any_validation.other_validated_models).to eq("User")
@ -165,8 +163,7 @@ describe DocumentationGenerator do
expect(any_validation.field).to eq("ppostcode_full") expect(any_validation.field).to eq("ppostcode_full")
expect(any_validation.error_message).to eq("Enter a valid postcode") expect(any_validation.error_message).to eq("Enter a valid postcode")
expect(any_validation.case).to eq("Previous postcode is known and current postcode is blank") expect(any_validation.case).to eq("Previous postcode is known and current postcode is blank")
expect(any_validation.from).not_to be_nil expect(any_validation.collection_year).to eq("2023/2024")
expect(any_validation.to).not_to be_nil
expect(any_validation.validation_type).to eq("format") expect(any_validation.validation_type).to eq("format")
expect(any_validation.hard_soft).to eq("hard") expect(any_validation.hard_soft).to eq("hard")
expect(any_validation.other_validated_models).to eq("User") expect(any_validation.other_validated_models).to eq("User")
@ -218,8 +215,7 @@ describe DocumentationGenerator do
expect(any_validation.field).to eq("ppostcode_full") expect(any_validation.field).to eq("ppostcode_full")
expect(any_validation.error_message).to eq("Enter a valid postcode") expect(any_validation.error_message).to eq("Enter a valid postcode")
expect(any_validation.case).to eq("Previous postcode is known and current postcode is blank") expect(any_validation.case).to eq("Previous postcode is known and current postcode is blank")
expect(any_validation.from).not_to be_nil expect(any_validation.collection_year).to eq("2023/2024")
expect(any_validation.to).not_to be_nil
expect(any_validation.validation_type).to eq("format") expect(any_validation.validation_type).to eq("format")
expect(any_validation.hard_soft).to eq("hard") expect(any_validation.hard_soft).to eq("hard")
expect(any_validation.other_validated_models).to eq("User") expect(any_validation.other_validated_models).to eq("User")

Loading…
Cancel
Save