diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 523037d9ea..3ffe9d6bc8 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -68,20 +68,15 @@ jobs: bundle exec rake db:schema:load bundle exec rails assets:precompile - - name: Run rspec and upload code coverage + - name: Run rspec env: DATABASE_HOST: localhost POSTGRES_USER: postgres CASA_DATABASE_PASSWORD: password POSTGRES_HOST_AUTH_METHOD: trust RUN_SIMPLECOV: true - CC_TEST_REPORTER_ID: 31464536e34ab26588cb951d0fa6b5898abdf401dbe912fd47274df298e432ac run: | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build RUBYOPT='-W:no-deprecated -W:no-experimental' bundle exec rspec - ./cc-test-reporter after-build --exit-code $? - name: Archive selenium screenshots if: ${{ failure() }} diff --git a/app/controllers/case_contacts/case_contacts_new_design_controller.rb b/app/controllers/case_contacts/case_contacts_new_design_controller.rb new file mode 100644 index 0000000000..57dd89a7a8 --- /dev/null +++ b/app/controllers/case_contacts/case_contacts_new_design_controller.rb @@ -0,0 +1,15 @@ +class CaseContacts::CaseContactsNewDesignController < ApplicationController + include LoadsCaseContacts + + def index + load_case_contacts + end + + def datatable + authorize CaseContact + case_contacts = policy_scope(current_organization.case_contacts) + datatable = CaseContactDatatable.new case_contacts, params + + render json: datatable + end +end diff --git a/app/controllers/case_contacts_controller.rb b/app/controllers/case_contacts_controller.rb index cb350158eb..5f4275ea82 100644 --- a/app/controllers/case_contacts_controller.rb +++ b/app/controllers/case_contacts_controller.rb @@ -1,30 +1,15 @@ # frozen_string_literal: true class CaseContactsController < ApplicationController + include LoadsCaseContacts + before_action :set_case_contact, only: %i[edit destroy] before_action :set_contact_types, only: %i[new edit create] before_action :require_organization! after_action :verify_authorized, except: %i[leave] def index - authorize CaseContact - - @current_organization_groups = current_organization_groups - - @filterrific = initialize_filterrific( - all_case_contacts, - params[:filterrific], - select_options: { - sorted_by: CaseContact.options_for_sorted_by - } - ) || return - - @pagy, @filtered_case_contacts = pagy(@filterrific.find) - case_contacts = CaseContact.case_hash_from_cases(@filtered_case_contacts) - case_contacts = case_contacts.select { |k, _v| current_user.casa_cases.pluck(:id).include?(k) } if current_user.volunteer? - case_contacts = case_contacts.select { |k, _v| k == params[:casa_case_id].to_i } if params[:casa_case_id].present? - - @presenter = CaseContactPresenter.new(case_contacts) + load_case_contacts end def drafts @@ -96,24 +81,6 @@ def set_contact_types @contact_types = ContactType.for_organization(current_organization) end - def current_organization_groups - current_organization.contact_type_groups - .includes(:contact_types) - .joins(:contact_types) - .where(contact_types: {active: true}) - .uniq - end - - def all_case_contacts - policy_scope(current_organization.case_contacts).preload( - :creator, - :followups, - contact_types: :contact_type_group, - contact_topic_answers: :contact_topic, - casa_case: :volunteers - ) - end - def additional_expense_params @additional_expense_params ||= AdditionalExpenseParamsService.new(params).calculate end diff --git a/app/controllers/concerns/loads_case_contacts.rb b/app/controllers/concerns/loads_case_contacts.rb new file mode 100644 index 0000000000..bc50aa1073 --- /dev/null +++ b/app/controllers/concerns/loads_case_contacts.rb @@ -0,0 +1,44 @@ +module LoadsCaseContacts + extend ActiveSupport::Concern + + private + + def load_case_contacts + authorize CaseContact + + @current_organization_groups = current_organization_groups + + @filterrific = initialize_filterrific( + all_case_contacts, + params[:filterrific], + select_options: { + sorted_by: CaseContact.options_for_sorted_by + } + ) || return + + @pagy, @filtered_case_contacts = pagy(@filterrific.find) + case_contacts = CaseContact.case_hash_from_cases(@filtered_case_contacts) + case_contacts = case_contacts.slice(*current_user.casa_cases.pluck(:id)) if current_user.volunteer? + case_contacts = case_contacts.select { |k, _v| k == params[:casa_case_id].to_i } if params[:casa_case_id].present? + + @presenter = CaseContactPresenter.new(case_contacts) + end + + def current_organization_groups + current_organization.contact_type_groups + .includes(:contact_types) + .joins(:contact_types) + .where(contact_types: {active: true}) + .uniq + end + + def all_case_contacts + policy_scope(current_organization.case_contacts).preload( + :creator, + :followups, + contact_types: :contact_type_group, + contact_topic_answers: :contact_topic, + casa_case: :volunteers + ) + end +end diff --git a/app/decorators/case_contact_decorator.rb b/app/decorators/case_contact_decorator.rb index c03d6d100c..3b40bd22f0 100644 --- a/app/decorators/case_contact_decorator.rb +++ b/app/decorators/case_contact_decorator.rb @@ -61,6 +61,14 @@ def contact_types end end + def contact_types_comma_separated + if object.contact_types.any? + object.contact_types&.map { |ct| ct.name }&.join(", ") + else + "No contact type specified" + end + end + def report_contact_types object.contact_types&.map { |ct| ct.name }&.join("|") end diff --git a/app/javascript/src/dashboard.js b/app/javascript/src/dashboard.js index 9bf091bb34..396769098f 100644 --- a/app/javascript/src/dashboard.js +++ b/app/javascript/src/dashboard.js @@ -8,7 +8,11 @@ const defineCaseContactsTable = function () { { scrollX: true, searching: false, - order: [[0, 'desc']] + order: [[2, 'desc']], + columnDefs: [ + { type: 'date', targets: 2 }, + { orderable: false, targets: [0, 1, -1] } // disable sort on bell, chevron, vertical elipses menu + ] } ) } diff --git a/app/views/case_contacts/case_contacts_new_design/index.html.erb b/app/views/case_contacts/case_contacts_new_design/index.html.erb new file mode 100644 index 0000000000..826a58173b --- /dev/null +++ b/app/views/case_contacts/case_contacts_new_design/index.html.erb @@ -0,0 +1,98 @@ +
+
+
+

Case Contacts

+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + <% @presenter.case_contacts.each do |casa_case_id, case_contacts| %> + <% case_contacts.each do |case_contact| %> + + + + + + + + + + + + + + <% end %> + <% end %> + +
Date
Case
Relationship
Medium
Created By
Contacted
Topics
Draft
+ + + + + <%= I18n.l(case_contact[:occurred_at], format: :full) if case_contact[:occurred_at].present? %> + + <%= @presenter.display_case_number(casa_case_id) %> + + <%= case_contact.decorate.contact_types_comma_separated %> + + <%= case_contact.medium_type&.capitalize %> + + <% if policy(case_contact).edit? %> + <% if current_user.volunteer? %> + <%= case_contact.creator&.display_name %> + <% else %> + <% if case_contact.creator&.supervisor? %> + <%= link_to case_contact.creator&.display_name, edit_supervisor_path(case_contact.creator), data: { turbo: false } %> + <% elsif case_contact.creator&.casa_admin? %> + <%= link_to case_contact.creator&.display_name, edit_users_path, data: { turbo: false } %> + <% else %> + <%= link_to case_contact.creator&.display_name, edit_volunteer_path(case_contact.creator), data: { turbo: false } %> + <% end %> + <% end %> + <% else %> + <%= case_contact.creator&.display_name %> + <% end %> + + <% if case_contact.contact_made %> + + <% else %> + + <% end %> + <%= "(#{"%02d:%02d" % [case_contact.duration_minutes / 60, case_contact.duration_minutes % 60]})" if case_contact.duration_minutes %> + + <%= case_contact.contact_topics.map(&:question).join(" | ") %> + + <% if !case_contact.active? %> + + Draft + + <% end %> + + +
+ +
+
diff --git a/config/routes.rb b/config/routes.rb index 7afc4de49d..bae1446957 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -92,7 +92,8 @@ get "case_contacts/leave", to: "case_contacts#leave", as: "leave_case_contacts_form" get "case_contacts/drafts", to: "case_contacts#drafts" - resources :case_contacts, except: %i[create update show] do + get "case_contacts/new_design", to: "case_contacts/case_contacts_new_design#index" + resources :case_contacts, except: %i[create update show], concerns: %i[with_datatable] do member do post :restore end diff --git a/spec/controllers/concerns/loads_case_contacts_spec.rb b/spec/controllers/concerns/loads_case_contacts_spec.rb new file mode 100644 index 0000000000..a8a269f6ef --- /dev/null +++ b/spec/controllers/concerns/loads_case_contacts_spec.rb @@ -0,0 +1,15 @@ +require "rails_helper" + +RSpec.describe LoadsCaseContacts do + let(:host) do + Class.new do + include LoadsCaseContacts + end + end + + it "exists and defines private API" do + expect(described_class).to be_a(Module) + expect(host.private_instance_methods) + .to include(:load_case_contacts, :current_organization_groups, :all_case_contacts) + end +end diff --git a/spec/requests/case_contacts/case_contacts_new_design_spec.rb b/spec/requests/case_contacts/case_contacts_new_design_spec.rb new file mode 100644 index 0000000000..a1f04b2cb2 --- /dev/null +++ b/spec/requests/case_contacts/case_contacts_new_design_spec.rb @@ -0,0 +1,43 @@ +require "rails_helper" + +RSpec.describe "/case_contacts_new_design", type: :request do + let(:organization) { create(:casa_org) } + let(:admin) { create(:casa_admin, casa_org: organization) } + + before { sign_in admin } + + describe "GET /index" do + subject(:request) do + get case_contacts_new_design_path + + response + end + + let!(:casa_case) { create(:casa_case, casa_org: organization) } + let!(:past_contact) { create(:case_contact, :active, casa_case: casa_case, occurred_at: 3.weeks.ago) } + let!(:recent_contact) { create(:case_contact, :active, casa_case: casa_case, occurred_at: 3.days.ago) } + let!(:draft_contact) { create(:case_contact, casa_case: casa_case, occurred_at: 5.days.ago, status: "started") } + + it { is_expected.to have_http_status(:success) } + + it "lists exactly two active contacts and one draft" do + doc = Nokogiri::HTML(request.body) + case_contact_rows = doc.css('[data-testid="case_contact-row"]') + expect(case_contact_rows.size).to eq(3) + end + + it "shows the draft badge exactly once" do + doc = Nokogiri::HTML(request.body) + expect(doc.css('[data-testid="draft-badge"]').count).to eq(1) + end + + it "orders contacts by occurred_at desc" do + body = request.body + + recent_index = body.index(I18n.l(recent_contact.occurred_at, format: :full)) + past_index = body.index(I18n.l(past_contact.occurred_at, format: :full)) + + expect(recent_index).to be < past_index + end + end +end