diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 0dc416865..f8a332189 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -95,9 +95,7 @@ module Instructeurs @has_export_notification = notify_exports? @last_export = last_export_for(statut) - @filtered_sorted_ids = procedure_presentation.filtered_sorted_ids(dossiers, statut, count: dossiers_count) - page = params[:page].presence || 1 @dossiers_count = @filtered_sorted_ids.size diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index bb6e6d874..278d6fc2c 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -14,6 +14,8 @@ class ProcedurePresentation < ApplicationRecord TYPE_DE_CHAMP = 'type_de_champ' FILTERS_VALUE_MAX_LENGTH = 100 + # https://www.postgresql.org/docs/current/datatype-numeric.html + PG_INTEGER_MAX_VALUE = 2147483647 belongs_to :assign_to, optional: false has_many :exports, dependent: :destroy @@ -25,6 +27,7 @@ class ProcedurePresentation < ApplicationRecord validate :check_allowed_sort_order validate :check_allowed_filter_columns validate :check_filters_max_length + validate :check_filters_max_integer def displayed_fields_for_headers [ @@ -313,6 +316,16 @@ class ProcedurePresentation < ApplicationRecord end end + def check_filters_max_integer + filters.values.flatten.each do |filter| + next if !filter.is_a?(Hash) + next if filter['column'] != 'id' + next if filter['value']&.to_i&. < PG_INTEGER_MAX_VALUE + + errors.add(:base, "Le filtre #{filter['label']} n'est pas un numéro de dossier possible") + end + end + def valid_column?(table, column, extra_columns = {}) valid_columns_for_table(table).include?(column) || extra_columns[table]&.include?(column) diff --git a/app/tasks/maintenance/clean_invalid_procedure_presentation_task.rb b/app/tasks/maintenance/clean_invalid_procedure_presentation_task.rb new file mode 100644 index 000000000..9da038ac4 --- /dev/null +++ b/app/tasks/maintenance/clean_invalid_procedure_presentation_task.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Maintenance + # PR: 10774 + # why: postgres does not support integer greater than ProcedurePresentation::PG_INTEGER_MAX_VALUE) + # it occures when user copypaste the dossier id twice (like missed copy paste,paste) + # once this huge integer is saved on procedure presentation, page with this filter can't be loaded + # when: run this migration when it appears in your maintenance tasks list, this file fix the data and we added some validations too + class CleanInvalidProcedurePresentationTask < MaintenanceTasks::Task + def collection + ProcedurePresentation.all + end + + def process(element) + element.filters = element.filters.transform_values do |filters_by_status| + filters_by_status.reject do |filter| + filter.is_a?(Hash) && + filter['column'] == 'id' && + (filter['value']&.to_i&. >= ProcedurePresentation::PG_INTEGER_MAX_VALUE) + end + end + element.save + end + + def count + # Optionally, define the number of rows that will be iterated over + # This is used to track the task's progress + end + end +end diff --git a/spec/models/procedure_presentation_spec.rb b/spec/models/procedure_presentation_spec.rb index 266d2c23e..7515a8369 100644 --- a/spec/models/procedure_presentation_spec.rb +++ b/spec/models/procedure_presentation_spec.rb @@ -55,6 +55,11 @@ describe ProcedurePresentation do context 'of filters' do it { expect(build(:procedure_presentation, filters: { "suivis" => [{ table: "user", column: "reset_password_token", "order" => "asc" }] })).to be_invalid } it { expect(build(:procedure_presentation, filters: { "suivis" => [{ table: "user", column: "email", "value" => "exceedingly long filter value" * 10 }] })).to be_invalid } + + describe 'check_filters_max_integer' do + it { expect(build(:procedure_presentation, filters: { "suivis" => [{ table: "self", column: "id", "value" => ProcedurePresentation::PG_INTEGER_MAX_VALUE.to_s }] })).to be_invalid } + it { expect(build(:procedure_presentation, filters: { "suivis" => [{ table: "self", column: "id", "value" => (ProcedurePresentation::PG_INTEGER_MAX_VALUE - 1).to_s }] })).to be_valid } + end end end diff --git a/spec/tasks/maintenance/clean_invalid_procedure_presentation_task_spec.rb b/spec/tasks/maintenance/clean_invalid_procedure_presentation_task_spec.rb new file mode 100644 index 000000000..6b95fe523 --- /dev/null +++ b/spec/tasks/maintenance/clean_invalid_procedure_presentation_task_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "rails_helper" + +module Maintenance + RSpec.describe CleanInvalidProcedurePresentationTask do + describe "#process" do + subject(:process) { described_class.process(element) } + let(:procedure) { create(:procedure) } + let(:groupe_instructeur) { create(:groupe_instructeur, procedure:, instructeurs: [build(:instructeur)]) } + let(:assign_to) { create(:assign_to, procedure:, instructeur: groupe_instructeur.instructeurs.first) } + let(:element) { create(:procedure_presentation, procedure:, assign_to:) } + + before { element.update_column(:filters, filters) } + + context 'when filter is valid' do + let(:filters) { { "suivis" => [{ 'table' => "self", 'column' => "id", "value" => (ProcedurePresentation::PG_INTEGER_MAX_VALUE - 1).to_s }] } } + it 'keeps it filters' do + expect { subject }.not_to change { element.reload.filters } + end + end + + context 'when filter is invalid, drop it' do + let(:filters) { { "suivis" => [{ 'table' => "self", 'column' => "id", "value" => (ProcedurePresentation::PG_INTEGER_MAX_VALUE).to_s }] } } + it 'drop invalid filters' do + expect { subject }.to change { element.reload.filters }.to({ "suivis" => [] }) + end + end + end + end +end