From 3897c25139bbdcc5e1046d9bc59aff34303fc3c8 Mon Sep 17 00:00:00 2001 From: David F Date: Fri, 22 May 2026 13:59:16 +0200 Subject: [PATCH] Move autocomplete_options from FilterComponent to filter classes. wp/74380 As per review feedback from oliverguenther. --- app/components/filter/filter_component.rb | 89 +------------------ .../users/user_filters_component.rb | 23 ----- app/models/queries/filters/base.rb | 4 + .../filters/shared/custom_fields/hierarchy.rb | 17 ++++ .../shared/custom_fields/list_optional.rb | 21 ++++- .../filters/shared/custom_fields/user.rb | 16 ++++ .../filters/shared/project_filter/optional.rb | 8 ++ .../filters/shared/project_filter/required.rb | 8 ++ .../projects/filters/project_status_filter.rb | 13 +++ .../queries/projects/filters/type_filter.rb | 13 +++ .../queries/users/filters/group_filter.rb | 12 +++ .../meetings/meeting_filters_component.rb | 20 ----- .../meetings/filters/attended_user_filter.rb | 5 ++ .../queries/meetings/filters/author_filter.rb | 5 ++ .../meetings/filters/invited_user_filter.rb | 5 ++ spec/support/pages/admin/users/index.rb | 4 +- 16 files changed, 129 insertions(+), 134 deletions(-) diff --git a/app/components/filter/filter_component.rb b/app/components/filter/filter_component.rb index 39543c0b41e..7d401cb053b 100644 --- a/app/components/filter/filter_component.rb +++ b/app/components/filter/filter_component.rb @@ -100,22 +100,8 @@ module Filter # @param filter [QueryFilter] the filter for which we want to pass additional attributes # @return [Hash] the additional attributes for the filter, yielded in map_filter def additional_filter_attributes(filter) - case filter - when Queries::Filters::Shared::ProjectFilter::Required, - Queries::Filters::Shared::ProjectFilter::Optional - { autocomplete_options: project_autocomplete_options } - when Queries::Filters::Shared::CustomFields::User - { autocomplete_options: user_autocomplete_options } - when Queries::Filters::Shared::CustomFields::ListOptional - { autocomplete_options: custom_field_list_autocomplete_options(filter) } - when Queries::Filters::Shared::CustomFields::Hierarchy - { autocomplete_options: custom_field_hierarchy_autocomplete_options(filter) } - when Queries::Projects::Filters::ProjectStatusFilter, - Queries::Projects::Filters::TypeFilter - { autocomplete_options: list_autocomplete_options(filter) } - else - {} - end + opts = filter.autocomplete_options + opts.any? ? { autocomplete_options: opts } : {} end def filter_form_class(filter, additional_attributes) @@ -131,76 +117,5 @@ module Filter Filters::Inputs::TextForm end end - - def custom_field_list_autocomplete_options(filter) - all_items = custom_field_allowed_items(filter) - selected = filter.values - options = { items: all_items } - options[:groupBy] = "project_name" if filter.custom_field.version? - autocomplete_options.merge(options).merge(model: all_items.select { |item| selected.include?(item[:id]) }) - end - - def custom_field_allowed_items(filter) - if filter.custom_field.version? - filter.allowed_values.map { |name, id, project_name| { name:, id:, project_name: } } - else - filter.allowed_values.map { |name, id| { name:, id: } } - end - end - - def custom_field_hierarchy_autocomplete_options(filter) - items = filter.allowed_values.map do |name, id| - path = name.split(" / ") - { name: path.last, id:, depth: path.length - 1 } - end - selected = filter.values - - autocomplete_options.merge({ items: }).merge(model: items.select { |item| selected.include?(item[:id]) }) - end - - def list_autocomplete_options(filter) - all_items = filter.allowed_values.map { |name, id| { name:, id: } } - selected = filter.values - autocomplete_options.merge( - items: all_items, - model: all_items.select { |item| selected.include?(item[:id]) } - ) - end - - def autocomplete_options - { - component: "opce-autocompleter", - bindValue: "id", - bindLabel: "name", - hideSelected: true, - defaultData: false - } - end - - def project_autocomplete_options - { - component: "opce-project-autocompleter", - resource: "projects", - filters: [ - { name: "active", operator: "=", values: ["t"] } - ] - } - end - - def user_autocomplete_options - { - component: "opce-user-autocompleter", - hideSelected: true, - defaultData: false, - placeholder: I18n.t(:label_user_search), - resource: "principals", - url: ::API::V3::Utilities::PathHelper::ApiV3Path.principals, - filters: [ - { name: "status", operator: "!", values: [Principal.statuses["locked"].to_s] } - ], - searchKey: "any_name_attribute", - focusDirectly: false - } - end end end diff --git a/app/components/users/user_filters_component.rb b/app/components/users/user_filters_component.rb index eb923be1ffa..710c9994a17 100644 --- a/app/components/users/user_filters_component.rb +++ b/app/components/users/user_filters_component.rb @@ -38,28 +38,5 @@ module Users .grep_v(Queries::Users::Filters::BlockedFilter) .sort_by(&:human_name) end - - protected - - def additional_filter_attributes(filter) - case filter - when Queries::Users::Filters::GroupFilter - { - autocomplete_options: { - component: "opce-user-autocompleter", - resource: "principals", - url: ::API::V3::Utilities::PathHelper::ApiV3Path.principals, - filters: [ - { name: "type", operator: "=", values: %w[Group] } - ], - searchKey: "any_name_attribute", - inputValue: filter.values, - bindValue: "id" - } - } - else - super - end - end end end diff --git a/app/models/queries/filters/base.rb b/app/models/queries/filters/base.rb index 1b049ce549a..58d7140aa4a 100644 --- a/app/models/queries/filters/base.rb +++ b/app/models/queries/filters/base.rb @@ -182,6 +182,10 @@ class Queries::Filters::Base errors.full_message(human_name, messages) end + def autocomplete_options + {} + end + protected def type_strategy diff --git a/app/models/queries/filters/shared/custom_fields/hierarchy.rb b/app/models/queries/filters/shared/custom_fields/hierarchy.rb index c29e545b9be..2c927879812 100644 --- a/app/models/queries/filters/shared/custom_fields/hierarchy.rb +++ b/app/models/queries/filters/shared/custom_fields/hierarchy.rb @@ -37,6 +37,23 @@ module Queries true end + def autocomplete_options + items = allowed_values.map do |name, id| + path = name.split(" / ") + { name: path.last, id:, depth: path.length - 1 } + end + + { + component: "opce-autocompleter", + bindValue: "id", + bindLabel: "name", + hideSelected: true, + defaultData: false, + items:, + model: items.select { |item| values.include?(item[:id]) } + } + end + def value_objects CustomField::Hierarchy::Item .where(id: @values) diff --git a/app/models/queries/filters/shared/custom_fields/list_optional.rb b/app/models/queries/filters/shared/custom_fields/list_optional.rb index ea5a0f1db68..94ad7cdc889 100644 --- a/app/models/queries/filters/shared/custom_fields/list_optional.rb +++ b/app/models/queries/filters/shared/custom_fields/list_optional.rb @@ -59,6 +59,24 @@ module Queries::Filters::Shared :list_optional end + def autocomplete_options # rubocop:disable Metrics/AbcSize + all_items = if custom_field.version? + allowed_values.map { |name, id, project_name| { name:, id:, project_name: } } + else + allowed_values.map { |name, id| { name:, id: } } + end + options = { items: all_items } + options[:groupBy] = "project_name" if custom_field.version? + + { + component: "opce-autocompleter", + bindValue: "id", + bindLabel: "name", + hideSelected: true, + defaultData: false + }.merge(options).merge(model: all_items.select { |item| values.include?(item[:id]) }) + end + protected def condition @@ -75,8 +93,7 @@ module Queries::Filters::Shared end def customized_strategy? - operator_strategy == Queries::Operators::CustomFields::EqualsAll || - operator_strategy == Queries::Operators::CustomFields::NotEqualsAll + [Queries::Operators::CustomFields::EqualsAll, Queries::Operators::CustomFields::NotEqualsAll].include?(operator_strategy) end def type_strategy_class diff --git a/app/models/queries/filters/shared/custom_fields/user.rb b/app/models/queries/filters/shared/custom_fields/user.rb index 907f5eafcc0..89e87f85afa 100644 --- a/app/models/queries/filters/shared/custom_fields/user.rb +++ b/app/models/queries/filters/shared/custom_fields/user.rb @@ -48,6 +48,22 @@ module Queries::Filters::Shared vals + user_groups_added(vals) end + def autocomplete_options + { + component: "opce-user-autocompleter", + hideSelected: true, + defaultData: false, + placeholder: I18n.t(:label_user_search), + resource: "principals", + url: ::API::V3::Utilities::PathHelper::ApiV3Path.principals, + filters: [ + { name: "status", operator: "!", values: [Principal.statuses["locked"].to_s] } + ], + searchKey: "any_name_attribute", + focusDirectly: false + } + end + private def group_members_added(vals) diff --git a/app/models/queries/filters/shared/project_filter/optional.rb b/app/models/queries/filters/shared/project_filter/optional.rb index ba92e84d8e9..95de9205222 100644 --- a/app/models/queries/filters/shared/project_filter/optional.rb +++ b/app/models/queries/filters/shared/project_filter/optional.rb @@ -51,6 +51,14 @@ module Queries::Filters::Shared::ProjectFilter::Optional # harm. @type_strategy ||= ::Queries::Filters::Strategies::IntegerListOptional.new(self) end + + def autocomplete_options + { + component: "opce-project-autocompleter", + resource: "projects", + filters: [{ name: "active", operator: "=", values: ["t"] }] + } + end end module ClassMethods diff --git a/app/models/queries/filters/shared/project_filter/required.rb b/app/models/queries/filters/shared/project_filter/required.rb index 951b2919c47..6ac038e857f 100644 --- a/app/models/queries/filters/shared/project_filter/required.rb +++ b/app/models/queries/filters/shared/project_filter/required.rb @@ -51,6 +51,14 @@ module Queries::Filters::Shared::ProjectFilter::Required # harm. @type_strategy ||= ::Queries::Filters::Strategies::IntegerList.new(self) end + + def autocomplete_options + { + component: "opce-project-autocompleter", + resource: "projects", + filters: [{ name: "active", operator: "=", values: ["t"] }] + } + end end module ClassMethods diff --git a/app/models/queries/projects/filters/project_status_filter.rb b/app/models/queries/projects/filters/project_status_filter.rb index 99fe4da849b..d23d264bf43 100644 --- a/app/models/queries/projects/filters/project_status_filter.rb +++ b/app/models/queries/projects/filters/project_status_filter.rb @@ -41,6 +41,19 @@ class Queries::Projects::Filters::ProjectStatusFilter < Queries::Projects::Filte :list_optional end + def autocomplete_options + all_items = allowed_values.map { |name, id| { name:, id: } } + { + component: "opce-autocompleter", + bindValue: "id", + bindLabel: "name", + hideSelected: true, + defaultData: false, + items: all_items, + model: all_items.select { |item| values.include?(item[:id]) } + } + end + def where operator_strategy.sql_for_field(values, model.table_name, :status_code) end diff --git a/app/models/queries/projects/filters/type_filter.rb b/app/models/queries/projects/filters/type_filter.rb index b5bbb363b02..05ceb9467e4 100644 --- a/app/models/queries/projects/filters/type_filter.rb +++ b/app/models/queries/projects/filters/type_filter.rb @@ -45,6 +45,19 @@ class Queries::Projects::Filters::TypeFilter < Queries::Projects::Filters::Base :list end + def autocomplete_options + all_items = allowed_values.map { |name, id| { name:, id: } } + { + component: "opce-autocompleter", + bindValue: "id", + bindLabel: "name", + hideSelected: true, + defaultData: false, + items: all_items, + model: all_items.select { |item| values.include?(item[:id]) } + } + end + def self.key :type_id end diff --git a/app/models/queries/users/filters/group_filter.rb b/app/models/queries/users/filters/group_filter.rb index 92ed4ad7a3e..e19be215199 100644 --- a/app/models/queries/users/filters/group_filter.rb +++ b/app/models/queries/users/filters/group_filter.rb @@ -34,4 +34,16 @@ class Queries::Users::Filters::GroupFilter < Queries::Users::Filters::UserFilter def human_name I18n.t(:label_group) end + + def autocomplete_options + { + component: "opce-user-autocompleter", + resource: "principals", + url: ::API::V3::Utilities::PathHelper::ApiV3Path.principals, + filters: [{ name: "type", operator: "=", values: %w[Group] }], + searchKey: "any_name_attribute", + inputValue: values, + bindValue: "id" + } + end end diff --git a/modules/meeting/app/components/meetings/meeting_filters_component.rb b/modules/meeting/app/components/meetings/meeting_filters_component.rb index 9c672b3cab9..d6459cff7d5 100644 --- a/modules/meeting/app/components/meetings/meeting_filters_component.rb +++ b/modules/meeting/app/components/meetings/meeting_filters_component.rb @@ -28,9 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. # ++ module Meetings - # rubocop:disable OpenProject/AddPreviewForViewComponent class MeetingFiltersComponent < Filter::FilterComponent - # rubocop:enable OpenProject/AddPreviewForViewComponent options :project def turbo_requests? = true @@ -41,24 +39,6 @@ module Meetings .sort_by(&:human_name) end - protected - - def additional_filter_attributes(filter) - case filter - when Queries::Meetings::Filters::AuthorFilter, - Queries::Meetings::Filters::AttendedUserFilter, - Queries::Meetings::Filters::InvitedUserFilter - { - autocomplete_options: { - component: "opce-user-autocompleter", - resource: "principals" - } - } - else - super - end - end - private def allowed_filter?(filter) diff --git a/modules/meeting/app/models/queries/meetings/filters/attended_user_filter.rb b/modules/meeting/app/models/queries/meetings/filters/attended_user_filter.rb index f73b77393c1..ce24bd7e8c1 100644 --- a/modules/meeting/app/models/queries/meetings/filters/attended_user_filter.rb +++ b/modules/meeting/app/models/queries/meetings/filters/attended_user_filter.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -76,6 +77,10 @@ class Queries::Meetings::Filters::AttendedUserFilter < Queries::Meetings::Filter :participants end + def autocomplete_options + { component: "opce-user-autocompleter", resource: "principals" } + end + def self.key :attended_user_id end diff --git a/modules/meeting/app/models/queries/meetings/filters/author_filter.rb b/modules/meeting/app/models/queries/meetings/filters/author_filter.rb index 6ef4363145f..42b639fd2c2 100644 --- a/modules/meeting/app/models/queries/meetings/filters/author_filter.rb +++ b/modules/meeting/app/models/queries/meetings/filters/author_filter.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -38,6 +39,10 @@ class Queries::Meetings::Filters::AuthorFilter < Queries::Meetings::Filters::Mee @type_strategy ||= ::Queries::Filters::Strategies::IntegerListOptional.new(self) end + def autocomplete_options + { component: "opce-user-autocompleter", resource: "principals" } + end + def self.key :author_id end diff --git a/modules/meeting/app/models/queries/meetings/filters/invited_user_filter.rb b/modules/meeting/app/models/queries/meetings/filters/invited_user_filter.rb index 0dc00eceee3..425848d0946 100644 --- a/modules/meeting/app/models/queries/meetings/filters/invited_user_filter.rb +++ b/modules/meeting/app/models/queries/meetings/filters/invited_user_filter.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -76,6 +77,10 @@ class Queries::Meetings::Filters::InvitedUserFilter < Queries::Meetings::Filters :participants end + def autocomplete_options + { component: "opce-user-autocompleter", resource: "principals" } + end + def self.key :invited_user_id end diff --git a/spec/support/pages/admin/users/index.rb b/spec/support/pages/admin/users/index.rb index 65731e2914c..d5158c43fc6 100644 --- a/spec/support/pages/admin/users/index.rb +++ b/spec/support/pages/admin/users/index.rb @@ -93,7 +93,7 @@ module Pages def filter_by_group(value) open_filter_panel - unless page.has_css?("li.advanced-filters--filter[data-filter-name='group']:not(.hidden)") + unless page.has_css?(".advanced-filters--filter[data-filter-name='group']:not([hidden])") select "Group", from: "add_filter_select" end @@ -134,7 +134,7 @@ module Pages end def within_filter(name, &) - within("li.advanced-filters--filter[data-filter-name='#{name}']:not(.hidden)", &) + within(".advanced-filters--filter[data-filter-name='#{name}']:not([hidden])", &) end def order_by(key)