diff --git a/app/forms/filters/inputs/base_filter_form.rb b/app/forms/filters/inputs/base_filter_form.rb index b10c9e952fbf..91ec5bf89209 100644 --- a/app/forms/filters/inputs/base_filter_form.rb +++ b/app/forms/filters/inputs/base_filter_form.rb @@ -106,11 +106,12 @@ def add_operator(group) "filter-name": @filter.name } ) do |select| - @filter.available_operators.each do |op| + @filter.available_operators.each do |operator| select.option( - label: op.human_name, - value: op.symbol, - selected: op.symbol == selected_operator + label: operator.human_name, + value: operator.symbol, + selected: operator.symbol == selected_operator, + **(operator.requires_value? ? {} : { "data-no-value": true }) ) end end diff --git a/app/forms/filters/inputs/date_form.rb b/app/forms/filters/inputs/date_form.rb index 9ed1d93ef181..7cd2b0410cbb 100644 --- a/app/forms/filters/inputs/date_form.rb +++ b/app/forms/filters/inputs/date_form.rb @@ -95,7 +95,7 @@ def on_date_div(builder, filter_name, value) label: :singleDay, hidden: value.nil?, leading_visual: { icon: :calendar }, - value:, + value: value || "", datepicker_options: { input_attributes: { "data-filter--filters-form-target" => "singleDay" } }, data: { "filter-name": filter_name } ) diff --git a/app/models/persisted_view.rb b/app/models/persisted_view.rb index 9646870b7842..fa7a405215a9 100644 --- a/app/models/persisted_view.rb +++ b/app/models/persisted_view.rb @@ -49,6 +49,7 @@ class PersistedView < ApplicationRecord scope :public_views, -> { where(public: true) } scope :private_views, ->(principal = User.current) { where(public: false, principal_id: principal.id) } + scope :with_children, -> { includes(:children) } scope :visible, (lambda do |principal = User.current| public_views.or(private_views(principal)) @@ -68,6 +69,17 @@ class << self attr_writer :allowed_children end + # Resolves a (potentially user-supplied) class name to a view class, but only + # if it is an allowed child of this view. Returns nil for any unknown or + # forbidden name. + # + # `constantize` is applied only to the developer-controlled `allowed_children` + # list, never to the passed-in name, so untrusted input can never resolve an + # arbitrary constant. + def self.allowed_child_class(name) + allowed_children.index_with(&:constantize)[name.to_s] + end + # Returns the query of this view or, if not set, the query of the parent view. def effective_query query || parent&.effective_query diff --git a/app/models/queries/work_packages/filter/filter_for_wp_mixin.rb b/app/models/queries/work_packages/filter/filter_for_wp_mixin.rb index 9ee968a8b8a1..d2cef34cb119 100644 --- a/app/models/queries/work_packages/filter/filter_for_wp_mixin.rb +++ b/app/models/queries/work_packages/filter/filter_for_wp_mixin.rb @@ -37,6 +37,27 @@ def allowed_values raise NotImplementedError, "There would be too many candidates" end + # Tell `Filters::FilterForm`'s dispatch to render these filters with a + # server-side autocompleter (the candidate set is too large for an inline + # `