From 9f6b4b341cf31c12d4b52de72ec0d0b8289a0e34 Mon Sep 17 00:00:00 2001 From: SHAMI_TOMITA Date: Wed, 28 Jun 2023 16:34:04 -0400 Subject: [PATCH 01/21] Co-authored-by: Eric Halverson Co-authored-by: James Garcia add datatable checkboxes to volunteer#index view for bulk assignment add system test for volunteers/index --- 3 | 503 ++++++++++++++++++ app/assets/stylesheets/application.scss | 1 + app/assets/stylesheets/pages/volunteers.scss | 13 + app/assets/stylesheets/shared/header.scss | 23 + .../supervisor_volunteers_controller.rb | 56 +- app/controllers/volunteers_controller.rb | 1 + app/javascript/application.js | 2 + app/javascript/src/dashboard.js | 38 +- app/models/volunteer.rb | 2 + app/policies/supervisor_volunteer_policy.rb | 4 + app/views/supervisors/index.html.erb | 6 +- app/views/volunteers/index.html.erb | 102 +++- config/routes.rb | 3 + db/schema.rb | 7 +- package.json | 2 + spec/requests/supervisor_volunteers_spec.rb | 115 ++++ spec/system/volunteers/index_spec.rb | 48 ++ yarn.lock | 23 + 18 files changed, 918 insertions(+), 31 deletions(-) create mode 100644 3 create mode 100644 app/assets/stylesheets/shared/header.scss diff --git a/3 b/3 new file mode 100644 index 0000000000..e45619a65e --- /dev/null +++ b/3 @@ -0,0 +1,503 @@ +/* global alert */ +/* global $ */ +const AsyncNotifier = require('../src/async_notifier') +let pageNotifier + +const defineCaseContactsTable = function () { + $('table#case_contacts').DataTable( + { + scrollX: true, + searching: false, + order: [[0, 'desc']] + } + ) +} + +$(() => { // JQuery's callback for the DOM loading + const asyncNotificationsElement = $('#async-notifications') + + if (asyncNotificationsElement.length) { + pageNotifier = new AsyncNotifier(asyncNotificationsElement) + } + + $.fn.dataTable.ext.search.push( + function (settings, data, dataIndex) { + if (settings.nTable.id !== 'casa-cases') { + return true + } + + const statusArray = [] + const assignedToVolunteerArray = [] + const assignedToMoreThanOneVolunteerArray = [] + const assignedToTransitionYouthArray = [] + const caseNumberPrefixArray = [] + + $('.status-options').find('input[type="checkbox"]').each(function () { + if ($(this).is(':checked')) { + statusArray.push($(this).data('value')) + } + }) + + $('.assigned-to-volunteer-options').find('input[type="checkbox"]').each(function () { + if ($(this).is(':checked')) { + assignedToVolunteerArray.push($(this).data('value')) + } + }) + + $('.more-than-one-volunteer-options').find('input[type="checkbox"]').each(function () { + if ($(this).is(':checked')) { + assignedToMoreThanOneVolunteerArray.push($(this).data('value')) + } + }) + + $('.transition-youth-options').find('input[type="checkbox"]').each(function () { + if ($(this).is(':checked')) { + assignedToTransitionYouthArray.push($(this).data('value')) + } + }) + + $('.case-number-prefix-options').find('input[type="checkbox"]').each(function () { + if ($(this).is(':checked')) { + caseNumberPrefixArray.push($(this).data('value')) + } + }) + + const possibleCaseNumberPrefixes = ['CINA', 'TPR'] + const status = data[3] + const assignedToVolunteer = (data[5] !== '' && data[5].split(',').length >= 1) ? 'Yes' : 'No' + const assignedToMoreThanOneVolunteer = (data[5] !== '' && data[5].split(',').length > 1) ? 'Yes' : 'No' + const assignedToTransitionYouth = data[4] + const caseNumberPrefix = possibleCaseNumberPrefixes.includes(data[0].split('-')[0]) ? data[0].split('-')[0] : 'None' + + return statusArray.includes(status) && + assignedToVolunteerArray.includes(assignedToVolunteer) && + assignedToMoreThanOneVolunteerArray.includes(assignedToMoreThanOneVolunteer) && + assignedToTransitionYouthArray.includes(assignedToTransitionYouth) && + caseNumberPrefixArray.includes(caseNumberPrefix) + } + ) + + const handleAjaxError = e => { + console.error(e) + if (e.responseJSON && e.responseJSON.error) { + alert(e.responseJSON.error) + } else { + const responseErrorMessage = e.response.statusText + ? `\n${e.response.statusText}\n` + : '' + + alert(`Sorry, try that again?\n${responseErrorMessage}\nIf you're seeing a problem, please fill out the Report A Site Issue + link to the bottom left near your email address.`) + } + } + + // Enable all data tables on dashboard but only filter on volunteers table + const editSupervisorPath = id => `/supervisors/${id}/edit` + const editVolunteerPath = id => `/volunteers/${id}/edit` + const impersonateVolunteerPath = id => `/volunteers/${id}/impersonate` + const casaCasePath = id => `/casa_cases/${id}` + const volunteersTable = $('table#volunteers').DataTable({ + autoWidth: false, + stateSave: true, + initComplete: function (settings, json) { + this.api().columns().every(function (index) { + const columnVisible = this.visible() + return $('#visibleColumns input[data-column="' + index + '"]').prop('checked', columnVisible) + }) + }, + stateSaveCallback: function (settings, data) { + $.ajax({ + url: '/preference_sets/table_state_update/' + settings.nTable.id + '_table', + + data: { + table_state: JSON.stringify(data) + }, + dataType: 'json', + type: 'POST', + error: function (jqXHR, textStatus, errorMessage) { + console.error(errorMessage) + pageNotifier.notify('Error while saving preferences', 'error') + } + }) + }, + stateSaveParams: function (settings, data) { + data.search.search = '' + return data + }, + stateLoadCallback: function (settings, callback) { + $.ajax({ + url: '/preference_sets/table_state/' + settings.nTable.id + '_table', + dataType: 'json', + type: 'GET', + success: function (json) { + callback(json) + } + }) + }, + order: [[7, 'desc']], + select: { + style: 'multi' + }, + columns: [ + { + data: 'id', + targets: 0, + searchable: false, + orderable: false, + checkboxes: { + selectRow: true, + stateSave: false + } + }, + { + name: 'display_name', + render: (data, type, row, meta) => { + return ` + Name + + ${row.display_name || row.email} + + ` + } + }, + { + name: 'email', + render: (data, type, row, meta) => row.email + }, + { + className: 'supervisor-column', + name: 'supervisor_name', + render: (data, type, row, meta) => { + return row.supervisor.id + ? ` + Supervisor + + ${row.supervisor.name} + + ` + : '' + } + }, + { + name: 'active', + render: (data, type, row, meta) => { + return ` + Status + ${row.active === 'true' ? 'Active' : 'Inactive'} + ` + }, + searchable: false + }, + { + name: 'has_transition_aged_youth_cases', + render: (data, type, row, meta) => { + return ` + Assigned to Transitioned Aged Youth + ${row.has_transition_aged_youth_cases === 'true' ? 'Yes πŸ¦‹' : 'No πŸ›'}` + }, + searchable: false + }, + { + name: 'casa_cases', + render: (data, type, row, meta) => { + const links = row.casa_cases.map(casaCase => { + return ` + ${casaCase.case_number} + ` + }) + const caseNumbers = ` + Case Number(s) + ${links.join(', ')} + ` + return caseNumbers + }, + orderable: false + }, + { + name: 'most_recent_attempt_occurred_at', + render: (data, type, row, meta) => { + return row.most_recent_attempt.case_id + ? ` + Last Attempted Contact + + ${row.most_recent_attempt.occurred_at} + + ` + : 'None ❌' + }, + searchable: false + }, + { + name: 'contacts_made_in_past_days', + render: (data, type, row, meta) => { + return ` + Contacts + ${row.contacts_made_in_past_days} + ` + }, + searchable: false + }, + { + name: 'hours_spent_in_days', + render: (data, type, row, meta) => { + return ` + Hours spent in last 30 days + ${row.hours_spent_in_days} + ` + }, + searchable: false + }, + { + name: 'has_any_extra_languages ', + render: (data, type, row, meta) => { + const languages = row.extra_languages.map(x => x.name).join(', ') + return row.extra_languages.length > 0 ? `🌎` : '' + }, + searchable: false + }, + { + name: 'actions', + orderable: false, + render: (data, type, row, meta) => { + return ` + Actions + + Edit + + + Impersonate + + ` + }, + searchable: false + } + ], + processing: true, + serverSide: true, + ajax: { + url: $('table#volunteers').data('source'), + type: 'POST', + data: function (d) { + const supervisorOptions = $('.supervisor-options input:checked') + const supervisorFilter = Array.from(supervisorOptions).map(option => option.dataset.value) + + const statusOptions = $('.status-options input:checked') + const statusFilter = Array.from(statusOptions).map(option => JSON.parse(option.dataset.value)) + + const transitionYouthOptions = $('.transition-youth-options input:checked') + const transitionYouthFilter = Array.from(transitionYouthOptions).map(option => JSON.parse(option.dataset.value)) + + const extraLanguageOptions = $('.extra-language-options input:checked') + const extraLanguageFilter = Array.from(extraLanguageOptions).map(option => JSON.parse(option.dataset.value)) + return $.extend({}, d, { + additional_filters: { + supervisor: supervisorFilter, + active: statusFilter, + transition_aged_youth: transitionYouthFilter, + extra_languages: extraLanguageFilter + } + }) + }, + error: handleAjaxError, + dataType: 'json' + }, + drawCallback: function (settings) { + $('[data-toggle=tooltip]').tooltip() + } + }) + + $('#form-bulk-assignment').on('submit', function (e) { + const form = this + const rowsSelected = volunteersTable.column(0).checkboxes.selected() + + $.each(rowsSelected, function (index, rowId) { + $(form).append( + $('') + .attr('type', 'hidden') + .attr('name', 'supervisor_volunteer[volunteer_ids][]') + .val(rowId) + ) + }) + }) + + volunteersTable.column(0).on('change', function () { + const rowsSelected = volunteersTable.column(0).checkboxes.selected() + if (rowsSelected.count() === 0) { + $('#volunteers-selected').html('') + } else { + $('#volunteers-selected').html('s (' + rowsSelected.count() + ')') + } + }) + + // Because the table saves state, we have to check/uncheck modal inputs based on what + // columns are visible + volunteersTable.columns().every(function (index) { + const columnVisible = this.visible() + $('#visibleColumns input[data-column="' + index + '"]').prop('checked', columnVisible) + return true + }) + + // Add Supervisors Table + const supervisorsTable = $('table#supervisors').DataTable({ + autoWidth: false, + stateSave: false, + order: [[1, 'asc']], // order by cast contacts + columns: [ + { + name: 'display_name', + className: 'min-width', + render: (data, type, row, meta) => { + return ` + + ${row.display_name || row.email} + + ` + } + }, + { + name: '', + className: 'min-width', + render: (data, type, row, meta) => { + const noContactVolunteers = Number(row.no_attempt_for_two_weeks) + const transitionAgedCaseVolunteers = Number(row.transitions_volunteers) + const activeContactVolunteers = Number(row.volunteer_assignments) - noContactVolunteers + const activeContactElement = activeContactVolunteers + ? ( + ` + + ${activeContactVolunteers} + + ` + ) + : '' + + const noContactElement = noContactVolunteers > 0 + ? ( + ` + + ${noContactVolunteers} + + ` + ) + : '' + + let volunteersCounterElement = '' + if (activeContactVolunteers <= 0 && noContactVolunteers <= 0) { + volunteersCounterElement = 'No assigned volunteers' + } else { + volunteersCounterElement = `${transitionAgedCaseVolunteers}` + } + + return ` +
+ ${activeContactElement + noContactElement + volunteersCounterElement} +
+ ` + } + }, + { + name: 'actions', + orderable: false, + render: (data, type, row, meta) => { + return ` + +
+ +
+
+ ` + }, + searchable: false + } + ], + processing: true, + serverSide: true, + ajax: { + url: $('table#supervisors').data('source'), + type: 'POST', + data: function (d) { + const statusOptions = $('.status-options input:checked') + const statusFilter = Array.from(statusOptions).map(option => JSON.parse(option.dataset.value)) + + return $.extend({}, d, { + additional_filters: { + active: statusFilter + } + }) + }, + error: handleAjaxError, + dataType: 'json' + }, + drawCallback: function (settings) { + $('[data-toggle=tooltip]').tooltip() + }, + createdRow: function (row, data, dataIndex, cells) { + row.setAttribute('id', `supervisor-${data.id}-information`) + } + }) + + const casaCasesTable = $('table#casa-cases').DataTable({ + autoWidth: false, + stateSave: false, + columnDefs: [], + language: { + emptyTable: 'No active cases' + } + }) + + casaCasesTable.columns().every(function (index) { + const columnVisible = this.visible() + + if (columnVisible) { + $('#visibleColumns input[data-column="' + index + '"]').prop('checked', true) + } else { + $('#visibleColumns input[data-column="' + index + '"]').prop('checked', false) + } + + return true + }) + + defineCaseContactsTable() + + function filterOutUnassignedVolunteers (checked) { + $('.supervisor-options').find('input[type="checkbox"]').not('#unassigned-vol-filter').each(function () { + this.checked = checked + }) + } + + $('#unassigned-vol-filter').on('click', function () { + if ($('#unassigned-vol-filter').is(':checked')) { + filterOutUnassignedVolunteers(false) + } else { + filterOutUnassignedVolunteers(true) + } + volunteersTable.draw() + }) + + $('.volunteer-filters input[type="checkbox"]').not('#unassigned-vol-filter').on('click', function () { + volunteersTable.draw() + }) + + $('.supervisor-filters input[type="checkbox"]').on('click', function () { + supervisorsTable.draw() + }) + + $('.casa-case-filters input[type="checkbox"]').on('click', function () { + casaCasesTable.draw() + }) + + $('input.toggle-visibility').on('click', function (e) { + // Get the column API object and toggle the visibility + const column = volunteersTable.column($(this).attr('data-column')) + column.visible(!column.visible()) + volunteersTable.columns.adjust().draw() + + const caseColumn = casaCasesTable.column($(this).attr('data-column')) + caseColumn.visible(!caseColumn.visible()) + casaCasesTable.columns.adjust().draw() + }) +}) + +export { defineCaseContactsTable } diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 2b1c62859f..2525e6b57c 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -5,6 +5,7 @@ ); @use "bootstrap-datepicker/dist/css/bootstrap-datepicker"; @use "datatables.net-dt/css/jquery.dataTables"; +@use "jquery-datatables-checkboxes/css/dataTables.checkboxes"; @use "@fortawesome/fontawesome-free/scss/fontawesome"; @use "@fortawesome/fontawesome-free/scss/solid"; @use "select2/dist/css/select2"; diff --git a/app/assets/stylesheets/pages/volunteers.scss b/app/assets/stylesheets/pages/volunteers.scss index 683a4b7f95..2d044fbd6a 100644 --- a/app/assets/stylesheets/pages/volunteers.scss +++ b/app/assets/stylesheets/pages/volunteers.scss @@ -18,3 +18,16 @@ body.volunteers { } } } + + +table#volunteers.dataTable.hover tbody tr.selected > * { + background-color: rgba(54, 92, 245, 0.1); + box-shadow: none; + color: inherit; +} + +table#volunteers.dataTable.hover > tbody > tr.selected:hover > * { + background-color: rgba(54, 92, 245, 0.15); + box-shadow: none !important; + color: inherit; +} diff --git a/app/assets/stylesheets/shared/header.scss b/app/assets/stylesheets/shared/header.scss new file mode 100644 index 0000000000..b4902edd78 --- /dev/null +++ b/app/assets/stylesheets/shared/header.scss @@ -0,0 +1,23 @@ +@use "../base/breakpoints.scss" as screen-sizes; + +.header { + .header-left { + .menu-toggle-btn { + display: none; + + .main-btn { + border-radius: 4px !important; + } + } + } +} + +@media only screen and (max-width: screen-sizes.$mobile) { + .header { + .header-left { + .menu-toggle-btn { + display: block; + } + } + } +} diff --git a/app/controllers/supervisor_volunteers_controller.rb b/app/controllers/supervisor_volunteers_controller.rb index a3f9616d6b..691a3e2961 100644 --- a/app/controllers/supervisor_volunteers_controller.rb +++ b/app/controllers/supervisor_volunteers_controller.rb @@ -25,13 +25,67 @@ def unassign redirect_to request.referer, notice: flash_message end + def bulk_assignment + authorize :supervisor_volunteer + if mass_assign_volunteers? + volunteer_ids = supervisor_volunteer_params[:volunteer_ids] + supervisor = supervisor_volunteer_params[:supervisor_id] + vol = "Volunteer".pluralize(volunteer_ids.length) + + if supervisor == "unassign" + name_array = bulk_unassign!(volunteer_ids) + flash_message = "#{vol} #{name_array.to_sentence} successfully unassigned" + else + supervisor = supervisor_volunteer_parent + name_array = bulk_assign!(supervisor, volunteer_ids) + flash_message = "#{vol} #{name_array.to_sentence} successfully reassigned to #{supervisor.display_name}" + end + + redirect_to volunteers_path, notice: flash_message + else + redirect_to volunteers_path, notice: "Please select at least one volunteer and one supervisor." + end + end + private def supervisor_volunteer_params - params.require(:supervisor_volunteer).permit(:supervisor_id, :volunteer_id) + params.require(:supervisor_volunteer).permit(:supervisor_id, :volunteer_id, volunteer_ids: []) end def supervisor_volunteer_parent Supervisor.find(params[:supervisor_id] || supervisor_volunteer_params[:supervisor_id]) end + + def mass_assign_volunteers? + supervisor_volunteer_params[:volunteer_ids] && supervisor_volunteer_params[:supervisor_id] ? true : false + end + + def bulk_assign!(supervisor, volunteer_ids) + created_volunteers = [] + volunteer_ids.each do |vol_id| + if (supervisor_volunteer = SupervisorVolunteer.find_by(volunteer_id: vol_id.to_i)) + supervisor_volunteer.update!(supervisor_id: supervisor.id) + else + supervisor_volunteer = supervisor.supervisor_volunteers.create(volunteer_id: vol_id.to_i) + end + supervisor_volunteer.is_active = true + volunteer = supervisor_volunteer.volunteer + supervisor_volunteer.save + created_volunteers << volunteer.display_name.to_s + end + created_volunteers + end + + def bulk_unassign!(volunteer_ids) + unassigned_volunteers = [] + volunteer_ids.each do |vol_id| + supervisor_volunteer = SupervisorVolunteer.find_by(volunteer_id: vol_id.to_i) + supervisor_volunteer.update(is_active: false) + volunteer = supervisor_volunteer.volunteer + supervisor_volunteer.save + unassigned_volunteers << volunteer.display_name.to_s # take into account single assignments and give multiple assignments proper format + end + unassigned_volunteers + end end diff --git a/app/controllers/volunteers_controller.rb b/app/controllers/volunteers_controller.rb index 8790dae554..0a3f9d6fa7 100644 --- a/app/controllers/volunteers_controller.rb +++ b/app/controllers/volunteers_controller.rb @@ -6,6 +6,7 @@ class VolunteersController < ApplicationController def index authorize Volunteer + @supervisors = policy_scope(current_organization.supervisors) end def show diff --git a/app/javascript/application.js b/app/javascript/application.js index c661eaf523..4cbefd7c25 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -8,6 +8,8 @@ import 'trix' import '@rails/actiontext' require('datatables.net-dt')(null, window.jQuery) // First parameter is the global object. Defaults to window if null +require('datatables.net-select')(null, window.jQuery) +require('jquery-datatables-checkboxes')(null, window.jQuery) require('select2')(window.jQuery) require('@rails/ujs').start() require('@rails/activestorage').start() diff --git a/app/javascript/src/dashboard.js b/app/javascript/src/dashboard.js index 0edc294589..d2d121e5ba 100644 --- a/app/javascript/src/dashboard.js +++ b/app/javascript/src/dashboard.js @@ -134,8 +134,21 @@ $(() => { // JQuery's callback for the DOM loading } }) }, - order: [[6, 'desc']], + order: [[7, 'desc']], + select: { + style: 'multi' + }, columns: [ + { + data: 'id', + targets: 0, + searchable: false, + orderable: false, + checkboxes: { + selectRow: true, + stateSave: false + } + }, { name: 'display_name', render: (data, type, row, meta) => { @@ -293,6 +306,29 @@ $(() => { // JQuery's callback for the DOM loading } }) + $('#form-bulk-assignment').on('submit', function (e) { + const form = this + const rowsSelected = volunteersTable.column(0).checkboxes.selected() + + $.each(rowsSelected, function (index, rowId) { + $(form).append( + $('') + .attr('type', 'hidden') + .attr('name', 'supervisor_volunteer[volunteer_ids][]') + .val(rowId) + ) + }) + }) + + volunteersTable.column(0).on('change', function () { + const rowsSelected = volunteersTable.column(0).checkboxes.selected() + if (rowsSelected.count() === 0) { + $('#volunteers-selected').html('') + } else { + $('#volunteers-selected').html('s (' + rowsSelected.count() + ')') + } + }) + // Because the table saves state, we have to check/uncheck modal inputs based on what // columns are visible volunteersTable.columns().every(function (index) { diff --git a/app/models/volunteer.rb b/app/models/volunteer.rb index 1304988afa..52440d3a5d 100644 --- a/app/models/volunteer.rb +++ b/app/models/volunteer.rb @@ -3,6 +3,7 @@ class Volunteer < User devise :invitable, invite_for: 1.year + BULK_COLUMN = "bulk" NAME_COLUMN = "name" EMAIL_COLUMN = "email" SUPERVISOR_COLUMN = "supervisor" @@ -16,6 +17,7 @@ class Volunteer < User EXTRA_LANGUAGES_COLUMN = "has_any_extra_languages" ACTIONS_COLUMN = "actions" TABLE_COLUMNS = [ + BULK_COLUMN, NAME_COLUMN, EMAIL_COLUMN, SUPERVISOR_COLUMN, diff --git a/app/policies/supervisor_volunteer_policy.rb b/app/policies/supervisor_volunteer_policy.rb index 3f065a6e8e..3391673264 100644 --- a/app/policies/supervisor_volunteer_policy.rb +++ b/app/policies/supervisor_volunteer_policy.rb @@ -6,4 +6,8 @@ def create? def unassign? user.casa_admin? || user.supervisor? end + + def bulk_assignment? + user.casa_admin? || user.supervisor? + end end diff --git a/app/views/supervisors/index.html.erb b/app/views/supervisors/index.html.erb index a3dc412b42..e8bcbd6855 100644 --- a/app/views/supervisors/index.html.erb +++ b/app/views/supervisors/index.html.erb @@ -129,18 +129,14 @@ - - <% @available_volunteers.each_with_index do |volunteer, index| %> + <% @available_volunteers.each do |volunteer| %> - diff --git a/app/views/volunteers/index.html.erb b/app/views/volunteers/index.html.erb index 6f527d4b5f..7408458f5e 100644 --- a/app/views/volunteers/index.html.erb +++ b/app/views/volunteers/index.html.erb @@ -161,32 +161,88 @@
+
Responsive Data Table
+ + +
-
Active volunteers not assigned to supervisors
Assigned to Case(s)
- <%= index + 1 %> - <%= link_to volunteer.display_name, edit_volunteer_path(volunteer) %>
- - - - - - - - - - - - - - - - - -
NameEmailSupervisorStatusAssigned To Transition Aged YouthCase Number(s)Last Attempted ContactContacts Made in Past 60 DaysHours spent in last 30 daysExtra LanguagesActions
+ <%= form_with(url: bulk_assignment_supervisor_volunteers_path, id: "form-bulk-assignment") do |form| %> + + + + + + + + + + + + + + + + + + + +
NameEmailSupervisorStatusAssigned To Transition Aged YouthCase Number(s)Last Attempted ContactContacts Made in Past 60 DaysHours spent in last 30 daysExtra LanguagesActions
+ +
+ +
+ <% end %> - + + diff --git a/config/routes.rb b/config/routes.rb index 4bf77eb278..2426b06d65 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -128,6 +128,9 @@ end end resources :supervisor_volunteers, only: %i[create] do + collection do + post :bulk_assignment + end member do patch :unassign end diff --git a/db/schema.rb b/db/schema.rb index ed5ef0a6d9..34d4d85192 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -363,6 +363,12 @@ t.index ["casa_org_id"], name: "index_judges_on_casa_org_id" end + create_table "jwt_denylist", force: :cascade do |t| + t.string "jti", null: false + t.datetime "exp", null: false + t.index ["jti"], name: "index_jwt_denylist_on_jti" + end + create_table "languages", force: :cascade do |t| t.string "name" t.bigint "casa_org_id", null: false @@ -392,7 +398,6 @@ create_table "learning_hours", force: :cascade do |t| t.bigint "user_id", null: false - t.integer "learning_type", default: 5 t.string "name", null: false t.integer "duration_minutes", null: false t.integer "duration_hours", null: false diff --git a/package.json b/package.json index 5b27bc1b00..9cbb722304 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,11 @@ "chart.js": "^4.4.0", "chartjs-adapter-luxon": "^1.3.1", "datatables.net-dt": "^1.13.6", + "datatables.net-select": "^1.7.0", "esbuild": "^0.19.4", "faker": "^5.5.3", "jquery": "^3.7.1", + "jquery-datatables-checkboxes": "^1.2.14", "js-cookie": "^3.0.5", "jstz": "^2.1.1", "lodash": "^4.17.21", diff --git a/spec/requests/supervisor_volunteers_spec.rb b/spec/requests/supervisor_volunteers_spec.rb index 88f7897f7a..3ea79675ff 100644 --- a/spec/requests/supervisor_volunteers_spec.rb +++ b/spec/requests/supervisor_volunteers_spec.rb @@ -157,4 +157,119 @@ end end end + # using describe just for clarity + describe "POST /bulk_assign" do + let!(:volunteer_1) { create(:volunteer, casa_org: casa_org) } + let!(:volunteer_2) { create(:volunteer, casa_org: casa_org) } + let!(:volunteer_3) { create(:volunteer, casa_org: casa_org) } + + context "when no pre-existing association between supervisor and volunteer exists" do + it "creates a new supervisor_volunteers association for each selected volunteer in bulk assign" do + valid_parameters = { + supervisor_volunteer: {supervisor_id: supervisor.id, volunteer_ids: [volunteer_1.id, volunteer_2.id, volunteer_3.id]} + } + sign_in(admin) + + expect { + post bulk_assignment_supervisor_volunteers_url, params: valid_parameters, headers: {HTTP_REFERER: supervisors_path.to_s} + }.to change(SupervisorVolunteer, :count).by(3) + expect(response).to redirect_to volunteers_path + end + end + + context "when an inactive association between supervisor and volunteer exists" do + let!(:association) do + create( + :supervisor_volunteer, + supervisor: supervisor, + volunteer: volunteer_1, + is_active: false + ) + end + + it "sets that association to active" do + valid_parameters = { + supervisor_volunteer: {supervisor_id: supervisor.id, volunteer_ids: [volunteer_1.id, volunteer_2.id, volunteer_3.id]} + } + sign_in(admin) + + expect { + post bulk_assignment_supervisor_volunteers_url, params: valid_parameters, headers: {HTTP_REFERER: supervisors_path.to_s} + }.to change(SupervisorVolunteer, :count).by(2) # bc 1 already exists, we are creating 2 new ones + expect(response).to redirect_to volunteers_path + + association.reload + expect(association.is_active?).to be(true) + end + end + + context "when passing the volunteer_ids as the supervisor_volunteer_params" do + let!(:association) do + create( + :supervisor_volunteer, + supervisor: supervisor, + volunteer: volunteer_1, + is_active: false + ) + end + + it "will still set the association as active while creaing two new supervisor_volunteer records" do + valid_parameters = { + supervisor_volunteer: {supervisor_id: supervisor.id, volunteer_ids: [volunteer_1.id, volunteer_2.id, volunteer_3.id]} + } + + sign_in(admin) + + expect { + post bulk_assignment_supervisor_volunteers_url, params: valid_parameters, headers: {HTTP_REFERER: supervisors_path.to_s} + }.to change(SupervisorVolunteer, :count).by(2) # bc 1 already exists, we are creating 2 new ones + expect(response).to redirect_to volunteers_path + + association.reload + expect(association.is_active?).to be(true) + end + end + end + + describe "POST /bulk_unassign" do + let!(:volunteer_1) { create(:volunteer, casa_org: casa_org) } + let!(:volunteer_2) { create(:volunteer, casa_org: casa_org) } + let!(:volunteer_3) { create(:volunteer, casa_org: casa_org) } + + context "when passing in several volunteer with selected --No supervior--" do + let!(:assigned_supervisor) { build(:supervisor, casa_org: casa_org) } + let!(:association_1) do + create( + :supervisor_volunteer, + supervisor: assigned_supervisor, + volunteer: volunteer_1, + is_active: true + ) + end + let!(:association_2) do + create( + :supervisor_volunteer, + supervisor: assigned_supervisor, + volunteer: volunteer_2, + is_active: true + ) + end + it "will set the association to inactive" do + valid_parameters = { + supervisor_volunteer: {supervisor_id: "unassign", volunteer_ids: [volunteer_1.id, volunteer_2.id]} + } + sign_in(admin) + + expect { + post bulk_assignment_supervisor_volunteers_url, params: valid_parameters, headers: {HTTP_REFERER: supervisors_path.to_s} + }.not_to change(SupervisorVolunteer, :count) + expect(response).to redirect_to volunteers_path + + association_1.reload + association_2.reload + expect(association_1.is_active?).to be(false) + expect(association_2.is_active?).to be(false) + end + end + end end diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index b37c72464b..e6253e1d14 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -175,6 +175,54 @@ expect(supervisor_cell.text).to eq "" end + + context "when bulk assignments" do + it "displays supervisor's name when multiple volunteers assigned supervisor", js: true do + name = "Superduper Visor" + supervisor = create(:supervisor, display_name: name, casa_org: organization) + create(:volunteer, casa_org: organization) + create(:volunteer, casa_org: organization) + sign_in admin + + visit volunteers_path + + find("tr.odd").click + find("tr.even").click + click_on "Manage Volunteer" + select supervisor.display_name, from: "supervisor_volunteer[supervisor_id]" + + click_on "Confirm" + + supervisor_cell1 = page.find("tbody .odd .supervisor-column") + supervisor_cell2 = page.find("tbody .even .supervisor-column") + + expect(supervisor_cell1.text).to eq supervisor.display_name + expect(supervisor_cell2.text).to eq supervisor.display_name + end + + it "displays no supervisor name when multiple volunteers unassigned supervisor", js: true do + name = "Superduper Visor" + supervisor = create(:supervisor, display_name: name, casa_org: organization) + create(:volunteer, supervisor: supervisor, casa_org: organization) + create(:volunteer, supervisor: supervisor, casa_org: organization) + sign_in admin + + visit volunteers_path + + find("tr.odd").click + find("tr.even").click + click_on "Manage Volunteer" + select "-- No Supervisor --", from: "supervisor_volunteer[supervisor_id]" + + click_on "Confirm" + + supervisor_cell1 = page.find("tbody .odd .supervisor-column") + supervisor_cell2 = page.find("tbody .even .supervisor-column") + + expect(supervisor_cell1.text).to eq "" + expect(supervisor_cell2.text).to eq "" + end + end end context "when timed out" do diff --git a/yarn.lock b/yarn.lock index a72b4d062d..28121c1572 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2910,6 +2910,21 @@ datatables.net-dt@^1.13.6: datatables.net ">=1.13.4" jquery ">=1.7" +datatables.net-select@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/datatables.net-select/-/datatables.net-select-1.7.0.tgz#a108752ee6109a49392d19fec7406c953be11665" + integrity sha512-ps8eL8S2gUV7EdzMraw8mfQlHXpfuc8TC2onBxdk0snP8eizPe85VhpI3r4ULvPRTTI7vcViz8E7JV8aayA2lw== + dependencies: + datatables.net ">=1.13.4" + jquery ">=1.7" + +datatables.net@>=1.10.8: + version "1.13.6" + resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.13.6.tgz#6e282adbbb2732e8df495611b8bb54e19f7a943e" + integrity sha512-rHNcnW+yEP9me82/KmRcid5eKrqPqW3+I/p1TwqCW3c/7GRYYkDyF6aJQOQ9DNS/pw+nyr4BVpjyJ3yoZXiFPg== + dependencies: + jquery ">=1.7" + datatables.net@>=1.13.4: version "1.13.5" resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.13.5.tgz#790a3d70d5e103f5465ed8c52a50eb242e1e2dc4" @@ -4980,6 +4995,14 @@ joi@^17.7.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" +jquery-datatables-checkboxes@^1.2.14: + version "1.2.14" + resolved "https://registry.yarnpkg.com/jquery-datatables-checkboxes/-/jquery-datatables-checkboxes-1.2.14.tgz#5bada981140b9ba645e80d6c2caa8bbbb4123936" + integrity sha512-99B6PmS3ZdinDyqF5vBiykd8B+NohHRBBs/a5/hrYRlsBxbWqQ2KtigSvAc2qFdxPOyrcCV75nWBYnlrtdCGgg== + dependencies: + datatables.net ">=1.10.8" + jquery ">=1.7" + jquery@>=1.7, "jquery@>=3.4.0 <4.0.0", jquery@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" From 409aedcc3408e8107bce39e4f36df077febd8701 Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Sat, 13 Jan 2024 13:13:14 -0500 Subject: [PATCH 02/21] Assign Multiple Volunteers // major redesign --- 3 | 503 ------------------ app/assets/stylesheets/application.scss | 1 - .../supervisor_volunteers_controller.rb | 94 ++-- app/javascript/application.js | 2 - .../controllers/disable_form_controller.js | 29 + app/javascript/controllers/index.js | 6 + .../controllers/select_all_controller.js | 69 +++ app/javascript/src/dashboard.js | 37 +- app/views/volunteers/index.html.erb | 27 +- package.json | 2 - spec/requests/supervisor_volunteers_spec.rb | 154 ++---- spec/system/volunteers/index_spec.rb | 191 +++++-- yarn.lock | 23 - 13 files changed, 380 insertions(+), 758 deletions(-) delete mode 100644 3 create mode 100644 app/javascript/controllers/disable_form_controller.js create mode 100644 app/javascript/controllers/select_all_controller.js diff --git a/3 b/3 deleted file mode 100644 index e45619a65e..0000000000 --- a/3 +++ /dev/null @@ -1,503 +0,0 @@ -/* global alert */ -/* global $ */ -const AsyncNotifier = require('../src/async_notifier') -let pageNotifier - -const defineCaseContactsTable = function () { - $('table#case_contacts').DataTable( - { - scrollX: true, - searching: false, - order: [[0, 'desc']] - } - ) -} - -$(() => { // JQuery's callback for the DOM loading - const asyncNotificationsElement = $('#async-notifications') - - if (asyncNotificationsElement.length) { - pageNotifier = new AsyncNotifier(asyncNotificationsElement) - } - - $.fn.dataTable.ext.search.push( - function (settings, data, dataIndex) { - if (settings.nTable.id !== 'casa-cases') { - return true - } - - const statusArray = [] - const assignedToVolunteerArray = [] - const assignedToMoreThanOneVolunteerArray = [] - const assignedToTransitionYouthArray = [] - const caseNumberPrefixArray = [] - - $('.status-options').find('input[type="checkbox"]').each(function () { - if ($(this).is(':checked')) { - statusArray.push($(this).data('value')) - } - }) - - $('.assigned-to-volunteer-options').find('input[type="checkbox"]').each(function () { - if ($(this).is(':checked')) { - assignedToVolunteerArray.push($(this).data('value')) - } - }) - - $('.more-than-one-volunteer-options').find('input[type="checkbox"]').each(function () { - if ($(this).is(':checked')) { - assignedToMoreThanOneVolunteerArray.push($(this).data('value')) - } - }) - - $('.transition-youth-options').find('input[type="checkbox"]').each(function () { - if ($(this).is(':checked')) { - assignedToTransitionYouthArray.push($(this).data('value')) - } - }) - - $('.case-number-prefix-options').find('input[type="checkbox"]').each(function () { - if ($(this).is(':checked')) { - caseNumberPrefixArray.push($(this).data('value')) - } - }) - - const possibleCaseNumberPrefixes = ['CINA', 'TPR'] - const status = data[3] - const assignedToVolunteer = (data[5] !== '' && data[5].split(',').length >= 1) ? 'Yes' : 'No' - const assignedToMoreThanOneVolunteer = (data[5] !== '' && data[5].split(',').length > 1) ? 'Yes' : 'No' - const assignedToTransitionYouth = data[4] - const caseNumberPrefix = possibleCaseNumberPrefixes.includes(data[0].split('-')[0]) ? data[0].split('-')[0] : 'None' - - return statusArray.includes(status) && - assignedToVolunteerArray.includes(assignedToVolunteer) && - assignedToMoreThanOneVolunteerArray.includes(assignedToMoreThanOneVolunteer) && - assignedToTransitionYouthArray.includes(assignedToTransitionYouth) && - caseNumberPrefixArray.includes(caseNumberPrefix) - } - ) - - const handleAjaxError = e => { - console.error(e) - if (e.responseJSON && e.responseJSON.error) { - alert(e.responseJSON.error) - } else { - const responseErrorMessage = e.response.statusText - ? `\n${e.response.statusText}\n` - : '' - - alert(`Sorry, try that again?\n${responseErrorMessage}\nIf you're seeing a problem, please fill out the Report A Site Issue - link to the bottom left near your email address.`) - } - } - - // Enable all data tables on dashboard but only filter on volunteers table - const editSupervisorPath = id => `/supervisors/${id}/edit` - const editVolunteerPath = id => `/volunteers/${id}/edit` - const impersonateVolunteerPath = id => `/volunteers/${id}/impersonate` - const casaCasePath = id => `/casa_cases/${id}` - const volunteersTable = $('table#volunteers').DataTable({ - autoWidth: false, - stateSave: true, - initComplete: function (settings, json) { - this.api().columns().every(function (index) { - const columnVisible = this.visible() - return $('#visibleColumns input[data-column="' + index + '"]').prop('checked', columnVisible) - }) - }, - stateSaveCallback: function (settings, data) { - $.ajax({ - url: '/preference_sets/table_state_update/' + settings.nTable.id + '_table', - - data: { - table_state: JSON.stringify(data) - }, - dataType: 'json', - type: 'POST', - error: function (jqXHR, textStatus, errorMessage) { - console.error(errorMessage) - pageNotifier.notify('Error while saving preferences', 'error') - } - }) - }, - stateSaveParams: function (settings, data) { - data.search.search = '' - return data - }, - stateLoadCallback: function (settings, callback) { - $.ajax({ - url: '/preference_sets/table_state/' + settings.nTable.id + '_table', - dataType: 'json', - type: 'GET', - success: function (json) { - callback(json) - } - }) - }, - order: [[7, 'desc']], - select: { - style: 'multi' - }, - columns: [ - { - data: 'id', - targets: 0, - searchable: false, - orderable: false, - checkboxes: { - selectRow: true, - stateSave: false - } - }, - { - name: 'display_name', - render: (data, type, row, meta) => { - return ` - Name - - ${row.display_name || row.email} - - ` - } - }, - { - name: 'email', - render: (data, type, row, meta) => row.email - }, - { - className: 'supervisor-column', - name: 'supervisor_name', - render: (data, type, row, meta) => { - return row.supervisor.id - ? ` - Supervisor - - ${row.supervisor.name} - - ` - : '' - } - }, - { - name: 'active', - render: (data, type, row, meta) => { - return ` - Status - ${row.active === 'true' ? 'Active' : 'Inactive'} - ` - }, - searchable: false - }, - { - name: 'has_transition_aged_youth_cases', - render: (data, type, row, meta) => { - return ` - Assigned to Transitioned Aged Youth - ${row.has_transition_aged_youth_cases === 'true' ? 'Yes πŸ¦‹' : 'No πŸ›'}` - }, - searchable: false - }, - { - name: 'casa_cases', - render: (data, type, row, meta) => { - const links = row.casa_cases.map(casaCase => { - return ` - ${casaCase.case_number} - ` - }) - const caseNumbers = ` - Case Number(s) - ${links.join(', ')} - ` - return caseNumbers - }, - orderable: false - }, - { - name: 'most_recent_attempt_occurred_at', - render: (data, type, row, meta) => { - return row.most_recent_attempt.case_id - ? ` - Last Attempted Contact - - ${row.most_recent_attempt.occurred_at} - - ` - : 'None ❌' - }, - searchable: false - }, - { - name: 'contacts_made_in_past_days', - render: (data, type, row, meta) => { - return ` - Contacts - ${row.contacts_made_in_past_days} - ` - }, - searchable: false - }, - { - name: 'hours_spent_in_days', - render: (data, type, row, meta) => { - return ` - Hours spent in last 30 days - ${row.hours_spent_in_days} - ` - }, - searchable: false - }, - { - name: 'has_any_extra_languages ', - render: (data, type, row, meta) => { - const languages = row.extra_languages.map(x => x.name).join(', ') - return row.extra_languages.length > 0 ? `🌎` : '' - }, - searchable: false - }, - { - name: 'actions', - orderable: false, - render: (data, type, row, meta) => { - return ` - Actions - - Edit - - - Impersonate - - ` - }, - searchable: false - } - ], - processing: true, - serverSide: true, - ajax: { - url: $('table#volunteers').data('source'), - type: 'POST', - data: function (d) { - const supervisorOptions = $('.supervisor-options input:checked') - const supervisorFilter = Array.from(supervisorOptions).map(option => option.dataset.value) - - const statusOptions = $('.status-options input:checked') - const statusFilter = Array.from(statusOptions).map(option => JSON.parse(option.dataset.value)) - - const transitionYouthOptions = $('.transition-youth-options input:checked') - const transitionYouthFilter = Array.from(transitionYouthOptions).map(option => JSON.parse(option.dataset.value)) - - const extraLanguageOptions = $('.extra-language-options input:checked') - const extraLanguageFilter = Array.from(extraLanguageOptions).map(option => JSON.parse(option.dataset.value)) - return $.extend({}, d, { - additional_filters: { - supervisor: supervisorFilter, - active: statusFilter, - transition_aged_youth: transitionYouthFilter, - extra_languages: extraLanguageFilter - } - }) - }, - error: handleAjaxError, - dataType: 'json' - }, - drawCallback: function (settings) { - $('[data-toggle=tooltip]').tooltip() - } - }) - - $('#form-bulk-assignment').on('submit', function (e) { - const form = this - const rowsSelected = volunteersTable.column(0).checkboxes.selected() - - $.each(rowsSelected, function (index, rowId) { - $(form).append( - $('') - .attr('type', 'hidden') - .attr('name', 'supervisor_volunteer[volunteer_ids][]') - .val(rowId) - ) - }) - }) - - volunteersTable.column(0).on('change', function () { - const rowsSelected = volunteersTable.column(0).checkboxes.selected() - if (rowsSelected.count() === 0) { - $('#volunteers-selected').html('') - } else { - $('#volunteers-selected').html('s (' + rowsSelected.count() + ')') - } - }) - - // Because the table saves state, we have to check/uncheck modal inputs based on what - // columns are visible - volunteersTable.columns().every(function (index) { - const columnVisible = this.visible() - $('#visibleColumns input[data-column="' + index + '"]').prop('checked', columnVisible) - return true - }) - - // Add Supervisors Table - const supervisorsTable = $('table#supervisors').DataTable({ - autoWidth: false, - stateSave: false, - order: [[1, 'asc']], // order by cast contacts - columns: [ - { - name: 'display_name', - className: 'min-width', - render: (data, type, row, meta) => { - return ` - - ${row.display_name || row.email} - - ` - } - }, - { - name: '', - className: 'min-width', - render: (data, type, row, meta) => { - const noContactVolunteers = Number(row.no_attempt_for_two_weeks) - const transitionAgedCaseVolunteers = Number(row.transitions_volunteers) - const activeContactVolunteers = Number(row.volunteer_assignments) - noContactVolunteers - const activeContactElement = activeContactVolunteers - ? ( - ` - - ${activeContactVolunteers} - - ` - ) - : '' - - const noContactElement = noContactVolunteers > 0 - ? ( - ` - - ${noContactVolunteers} - - ` - ) - : '' - - let volunteersCounterElement = '' - if (activeContactVolunteers <= 0 && noContactVolunteers <= 0) { - volunteersCounterElement = 'No assigned volunteers' - } else { - volunteersCounterElement = `${transitionAgedCaseVolunteers}` - } - - return ` -
- ${activeContactElement + noContactElement + volunteersCounterElement} -
- ` - } - }, - { - name: 'actions', - orderable: false, - render: (data, type, row, meta) => { - return ` - -
- -
-
- ` - }, - searchable: false - } - ], - processing: true, - serverSide: true, - ajax: { - url: $('table#supervisors').data('source'), - type: 'POST', - data: function (d) { - const statusOptions = $('.status-options input:checked') - const statusFilter = Array.from(statusOptions).map(option => JSON.parse(option.dataset.value)) - - return $.extend({}, d, { - additional_filters: { - active: statusFilter - } - }) - }, - error: handleAjaxError, - dataType: 'json' - }, - drawCallback: function (settings) { - $('[data-toggle=tooltip]').tooltip() - }, - createdRow: function (row, data, dataIndex, cells) { - row.setAttribute('id', `supervisor-${data.id}-information`) - } - }) - - const casaCasesTable = $('table#casa-cases').DataTable({ - autoWidth: false, - stateSave: false, - columnDefs: [], - language: { - emptyTable: 'No active cases' - } - }) - - casaCasesTable.columns().every(function (index) { - const columnVisible = this.visible() - - if (columnVisible) { - $('#visibleColumns input[data-column="' + index + '"]').prop('checked', true) - } else { - $('#visibleColumns input[data-column="' + index + '"]').prop('checked', false) - } - - return true - }) - - defineCaseContactsTable() - - function filterOutUnassignedVolunteers (checked) { - $('.supervisor-options').find('input[type="checkbox"]').not('#unassigned-vol-filter').each(function () { - this.checked = checked - }) - } - - $('#unassigned-vol-filter').on('click', function () { - if ($('#unassigned-vol-filter').is(':checked')) { - filterOutUnassignedVolunteers(false) - } else { - filterOutUnassignedVolunteers(true) - } - volunteersTable.draw() - }) - - $('.volunteer-filters input[type="checkbox"]').not('#unassigned-vol-filter').on('click', function () { - volunteersTable.draw() - }) - - $('.supervisor-filters input[type="checkbox"]').on('click', function () { - supervisorsTable.draw() - }) - - $('.casa-case-filters input[type="checkbox"]').on('click', function () { - casaCasesTable.draw() - }) - - $('input.toggle-visibility').on('click', function (e) { - // Get the column API object and toggle the visibility - const column = volunteersTable.column($(this).attr('data-column')) - column.visible(!column.visible()) - volunteersTable.columns.adjust().draw() - - const caseColumn = casaCasesTable.column($(this).attr('data-column')) - caseColumn.visible(!caseColumn.visible()) - casaCasesTable.columns.adjust().draw() - }) -}) - -export { defineCaseContactsTable } diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 269be48d53..4f45087b59 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -5,7 +5,6 @@ ); @use "bootstrap-datepicker/dist/css/bootstrap-datepicker"; @use "datatables.net-dt/css/jquery.dataTables"; -@use "jquery-datatables-checkboxes/css/dataTables.checkboxes"; @use "@fortawesome/fontawesome-free/scss/fontawesome"; @use "@fortawesome/fontawesome-free/scss/solid"; @use "select2/dist/css/select2"; diff --git a/app/controllers/supervisor_volunteers_controller.rb b/app/controllers/supervisor_volunteers_controller.rb index 691a3e2961..344af4f849 100644 --- a/app/controllers/supervisor_volunteers_controller.rb +++ b/app/controllers/supervisor_volunteers_controller.rb @@ -3,48 +3,42 @@ class SupervisorVolunteersController < ApplicationController def create authorize :supervisor_volunteer - supervisor_volunteer = supervisor_volunteer_parent.supervisor_volunteers.find_or_create_by!(supervisor_volunteer_params) - supervisor_volunteer.is_active = true unless supervisor_volunteer&.is_active? - volunteer = supervisor_volunteer.volunteer - supervisor = supervisor_volunteer.supervisor - supervisor_volunteer.save - flash_message = "#{volunteer.display_name} successfully assigned to #{supervisor.display_name}." + volunteer = Volunteer.find(supervisor_volunteer_params[:volunteer_id]) + supervisor = set_supervisor + if assign_volunteer_to_supervisor(volunteer, supervisor) + flash[:notice] = "#{volunteer.display_name} successfully assigned to #{supervisor.display_name}." + else + flash[:alert] = "Something went wrong. Please try again." + end - redirect_to request.referer, notice: flash_message + redirect_to request.referer end def unassign authorize :supervisor_volunteer volunteer = Volunteer.find(params[:id]) - supervisor_volunteer = volunteer.supervisor_volunteer - supervisor = volunteer.supervisor - supervisor_volunteer.is_active = false - supervisor_volunteer.save! - flash_message = "#{volunteer.display_name} was unassigned from #{supervisor.display_name}." + supervisor = volunteer.supervisor_volunteer.supervisor + if unassign_volunteers_supervisor(volunteer) + flash[:notice] = "#{volunteer.display_name} was unassigned from #{supervisor.display_name}." + else + flash[:alert] = "Something went wrong. Please try again." + end - redirect_to request.referer, notice: flash_message + redirect_to request.referer end def bulk_assignment authorize :supervisor_volunteer - if mass_assign_volunteers? - volunteer_ids = supervisor_volunteer_params[:volunteer_ids] - supervisor = supervisor_volunteer_params[:supervisor_id] - vol = "Volunteer".pluralize(volunteer_ids.length) - if supervisor == "unassign" - name_array = bulk_unassign!(volunteer_ids) - flash_message = "#{vol} #{name_array.to_sentence} successfully unassigned" - else - supervisor = supervisor_volunteer_parent - name_array = bulk_assign!(supervisor, volunteer_ids) - flash_message = "#{vol} #{name_array.to_sentence} successfully reassigned to #{supervisor.display_name}" - end - - redirect_to volunteers_path, notice: flash_message + volunteers = policy_scope(current_organization.volunteers).where(id: params[:supervisor_volunteer][:volunteer_ids]) + supervisor = policy_scope(current_organization.supervisors).where(id: params[:supervisor_volunteer][:supervisor_id]).first + if bulk_change_supervisor(supervisor, volunteers) + flash[:notice] = "#{"Volunteer".pluralize(volunteers.count)} successfully assigned to new supervisor." else - redirect_to volunteers_path, notice: "Please select at least one volunteer and one supervisor." + flash[:alert] = "Something went wrong. The #{"volunteer".pluralize(volunteers.count)} could not be assigned." end + + redirect_to volunteers_path end private @@ -53,39 +47,29 @@ def supervisor_volunteer_params params.require(:supervisor_volunteer).permit(:supervisor_id, :volunteer_id, volunteer_ids: []) end - def supervisor_volunteer_parent + def set_supervisor Supervisor.find(params[:supervisor_id] || supervisor_volunteer_params[:supervisor_id]) end - def mass_assign_volunteers? - supervisor_volunteer_params[:volunteer_ids] && supervisor_volunteer_params[:supervisor_id] ? true : false - end - - def bulk_assign!(supervisor, volunteer_ids) - created_volunteers = [] - volunteer_ids.each do |vol_id| - if (supervisor_volunteer = SupervisorVolunteer.find_by(volunteer_id: vol_id.to_i)) - supervisor_volunteer.update!(supervisor_id: supervisor.id) - else - supervisor_volunteer = supervisor.supervisor_volunteers.create(volunteer_id: vol_id.to_i) + def bulk_change_supervisor(supervisor, volunteers) + if supervisor + volunteers.each do |volunteer| + assign_volunteer_to_supervisor(volunteer, supervisor) + end + else + volunteers.each do |volunteer| + unassign_volunteers_supervisor(volunteer) end - supervisor_volunteer.is_active = true - volunteer = supervisor_volunteer.volunteer - supervisor_volunteer.save - created_volunteers << volunteer.display_name.to_s end - created_volunteers end - def bulk_unassign!(volunteer_ids) - unassigned_volunteers = [] - volunteer_ids.each do |vol_id| - supervisor_volunteer = SupervisorVolunteer.find_by(volunteer_id: vol_id.to_i) - supervisor_volunteer.update(is_active: false) - volunteer = supervisor_volunteer.volunteer - supervisor_volunteer.save - unassigned_volunteers << volunteer.display_name.to_s # take into account single assignments and give multiple assignments proper format - end - unassigned_volunteers + def assign_volunteer_to_supervisor(volunteer, supervisor) + unassign_volunteers_supervisor(volunteer) + supervisor_volunteer = supervisor.supervisor_volunteers.find_or_create_by!(volunteer: volunteer) + supervisor_volunteer.update!(is_active: true) + end + + def unassign_volunteers_supervisor(volunteer) + volunteer.supervisor_volunteer&.update(is_active: false) end end diff --git a/app/javascript/application.js b/app/javascript/application.js index a64be11e3c..36211fe518 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -9,8 +9,6 @@ import '@rails/actiontext' import './datatable.js' require('datatables.net-dt')(null, window.jQuery) // First parameter is the global object. Defaults to window if null -require('datatables.net-select')(null, window.jQuery) -require('jquery-datatables-checkboxes')(null, window.jQuery) require('select2')(window.jQuery) require('@rails/ujs').start() require('@rails/activestorage').start() diff --git a/app/javascript/controllers/disable_form_controller.js b/app/javascript/controllers/disable_form_controller.js new file mode 100644 index 0000000000..7c6b4fdd61 --- /dev/null +++ b/app/javascript/controllers/disable_form_controller.js @@ -0,0 +1,29 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + static targets = ['submitButton', 'input'] + static values = { + unallowed: { type: Array } + } + + static classes = ['disabled', 'enabled'] + + validate () { + let invalid = false + this.inputTargets.forEach(input => { + if (this.unallowedValue.includes(input.value)) { + invalid = true + } + }) + + if (invalid) { + this.submitButtonTarget.disabled = true + this.submitButtonTarget.classList.add(this.disabledClass) + this.submitButtonTarget.classList.remove(...this.enabledClasses) + } else { + this.submitButtonTarget.disabled = false + this.submitButtonTarget.classList.remove(this.disabledClass) + this.submitButtonTarget.classList.add(...this.enabledClasses) + } + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 7044ce11aa..6f6f3b0772 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -4,6 +4,8 @@ import { application } from './application' +import DisableFormController from './disable_form_controller' + import DismissController from './dismiss_controller' import ExtendedNestedFormController from './extended_nested_form_controller' @@ -14,13 +16,17 @@ import MultipleSelectController from './multiple_select_controller' import NavbarController from './navbar_controller' +import SelectAllController from './select_all_controller' + import SidebarController from './sidebar_controller' import SidebarGroupController from './sidebar_group_controller' +application.register('disable-form', DisableFormController) application.register('dismiss', DismissController) application.register('extended-nested-form', ExtendedNestedFormController) application.register('hello', HelloController) application.register('multiple-select', MultipleSelectController) application.register('navbar', NavbarController) +application.register('select-all', SelectAllController) application.register('sidebar', SidebarController) application.register('sidebar-group', SidebarGroupController) diff --git a/app/javascript/controllers/select_all_controller.js b/app/javascript/controllers/select_all_controller.js new file mode 100644 index 0000000000..39cdb60545 --- /dev/null +++ b/app/javascript/controllers/select_all_controller.js @@ -0,0 +1,69 @@ +import { Controller } from '@hotwired/stimulus' + +// Connects to data-controller="select-all" +export default class extends Controller { + static targets = ['checkboxAll', 'checkbox', 'button', 'buttonLabel'] + static values = { + allChecked: { type: Boolean, default: false }, + buttonLabel: { type: String } + } + + static classes = ['hidden'] + + toggleAll () { + this.allCheckedValue = !this.allCheckedValue + + this.checkboxTargets.forEach(checkbox => { + checkbox.checked = this.allCheckedValue + }) + + this.toggleButton() + } + + toggleSingle () { + this.toggleCheckedAll() + this.toggleButton() + } + + toggleCheckedAll () { + const numChecked = this.getNumberChecked() + const numTotal = this.getTotalCheckboxes() + + this.allCheckedValue = numChecked === numTotal + this.checkboxAllTarget.checked = this.allCheckedValue + + if (numChecked === 0) { + this.checkboxAllTarget.indeterminate = false + } else { + this.checkboxAllTarget.indeterminate = numChecked < numTotal + } + } + + toggleButton () { + if (this.hasButtonTarget) { + const numChecked = this.getNumberChecked() + if (numChecked > 0) { + if (this.hasButtonLabelTarget) { + let label = this.buttonLabelValue + if (numChecked > 1) { + label += 's' + } + label += ' (' + numChecked + ')' + this.buttonLabelTarget.innerHTML = label + } + + this.buttonTarget.classList.remove(this.hiddenClass) + } else { + this.buttonTarget.classList.add(this.hiddenClass) + } + } + } + + getNumberChecked () { + return this.checkboxTargets.filter((checkbox) => checkbox.checked).length + } + + getTotalCheckboxes () { + return this.checkboxTargets.length + } +} diff --git a/app/javascript/src/dashboard.js b/app/javascript/src/dashboard.js index e28c84e6d0..9bf091bb34 100644 --- a/app/javascript/src/dashboard.js +++ b/app/javascript/src/dashboard.js @@ -135,18 +135,16 @@ $(() => { // JQuery's callback for the DOM loading }) }, order: [[7, 'desc']], - select: { - style: 'multi' - }, columns: [ { data: 'id', targets: 0, searchable: false, orderable: false, - checkboxes: { - selectRow: true, - stateSave: false + render: (data, type, row, meta) => { + return ` + + ` } }, { @@ -261,10 +259,10 @@ $(() => { // JQuery's callback for the DOM loading render: (data, type, row, meta) => { return ` Actions - + Edit - + Impersonate ` @@ -306,29 +304,6 @@ $(() => { // JQuery's callback for the DOM loading } }) - $('#form-bulk-assignment').on('submit', function (e) { - const form = this - const rowsSelected = volunteersTable.column(0).checkboxes.selected() - - $.each(rowsSelected, function (index, rowId) { - $(form).append( - $('') - .attr('type', 'hidden') - .attr('name', 'supervisor_volunteer[volunteer_ids][]') - .val(rowId) - ) - }) - }) - - volunteersTable.column(0).on('change', function () { - const rowsSelected = volunteersTable.column(0).checkboxes.selected() - if (rowsSelected.count() === 0) { - $('#volunteers-selected').html('') - } else { - $('#volunteers-selected').html('s (' + rowsSelected.count() + ')') - } - }) - // Because the table saves state, we have to check/uncheck modal inputs based on what // columns are visible volunteersTable.columns().every(function (index) { diff --git a/app/views/volunteers/index.html.erb b/app/views/volunteers/index.html.erb index 84dfe70c6f..17ceadb0ce 100644 --- a/app/views/volunteers/index.html.erb +++ b/app/views/volunteers/index.html.erb @@ -157,26 +157,27 @@ -
+
-
Responsive Data Table
- - - +
- <%= form_with(url: bulk_assignment_supervisor_volunteers_path, id: "form-bulk-assignment") do |form| %> + <%= form_with(url: bulk_assignment_supervisor_volunteers_path, id: "form-bulk-assignment", data: { controller: "disable-form", "disable-form-unallowed-value": '["unselected"]', "disable-form-disabled-class": "deactive-btn", "disable-form-enabled-class": "btn-hover dark-btn" }) do |form| %> - + @@ -211,11 +212,11 @@
- + + <% @supervisors&.active&.each do |supervisor| %> <% end %> @@ -229,7 +230,7 @@ type="button"> Close - <%= form.button type: :submit, class: "submit-bulk-assignment main-btn dark-btn btn-hover m-1" do %> + <%= form.button type: :submit, class: "main-btn deactive-btn m-1", data: { "disable-form-target": "submitButton" }, disabled: true do %> Confirm <% end %>
diff --git a/package.json b/package.json index b72bf31d59..782fa26cfa 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,9 @@ "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "datatables.net-dt": "^1.13.8", - "datatables.net-select": "^1.7.0", "esbuild": "^0.19.9", "faker": "^5.5.3", "jquery": "^3.7.1", - "jquery-datatables-checkboxes": "^1.2.14", "js-cookie": "^3.0.5", "jstz": "^2.1.1", "lodash": "^4.17.21", diff --git a/spec/requests/supervisor_volunteers_spec.rb b/spec/requests/supervisor_volunteers_spec.rb index 3ea79675ff..aa60864918 100644 --- a/spec/requests/supervisor_volunteers_spec.rb +++ b/spec/requests/supervisor_volunteers_spec.rb @@ -1,12 +1,13 @@ require "rails_helper" RSpec.describe "/supervisor_volunteers", type: :request do - let!(:admin) { build(:casa_admin) } let!(:casa_org) { build(:casa_org) } + let!(:admin) { build(:casa_admin, casa_org: casa_org) } let!(:supervisor) { create(:supervisor, casa_org: casa_org) } - let!(:volunteer) { create(:volunteer, casa_org: casa_org) } - context "POST /create" do + describe "POST /create" do + let!(:volunteer) { create(:volunteer, casa_org: casa_org) } + context "when no pre-existing association between supervisor and volunteer exists" do it "creates a new supervisor_volunteers association" do valid_parameters = { @@ -89,8 +90,7 @@ it "will still set the association as active" do valid_parameters = { - supervisor_volunteer: {supervisor_id: supervisor.id}, - volunteer_id: volunteer.id + supervisor_volunteer: {supervisor_id: supervisor.id, volunteer_id: volunteer.id} } sign_in(admin) @@ -105,7 +105,8 @@ end end - context "PATCH /unassign" do + describe "PATCH /unassign" do + let!(:volunteer) { create(:volunteer, casa_org: casa_org) } let!(:association) do create(:supervisor_volunteer, supervisor: supervisor, volunteer: volunteer) end @@ -157,118 +158,81 @@ end end end - # using describe just for clarity - describe "POST /bulk_assign" do + + describe "POST /bulk_assignment" do let!(:volunteer_1) { create(:volunteer, casa_org: casa_org) } let!(:volunteer_2) { create(:volunteer, casa_org: casa_org) } let!(:volunteer_3) { create(:volunteer, casa_org: casa_org) } - - context "when no pre-existing association between supervisor and volunteer exists" do - it "creates a new supervisor_volunteers association for each selected volunteer in bulk assign" do - valid_parameters = { - supervisor_volunteer: {supervisor_id: supervisor.id, volunteer_ids: [volunteer_1.id, volunteer_2.id, volunteer_3.id]} + let(:supervisor_id) { supervisor.id } + let(:params) do + { + supervisor_volunteer: { + supervisor_id: supervisor_id, + volunteer_ids: [volunteer_1.id, volunteer_2.id, volunteer_3.id] } - sign_in(admin) + } + end - expect { - post bulk_assignment_supervisor_volunteers_url, params: valid_parameters, headers: {HTTP_REFERER: supervisors_path.to_s} - }.to change(SupervisorVolunteer, :count).by(3) - expect(response).to redirect_to volunteers_path - end + subject do + post bulk_assignment_supervisor_volunteers_url, params: params end - context "when an inactive association between supervisor and volunteer exists" do - let!(:association) do - create( - :supervisor_volunteer, - supervisor: supervisor, - volunteer: volunteer_1, - is_active: false - ) + it "creates an association for each volunteer" do + sign_in(admin) + + expect { subject }.to change(SupervisorVolunteer, :count).by(3) + end + + context "when association already exists" do + let!(:associations) do + [ + create(:supervisor_volunteer, :inactive, supervisor: supervisor, volunteer: volunteer_1), + create(:supervisor_volunteer, :inactive, supervisor: supervisor, volunteer: volunteer_2), + create(:supervisor_volunteer, :inactive, supervisor: supervisor, volunteer: volunteer_3) + ] end - it "sets that association to active" do - valid_parameters = { - supervisor_volunteer: {supervisor_id: supervisor.id, volunteer_ids: [volunteer_1.id, volunteer_2.id, volunteer_3.id]} - } + it "sets to active" do sign_in(admin) + subject - expect { - post bulk_assignment_supervisor_volunteers_url, params: valid_parameters, headers: {HTTP_REFERER: supervisors_path.to_s} - }.to change(SupervisorVolunteer, :count).by(2) # bc 1 already exists, we are creating 2 new ones - expect(response).to redirect_to volunteers_path + associations.each do |association| + association.reload + expect(association.is_active?).to be true + end + end - association.reload - expect(association.is_active?).to be(true) + it "does not create new associations" do + sign_in(admin) + + expect { subject }.to change(SupervisorVolunteer, :count).by(0) end end - context "when passing the volunteer_ids as the supervisor_volunteer_params" do - let!(:association) do - create( - :supervisor_volunteer, - supervisor: supervisor, - volunteer: volunteer_1, - is_active: false - ) + context "when none passed as supervisor" do + let(:supervisor_id) { "" } + let!(:associations) do + [ + create(:supervisor_volunteer, supervisor: supervisor, volunteer: volunteer_1), + create(:supervisor_volunteer, supervisor: supervisor, volunteer: volunteer_2), + create(:supervisor_volunteer, supervisor: supervisor, volunteer: volunteer_3) + ] end - it "will still set the association as active while creaing two new supervisor_volunteer records" do - valid_parameters = { - supervisor_volunteer: {supervisor_id: supervisor.id, volunteer_ids: [volunteer_1.id, volunteer_2.id, volunteer_3.id]} - } - + it "sets associations to inactive" do sign_in(admin) + subject - expect { - post bulk_assignment_supervisor_volunteers_url, params: valid_parameters, headers: {HTTP_REFERER: supervisors_path.to_s} - }.to change(SupervisorVolunteer, :count).by(2) # bc 1 already exists, we are creating 2 new ones - expect(response).to redirect_to volunteers_path - - association.reload - expect(association.is_active?).to be(true) + associations.each do |association| + association.reload + expect(association.is_active?).to be false + end end - end - end - - describe "POST /bulk_unassign" do - let!(:volunteer_1) { create(:volunteer, casa_org: casa_org) } - let!(:volunteer_2) { create(:volunteer, casa_org: casa_org) } - let!(:volunteer_3) { create(:volunteer, casa_org: casa_org) } - context "when passing in several volunteer with selected --No supervior--" do - let!(:assigned_supervisor) { build(:supervisor, casa_org: casa_org) } - let!(:association_1) do - create( - :supervisor_volunteer, - supervisor: assigned_supervisor, - volunteer: volunteer_1, - is_active: true - ) - end - let!(:association_2) do - create( - :supervisor_volunteer, - supervisor: assigned_supervisor, - volunteer: volunteer_2, - is_active: true - ) - end - it "will set the association to inactive" do - valid_parameters = { - supervisor_volunteer: {supervisor_id: "unassign", volunteer_ids: [volunteer_1.id, volunteer_2.id]} - } + it "does not remove associations" do sign_in(admin) - expect { - post bulk_assignment_supervisor_volunteers_url, params: valid_parameters, headers: {HTTP_REFERER: supervisors_path.to_s} - }.not_to change(SupervisorVolunteer, :count) - expect(response).to redirect_to volunteers_path - - association_1.reload - association_2.reload - expect(association_1.is_active?).to be(false) - expect(association_2.is_active?).to be(false) + expect { subject }.to change(SupervisorVolunteer, :count).by(0) end end end diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index e6253e1d14..106b7d11bf 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "view all volunteers", type: :system do +RSpec.describe "view all volunteers", type: :system, js: true do let(:organization) { create(:casa_org) } let(:admin) { create(:casa_admin, casa_org: organization) } @@ -175,52 +175,177 @@ expect(supervisor_cell.text).to eq "" end + end - context "when bulk assignments" do - it "displays supervisor's name when multiple volunteers assigned supervisor", js: true do - name = "Superduper Visor" - supervisor = create(:supervisor, display_name: name, casa_org: organization) - create(:volunteer, casa_org: organization) + describe "Manage Volunteers button" do + before do + sign_in admin + visit volunteers_path + end + + it "does not display by default" do + expect(page).not_to have_text "Manage Volunteer" + end + + context "when one or more volunteers selected" do + let!(:volunteers) { + [ + create(:volunteer, casa_org: organization), + create(:volunteer, casa_org: organization), + create(:volunteer, casa_org: organization) + ] + } + + it "is displayed" do + find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}").click + + expect(page).to have_text "Manage Volunteer" + end + + it "displays number of volunteers selected" do + volunteers.each_with_index do |volunteer, index| + find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}").click + button = find("[data-select-all-target='buttonLabel']") + expect(button).to have_text "(#{index + 1})" + end + end + + it "text matches pluralization of volunteers selected" do + find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}").click + expect(page).not_to have_text "Manage Volunteers" + + find("#supervisor_volunteer_volunteer_ids_#{volunteers[1].id}").click + expect(page).to have_text "Manage Volunteers" + end + + it "is hidden when all volunteers unchecked" do + find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}").click + expect(page).to have_text "Manage Volunteer" + + find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}").click + expect(page).not_to have_text "Manage Volunteer" + end + end + end + + describe "Select All Checkbox" do + let!(:volunteers) { + [ + create(:volunteer, casa_org: organization), + create(:volunteer, casa_org: organization), create(:volunteer, casa_org: organization) - sign_in admin + ] + } - visit volunteers_path + before do + sign_in admin + visit volunteers_path + end - find("tr.odd").click - find("tr.even").click - click_on "Manage Volunteer" - select supervisor.display_name, from: "supervisor_volunteer[supervisor_id]" + it "selects all volunteers" do + find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}") # wait for volunteers to be displayed + find("[data-select-all-target='checkboxAll']").click - click_on "Confirm" + volunteers.each do |volunteer| + expect(find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}").checked?).to be true + end + end - supervisor_cell1 = page.find("tbody .odd .supervisor-column") - supervisor_cell2 = page.find("tbody .even .supervisor-column") + context "when all are checked" do + before do + volunteers.each do |volunteer| + find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}").click + end + end + + it "deselects all volunteers" do + find("[data-select-all-target='checkboxAll']").click + + volunteers.each do |volunteer| + expect(find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}").checked?).to be false + end + end + end + + context "when some are checked" do + before do + find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}").click + end - expect(supervisor_cell1.text).to eq supervisor.display_name - expect(supervisor_cell2.text).to eq supervisor.display_name + it "is semi-checked (indeterminate)" do + expect(find("[data-select-all-target='checkboxAll']").checked?).to be false + expect(find("[data-select-all-target='checkboxAll']")[:indeterminate]).to eq("true") end - it "displays no supervisor name when multiple volunteers unassigned supervisor", js: true do - name = "Superduper Visor" - supervisor = create(:supervisor, display_name: name, casa_org: organization) - create(:volunteer, supervisor: supervisor, casa_org: organization) - create(:volunteer, supervisor: supervisor, casa_org: organization) - sign_in admin + it "selects all volunteers" do + find("[data-select-all-target='checkboxAll']").click + + volunteers.each do |volunteer| + expect(find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}").checked?).to be true + end + end + end + end + + describe "Select Supervisor Modal Submit button" do + let!(:volunteer) { create(:volunteer, casa_org: organization) } + let!(:supervisor) { create(:supervisor, casa_org: organization) } + + before do + sign_in admin + visit volunteers_path + find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}").click + find("[data-select-all-target='button']").click + end - visit volunteers_path + it "is disabled by default" do + button = find("[data-disable-form-target='submitButton']") + expect(button.disabled?).to be true + expect(button[:class].include?("deactive-btn")).to be true + expect(button[:class].include?("dark-btn")).to be false + expect(button[:class].include?("btn-hover")).to be false + end - find("tr.odd").click - find("tr.even").click - click_on "Manage Volunteer" - select "-- No Supervisor --", from: "supervisor_volunteer[supervisor_id]" + context "when none is selected" do + before do + select "None", from: "supervisor_volunteer_supervisor_id" + end - click_on "Confirm" + it "is enabled" do + button = find("[data-disable-form-target='submitButton']") + expect(button.disabled?).to be false + expect(button[:class].include?("deactive-btn")).to be false + expect(button[:class].include?("dark-btn")).to be true + expect(button[:class].include?("btn-hover")).to be true + end + end - supervisor_cell1 = page.find("tbody .odd .supervisor-column") - supervisor_cell2 = page.find("tbody .even .supervisor-column") + context "when a supervisor is selected" do + before do + select supervisor.display_name, from: "supervisor_volunteer_supervisor_id" + end + + it "is enabled" do + button = find("[data-disable-form-target='submitButton']") + expect(button.disabled?).to be false + expect(button[:class].include?("deactive-btn")).to be false + expect(button[:class].include?("dark-btn")).to be true + expect(button[:class].include?("btn-hover")).to be true + end + end + + context "when Choose a supervisor is selected" do + before do + select supervisor.display_name, from: "supervisor_volunteer_supervisor_id" + select "Choose a supervisor", from: "supervisor_volunteer_supervisor_id" + end - expect(supervisor_cell1.text).to eq "" - expect(supervisor_cell2.text).to eq "" + it "is disabled" do + button = find("[data-disable-form-target='submitButton']") + expect(button.disabled?).to be true + expect(button[:class].include?("deactive-btn")).to be true + expect(button[:class].include?("dark-btn")).to be false + expect(button[:class].include?("btn-hover")).to be false end end end diff --git a/yarn.lock b/yarn.lock index ebd55a92e6..27389dfb87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2344,21 +2344,6 @@ datatables.net-dt@^1.13.8: datatables.net "1.13.8" jquery ">=1.7" -datatables.net-select@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/datatables.net-select/-/datatables.net-select-1.7.0.tgz#a108752ee6109a49392d19fec7406c953be11665" - integrity sha512-ps8eL8S2gUV7EdzMraw8mfQlHXpfuc8TC2onBxdk0snP8eizPe85VhpI3r4ULvPRTTI7vcViz8E7JV8aayA2lw== - dependencies: - datatables.net ">=1.13.4" - jquery ">=1.7" - -datatables.net@>=1.10.8: - version "1.13.6" - resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.13.6.tgz#6e282adbbb2732e8df495611b8bb54e19f7a943e" - integrity sha512-rHNcnW+yEP9me82/KmRcid5eKrqPqW3+I/p1TwqCW3c/7GRYYkDyF6aJQOQ9DNS/pw+nyr4BVpjyJ3yoZXiFPg== - dependencies: - jquery ">=1.7" - datatables.net@1.13.8: version "1.13.8" resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.13.8.tgz#05a2fb5a036b0b65b66d1bb1eae0ba018aaea8a3" @@ -4024,14 +4009,6 @@ joi@^17.11.0: "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" -jquery-datatables-checkboxes@^1.2.14: - version "1.2.14" - resolved "https://registry.yarnpkg.com/jquery-datatables-checkboxes/-/jquery-datatables-checkboxes-1.2.14.tgz#5bada981140b9ba645e80d6c2caa8bbbb4123936" - integrity sha512-99B6PmS3ZdinDyqF5vBiykd8B+NohHRBBs/a5/hrYRlsBxbWqQ2KtigSvAc2qFdxPOyrcCV75nWBYnlrtdCGgg== - dependencies: - datatables.net ">=1.10.8" - jquery ">=1.7" - jquery@>=1.7, "jquery@>=3.4.0 <4.0.0", jquery@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" From f15d1ec3ad8d89bb3eb39fa7c8d44582bb282de9 Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Sat, 13 Jan 2024 13:39:12 -0500 Subject: [PATCH 03/21] AssignMultipleVolunteersAtOnce // response to test issues and code climate --- .../controllers/select_all_controller.js | 22 ++++++++++++------- spec/system/volunteers/index_spec.rb | 1 + 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/javascript/controllers/select_all_controller.js b/app/javascript/controllers/select_all_controller.js index 39cdb60545..62e21d218b 100644 --- a/app/javascript/controllers/select_all_controller.js +++ b/app/javascript/controllers/select_all_controller.js @@ -43,14 +43,7 @@ export default class extends Controller { if (this.hasButtonTarget) { const numChecked = this.getNumberChecked() if (numChecked > 0) { - if (this.hasButtonLabelTarget) { - let label = this.buttonLabelValue - if (numChecked > 1) { - label += 's' - } - label += ' (' + numChecked + ')' - this.buttonLabelTarget.innerHTML = label - } + this.setButtonLabel(numChecked) this.buttonTarget.classList.remove(this.hiddenClass) } else { @@ -59,6 +52,19 @@ export default class extends Controller { } } + setButtonLabel (numChecked) { + if (this.hasButtonLabelTarget) { + let label = this.buttonLabelValue + + if (numChecked > 1) { + label += 's' + } + label += ' (' + numChecked + ')' + + this.buttonLabelTarget.innerHTML = label + } + } + getNumberChecked () { return this.checkboxTargets.filter((checkbox) => checkbox.checked).length } diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index 106b7d11bf..6cc305bd16 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -294,6 +294,7 @@ before do sign_in admin visit volunteers_path + find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}") find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}").click find("[data-select-all-target='button']").click end From cb7d40e1d1a257079c72b71886a84dbb3edef27d Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Sat, 13 Jan 2024 13:52:08 -0500 Subject: [PATCH 04/21] test fixes --- spec/system/volunteers/index_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index 6cc305bd16..610baabb41 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -240,10 +240,10 @@ before do sign_in admin visit volunteers_path + find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}") # wait for volunteers to be displayed end it "selects all volunteers" do - find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}") # wait for volunteers to be displayed find("[data-select-all-target='checkboxAll']").click volunteers.each do |volunteer| From a47360647a1ab75a4d6e85e32458beb2063701eb Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Sat, 13 Jan 2024 14:24:12 -0500 Subject: [PATCH 05/21] test fixes --- spec/system/volunteers/index_spec.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index 610baabb41..d0eaab9cfe 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -492,17 +492,5 @@ expect(input_search.value).to eq("") end end - - context "when timed out" do - it "prompts login" do - sign_in supervisor - visit volunteers_path - click_on "Supervisor" - allow_any_instance_of(User).to receive(:timedout?).and_return true - visit volunteers_path - expect(page).to have_text "sign in again to continue" - expect(current_path).to eq new_user_session_path - end - end end end From cb4107ccfd8131c0afce3a82eae2efecd07e0988 Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Sun, 14 Jan 2024 09:23:01 -0500 Subject: [PATCH 06/21] test fixes --- spec/system/volunteers/index_spec.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index d0eaab9cfe..f1c5155fd1 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -350,18 +350,6 @@ end end end - - context "when timed out" do - it "prompts login" do - sign_in admin - visit volunteers_path - click_on "Supervisor" - allow_any_instance_of(User).to receive(:timedout?).and_return true - visit volunteers_path - expect(page).to have_text "sign in again to continue" - expect(current_path).to eq new_user_session_path - end - end end context "supervisor user" do From 6f728d52cfdf7d35ff9cd619f8f551796d34d217 Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Wed, 31 Jan 2024 22:26:33 -0500 Subject: [PATCH 07/21] Lint fix --- app/javascript/controllers/index.js | 50 ++++++++++++++--------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index a675c652ed..bc8e48c2a4 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -2,40 +2,40 @@ // Run that command whenever you add a new controller or create them with // ./bin/rails generate stimulus controllerName -import { application } from "./application" +import { application } from './application' -import AlertController from "./alert_controller" -application.register("alert", AlertController) +import AlertController from './alert_controller' -import AutosaveController from "./autosave_controller" -application.register("autosave", AutosaveController) +import AutosaveController from './autosave_controller' -import DisableFormController from "./disable_form_controller" -application.register("disable-form", DisableFormController) +import DisableFormController from './disable_form_controller' -import DismissController from "./dismiss_controller" -application.register("dismiss", DismissController) +import DismissController from './dismiss_controller' -import ExtendedNestedFormController from "./extended_nested_form_controller" -application.register("extended-nested-form", ExtendedNestedFormController) +import ExtendedNestedFormController from './extended_nested_form_controller' -import HelloController from "./hello_controller" -application.register("hello", HelloController) +import HelloController from './hello_controller' -import IconToggleController from "./icon_toggle_controller" -application.register("icon-toggle", IconToggleController) +import IconToggleController from './icon_toggle_controller' -import MultipleSelectController from "./multiple_select_controller" -application.register("multiple-select", MultipleSelectController) +import MultipleSelectController from './multiple_select_controller' -import NavbarController from "./navbar_controller" -application.register("navbar", NavbarController) +import NavbarController from './navbar_controller' -import SelectAllController from "./select_all_controller" -application.register("select-all", SelectAllController) +import SelectAllController from './select_all_controller' -import SidebarController from "./sidebar_controller" -application.register("sidebar", SidebarController) +import SidebarController from './sidebar_controller' -import SidebarGroupController from "./sidebar_group_controller" -application.register("sidebar-group", SidebarGroupController) +import SidebarGroupController from './sidebar_group_controller' +application.register('alert', AlertController) +application.register('autosave', AutosaveController) +application.register('disable-form', DisableFormController) +application.register('dismiss', DismissController) +application.register('extended-nested-form', ExtendedNestedFormController) +application.register('hello', HelloController) +application.register('icon-toggle', IconToggleController) +application.register('multiple-select', MultipleSelectController) +application.register('navbar', NavbarController) +application.register('select-all', SelectAllController) +application.register('sidebar', SidebarController) +application.register('sidebar-group', SidebarGroupController) From 420efb1b2477f493fe872fd3d860897dacae49c0 Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Sun, 11 Feb 2024 07:55:34 -0500 Subject: [PATCH 08/21] fixes for tests --- app/models/user.rb | 2 +- spec/models/user_spec.rb | 10 +++++----- spec/system/volunteers/index_spec.rb | 14 +++++++++----- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 22c5e96710..ada70e5f2c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -119,7 +119,7 @@ def no_attempt_for_two_weeks if volunteer.supervisor.active? && volunteer.case_assignments.any? { |assignment| assignment.active? } && (volunteer.case_contacts.none? || - volunteer.case_contacts.maximum(:occurred_at) < (Time.zone.now - 14.days)) + volunteer.case_contacts.maximum(:created_at) < (Time.zone.now - 14.days)) no_attempt_count += 1 end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f9457855bc..b320210383 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -133,7 +133,7 @@ volunteer_1 = create(:volunteer, :with_casa_cases, supervisor: supervisor) case_of_interest_1 = volunteer_1.casa_cases.first - create(:case_contact, creator: volunteer_1, casa_case: case_of_interest_1, contact_made: true, occurred_at: 1.week.ago) + create(:case_contact, creator: volunteer_1, casa_case: case_of_interest_1, contact_made: true, created_at: 1.week.ago) expect(supervisor.no_attempt_for_two_weeks).to eq(0) end @@ -143,8 +143,8 @@ case_of_interest_1 = volunteer_1.casa_cases.first case_of_interest_2 = volunteer_2.casa_cases.first - create(:case_contact, creator: volunteer_1, casa_case: case_of_interest_1, contact_made: true, occurred_at: 1.week.ago) - create(:case_contact, creator: volunteer_2, casa_case: case_of_interest_2, contact_made: true, occurred_at: 3.weeks.ago) + create(:case_contact, creator: volunteer_1, casa_case: case_of_interest_1, contact_made: true, created_at: 1.week.ago) + create(:case_contact, creator: volunteer_2, casa_case: case_of_interest_2, contact_made: true, created_at: 3.weeks.ago) expect(supervisor.no_attempt_for_two_weeks).to eq(1) end @@ -158,12 +158,12 @@ expect(supervisor.no_attempt_for_two_weeks).to eq(0) end - it "returns one for a volunteer who has attempted contact in at least one contact_case with occurred_at after 2 weeks" do + it "returns one for a volunteer who has attempted contact in at least one contact_case with created_at after 2 weeks" do volunteer_1 = create(:volunteer, :with_casa_cases, supervisor: supervisor) case_of_interest_1 = volunteer_1.casa_cases.first - build_stubbed(:case_contact, creator: volunteer_1, casa_case: case_of_interest_1, contact_made: true, occurred_at: 3.weeks.ago) + build_stubbed(:case_contact, creator: volunteer_1, casa_case: case_of_interest_1, contact_made: true, created_at: 3.weeks.ago) expect(supervisor.no_attempt_for_two_weeks).to eq(1) end diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index f1c5155fd1..8bbaa68664 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -180,10 +180,10 @@ describe "Manage Volunteers button" do before do sign_in admin - visit volunteers_path end it "does not display by default" do + visit volunteers_path expect(page).not_to have_text "Manage Volunteer" end @@ -197,21 +197,24 @@ } it "is displayed" do - find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}").click + visit volunteers_path + find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}", wait: 3).click expect(page).to have_text "Manage Volunteer" end it "displays number of volunteers selected" do + visit volunteers_path volunteers.each_with_index do |volunteer, index| - find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}").click + find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}", wait: 3).click button = find("[data-select-all-target='buttonLabel']") expect(button).to have_text "(#{index + 1})" end end it "text matches pluralization of volunteers selected" do - find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}").click + visit volunteers_path + find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}", wait: 3).click expect(page).not_to have_text "Manage Volunteers" find("#supervisor_volunteer_volunteer_ids_#{volunteers[1].id}").click @@ -219,7 +222,8 @@ end it "is hidden when all volunteers unchecked" do - find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}").click + visit volunteers_path + find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}", wait: 3).click expect(page).to have_text "Manage Volunteer" find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}").click From 1855f40c06b1c1fbd607099b93d5fd764045a51c Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Sun, 11 Feb 2024 08:21:57 -0500 Subject: [PATCH 09/21] Test fixes --- spec/system/volunteers/index_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index 8bbaa68664..d8d85031ea 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -178,6 +178,14 @@ end describe "Manage Volunteers button" do + let!(:volunteers) { + [ + create(:volunteer, casa_org: organization), + create(:volunteer, casa_org: organization), + create(:volunteer, casa_org: organization) + ] + } + before do sign_in admin end @@ -188,14 +196,6 @@ end context "when one or more volunteers selected" do - let!(:volunteers) { - [ - create(:volunteer, casa_org: organization), - create(:volunteer, casa_org: organization), - create(:volunteer, casa_org: organization) - ] - } - it "is displayed" do visit volunteers_path find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}", wait: 3).click From e97f93e7f70b50a431cd3c2e2d61976515cabb62 Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Sun, 11 Feb 2024 08:35:22 -0500 Subject: [PATCH 10/21] Test fixes --- spec/system/volunteers/index_spec.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index d8d85031ea..be79aff1a0 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -297,13 +297,13 @@ before do sign_in admin - visit volunteers_path - find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}") - find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}").click - find("[data-select-all-target='button']").click end it "is disabled by default" do + visit volunteers_path + find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}", wait: 3).click + find("[data-select-all-target='button']").click + button = find("[data-disable-form-target='submitButton']") expect(button.disabled?).to be true expect(button[:class].include?("deactive-btn")).to be true @@ -313,11 +313,14 @@ context "when none is selected" do before do - select "None", from: "supervisor_volunteer_supervisor_id" end it "is enabled" do - button = find("[data-disable-form-target='submitButton']") + visit volunteers_path + find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}", wait: 3).click + find("[data-select-all-target='button']").click + select "None", from: "supervisor_volunteer_supervisor_id" + expect(button.disabled?).to be false expect(button[:class].include?("deactive-btn")).to be false expect(button[:class].include?("dark-btn")).to be true From 299a9d31e797f8c21889d94264bbacf89f87d9cb Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Sun, 11 Feb 2024 08:49:01 -0500 Subject: [PATCH 11/21] Test fixes --- spec/system/volunteers/index_spec.rb | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index be79aff1a0..5ad20bf555 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -312,15 +312,14 @@ end context "when none is selected" do - before do - end - it "is enabled" do visit volunteers_path find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}", wait: 3).click find("[data-select-all-target='button']").click select "None", from: "supervisor_volunteer_supervisor_id" + button = find("[data-disable-form-target='submitButton']") + expect(button.disabled?).to be false expect(button[:class].include?("deactive-btn")).to be false expect(button[:class].include?("dark-btn")).to be true @@ -329,11 +328,12 @@ end context "when a supervisor is selected" do - before do - select supervisor.display_name, from: "supervisor_volunteer_supervisor_id" - end - it "is enabled" do + visit volunteers_path + find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}", wait: 3).click + find("[data-select-all-target='button']").click + + select supervisor.display_name, from: "supervisor_volunteer_supervisor_id" button = find("[data-disable-form-target='submitButton']") expect(button.disabled?).to be false expect(button[:class].include?("deactive-btn")).to be false @@ -343,12 +343,13 @@ end context "when Choose a supervisor is selected" do - before do + it "is disabled" do + visit volunteers_path + find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}", wait: 3).click + find("[data-select-all-target='button']").click + select supervisor.display_name, from: "supervisor_volunteer_supervisor_id" select "Choose a supervisor", from: "supervisor_volunteer_supervisor_id" - end - - it "is disabled" do button = find("[data-disable-form-target='submitButton']") expect(button.disabled?).to be true expect(button[:class].include?("deactive-btn")).to be true From a41d310e314ae6421d6746ae2371c61ce6cb758d Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Sun, 11 Feb 2024 09:32:09 -0500 Subject: [PATCH 12/21] Test fixes --- spec/system/volunteers/index_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index 5ad20bf555..6279511491 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -243,11 +243,11 @@ before do sign_in admin - visit volunteers_path - find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}") # wait for volunteers to be displayed end it "selects all volunteers" do + visit volunteers_path + find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}") # Wait for data table to be loaded find("[data-select-all-target='checkboxAll']").click volunteers.each do |volunteer| @@ -256,13 +256,12 @@ end context "when all are checked" do - before do + it "deselects all volunteers" do + visit volunteers_path volunteers.each do |volunteer| find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}").click end - end - it "deselects all volunteers" do find("[data-select-all-target='checkboxAll']").click volunteers.each do |volunteer| @@ -272,16 +271,17 @@ end context "when some are checked" do - before do + it "is semi-checked (indeterminate)" do + visit volunteers_path find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}").click - end - it "is semi-checked (indeterminate)" do expect(find("[data-select-all-target='checkboxAll']").checked?).to be false expect(find("[data-select-all-target='checkboxAll']")[:indeterminate]).to eq("true") end it "selects all volunteers" do + visit volunteers_path + find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}").click find("[data-select-all-target='checkboxAll']").click volunteers.each do |volunteer| From 25e760954e454bca5691244840c5ac72ba874ead Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Sun, 11 Feb 2024 09:43:17 -0500 Subject: [PATCH 13/21] Test fixes --- spec/system/volunteers/index_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index 6279511491..a8bb1fbf98 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -207,7 +207,7 @@ visit volunteers_path volunteers.each_with_index do |volunteer, index| find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}", wait: 3).click - button = find("[data-select-all-target='buttonLabel']") + button = find("[data-select-all-target='buttonLabel']", wait: 3) expect(button).to have_text "(#{index + 1})" end end From cd3d92d4afe73b6b66f97bbf7e51a22a1f1e9b8b Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Sun, 11 Feb 2024 10:53:14 -0500 Subject: [PATCH 14/21] Test fixes --- spec/system/volunteers/index_spec.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index a8bb1fbf98..648fbf0047 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -263,6 +263,7 @@ end find("[data-select-all-target='checkboxAll']").click + sleep(1) volunteers.each do |volunteer| expect(find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}").checked?).to be false @@ -274,6 +275,7 @@ it "is semi-checked (indeterminate)" do visit volunteers_path find("#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}").click + sleep(1) expect(find("[data-select-all-target='checkboxAll']").checked?).to be false expect(find("[data-select-all-target='checkboxAll']")[:indeterminate]).to eq("true") @@ -315,7 +317,7 @@ it "is enabled" do visit volunteers_path find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}", wait: 3).click - find("[data-select-all-target='button']").click + find("[data-select-all-target='button']", wait: 3).click select "None", from: "supervisor_volunteer_supervisor_id" button = find("[data-disable-form-target='submitButton']") @@ -331,7 +333,7 @@ it "is enabled" do visit volunteers_path find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}", wait: 3).click - find("[data-select-all-target='button']").click + find("[data-select-all-target='button']", wait: 3).click select supervisor.display_name, from: "supervisor_volunteer_supervisor_id" button = find("[data-disable-form-target='submitButton']") @@ -346,7 +348,7 @@ it "is disabled" do visit volunteers_path find("#supervisor_volunteer_volunteer_ids_#{volunteer.id}", wait: 3).click - find("[data-select-all-target='button']").click + find("[data-select-all-target='button']", wait: 3).click select supervisor.display_name, from: "supervisor_volunteer_supervisor_id" select "Choose a supervisor", from: "supervisor_volunteer_supervisor_id" From 6ebe5bf997494026676e6a2624a4ca730857f29d Mon Sep 17 00:00:00 2001 From: Sam Williams Date: Tue, 13 Feb 2024 10:15:17 -0500 Subject: [PATCH 15/21] Case contact rake task bug --- .../deployment/20231125151610_set_case_contacts_as_active.rake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake b/lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake index fb966dd7f7..daa2e90ae7 100644 --- a/lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake +++ b/lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake @@ -4,8 +4,9 @@ namespace :after_party do puts "Running deploy task 'set_case_contacts_as_active'" CaseContact.where.not(casa_case_id: nil).each do |cc| + p cc.id cc.additional_expenses.each do |additional_expense| - additional_expense.update(other_expenses_describe: "No description given") unless additional_expense.other_expenses_describe + additional_expense.update!(other_expenses_describe: "No description given") unless additional_expense.other_expenses_describe end cc.update!(status: "active", draft_case_ids: [cc.casa_case_id]) From 64bcebdf3bff82a43ab531a720d64f1ffa4746b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:07:07 +0000 Subject: [PATCH 16/21] build(deps): bump esbuild from 0.19.11 to 0.20.1 Bumps [esbuild](https://github.com/evanw/esbuild) from 0.19.11 to 0.20.1. - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.19.11...v0.20.1) --- updated-dependencies: - dependency-name: esbuild dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 282 +++++++++++++++++++++++++-------------------------- 2 files changed, 142 insertions(+), 142 deletions(-) diff --git a/package.json b/package.json index c045a59a33..b67a6dc35c 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "datatables.net-dt": "^1.13.10", - "esbuild": "^0.19.11", + "esbuild": "^0.20.1", "faker": "^5.5.3", "jquery": "^3.6.4", "js-cookie": "^3.0.5", diff --git a/yarn.lock b/yarn.lock index 8741a73d41..0b3c0db5f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1021,120 +1021,120 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@esbuild/aix-ppc64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz#2acd20be6d4f0458bc8c784103495ff24f13b1d3" - integrity sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g== - -"@esbuild/android-arm64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz#b45d000017385c9051a4f03e17078abb935be220" - integrity sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q== - -"@esbuild/android-arm@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.11.tgz#f46f55414e1c3614ac682b29977792131238164c" - integrity sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw== - -"@esbuild/android-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.11.tgz#bfc01e91740b82011ef503c48f548950824922b2" - integrity sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg== - -"@esbuild/darwin-arm64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz#533fb7f5a08c37121d82c66198263dcc1bed29bf" - integrity sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ== - -"@esbuild/darwin-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz#62f3819eff7e4ddc656b7c6815a31cf9a1e7d98e" - integrity sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g== - -"@esbuild/freebsd-arm64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz#d478b4195aa3ca44160272dab85ef8baf4175b4a" - integrity sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA== - -"@esbuild/freebsd-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz#7bdcc1917409178257ca6a1a27fe06e797ec18a2" - integrity sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw== - -"@esbuild/linux-arm64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz#58ad4ff11685fcc735d7ff4ca759ab18fcfe4545" - integrity sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg== - -"@esbuild/linux-arm@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz#ce82246d873b5534d34de1e5c1b33026f35e60e3" - integrity sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q== - -"@esbuild/linux-ia32@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz#cbae1f313209affc74b80f4390c4c35c6ab83fa4" - integrity sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA== - -"@esbuild/linux-loong64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz#5f32aead1c3ec8f4cccdb7ed08b166224d4e9121" - integrity sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg== - -"@esbuild/linux-mips64el@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz#38eecf1cbb8c36a616261de858b3c10d03419af9" - integrity sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg== - -"@esbuild/linux-ppc64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz#9c5725a94e6ec15b93195e5a6afb821628afd912" - integrity sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA== - -"@esbuild/linux-riscv64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz#2dc4486d474a2a62bbe5870522a9a600e2acb916" - integrity sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ== - -"@esbuild/linux-s390x@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz#4ad8567df48f7dd4c71ec5b1753b6f37561a65a8" - integrity sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q== - -"@esbuild/linux-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz#b7390c4d5184f203ebe7ddaedf073df82a658766" - integrity sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA== - -"@esbuild/netbsd-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz#d633c09492a1721377f3bccedb2d821b911e813d" - integrity sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ== - -"@esbuild/openbsd-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz#17388c76e2f01125bf831a68c03a7ffccb65d1a2" - integrity sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw== - -"@esbuild/sunos-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz#e320636f00bb9f4fdf3a80e548cb743370d41767" - integrity sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ== - -"@esbuild/win32-arm64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz#c778b45a496e90b6fc373e2a2bb072f1441fe0ee" - integrity sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ== - -"@esbuild/win32-ia32@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz#481a65fee2e5cce74ec44823e6b09ecedcc5194c" - integrity sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg== - -"@esbuild/win32-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz#a5d300008960bb39677c46bf16f53ec70d8dee04" - integrity sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw== +"@esbuild/aix-ppc64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz#eafa8775019b3650a77e8310ba4dbd17ca7af6d5" + integrity sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA== + +"@esbuild/android-arm64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz#68791afa389550736f682c15b963a4f37ec2f5f6" + integrity sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A== + +"@esbuild/android-arm@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.1.tgz#38c91d8ee8d5196f7fbbdf4f0061415dde3a473a" + integrity sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw== + +"@esbuild/android-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.1.tgz#93f6190ce997b313669c20edbf3645fc6c8d8f22" + integrity sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA== + +"@esbuild/darwin-arm64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz#0d391f2e81fda833fe609182cc2fbb65e03a3c46" + integrity sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA== + +"@esbuild/darwin-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz#92504077424584684862f483a2242cfde4055ba2" + integrity sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA== + +"@esbuild/freebsd-arm64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz#a1646fa6ba87029c67ac8a102bb34384b9290774" + integrity sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw== + +"@esbuild/freebsd-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz#41c9243ab2b3254ea7fb512f71ffdb341562e951" + integrity sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg== + +"@esbuild/linux-arm64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz#f3c1e1269fbc9eedd9591a5bdd32bf707a883156" + integrity sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w== + +"@esbuild/linux-arm@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz#4503ca7001a8ee99589c072801ce9d7540717a21" + integrity sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw== + +"@esbuild/linux-ia32@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz#98c474e3e0cbb5bcbdd8561a6e65d18f5767ce48" + integrity sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw== + +"@esbuild/linux-loong64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz#a8097d28d14b9165c725fe58fc438f80decd2f33" + integrity sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA== + +"@esbuild/linux-mips64el@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz#c44f6f0d7d017c41ad3bb15bfdb69b690656b5ea" + integrity sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA== + +"@esbuild/linux-ppc64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz#0765a55389a99237b3c84227948c6e47eba96f0d" + integrity sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw== + +"@esbuild/linux-riscv64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz#e4153b032288e3095ddf4c8be07893781b309a7e" + integrity sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg== + +"@esbuild/linux-s390x@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz#b9ab8af6e4b73b26d63c1c426d7669a5d53eb5a7" + integrity sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ== + +"@esbuild/linux-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz#0b25da17ac38c3e11cdd06ca3691d4d6bef2755f" + integrity sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA== + +"@esbuild/netbsd-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz#3148e48406cd0d4f7ba1e0bf3f4d77d548c98407" + integrity sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg== + +"@esbuild/openbsd-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz#7b73e852986a9750192626d377ac96ac2b749b76" + integrity sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw== + +"@esbuild/sunos-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz#402a441cdac2eee98d8be378c7bc23e00c1861c5" + integrity sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q== + +"@esbuild/win32-arm64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz#36c4e311085806a6a0c5fc54d1ac4d7b27e94d7b" + integrity sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A== + +"@esbuild/win32-ia32@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz#0cf933be3fb9dc58b45d149559fe03e9e22b54fe" + integrity sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw== + +"@esbuild/win32-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz#77583b6ea54cee7c1410ebbd54051b6a3fcbd8ba" + integrity sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA== "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" @@ -2542,34 +2542,34 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -esbuild@^0.19.11: - version "0.19.11" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.11.tgz#4a02dca031e768b5556606e1b468fe72e3325d96" - integrity sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA== +esbuild@^0.20.1: + version "0.20.1" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.1.tgz#1e4cbb380ad1959db7609cb9573ee77257724a3e" + integrity sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA== optionalDependencies: - "@esbuild/aix-ppc64" "0.19.11" - "@esbuild/android-arm" "0.19.11" - "@esbuild/android-arm64" "0.19.11" - "@esbuild/android-x64" "0.19.11" - "@esbuild/darwin-arm64" "0.19.11" - "@esbuild/darwin-x64" "0.19.11" - "@esbuild/freebsd-arm64" "0.19.11" - "@esbuild/freebsd-x64" "0.19.11" - "@esbuild/linux-arm" "0.19.11" - "@esbuild/linux-arm64" "0.19.11" - "@esbuild/linux-ia32" "0.19.11" - "@esbuild/linux-loong64" "0.19.11" - "@esbuild/linux-mips64el" "0.19.11" - "@esbuild/linux-ppc64" "0.19.11" - "@esbuild/linux-riscv64" "0.19.11" - "@esbuild/linux-s390x" "0.19.11" - "@esbuild/linux-x64" "0.19.11" - "@esbuild/netbsd-x64" "0.19.11" - "@esbuild/openbsd-x64" "0.19.11" - "@esbuild/sunos-x64" "0.19.11" - "@esbuild/win32-arm64" "0.19.11" - "@esbuild/win32-ia32" "0.19.11" - "@esbuild/win32-x64" "0.19.11" + "@esbuild/aix-ppc64" "0.20.1" + "@esbuild/android-arm" "0.20.1" + "@esbuild/android-arm64" "0.20.1" + "@esbuild/android-x64" "0.20.1" + "@esbuild/darwin-arm64" "0.20.1" + "@esbuild/darwin-x64" "0.20.1" + "@esbuild/freebsd-arm64" "0.20.1" + "@esbuild/freebsd-x64" "0.20.1" + "@esbuild/linux-arm" "0.20.1" + "@esbuild/linux-arm64" "0.20.1" + "@esbuild/linux-ia32" "0.20.1" + "@esbuild/linux-loong64" "0.20.1" + "@esbuild/linux-mips64el" "0.20.1" + "@esbuild/linux-ppc64" "0.20.1" + "@esbuild/linux-riscv64" "0.20.1" + "@esbuild/linux-s390x" "0.20.1" + "@esbuild/linux-x64" "0.20.1" + "@esbuild/netbsd-x64" "0.20.1" + "@esbuild/openbsd-x64" "0.20.1" + "@esbuild/sunos-x64" "0.20.1" + "@esbuild/win32-arm64" "0.20.1" + "@esbuild/win32-ia32" "0.20.1" + "@esbuild/win32-x64" "0.20.1" escalade@^3.1.1: version "3.1.1" From cd47ca42ef10b6fb2c1bb21e4858056f83c133f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:07:37 +0000 Subject: [PATCH 17/21] build(deps): bump sass from 1.70.0 to 1.71.0 Bumps [sass](https://github.com/sass/dart-sass) from 1.70.0 to 1.71.0. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.70.0...1.71.0) --- updated-dependencies: - dependency-name: sass dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c045a59a33..150f3693d5 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "lodash": "^4.17.21", "luxon": "^3.4.0", "popper.js": "^1.16.1", - "sass": "^1.70.0", + "sass": "^1.71.0", "select2": "^4.0.13", "select2-bootstrap-5-theme": "^1.3.0", "stimulus-rails-nested-form": "^4.1.0", diff --git a/yarn.lock b/yarn.lock index 8741a73d41..bf016c8a82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4971,10 +4971,10 @@ safe-regex-test@^1.0.0: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass@^1.70.0: - version "1.70.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.70.0.tgz#761197419d97b5358cb25f9dd38c176a8a270a75" - integrity sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ== +sass@^1.71.0: + version "1.71.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.71.0.tgz#b3085759b9b2ab503a977aecb7e91153bf941117" + integrity sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" From cc419e41f42322be760d1ba76948520d3f2f95d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:35:18 +0000 Subject: [PATCH 18/21] build(deps): bump jsbundling-rails from 1.2.2 to 1.3.0 Bumps [jsbundling-rails](https://github.com/rails/jsbundling-rails) from 1.2.2 to 1.3.0. - [Release notes](https://github.com/rails/jsbundling-rails/releases) - [Commits](https://github.com/rails/jsbundling-rails/compare/v1.2.2...v1.3.0) --- updated-dependencies: - dependency-name: jsbundling-rails dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ddb302c2e7..50847ebb88 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -261,13 +261,13 @@ GEM mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) io-console (0.7.2) - irb (1.11.1) + irb (1.11.2) rdoc reline (>= 0.4.2) jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) - jsbundling-rails (1.2.2) + jsbundling-rails (1.3.0) railties (>= 6.0.0) json (2.6.3) json-schema (4.1.1) @@ -301,7 +301,7 @@ GEM mini_magick (4.11.0) mini_mime (1.1.5) mini_portile2 (2.8.5) - minitest (5.22.0) + minitest (5.22.2) multi_xml (0.6.0) multipart-post (2.3.0) mutex_m (0.2.0) From 52bb7d6dcc99cf9fb7678c18f59199dd5e259439 Mon Sep 17 00:00:00 2001 From: compwron Date: Sun, 25 Feb 2024 11:03:45 -0800 Subject: [PATCH 19/21] let rake task succeed --- ...125151610_set_case_contacts_as_active.rake | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake b/lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake index daa2e90ae7..91d5493fb2 100644 --- a/lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake +++ b/lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake @@ -2,29 +2,30 @@ namespace :after_party do desc "Deployment task: set_case_contacts_as_active" task set_case_contacts_as_active: :environment do puts "Running deploy task 'set_case_contacts_as_active'" + require "bugsnag" + Bugsnag.configure do |config| + config.api_key = ENV["BUGSNAG_API_KEY"] + config.ignore_classes << ActiveRecord::RecordNotFound + config.release_stage = ENV["HEROKU_APP_NAME"] || ENV["APP_ENVIRONMENT"] + callback = proc do |event| + event.set_user(current_user&.id, current_user&.email) + end + + config.add_on_error(callback) + end CaseContact.where.not(casa_case_id: nil).each do |cc| p cc.id cc.additional_expenses.each do |additional_expense| - additional_expense.update!(other_expenses_describe: "No description given") unless additional_expense.other_expenses_describe + success = additional_expense.update(other_expenses_describe: "No description given") unless additional_expense.other_expenses_describe + if !success + Bugsnag.notify("Failed to update additional expense #{additional_expense.id} with errors: #{additional_expense.error_messages}") + end end cc.update!(status: "active", draft_case_ids: [cc.casa_case_id]) rescue => e begin - require "bugsnag" - Bugsnag.configure do |config| - config.api_key = ENV["BUGSNAG_API_KEY"] - config.ignore_classes << ActiveRecord::RecordNotFound - config.release_stage = ENV["HEROKU_APP_NAME"] || ENV["APP_ENVIRONMENT"] - - callback = proc do |event| - event.set_user(current_user&.id, current_user&.email) - end - - config.add_on_error(callback) - end - Bugsnag.notify(e) rescue => e2 p e2 From 6546fc6262b5223927b4b5d1c866a4192505a581 Mon Sep 17 00:00:00 2001 From: compwron Date: Sun, 25 Feb 2024 13:27:58 -0800 Subject: [PATCH 20/21] remove bugsnagging which causes memory issues on prod deploy --- ...125151610_set_case_contacts_as_active.rake | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake b/lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake index 91d5493fb2..f2df6b061a 100644 --- a/lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake +++ b/lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake @@ -2,35 +2,12 @@ namespace :after_party do desc "Deployment task: set_case_contacts_as_active" task set_case_contacts_as_active: :environment do puts "Running deploy task 'set_case_contacts_as_active'" - require "bugsnag" - Bugsnag.configure do |config| - config.api_key = ENV["BUGSNAG_API_KEY"] - config.ignore_classes << ActiveRecord::RecordNotFound - config.release_stage = ENV["HEROKU_APP_NAME"] || ENV["APP_ENVIRONMENT"] - callback = proc do |event| - event.set_user(current_user&.id, current_user&.email) - end - - config.add_on_error(callback) - end CaseContact.where.not(casa_case_id: nil).each do |cc| - p cc.id cc.additional_expenses.each do |additional_expense| - success = additional_expense.update(other_expenses_describe: "No description given") unless additional_expense.other_expenses_describe - if !success - Bugsnag.notify("Failed to update additional expense #{additional_expense.id} with errors: #{additional_expense.error_messages}") - end - end - - cc.update!(status: "active", draft_case_ids: [cc.casa_case_id]) - rescue => e - begin - Bugsnag.notify(e) - rescue => e2 - p e2 + additional_expense.update(other_expenses_describe: "No description given") unless additional_expense.other_expenses_describe end - p e + cc.update(status: "active", draft_case_ids: [cc.casa_case_id]) end # Update task as completed. If you remove the line below, the task will From d1698d630caa8ac346e7303d9fe28977d6ffb4cd Mon Sep 17 00:00:00 2001 From: compwron Date: Mon, 26 Feb 2024 00:21:26 -0800 Subject: [PATCH 21/21] bundle update --conservative --group test development --- Gemfile.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 50847ebb88..a5eb6d3cf9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -107,23 +107,23 @@ GEM bigdecimal (3.1.6) bindex (0.8.1) blueprinter (0.30.0) - brakeman (6.1.1) + brakeman (6.1.2) racc bugsnag (6.26.3) concurrent-ruby (~> 1.0) builder (3.2.4) - bullet (7.1.4) + bullet (7.1.6) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) bundler-audit (0.9.1) bundler (>= 1.2.0, < 3) thor (~> 1.0) byebug (11.1.3) - capybara (3.39.2) + capybara (3.40.0) addressable matrix mini_mime (>= 0.1.3) - nokogiri (~> 1.8) + nokogiri (~> 1.11) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) @@ -276,7 +276,7 @@ GEM language_server-protocol (3.17.0.3) launchy (2.5.0) addressable (~> 2.7) - letter_opener (1.8.1) + letter_opener (1.9.0) launchy (>= 2.2, < 3) lint_roller (1.1.0) llhttp-ffi (0.4.0) @@ -425,7 +425,7 @@ GEM rspec-mocks (3.12.6) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) - rspec-rails (6.1.0) + rspec-rails (6.1.1) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) @@ -539,7 +539,7 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webmock (3.19.1) + webmock (3.23.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0)
+ + Name Email Supervisor