From 502b213860d6fed7bb032e9aeeb228d6574d3fb3 Mon Sep 17 00:00:00 2001 From: Sebastien Lavoie Date: Fri, 7 Jun 2024 14:47:23 -0400 Subject: [PATCH] Add support for bounded enumerables --- app/helpers/maintenance_tasks/tasks_helper.rb | 27 ++++++++++++++++--- .../app/tasks/maintenance/params_task.rb | 4 +++ .../maintenance_tasks/tasks_helper_test.rb | 2 ++ .../maintenance_tasks/task_data_show_test.rb | 2 ++ test/system/maintenance_tasks/tasks_test.rb | 12 +++++++++ 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/app/helpers/maintenance_tasks/tasks_helper.rb b/app/helpers/maintenance_tasks/tasks_helper.rb index 24f9c918..345c377b 100644 --- a/app/helpers/maintenance_tasks/tasks_helper.rb +++ b/app/helpers/maintenance_tasks/tasks_helper.rb @@ -7,6 +7,8 @@ module MaintenanceTasks # # @api private module TasksHelper + MAX_INCLUSION_ITEMS = 1000 + STATUS_COLOURS = { "new" => ["is-primary"], "enqueued" => ["is-primary is-light"], @@ -101,10 +103,21 @@ def csv_file_download_path(run) ) end - # Resolves values covered by the inclusion validator for a task attribute. - # Only Arrays are supported. - # Procs and lambdas are also supported, but only if they take no arguments and return an Array. - # Option types such as Symbols, and Range are not supported and return nil. + # Resolves values covered by the inclusion validator for a task attribute, + # but only if it can be resolved to an array in a stateless manner. + # + # Examples of supported constraints: + # - Arrays + # - Enumerable that can be converted to an Array + # - Procs and lambdas, but only if they take no arguments and return a value meeting the requirements above. + # + # Examples of unsupported constraints, which would return nil: + # - Symbols (which would normally resolve to an instance method) + # - Procs and lambdas that take an argument (which would be the instance) + # - Unbounded ranges (e.g. `1..`) + # + # If the resulting Array is larger than #MAX_INCLUSION_ITEMS, the method will return nil. + # This is meant to make sure the process does not misbehave if the inclusion range is giant or infinite. # # Returned values are used to populate a dropdown list of options. # @@ -120,6 +133,12 @@ def resolve_inclusion_value(task_class, parameter_name) in_option = inclusion_validator.options[:in] || inclusion_validator.options[:within] in_option = in_option.call if in_option.is_a?(Proc) && in_option.arity.zero? + + if in_option.is_a?(Enumerable) + items = in_option.take(MAX_INCLUSION_ITEMS + 1).to_a + in_option = items if items.size <= MAX_INCLUSION_ITEMS + end + in_option if in_option.is_a?(Array) end diff --git a/test/dummy/app/tasks/maintenance/params_task.rb b/test/dummy/app/tasks/maintenance/params_task.rb index 92562add..14079add 100644 --- a/test/dummy/app/tasks/maintenance/params_task.rb +++ b/test/dummy/app/tasks/maintenance/params_task.rb @@ -30,11 +30,15 @@ class ParamsTask < MaintenanceTasks::Task attribute :text_integer_attr_proc_arg, :integer attribute :text_integer_attr_undefined_symbol, :integer attribute :text_integer_attr_unbounded_range, :integer + attribute :text_integer_attr_bounded_range, :integer + attribute :text_integer_attr_enumerable, :integer validates_inclusion_of :text_integer_attr_proc_no_arg, in: proc { [100, 200, 300] }, allow_nil: true validates_inclusion_of :text_integer_attr_proc_arg, in: proc { |_task| [100, 200, 300] }, allow_nil: true validates_inclusion_of :text_integer_attr_undefined_symbol, in: :undefined_symbol, allow_nil: true validates_inclusion_of :text_integer_attr_unbounded_range, in: (100..), allow_nil: true + validates_inclusion_of :text_integer_attr_bounded_range, in: (100..120), allow_nil: true + validates_inclusion_of :text_integer_attr_enumerable, in: (100..120).step(5), allow_nil: true class << self attr_accessor :fast_task diff --git a/test/helpers/maintenance_tasks/tasks_helper_test.rb b/test/helpers/maintenance_tasks/tasks_helper_test.rb index b212c444..a960ad16 100644 --- a/test/helpers/maintenance_tasks/tasks_helper_test.rb +++ b/test/helpers/maintenance_tasks/tasks_helper_test.rb @@ -96,6 +96,8 @@ class TasksHelperTest < ActionView::TestCase "integer_dropdown_attr", "boolean_dropdown_attr", "text_integer_attr_proc_no_arg", + "text_integer_attr_bounded_range", + "text_integer_attr_enumerable", ].each do |attribute| assert_match "Select a value", markup(attribute).squish end diff --git a/test/models/maintenance_tasks/task_data_show_test.rb b/test/models/maintenance_tasks/task_data_show_test.rb index 9fb12194..56f831b1 100644 --- a/test/models/maintenance_tasks/task_data_show_test.rb +++ b/test/models/maintenance_tasks/task_data_show_test.rb @@ -93,6 +93,8 @@ class TaskDataShowTest < ActiveSupport::TestCase "text_integer_attr_proc_arg", "text_integer_attr_undefined_symbol", "text_integer_attr_unbounded_range", + "text_integer_attr_bounded_range", + "text_integer_attr_enumerable", ], TaskDataShow.new("Maintenance::ParamsTask").parameter_names, ) diff --git a/test/system/maintenance_tasks/tasks_test.rb b/test/system/maintenance_tasks/tasks_test.rb index cd1998b3..0c6c969f 100644 --- a/test/system/maintenance_tasks/tasks_test.rb +++ b/test/system/maintenance_tasks/tasks_test.rb @@ -128,6 +128,18 @@ class TasksTest < ApplicationSystemTestCase integer_dropdown_field_options = integer_dropdown_field.find_all("option").map { |option| option[:value] } assert_equal(["", "100", "200", "300"], integer_dropdown_field_options) + integer_dropdown_field = page.find_field("task[text_integer_attr_bounded_range]") + assert_equal("select", integer_dropdown_field.tag_name) + assert_equal("select-one", integer_dropdown_field[:type]) + integer_dropdown_field_options = integer_dropdown_field.find_all("option").map { |option| option[:value] } + assert_equal([""] + (100..120).to_a.map(&:to_s), integer_dropdown_field_options) + + integer_dropdown_field = page.find_field("task[text_integer_attr_enumerable]") + assert_equal("select", integer_dropdown_field.tag_name) + assert_equal("select-one", integer_dropdown_field[:type]) + integer_dropdown_field_options = integer_dropdown_field.find_all("option").map { |option| option[:value] } + assert_equal(["", "100", "105", "110", "115", "120"], integer_dropdown_field_options) + boolean_dropdown_field = page.find_field("task[boolean_dropdown_attr]") assert_equal("select", boolean_dropdown_field.tag_name) assert_equal("select-one", boolean_dropdown_field[:type])