Browse Source

CLDC-1101: Case log filter by organisation (#522)

* Rename org filter

* Ensure filters work when ID is passed

* UI init

* Lint

* Fix filter nesting

* Reduce width

* Set checked status of filters correctly

* Test filter presence

* Add filter test

* Rubocop

* Tweak styles for autocomplete within filter

Co-authored-by: Paul Robert Lloyd <me+git@paulrobertlloyd.com>
pull/619/head
baarkerlounger 3 years ago committed by baarkerlounger
parent
commit
91a9137c75
  1. 5
      app/controllers/case_logs_controller.rb
  2. 6
      app/frontend/styles/_filter-layout.scss
  3. 15
      app/frontend/styles/_filter.scss
  4. 8
      app/frontend/styles/application.scss
  5. 8
      app/helpers/filters_helper.rb
  6. 2
      app/models/case_log.rb
  7. 2
      app/models/organisation.rb
  8. 10
      app/models/user.rb
  9. 23
      app/views/case_logs/_log_filters.erb
  10. 20
      app/views/filters/_radio_filter.html.erb
  11. 7
      app/views/filters/_select_filter.html.erb
  12. 30
      spec/helpers/filters_helper_spec.rb
  13. 25
      spec/models/case_log_spec.rb
  14. 8
      spec/models/user_spec.rb
  15. 47
      spec/requests/case_logs_controller_spec.rb

5
app/controllers/case_logs_controller.rb

@ -126,7 +126,7 @@ private
if session[:case_logs_filters].present? if session[:case_logs_filters].present?
filters = JSON.parse(session[:case_logs_filters]) filters = JSON.parse(session[:case_logs_filters])
filters.each do |category, values| filters.each do |category, values|
next if values.reject(&:empty?).blank? next if Array(values).reject(&:empty?).blank?
query = query.public_send("filter_by_#{category}", values, current_user) query = query.public_send("filter_by_#{category}", values, current_user)
end end
@ -137,8 +137,7 @@ private
def set_session_filters def set_session_filters
new_filters = session[:case_logs_filters].present? ? JSON.parse(session[:case_logs_filters]) : {} new_filters = session[:case_logs_filters].present? ? JSON.parse(session[:case_logs_filters]) : {}
%i[status years].each { |filter| new_filters[filter] = params[filter] if params[filter].present? } current_user.case_logs_filters.each { |filter| new_filters[filter] = params[filter] if params[filter].present? }
new_filters[:user] = [params[:user]] if params[:user].present?
session[:case_logs_filters] = new_filters.to_json session[:case_logs_filters] = new_filters.to_json
end end

6
app/frontend/styles/_filter-layout.scss

@ -3,7 +3,7 @@
} }
.app-filter-layout__filter { .app-filter-layout__filter {
@include govuk-media-query(desktop) { @include govuk-media-query(wide) {
float: left; float: left;
min-width: govuk-grid-width("one-quarter"); min-width: govuk-grid-width("one-quarter");
} }
@ -12,7 +12,7 @@
.js-enabled .app-filter-layout__filter { .js-enabled .app-filter-layout__filter {
outline: 0 none; outline: 0 none;
@include govuk-media-query($until: desktop) { @include govuk-media-query($until: wide) {
background-color: govuk-colour("light-grey"); background-color: govuk-colour("light-grey");
bottom: govuk-spacing(1); bottom: govuk-spacing(1);
border: 1px solid govuk-colour("mid-grey"); border: 1px solid govuk-colour("mid-grey");
@ -32,7 +32,7 @@
} }
.app-filter-layout__content { .app-filter-layout__content {
@include govuk-media-query(desktop) { @include govuk-media-query(wide) {
float: right; float: right;
max-width: calc(#{govuk-grid-width("three-quarters")} - #{govuk-spacing(6)}); max-width: calc(#{govuk-grid-width("three-quarters")} - #{govuk-spacing(6)});
width: 100%; width: 100%;

15
app/frontend/styles/_filter.scss

@ -98,4 +98,19 @@
background-color: govuk-colour("white"); background-color: govuk-colour("white");
} }
} }
.autocomplete__input {
@include govuk-font(16);
background-color: govuk-colour("white");
}
.autocomplete__wrapper {
@include govuk-media-query(wide) {
max-width: 20ex;
}
}
.autocomplete__option {
@include govuk-font(16);
}
} }

8
app/frontend/styles/application.scss

@ -11,6 +11,14 @@ $govuk-image-url-function: frontend-image-url;
$govuk-global-styles: true; $govuk-global-styles: true;
$govuk-new-link-styles: true; $govuk-new-link-styles: true;
// Add additional breakpoint named `wide`
$govuk-breakpoints: (
mobile: 320px,
tablet: 641px,
desktop: 769px,
wide: 921px,
);
@import "govuk-frontend-styles"; @import "govuk-frontend-styles";
@import "govuk-prototype-styles"; @import "govuk-prototype-styles";

8
app/helpers/filters_helper.rb

@ -4,6 +4,8 @@ module FiltersHelper
selected_filters = JSON.parse(session[:case_logs_filters]) selected_filters = JSON.parse(session[:case_logs_filters])
return true if selected_filters.blank? && filter == "user" && value == :all return true if selected_filters.blank? && filter == "user" && value == :all
return true if selected_filters.blank? && filter == "organisation_select" && value == :all
return true if selected_filters["organisation"].present? && filter == "organisation_select" && value == :specific_org
return false if selected_filters[filter].blank? return false if selected_filters[filter].blank?
selected_filters[filter].include?(value.to_s) selected_filters[filter].include?(value.to_s)
@ -14,4 +16,10 @@ module FiltersHelper
CaseLog.statuses.keys.map { |status| statuses[status] = status.humanize } CaseLog.statuses.keys.map { |status| statuses[status] = status.humanize }
statuses statuses
end end
def selected_option(filter)
return false unless session[:case_logs_filters]
JSON.parse(session[:case_logs_filters])[filter]
end
end end

2
app/models/case_log.rb

@ -34,7 +34,7 @@ class CaseLog < ApplicationRecord
belongs_to :managing_organisation, class_name: "Organisation" belongs_to :managing_organisation, class_name: "Organisation"
belongs_to :created_by, class_name: "User" belongs_to :created_by, class_name: "User"
scope :for_organisation, ->(org) { where(owning_organisation: org).or(where(managing_organisation: org)) } scope :filter_by_organisation, ->(org, _user = nil) { where(owning_organisation: org).or(where(managing_organisation: org)) }
scope :filter_by_status, ->(status, _user = nil) { where status: } scope :filter_by_status, ->(status, _user = nil) { where status: }
scope :filter_by_years, lambda { |years, _user = nil| scope :filter_by_years, lambda { |years, _user = nil|
first_year = years.shift first_year = years.shift

2
app/models/organisation.rb

@ -18,7 +18,7 @@ class Organisation < ApplicationRecord
validates :provider_type, presence: true validates :provider_type, presence: true
def case_logs def case_logs
CaseLog.for_organisation(self) CaseLog.filter_by_organisation(self)
end end
def completed_case_logs def completed_case_logs

10
app/models/user.rb

@ -36,7 +36,7 @@ class User < ApplicationRecord
if support? if support?
CaseLog.all CaseLog.all
else else
CaseLog.for_organisation(organisation) CaseLog.filter_by_organisation(organisation)
end end
end end
@ -88,4 +88,12 @@ class User < ApplicationRecord
ROLES.except(:support) ROLES.except(:support)
end end
def case_logs_filters
if support?
%i[status years user organisation]
else
%i[status years user]
end
end
end end

23
app/views/case_logs/_log_filters.erb

@ -6,12 +6,31 @@
<div class="app-filter__content"> <div class="app-filter__content">
<%= form_with url: "/logs", html: { method: :get } do |f| %> <%= form_with url: "/logs", html: { method: :get } do |f| %>
<% years = {"2021": "2021/22", "2022": "2022/23"} %> <% years = {"2021": "2021/22", "2022": "2022/23"} %>
<% all_or_yours = {"all": "All", "yours": "Yours"} %> <% all_or_yours = {"all": { label: "All" }, "yours": { label: "Yours" } } %>
<%= render partial: "filters/checkbox_filter", locals: { f: f, options: years, label: "Collection year", category: "years" } %> <%= render partial: "filters/checkbox_filter", locals: { f: f, options: years, label: "Collection year", category: "years" } %>
<%= render partial: "filters/checkbox_filter", locals: { f: f, options: status_filters, label: "Status", category: "status" } %> <%= render partial: "filters/checkbox_filter", locals: { f: f, options: status_filters, label: "Status", category: "status" } %>
<%= render partial: "filters/radio_filter", locals: { f: f, options: all_or_yours, label: "Logs", category: "user", } %> <%= render partial: "filters/radio_filter", locals: { f: f, options: all_or_yours, label: "Logs", category: "user", } %>
<% if @current_user.support? %>
<%= render partial: "filters/radio_filter", locals: {
f: f,
options: {
"all": { label: "All" },
"specific_org": {
label: "Specific organisation",
conditional_filter: {
type: "select",
label: "Organisation",
category: "organisation",
options: [OpenStruct.new(id: "", name: "Select an option")] + Organisation.all.map { |org| OpenStruct.new(id: org.id, name: org.name) }
}
}
},
label: "Organisation",
category: "organisation_select"
} %>
<% end %>
<%= f.govuk_submit "Apply filters", class: "govuk-!-margin-bottom-0" %> <%= f.govuk_submit "Apply filters", class: "govuk-!-margin-bottom-0" %>
<% end %> <% end %>
</div> </div>
</div> </div>
</div> </div>

20
app/views/filters/_radio_filter.html.erb

@ -1,8 +1,18 @@
<%= f.govuk_radio_buttons_fieldset category.to_sym, legend: { text: label, size: "s" }, small: true, form_group: { classes: "app-filter__group" } do %> <%= f.govuk_radio_buttons_fieldset category.to_sym, legend: { text: label, size: "s" }, small: true, form_group: { classes: "app-filter__group" } do %>
<% options.map do |key, option| %> <% options.map do |key, option| %>
<%= f.govuk_radio_button category, key.to_s, <%= f.govuk_radio_button category, key.to_s,
label: { text: option }, label: { text: option[:label] },
checked: filter_selected?(category, key), checked: filter_selected?(category, key),
size: "s" %> size: "s" do %>
<% if option[:conditional_filter] %>
<%= render partial: "filters/#{option[:conditional_filter][:type]}_filter", locals: {
f:,
collection: option[:conditional_filter][:options],
category: option[:conditional_filter][:category],
label: option[:conditional_filter][:label],
secondary: true,
} %>
<% end %>
<% end %> <% end %>
<% end %>
<% end %> <% end %>

7
app/views/filters/_select_filter.html.erb

@ -0,0 +1,7 @@
<%= f.govuk_collection_select category.to_sym,
collection,
:id,
:name,
label: { hidden: secondary },
options: { disabled: [""], selected: selected_option(category) },
"data-controller": "accessible-autocomplete" %>

30
spec/helpers/filters_helper_spec.rb

@ -16,8 +16,8 @@ RSpec.describe FiltersHelper do
context "when looking at the all value" do context "when looking at the all value" do
it "returns true if no filters have been set yet" do it "returns true if no filters have been set yet" do
expect(filter_selected?("user", :all)).to be_truthy expect(filter_selected?("user", :all)).to be true
expect(filter_selected?("user", :yours)).to be_falsey expect(filter_selected?("user", :yours)).to be false
end end
end end
end end
@ -28,11 +28,33 @@ RSpec.describe FiltersHelper do
end end
it "returns false for non selected filters" do it "returns false for non selected filters" do
expect(filter_selected?("status", "completed")).to be_falsey expect(filter_selected?("status", "completed")).to be false
end end
it "returns true for selected filter" do it "returns true for selected filter" do
expect(filter_selected?("status", "in_progress")).to be_truthy expect(filter_selected?("status", "in_progress")).to be true
end
end
context "when support user is using the organisation filter" do
before do
session[:case_logs_filters] = { "organisation": "1" }.to_json
end
it "returns true for the parent organisation_select filter" do
expect(filter_selected?("organisation_select", :specific_org)).to be true
expect(filter_selected?("organisation_select", :all)).to be false
end
end
context "when support user has not set the organisation_select filter" do
before do
session[:case_logs_filters] = {}.to_json
end
it "defaults to all organisations" do
expect(filter_selected?("organisation_select", :all)).to be true
expect(filter_selected?("organisation_select", :specific_org)).to be false
end end
end end
end end

25
spec/models/case_log_spec.rb

@ -1893,6 +1893,31 @@ RSpec.describe CaseLog do
end end
end end
context "when filtering by organisation" do
let(:organisation_1) { FactoryBot.create(:organisation) }
let(:organisation_2) { FactoryBot.create(:organisation) }
let(:organisation_3) { FactoryBot.create(:organisation) }
before do
FactoryBot.create(:case_log, :in_progress, owning_organisation: organisation_1, managing_organisation: organisation_1)
FactoryBot.create(:case_log, :completed, owning_organisation: organisation_1, managing_organisation: organisation_2)
FactoryBot.create(:case_log, :completed, owning_organisation: organisation_2, managing_organisation: organisation_1)
FactoryBot.create(:case_log, :completed, owning_organisation: organisation_2, managing_organisation: organisation_2)
end
it "filters by given organisation id" do
expect(described_class.filter_by_organisation([organisation_1.id]).count).to eq(3)
expect(described_class.filter_by_organisation([organisation_1.id, organisation_2.id]).count).to eq(4)
expect(described_class.filter_by_organisation([organisation_3.id]).count).to eq(0)
end
it "filters by given organisation" do
expect(described_class.filter_by_organisation([organisation_1]).count).to eq(3)
expect(described_class.filter_by_organisation([organisation_1, organisation_2]).count).to eq(4)
expect(described_class.filter_by_organisation([organisation_3]).count).to eq(0)
end
end
context "when filtering on status" do context "when filtering on status" do
it "allows filtering on a single status" do it "allows filtering on a single status" do
expect(described_class.filter_by_status(%w[in_progress]).count).to eq(2) expect(described_class.filter_by_status(%w[in_progress]).count).to eq(2)

8
spec/models/user_spec.rb

@ -97,6 +97,10 @@ RSpec.describe User, type: :model do
data_coordinator: 2, data_coordinator: 2,
}) })
end end
it "can filter case logs by user, year and status" do
expect(user.case_logs_filters).to eq(%i[status years user])
end
end end
context "when the user is a Customer Support person" do context "when the user is a Customer Support person" do
@ -119,6 +123,10 @@ RSpec.describe User, type: :model do
support: 99, support: 99,
}) })
end end
it "can filter case logs by user, year, status and organisation" do
expect(user.case_logs_filters).to eq(%i[status years user organisation])
end
end end
end end

47
spec/requests/case_logs_controller_spec.rb

@ -186,6 +186,7 @@ RSpec.describe CaseLogsController, type: :request do
context "when filtering" do context "when filtering" do
context "with status filter" do context "with status filter" do
let(:organisation_2) { FactoryBot.create(:organisation) }
let!(:in_progress_case_log) do let!(:in_progress_case_log) do
FactoryBot.create(:case_log, :in_progress, FactoryBot.create(:case_log, :in_progress,
owning_organisation: organisation, owning_organisation: organisation,
@ -193,7 +194,7 @@ RSpec.describe CaseLogsController, type: :request do
end end
let!(:completed_case_log) do let!(:completed_case_log) do
FactoryBot.create(:case_log, :completed, FactoryBot.create(:case_log, :completed,
owning_organisation: organisation, owning_organisation: organisation_2,
managing_organisation: organisation) managing_organisation: organisation)
end end
@ -209,6 +210,12 @@ RSpec.describe CaseLogsController, type: :request do
expect(page).not_to have_link(completed_case_log.id.to_s) expect(page).not_to have_link(completed_case_log.id.to_s)
end end
it "filters on organisation" do
get "/logs?organisation[]=#{organisation_2.id}", headers: headers, params: {}
expect(page).to have_link(completed_case_log.id.to_s)
expect(page).not_to have_link(in_progress_case_log.id.to_s)
end
it "does not reset the filters" do it "does not reset the filters" do
get "/logs?status[]=in_progress", headers: headers, params: {} get "/logs?status[]=in_progress", headers: headers, params: {}
expect(page).to have_link(in_progress_case_log.id.to_s) expect(page).to have_link(in_progress_case_log.id.to_s)
@ -344,6 +351,44 @@ RSpec.describe CaseLogsController, type: :request do
it "shows the download csv link" do it "shows the download csv link" do
expect(page).to have_link("Download (CSV)", href: "/logs.csv") expect(page).to have_link("Download (CSV)", href: "/logs.csv")
end end
it "does not show the organisation filter" do
expect(page).not_to have_field("organisation-field")
end
end
context "when the user is a customer support user" do
let(:user) { FactoryBot.create(:user, :support) }
let(:org_1) { FactoryBot.create(:organisation) }
let(:org_2) { FactoryBot.create(:organisation) }
let(:tenant_code_1) { "TC5638" }
let(:tenant_code_2) { "TC8745" }
before do
FactoryBot.create(:case_log, :in_progress, owning_organisation: org_1, tenant_code: tenant_code_1)
FactoryBot.create(:case_log, :in_progress, owning_organisation: org_2, tenant_code: tenant_code_2)
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
end
it "does show the organisation filter" do
get "/logs", headers:, params: {}
expect(page).to have_field("organisation-field")
end
it "shows all logs by default" do
get "/logs", headers:, params: {}
expect(page).to have_content(tenant_code_1)
expect(page).to have_content(tenant_code_2)
end
context "when filtering by organisation" do
it "only show the selected organisations logs" do
get "/logs?organisation_select=specific_org&organisation=#{org_1.id}", headers:, params: {}
expect(page).to have_content(tenant_code_1)
expect(page).not_to have_content(tenant_code_2)
end
end
end end
context "when there are more than 20 logs" do context "when there are more than 20 logs" do

Loading…
Cancel
Save