From 9be85a53e4af6fc634c801883ceda6b8de02cbbc Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Fri, 7 Feb 2025 23:21:03 +0200 Subject: [PATCH 01/16] Fix perceived complexity error --- spec/support/pages/projects/index.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/pages/projects/index.rb b/spec/support/pages/projects/index.rb index 52fa4beb5b14..c73c03fc4f0d 100644 --- a/spec/support/pages/projects/index.rb +++ b/spec/support/pages/projects/index.rb @@ -296,7 +296,7 @@ def autocomplete_options_for(custom_field) visible_user_auto_completer_options end - def apply_operator(name, human_operator) + def apply_operator(human_operator, name) select(human_operator, from: "operator") unless boolean_filter?(name) end From 2bf950fccd02bbcd73f99fed734248957b4249e1 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 11 Feb 2025 12:17:12 +0200 Subject: [PATCH 02/16] [#61398] Version autocompleter for filter values on the project list https://community.openproject.org/work_packages/61398 --- app/components/filter/filter_component.rb | 24 +++++-- app/models/custom_field.rb | 68 ++++++++++--------- .../op-autocompleter.component.html | 5 +- .../op-autocompleter.component.sass | 2 + 4 files changed, 61 insertions(+), 38 deletions(-) diff --git a/app/components/filter/filter_component.rb b/app/components/filter/filter_component.rb index 5412a4e34c2a..28d1699cd3a9 100644 --- a/app/components/filter/filter_component.rb +++ b/app/components/filter/filter_component.rb @@ -70,8 +70,9 @@ def additional_filter_attributes(filter) { autocomplete_options: project_autocomplete_options } when Queries::Filters::Shared::CustomFields::User { autocomplete_options: user_autocomplete_options } - when Queries::Filters::Shared::CustomFields::ListOptional, - Queries::Projects::Filters::ProjectStatusFilter, + when Queries::Filters::Shared::CustomFields::ListOptional + { autocomplete_options: custom_field_list_autocomplete_options(filter) } + when Queries::Projects::Filters::ProjectStatusFilter, Queries::Projects::Filters::TypeFilter { autocomplete_options: list_autocomplete_options(filter) } else @@ -79,11 +80,26 @@ def additional_filter_attributes(filter) end end + def custom_field_list_autocomplete_options(filter) + items = if filter.custom_field.field_format == "version" + filter.allowed_values.map { |name, id, project_name| { name:, id:, project_name: } } + else + filter.allowed_values.map { |name, id| { name:, id: } } + end + + autocomplete_options.merge(items:, model: filter.values) + end + def list_autocomplete_options(filter) + autocomplete_options.merge( + items: filter.allowed_values.map { |name, id| { name:, id: } }, + model: filter.values + ) + end + + def autocomplete_options { component: "opce-autocompleter", - items: filter.allowed_values.map { |name, id| { name:, id: } }, - model: filter.values, bindValue: "id", bindLabel: "name", hideSelected: true diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 426aebfd36a7..cce8bfcbbbe9 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -315,30 +317,36 @@ def cache_key private def possible_version_values_options(obj) - mapped_with_deduced_project(obj) do |project| - if project&.persisted? - project.shared_versions - else - Version.systemwide - end - end + project = deduce_project(obj) + + versions = if project&.persisted? + project.shared_versions + else + Version.systemwide + end + + versions.includes(:project) + .sort + .map { |u| [u.name, u.id.to_s, u.project.name] } end def possible_user_values_options(obj) - mapped_with_deduced_project(obj) do |project| - scope = if project&.persisted? - project.principals - else - Principal - .in_visible_project_or_me(User.current) - end - - user_format_columns = User::USER_FORMATS_STRUCTURE[Setting.user_format].map(&:to_s) - # Always include lastname if not already included, as Groups always need a lastname (alias for name) - user_format_columns << "lastname" unless user_format_columns.include?("lastname") - - scope.select(*user_format_columns, "id", "type") - end + project = deduce_project(obj) + + users = if project&.persisted? + project.principals + else + Principal + .in_visible_project_or_me(User.current) + end + + user_format_columns = User::USER_FORMATS_STRUCTURE[Setting.user_format].map(&:to_s) + # Always include lastname if not already included, as Groups always need a lastname (alias for name) + user_format_columns << "lastname" unless user_format_columns.include?("lastname") + + users.select(*user_format_columns, "id", "type") + .sort + .map { |u| [u.name, u.id.to_s] } end def possible_list_values_options @@ -353,18 +361,12 @@ def possible_values_from_arg(arg) end end - def mapped_with_deduced_project(project) - project = if project.is_a?(Project) - project - elsif project.respond_to?(:project) - project.project - end - - result = yield project - - result - .sort - .map { |u| [u.name, u.id.to_s] } + def deduce_project(project) + if project.is_a?(Project) + project + elsif project.respond_to?(:project) + project.project + end end def destroy_help_text diff --git a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html index 9b44f22c02a2..921ef7563e4d 100644 --- a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html +++ b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html @@ -183,7 +183,6 @@ [ngClass]="highlighting('status',item.status?.id)" class="op-autocompleter--wp-status" > - @@ -218,6 +217,10 @@ class="op-autocompleter__option-principal-email" *ngIf="item.email" [ngOptionHighlight]="search">{{ item.email }} + ({{ item.project_name }}) diff --git a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.sass b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.sass index b8022bb77474..e9fc8981b342 100644 --- a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.sass +++ b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.sass @@ -50,12 +50,14 @@ text-overflow: ellipsis &__option-principal-email + &__option-project-name color: var(--fgColor-muted) font-size: var(--font-size-small) margin-left: var(--stack-gap-condensed) @media screen and (max-width: $breakpoint-sm) &__option-principal-email + &__option-project-name display: block margin-left: 0 margin-top: var(--control-xsmall-gap) From cae99b53c0e67bd635a54d04d6f40f70f6732279 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Mon, 17 Feb 2025 23:30:32 +0200 Subject: [PATCH 03/16] Refactor complex method --- app/models/custom_field.rb | 43 +++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index cce8bfcbbbe9..eddd0997e48b 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -318,12 +318,7 @@ def cache_key def possible_version_values_options(obj) project = deduce_project(obj) - - versions = if project&.persisted? - project.shared_versions - else - Version.systemwide - end + versions = deduce_versions(project) versions.includes(:project) .sort @@ -332,17 +327,7 @@ def possible_version_values_options(obj) def possible_user_values_options(obj) project = deduce_project(obj) - - users = if project&.persisted? - project.principals - else - Principal - .in_visible_project_or_me(User.current) - end - - user_format_columns = User::USER_FORMATS_STRUCTURE[Setting.user_format].map(&:to_s) - # Always include lastname if not already included, as Groups always need a lastname (alias for name) - user_format_columns << "lastname" unless user_format_columns.include?("lastname") + users = deduce_principals(project) users.select(*user_format_columns, "id", "type") .sort @@ -369,6 +354,30 @@ def deduce_project(project) end end + def deduce_principals(project) + if project&.persisted? + project.principals + else + Principal + .in_visible_project_or_me(User.current) + end + end + + def deduce_versions(project) + if project&.persisted? + project.shared_versions + else + Version.systemwide + end + end + + def user_format_columns + user_format_columns = User::USER_FORMATS_STRUCTURE[Setting.user_format].map(&:to_s) + # Always include lastname if not already included, as Groups always need a lastname (alias for name) + user_format_columns << "lastname" unless user_format_columns.include?("lastname") + user_format_columns + end + def destroy_help_text AttributeHelpText .where(attribute_name:) From 3e28d3ed536cfe4e85b0798ee19db6f88fdb17fc Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 18 Feb 2025 00:07:58 +0200 Subject: [PATCH 04/16] Fix specs --- spec/models/custom_field_spec.rb | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb index 88e7f98f98b5..51b4e378f5b6 100644 --- a/spec/models/custom_field_spec.rb +++ b/spec/models/custom_field_spec.rb @@ -289,20 +289,25 @@ end context "for a version custom field" do - let(:versions) { [build_stubbed(:version), build_stubbed(:version)] } + let(:versions) { [build_stubbed(:version, project:), build_stubbed(:version, project:)] } + let(:shared_versions_scope) { instance_double(ActiveRecord::Relation) } before do field.field_format = "version" + allow(shared_versions_scope) + .to receive(:includes) + .with(:project) + .and_return(versions) end context "with a project provided" do it "returns the project's shared_versions" do allow(project) .to receive(:shared_versions) - .and_return(versions) + .and_return(shared_versions_scope) expect(field.possible_values_options(project)) - .to eql(versions.sort.map { |u| [u.name, u.id.to_s] }) + .to eql(versions.sort.map { |u| [u.name, u.id.to_s, project.name] }) end end @@ -312,10 +317,10 @@ it "returns the project's shared_versions" do allow(project) .to receive(:shared_versions) - .and_return(versions) + .and_return(shared_versions_scope) expect(field.possible_values_options(project)) - .to eql(versions.sort.map { |u| [u.name, u.id.to_s] }) + .to eql(versions.sort.map { |u| [u.name, u.id.to_s, project.name] }) end end @@ -323,10 +328,10 @@ it "returns the systemwide versions" do allow(Version) .to receive(:systemwide) - .and_return(versions) + .and_return(shared_versions_scope) expect(field.possible_values_options) - .to eql(versions.sort.map { |u| [u.name, u.id.to_s] }) + .to eql(versions.sort.map { |u| [u.name, u.id.to_s, project.name] }) end end end From 65c0cea6b83a7c726fdddf3ace1ca3850b2636bf Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 19 Feb 2025 13:13:56 +0200 Subject: [PATCH 05/16] Fix scope related specs --- spec/lib/custom_field_form_builder_spec.rb | 15 ++++++++++++--- .../custom_actions/actions/custom_field_spec.rb | 15 +++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/spec/lib/custom_field_form_builder_spec.rb b/spec/lib/custom_field_form_builder_spec.rb index 06337c5ed128..f5563b63efa3 100644 --- a/spec/lib/custom_field_form_builder_spec.rb +++ b/spec/lib/custom_field_form_builder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -57,6 +59,8 @@ build_stubbed(:user) end + let(:scope) { instance_double(ActiveRecord::Relation) } + before do without_partial_double_verification do allow(resource) @@ -255,7 +259,6 @@ let(:project) { build_stubbed(:project) } let(:user1) { build_stubbed(:user) } let(:user2) { build_stubbed(:user) } - let(:scope) { instance_double(ActiveRecord::Relation) } let(:resource) { project } @@ -273,8 +276,10 @@ .and_return(scope) allow(scope) - .to receive(:select) - .and_return([user1, user2]) + .to receive_messages( + select: [user1, user2], + includes: scope + ) end it_behaves_like "wrapped in container", "select-container" do @@ -331,6 +336,10 @@ allow(project) .to receive(:shared_versions) + .and_return(scope) + + allow(scope) + .to receive(:includes) .and_return([version1, version2]) end end diff --git a/spec/models/custom_actions/actions/custom_field_spec.rb b/spec/models/custom_actions/actions/custom_field_spec.rb index afc2c54b1f00..04ec6aaf8ce7 100644 --- a/spec/models/custom_actions/actions/custom_field_spec.rb +++ b/spec/models/custom_actions/actions/custom_field_spec.rb @@ -29,6 +29,7 @@ require_relative "../shared_expectations" RSpec.describe CustomActions::Actions::CustomField do + let(:scope) { instance_double(ActiveRecord::Relation) } let(:list_custom_field) do build_stubbed(:list_wp_custom_field, custom_options: [build_stubbed(:custom_option, value: "A"), @@ -406,9 +407,13 @@ let(:versions) { [z_version, a_version, m_version] } before do + allow(scope) + .to receive(:includes) + .and_return(versions) + allow(Version) .to receive(:systemwide) - .and_return(versions) + .and_return(scope) end context "for a non required field" do @@ -441,7 +446,6 @@ build_stubbed(:user), build_stubbed(:user)] end - let(:scope) { instance_double(ActiveRecord::Relation) } before do allow(Principal) @@ -520,7 +524,6 @@ build_stubbed(:user), build_stubbed(:user)] end - let(:scope) { instance_double(ActiveRecord::Relation) } before do allow(Principal) @@ -551,9 +554,13 @@ end before do + allow(scope) + .to receive(:includes) + .and_return(versions) + allow(Version) .to receive(:systemwide) - .and_return(versions) + .and_return(scope) end it_behaves_like "associated custom action validations" do From e17bb32119302577cca8d4a3879af481d0c1cd87 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Fri, 21 Feb 2025 15:55:23 +0200 Subject: [PATCH 06/16] Use include_blank options on the work package bulk edit form --- app/views/work_packages/bulk/edit.html.erb | 46 +++++++++++++++------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/app/views/work_packages/bulk/edit.html.erb b/app/views/work_packages/bulk/edit.html.erb index 7a682f26de31..10018db61eaf 100644 --- a/app/views/work_packages/bulk/edit.html.erb +++ b/app/views/work_packages/bulk/edit.html.erb @@ -55,14 +55,22 @@ See COPYRIGHT and LICENSE files for more details.
<%= styled_label_tag :work_package_type_id, WorkPackage.human_attribute_name(:type) %>
- <%= styled_select_tag("work_package[type_id]", "".html_safe + options_from_collection_for_select(@types, :id, :name)) %> + <%= styled_select_tag( + "work_package[type_id]", + options_from_collection_for_select(@types, :id, :name), + include_blank: t(:label_no_change_option) + ) %>
<% if @available_statuses.any? %>
<%= styled_label_tag :work_package_status_id, WorkPackage.human_attribute_name(:status) %>
- <%= styled_select_tag("work_package[status_id]", "".html_safe + options_from_collection_for_select(@available_statuses, :id, :name)) %> + <%= styled_select_tag( + "work_package[status_id]", + options_from_collection_for_select(@available_statuses, :id, :name), + include_blank: t(:label_no_change_option) + ) %>
<% else %> @@ -76,16 +84,21 @@ See COPYRIGHT and LICENSE files for more details.
<%= styled_label_tag :work_package_priority_id, WorkPackage.human_attribute_name(:priority) %>
- <%= styled_select_tag("work_package[priority_id]", "".html_safe + options_from_collection_for_select(IssuePriority.active, :id, :name)) %> + <%= styled_select_tag( + "work_package[priority_id]", + options_from_collection_for_select(IssuePriority.active, :id, :name), + include_blank: t(:label_no_change_option) + ) %>
<%= styled_label_tag :work_package_assigned_to_id, WorkPackage.human_attribute_name(:assigned_to) %>
<%= styled_select_tag( - "work_package[assigned_to_id]", content_tag("option", t(:label_no_change_option), value: "") + - content_tag("option", t(:label_nobody), value: "none") + - options_from_collection_for_select(@assignables, :id, :name) + "work_package[assigned_to_id]", + content_tag("option", t(:label_nobody), value: "none") + + options_from_collection_for_select(@assignables, :id, :name), + include_blank: t(:label_no_change_option) ) %>
@@ -93,9 +106,10 @@ See COPYRIGHT and LICENSE files for more details. <%= styled_label_tag :work_package_responsible_id, WorkPackage.human_attribute_name(:responsible) %>
<%= styled_select_tag( - "work_package[responsible_id]", content_tag("option", t(:label_no_change_option), value: "") + - content_tag("option", t(:label_nobody), value: "none") + - options_from_collection_for_select(@responsibles, :id, :name) + "work_package[responsible_id]", + content_tag("option", t(:label_nobody), value: "none") + + options_from_collection_for_select(@responsibles, :id, :name), + include_blank: t(:label_no_change_option) ) %>
@@ -104,9 +118,10 @@ See COPYRIGHT and LICENSE files for more details. <%= styled_label_tag :work_package_category_id, WorkPackage.human_attribute_name(:category) %>
<%= styled_select_tag( - "work_package[category_id]", content_tag("option", t(:label_no_change_option), value: "") + - content_tag("option", t(:label_none), value: "none") + - options_from_collection_for_select(@project.categories, :id, :name) + "work_package[category_id]", + content_tag("option", t(:label_none), value: "none") + + options_from_collection_for_select(@project.categories, :id, :name), + include_blank: t(:label_no_change_option) ) %>
@@ -115,9 +130,10 @@ See COPYRIGHT and LICENSE files for more details. <%= styled_label_tag :work_package_version_id, WorkPackage.human_attribute_name(:version) %>
<%= styled_select_tag( - "work_package[version_id]", content_tag("option", t(:label_no_change_option), value: "") + - content_tag("option", t(:label_none), value: "none") + - version_options_for_select(@project.shared_versions.with_status_open.order_by_semver_name) + "work_package[version_id]", + content_tag("option", t(:label_none), value: "none") + + version_options_for_select(@project.shared_versions.with_status_open.order_by_semver_name), + include_blank: t(:label_no_change_option) ) %>
From ab3633204efc4e668f154b36c59f14a4dabd39e8 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:38:06 +0200 Subject: [PATCH 07/16] Use include blank options on the custom fields helpers. --- app/helpers/custom_fields_helper.rb | 50 +++++++++++++++++------------ 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/app/helpers/custom_fields_helper.rb b/app/helpers/custom_fields_helper.rb index 122cd405541f..fa29aeb7d05e 100644 --- a/app/helpers/custom_fields_helper.rb +++ b/app/helpers/custom_fields_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -88,18 +90,19 @@ def custom_field_tag(name, custom_value) # rubocop:disable Metrics/AbcSize,Metri required: custom_field.is_required) hidden_tag + checkbox_tag when "list" - blank_option = if custom_field.is_required? && custom_field.default_value.blank? - "" - elsif custom_field.is_required? && custom_field.default_value.present? - "" - else - "" - end - - options = blank_option.html_safe + options_for_select(custom_field.possible_values_options(custom_value.customized), - custom_value.value) - - styled_select_tag(field_name, options, id: field_id, container_class: "-middle", required: custom_field.is_required) + include_blank = !custom_field.is_required? || + (custom_field.default_value.blank? ? I18n.t(:actionview_instancetag_blank_option) : false) + + options = [custom_field.possible_values_options(custom_value.customized), custom_value.value] + + styled_select_tag( + field_name, + options_for_select(*options), + id: field_id, + container_class: "-middle", + required: custom_field.is_required, + include_blank: + ) else styled_text_field_tag(field_name, custom_value.value, id: field_id, container_class: "-middle", required: custom_field.is_required) @@ -158,12 +161,14 @@ def custom_field_tag_for_bulk_edit(name, custom_field, project = nil) # rubocop: when "text" styled_text_area_tag(field_name, "", id: field_id, rows: 3, with_text_formatting: true) when "bool" - styled_select_tag(field_name, options_for_select([[I18n.t(:label_no_change_option), ""], - ([I18n.t(:label_none), "none"] unless custom_field.required?), - [I18n.t(:general_text_yes), "1"], - [I18n.t(:general_text_no), "0"]].compact), id: field_id) + styled_select_tag(field_name, + options_for_select([([I18n.t(:label_none), "none"] unless custom_field.required?), + [I18n.t(:general_text_yes), "1"], + [I18n.t(:general_text_no), "0"]].compact), + id: field_id, + include_blank: I18n.t(:label_no_change_option)) when "list" - base_options = [[I18n.t(:label_no_change_option), ""]] + base_options = [] unless custom_field.required? unset_label = custom_field.field_format == "user" ? :label_nobody : :label_none base_options << [I18n.t(unset_label), "none"] @@ -171,9 +176,10 @@ def custom_field_tag_for_bulk_edit(name, custom_field, project = nil) # rubocop: styled_select_tag(field_name, options_for_select(base_options + custom_field.possible_values_options(project)), id: field_id, - multiple: custom_field.multi_value?) + multiple: custom_field.multi_value?, + include_blank: I18n.t(:label_no_change_option)) when "hierarchy" - base_options = [[I18n.t(:label_no_change_option), ""]] + base_options = [] result = CustomFields::Hierarchy::HierarchicalItemService.new .get_descendants(item: custom_field.hierarchy_root, include_self: false) .either( @@ -184,7 +190,11 @@ def custom_field_tag_for_bulk_edit(name, custom_field, project = nil) # rubocop: label = item.short.present? ? "#{item.label} (#{item.short})" : item.label [label, item.id] end - styled_select_tag(field_name, options_for_select(options), id: field_id, multiple: custom_field.multi_value?) + styled_select_tag(field_name, + options_for_select(options), + id: field_id, + multiple: custom_field.multi_value?, + include_blank: I18n.t(:label_no_change_option)) else styled_text_field_tag(field_name, "", id: field_id) end From 703a5212ca5c9c92db24b726a5cc1f5004aa9d20 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:18:19 +0200 Subject: [PATCH 08/16] Use version groups for the version html autocompleters. --- app/components/filter/filter_component.rb | 2 +- app/helpers/custom_fields_helper.rb | 85 +++-------------------- app/models/custom_field.rb | 30 ++++---- app/models/projects/versions.rb | 4 +- lib/custom_field_form_builder.rb | 20 ++++-- 5 files changed, 44 insertions(+), 97 deletions(-) diff --git a/app/components/filter/filter_component.rb b/app/components/filter/filter_component.rb index 28d1699cd3a9..755fb17fd5ab 100644 --- a/app/components/filter/filter_component.rb +++ b/app/components/filter/filter_component.rb @@ -81,7 +81,7 @@ def additional_filter_attributes(filter) end def custom_field_list_autocomplete_options(filter) - items = if filter.custom_field.field_format == "version" + items = 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: } } diff --git a/app/helpers/custom_fields_helper.rb b/app/helpers/custom_fields_helper.rb index fa29aeb7d05e..2bde1027da1a 100644 --- a/app/helpers/custom_fields_helper.rb +++ b/app/helpers/custom_fields_helper.rb @@ -64,76 +64,6 @@ def custom_fields_tabs ] end - # Return custom field html tag corresponding to its format - def custom_field_tag(name, custom_value) # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity - custom_field = custom_value.custom_field - field_name = "#{name}[custom_field_values][#{custom_field.id}]" - field_id = "#{name}_custom_field_values_#{custom_field.id}" - - field_format = OpenProject::CustomFieldFormat.find_by(name: custom_field.field_format) - - tag = case field_format.try(:edit_as) - when "date" - angular_component_tag "opce-basic-single-date-picker", - inputs: { - required: custom_field.is_required, - value: custom_value.value, - id: field_id, - name: field_name - } - when "text" - styled_text_area_tag(field_name, custom_value.value, id: field_id, rows: 3, container_class: "-middle", - required: custom_field.is_required) - when "bool" - hidden_tag = hidden_field_tag(field_name, "0") - checkbox_tag = styled_check_box_tag(field_name, "1", custom_value.typed_value, id: field_id, - required: custom_field.is_required) - hidden_tag + checkbox_tag - when "list" - include_blank = !custom_field.is_required? || - (custom_field.default_value.blank? ? I18n.t(:actionview_instancetag_blank_option) : false) - - options = [custom_field.possible_values_options(custom_value.customized), custom_value.value] - - styled_select_tag( - field_name, - options_for_select(*options), - id: field_id, - container_class: "-middle", - required: custom_field.is_required, - include_blank: - ) - else - styled_text_field_tag(field_name, custom_value.value, id: field_id, container_class: "-middle", - required: custom_field.is_required) - end - - tag = content_tag :span, tag, lang: custom_field.name_locale, class: "form--field-container" - - if custom_value.errors.empty? - tag - else - ActionView::Base.wrap_with_error_span(tag, custom_value, "value") - end - end - - # Return custom field label tag - def custom_field_label_tag(name, custom_value) - content_tag "label", h(custom_value.custom_field.name) + - (custom_value.custom_field.is_required? ? content_tag("span", " *", class: "required") : ""), - for: "#{name}_custom_field_values_#{custom_value.custom_field.id}", - class: "form--label #{custom_value.errors.empty? ? nil : 'error'}", - lang: custom_value.custom_field.name_locale - end - - def hidden_custom_field_label_tag(name, custom_value) - content_tag "label", h(custom_value.custom_field.name) + - (custom_value.custom_field.is_required? ? content_tag("span", " *", class: "required") : ""), - for: "#{name}_custom_field_values_#{custom_value.custom_field.id}", - class: "hidden-for-sighted", - lang: custom_value.custom_field.name_locale - end - def blank_custom_field_label_tag(name, custom_field) content_tag "label", h(custom_field.name) + (custom_field.is_required? ? content_tag("span", " *", class: "required") : ""), @@ -141,11 +71,6 @@ def blank_custom_field_label_tag(name, custom_field) class: "form--label" end - # Return custom field tag with its label tag - def custom_field_tag_with_label(name, custom_value) - custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value) - end - def custom_field_tag_for_bulk_edit(name, custom_field, project = nil) # rubocop:disable Metrics/AbcSize field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_id = "#{name}_custom_field_values_#{custom_field.id}" @@ -173,8 +98,16 @@ def custom_field_tag_for_bulk_edit(name, custom_field, project = nil) # rubocop: unset_label = custom_field.field_format == "user" ? :label_nobody : :label_none base_options << [I18n.t(unset_label), "none"] end + + possible_values = custom_field.possible_values_options(project) + options = if custom_field.version? + grouped_options_for_select(possible_values.group_by(&:last).to_a) + else + options_for_select(possible_values) + end + styled_select_tag(field_name, - options_for_select(base_options + custom_field.possible_values_options(project)), + options_for_select(base_options) + options, id: field_id, multiple: custom_field.multi_value?, include_blank: I18n.t(:label_no_change_option)) diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index eddd0997e48b..cf84be290fb6 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -165,8 +165,10 @@ def value_of(value) # You MUST NOT pass a customizable if this CF has any other format def possible_values(obj = nil) case field_format - when "user", "version" - possible_values_options(obj).map(&:last) + when "user" + possible_users(obj).pluck(:id).map(&:to_s) + when "version" + possible_versions(obj).pluck(:id).map(&:to_s) when "list" custom_options else @@ -316,22 +318,26 @@ def cache_key private - def possible_version_values_options(obj) + def possible_versions(obj) project = deduce_project(obj) - versions = deduce_versions(project) + deduce_versions(project) + end - versions.includes(:project) - .sort - .map { |u| [u.name, u.id.to_s, u.project.name] } + def possible_version_values_options(obj) + possible_versions(obj).references(:project) + .sort + .map { |u| [u.name, u.id.to_s, u.project.name] } end - def possible_user_values_options(obj) + def possible_users(obj) project = deduce_project(obj) - users = deduce_principals(project) + deduce_principals(project) + end - users.select(*user_format_columns, "id", "type") - .sort - .map { |u| [u.name, u.id.to_s] } + def possible_user_values_options(obj) + possible_users(obj).select(*user_format_columns, "id", "type") + .sort + .map { |u| [u.name, u.id.to_s] } end def possible_list_values_options diff --git a/app/models/projects/versions.rb b/app/models/projects/versions.rb index b271f8d6852c..d26e0661da3c 100644 --- a/app/models/projects/versions.rb +++ b/app/models/projects/versions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -51,7 +53,7 @@ def shared_versions Version.shared_with(self) end - # Returns all versions a work package can be assigned to. Opposed to + # Returns all versions a work package can be assigned to. Opposed to # #shared_versions this returns an array of Versions, not a scope. # # The main benefit is in scenarios where work packages' projects are eager diff --git a/lib/custom_field_form_builder.rb b/lib/custom_field_form_builder.rb index 84e04e288399..43e84daa00b5 100644 --- a/lib/custom_field_form_builder.rb +++ b/lib/custom_field_form_builder.rb @@ -83,17 +83,23 @@ def custom_field_input(options = {}) end end - # rubocop:enable Metrics/AbcSize - def custom_field_input_list(field, input_options) customized = Array(custom_value).first&.customized - possible_options = custom_field.possible_values_options(customized) - select_options = custom_field_select_options_for_object - selected_options = Array(custom_value).map(&:value) - selectable_options = template.options_for_select(possible_options, selected_options) + selectable_options = custom_field_input_list_options(customized, custom_value) input_options[:multiple] = custom_field.multi_value? - select(field, selectable_options, select_options, input_options).html_safe + select(field, selectable_options, custom_field_select_options_for_object, input_options) + end + + def custom_field_input_list_options(customized, selected) + options = custom_field.possible_values_options(customized) + selected_options = Array(selected).map(&:value) + + if custom_field.version? + template.grouped_options_for_select(options.group_by(&:last).to_a, selected_options) + else + template.options_for_select(options, selected_options) + end end def custom_field_select_options_for_object From 65eb5a576383c097ed6c1aa838b0434761d12f1c Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Mon, 24 Feb 2025 17:19:10 +0200 Subject: [PATCH 09/16] Fix specs --- lib/custom_field_form_builder.rb | 6 +++++- spec/lib/custom_field_form_builder_spec.rb | 18 +++++++++++++----- .../actions/custom_field_spec.rb | 14 +++++++++----- spec/models/custom_field_spec.rb | 6 ++++-- 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/lib/custom_field_form_builder.rb b/lib/custom_field_form_builder.rb index 43e84daa00b5..836361c992d8 100644 --- a/lib/custom_field_form_builder.rb +++ b/lib/custom_field_form_builder.rb @@ -96,7 +96,11 @@ def custom_field_input_list_options(customized, selected) selected_options = Array(selected).map(&:value) if custom_field.version? - template.grouped_options_for_select(options.group_by(&:last).to_a, selected_options) + grouped_options = Hash.new { |hsh, key| hsh[key] = [] } + options.each do |label, value, group_key| + grouped_options[group_key] << [label, value] + end + template.grouped_options_for_select(grouped_options, selected_options) else template.options_for_select(options, selected_options) end diff --git a/spec/lib/custom_field_form_builder_spec.rb b/spec/lib/custom_field_form_builder_spec.rb index f5563b63efa3..4eac6fc9a787 100644 --- a/spec/lib/custom_field_form_builder_spec.rb +++ b/spec/lib/custom_field_form_builder_spec.rb @@ -339,7 +339,7 @@ .and_return(scope) allow(scope) - .to receive(:includes) + .to receive(:references) .and_return([version1, version2]) end end @@ -355,8 +355,12 @@ name="user[#{custom_field.id}]" no_label="true"> - - + + + + + + }).at_path("select") end @@ -373,8 +377,12 @@ name="user[#{custom_field.id}]" no_label="true"> - - + + + + + + }).at_path("select") end diff --git a/spec/models/custom_actions/actions/custom_field_spec.rb b/spec/models/custom_actions/actions/custom_field_spec.rb index 04ec6aaf8ce7..644856e59880 100644 --- a/spec/models/custom_actions/actions/custom_field_spec.rb +++ b/spec/models/custom_actions/actions/custom_field_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -110,7 +112,7 @@ expect(described_class.all.map(&:custom_field)) .to match_array(custom_fields) - described_class.all.each do |subclass| + described_class.find_each do |subclass| expect(subclass.ancestors).to include(described_class) end end @@ -408,7 +410,8 @@ before do allow(scope) - .to receive(:includes) + .to receive(:references) + .with(:project) .and_return(versions) allow(Version) @@ -555,7 +558,8 @@ before do allow(scope) - .to receive(:includes) + .to receive(:references) + .with(:project) .and_return(versions) allow(Version) @@ -577,8 +581,8 @@ it_behaves_like "bool custom action validations" do let(:allowed_values) do [ - { true: OpenProject::Database::DB_VALUE_TRUE }, - { false: OpenProject::Database::DB_VALUE_FALSE } + { true => OpenProject::Database::DB_VALUE_TRUE }, + { false => OpenProject::Database::DB_VALUE_FALSE } ] end end diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb index 51b4e378f5b6..56dde15fad23 100644 --- a/spec/models/custom_field_spec.rb +++ b/spec/models/custom_field_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -156,7 +158,7 @@ it "is not valid" do expect(field) - .to be_invalid + .not_to be_valid end end @@ -295,7 +297,7 @@ before do field.field_format = "version" allow(shared_versions_scope) - .to receive(:includes) + .to receive(:references) .with(:project) .and_return(versions) end From 187dd7ed7562d790801ba0da9acfde3f87cb6d95 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Mon, 24 Feb 2025 21:50:49 +0200 Subject: [PATCH 10/16] Update list filter strategy to use the second value from the allowed_values --- app/components/filter/filter_component.rb | 11 +++++++---- app/models/queries/filters/strategies/list.rb | 4 ++-- .../op-autocompleter/op-autocompleter.component.html | 4 ---- .../op-autocompleter/op-autocompleter.component.sass | 2 -- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/app/components/filter/filter_component.rb b/app/components/filter/filter_component.rb index 755fb17fd5ab..dff5ad3a70b8 100644 --- a/app/components/filter/filter_component.rb +++ b/app/components/filter/filter_component.rb @@ -81,13 +81,16 @@ def additional_filter_attributes(filter) end def custom_field_list_autocomplete_options(filter) - items = if filter.custom_field.version? - filter.allowed_values.map { |name, id, project_name| { name:, id:, project_name: } } + options = if filter.custom_field.version? + { + items: filter.allowed_values.map { |name, id, project_name| { name:, id:, project_name: } }, + groupBy: "project_name" + } else - filter.allowed_values.map { |name, id| { name:, id: } } + { items: filter.allowed_values.map { |name, id| { name:, id: } } } end - autocomplete_options.merge(items:, model: filter.values) + autocomplete_options.merge(options).merge(model: filter.values) end def list_autocomplete_options(filter) diff --git a/app/models/queries/filters/strategies/list.rb b/app/models/queries/filters/strategies/list.rb index 6476b0984fe4..dc5415ea95c2 100644 --- a/app/models/queries/filters/strategies/list.rb +++ b/app/models/queries/filters/strategies/list.rb @@ -52,11 +52,11 @@ def validate end def valid_values! - filter.values &= (allowed_values.map(&:last).map(&:to_s) + ["-1"]) + filter.values &= (allowed_values.map(&:second).map(&:to_s) + ["-1"]) end def non_valid_values? - (values.reject(&:blank?) & (allowed_values.map(&:last).map(&:to_s) + ["-1"])) != values.reject(&:blank?) + (values.reject(&:blank?) & (allowed_values.map(&:second).map(&:to_s) + ["-1"])) != values.reject(&:blank?) end end end diff --git a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html index 921ef7563e4d..a46c860ca180 100644 --- a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html +++ b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html @@ -217,10 +217,6 @@ class="op-autocompleter__option-principal-email" *ngIf="item.email" [ngOptionHighlight]="search">{{ item.email }} - ({{ item.project_name }}) diff --git a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.sass b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.sass index e9fc8981b342..b8022bb77474 100644 --- a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.sass +++ b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.sass @@ -50,14 +50,12 @@ text-overflow: ellipsis &__option-principal-email - &__option-project-name color: var(--fgColor-muted) font-size: var(--font-size-small) margin-left: var(--stack-gap-condensed) @media screen and (max-width: $breakpoint-sm) &__option-principal-email - &__option-project-name display: block margin-left: 0 margin-top: var(--control-xsmall-gap) From 4f899436c0798f04a76d3edb26bd894fbd62dd1a Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Mon, 24 Feb 2025 22:02:29 +0200 Subject: [PATCH 11/16] Refactor filter allowed_values --- app/components/filter/filter_component.rb | 14 +++++++------- .../queries/filters/strategies/boolean_list.rb | 4 +++- app/models/queries/filters/strategies/list.rb | 13 ++++++++----- app/models/queries/filters/strategies/relation.rb | 4 +++- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/app/components/filter/filter_component.rb b/app/components/filter/filter_component.rb index dff5ad3a70b8..a36e18f1c884 100644 --- a/app/components/filter/filter_component.rb +++ b/app/components/filter/filter_component.rb @@ -82,13 +82,13 @@ def additional_filter_attributes(filter) def custom_field_list_autocomplete_options(filter) options = if filter.custom_field.version? - { - items: filter.allowed_values.map { |name, id, project_name| { name:, id:, project_name: } }, - groupBy: "project_name" - } - else - { items: filter.allowed_values.map { |name, id| { name:, id: } } } - end + { + items: filter.allowed_values.map { |name, id, project_name| { name:, id:, project_name: } }, + groupBy: "project_name" + } + else + { items: filter.allowed_values.map { |name, id| { name:, id: } } } + end autocomplete_options.merge(options).merge(model: filter.values) end diff --git a/app/models/queries/filters/strategies/boolean_list.rb b/app/models/queries/filters/strategies/boolean_list.rb index 133107415931..a43eca129563 100644 --- a/app/models/queries/filters/strategies/boolean_list.rb +++ b/app/models/queries/filters/strategies/boolean_list.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -37,7 +39,7 @@ def validate end def valid_values! - filter.values &= allowed_values.map { |v| v.last.to_s } + filter.values &= allowed_values.map { |_, v| v.to_s } end private diff --git a/app/models/queries/filters/strategies/list.rb b/app/models/queries/filters/strategies/list.rb index dc5415ea95c2..0a4eaf4d76ce 100644 --- a/app/models/queries/filters/strategies/list.rb +++ b/app/models/queries/filters/strategies/list.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -28,9 +30,6 @@ module Queries::Filters::Strategies class List < BaseStrategy - delegate :allowed_values, - to: :filter - self.supported_operators = ["=", "!"] self.default_operator = "=" @@ -51,12 +50,16 @@ def validate end end + def allowed_values + filter.allowed_values.map { |_, v| v.to_s } + end + def valid_values! - filter.values &= (allowed_values.map(&:second).map(&:to_s) + ["-1"]) + filter.values &= (allowed_values + ["-1"]) end def non_valid_values? - (values.reject(&:blank?) & (allowed_values.map(&:second).map(&:to_s) + ["-1"])) != values.reject(&:blank?) + (values.compact_blank & (allowed_values + ["-1"])) != values.compact_blank end end end diff --git a/app/models/queries/filters/strategies/relation.rb b/app/models/queries/filters/strategies/relation.rb index 236390d3294a..ef8109d0d802 100644 --- a/app/models/queries/filters/strategies/relation.rb +++ b/app/models/queries/filters/strategies/relation.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -48,7 +50,7 @@ def validate end def valid_values! - filter.values &= allowed_values.map { |v| v.last.to_s } + filter.values &= allowed_values.map { |_, v| v.to_s } end private From b78e17a22049747ab92fb44b11af559a4feeaba0 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Mon, 24 Feb 2025 22:25:09 +0200 Subject: [PATCH 12/16] Fix perceived complexity error in custom fields helper. --- app/helpers/custom_fields_helper.rb | 32 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/app/helpers/custom_fields_helper.rb b/app/helpers/custom_fields_helper.rb index 2bde1027da1a..a7a81e5bdd27 100644 --- a/app/helpers/custom_fields_helper.rb +++ b/app/helpers/custom_fields_helper.rb @@ -93,21 +93,8 @@ def custom_field_tag_for_bulk_edit(name, custom_field, project = nil) # rubocop: id: field_id, include_blank: I18n.t(:label_no_change_option)) when "list" - base_options = [] - unless custom_field.required? - unset_label = custom_field.field_format == "user" ? :label_nobody : :label_none - base_options << [I18n.t(unset_label), "none"] - end - - possible_values = custom_field.possible_values_options(project) - options = if custom_field.version? - grouped_options_for_select(possible_values.group_by(&:last).to_a) - else - options_for_select(possible_values) - end - styled_select_tag(field_name, - options_for_select(base_options) + options, + options_for_list(custom_field, project), id: field_id, multiple: custom_field.multi_value?, include_blank: I18n.t(:label_no_change_option)) @@ -164,4 +151,21 @@ def label_for_custom_field_format(format_string) "#{label}#{suffix}" end + + def options_for_list(custom_field, project) + base_options = [] + unless custom_field.required? + unset_label = custom_field.field_format == "user" ? :label_nobody : :label_none + base_options << [I18n.t(unset_label), "none"] + end + + possible_values = custom_field.possible_values_options(project) + options = if custom_field.version? + grouped_options_for_select(possible_values.group_by(&:last).to_a) + else + options_for_select(possible_values) + end + + options_for_select(base_options) + options + end end From c7d76b6f736ed5b96ddfc637d3c3fb73f972a8c4 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 25 Feb 2025 15:42:37 +0200 Subject: [PATCH 13/16] Change the primerized version autocompleter to handle grouped options --- .../inputs/multi_version_select_list.rb | 1 + .../inputs/single_version_select_list.rb | 4 +++- .../custom_fields/inputs/version_select.rb | 18 ++++++++---------- .../forms/dsl/autocompleter_input.rb | 6 ++++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app/forms/custom_fields/inputs/multi_version_select_list.rb b/app/forms/custom_fields/inputs/multi_version_select_list.rb index 9d6c65f3f91f..2173bdaa8bdd 100644 --- a/app/forms/custom_fields/inputs/multi_version_select_list.rb +++ b/app/forms/custom_fields/inputs/multi_version_select_list.rb @@ -48,6 +48,7 @@ class CustomFields::Inputs::MultiVersionSelectList < CustomFields::Inputs::Base: list.option( label: version.name, value: version.id, + group_by: version.project.name, selected: selected?(version) ) end diff --git a/app/forms/custom_fields/inputs/single_version_select_list.rb b/app/forms/custom_fields/inputs/single_version_select_list.rb index d2649566f834..0d534a7107f9 100644 --- a/app/forms/custom_fields/inputs/single_version_select_list.rb +++ b/app/forms/custom_fields/inputs/single_version_select_list.rb @@ -41,7 +41,9 @@ class CustomFields::Inputs::SingleVersionSelectList < CustomFields::Inputs::Base custom_value_form.autocompleter(**version_input_attributes) do |list| assignable_custom_field_values(@custom_field).each do |version| list.option( - label: version.name, value: version.id, + label: version.name, + value: version.id, + group_by: version.project.name, selected: selected?(version) ) end diff --git a/app/forms/custom_fields/inputs/version_select.rb b/app/forms/custom_fields/inputs/version_select.rb index 8ae754b7b1f2..1ea18a46e746 100644 --- a/app/forms/custom_fields/inputs/version_select.rb +++ b/app/forms/custom_fields/inputs/version_select.rb @@ -38,23 +38,21 @@ def version_input_attributes end def additional_attributes + autocomplete_options = { groupBy: "group_by" } + if @object.blank? || (@object.respond_to?(:project) && @object.project.blank?) - { - autocomplete_options: { - disabled: true, - placeholder: I18n.t("custom_fields.placeholder_version_select") - } - } - else - {} + autocomplete_options[:disabled] = true + autocomplete_options[:placeholder] = I18n.t("custom_fields.placeholder_version_select") end + + { autocomplete_options: } end def assignable_versions(only_open:) if @object.is_a?(Project) - @object.assignable_versions(only_open: only_open) + @object.assignable_versions(only_open:) elsif @object.respond_to?(:project) && @object.project.present? - @object.project.assignable_versions(only_open: only_open) + @object.project.assignable_versions(only_open:) else Version.none end diff --git a/lib/primer/open_project/forms/dsl/autocompleter_input.rb b/lib/primer/open_project/forms/dsl/autocompleter_input.rb index 05478085c74e..62ffa20a8541 100644 --- a/lib/primer/open_project/forms/dsl/autocompleter_input.rb +++ b/lib/primer/open_project/forms/dsl/autocompleter_input.rb @@ -8,19 +8,21 @@ class AutocompleterInput < Primer::Forms::Dsl::Input attr_reader :name, :label, :autocomplete_options, :select_options, :wrapper_data_attributes class Option - attr_reader :label, :value, :selected, :classes + attr_reader :label, :value, :selected, :classes, :group_by - def initialize(label:, value:, classes: nil, selected: false) + def initialize(label:, value:, classes: nil, selected: false, group_by: nil) @label = label @value = value @selected = selected @classes = classes + @group_by = group_by end def to_h { id: value, name: label, + group_by:, classes: }.compact end From 2f78237baaf21bb6444d3c5cd6c539798b367c3c Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 25 Feb 2025 16:46:19 +0200 Subject: [PATCH 14/16] Fix primer autocompleter missing bindValue bug, hide already selected values. --- .../open_project/forms/autocompleter.rb | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/primer/open_project/forms/autocompleter.rb b/lib/primer/open_project/forms/autocompleter.rb index 0eab3cfe3304..5e73a02a986a 100644 --- a/lib/primer/open_project/forms/autocompleter.rb +++ b/lib/primer/open_project/forms/autocompleter.rb @@ -20,25 +20,42 @@ def initialize(input:, autocomplete_options:, wrapper_data_attributes: {}) @wrapper_data_attributes = wrapper_data_attributes end - def extend_autocomplete_inputs(inputs) # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity - inputs[:classes] = "ng-select--primerized #{@input.invalid? ? '-error' : ''}" - inputs[:inputName] ||= builder.field_name(@input.name) - inputs[:labelForId] ||= builder.field_id(@input.name) - inputs[:defaultData] = true unless inputs.key?(:defaultData) + def extend_autocomplete_inputs(inputs) + inputs = autocomplete_input_defaults(inputs) if inputs.delete(:decorated) - inputs[:items] = @input.select_options.map(&:to_h) - selected = @input.select_options.filter_map { |option| option.to_h if option.selected } - inputs[:model] = inputs[:multiple] ? selected : selected.first - inputs[:defaultData] = false - inputs[:additionalClassProperty] = "classes" - inputs[:bindLabel] = "name" + inputs = autocomplete_input_decorated(inputs) elsif builder.object inputs[:inputValue] ||= builder.object.send(@input.name) end inputs end + + private + + def autocomplete_input_defaults(inputs) + inputs[:classes] = "ng-select--primerized #{@input.invalid? ? '-error' : ''}" + inputs[:inputName] ||= builder.field_name(@input.name) + inputs[:labelForId] ||= builder.field_id(@input.name) + inputs[:defaultData] = true unless inputs.key?(:defaultData) + inputs[:hideSelected] = true + inputs + end + + def autocomplete_input_decorated(inputs) + selected = @input.select_options.filter_map { |option| option.to_h if option.selected } + model = inputs[:multiple] ? selected : selected.first + + inputs.merge( + items: @input.select_options.map(&:to_h), + model:, + defaultData: false, + additionalClassProperty: "classes", + bindLabel: "name", + bindValue: "id" + ) + end end end end From 7fe2c3ebb966a32423739eeb94043d3b341e39bf Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:28:22 +0200 Subject: [PATCH 15/16] Fix spec --- app/models/queries/filters/strategies/list.rb | 11 +++++------ .../custom_actions/actions/custom_field_spec.rb | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/models/queries/filters/strategies/list.rb b/app/models/queries/filters/strategies/list.rb index 0a4eaf4d76ce..01af460e5645 100644 --- a/app/models/queries/filters/strategies/list.rb +++ b/app/models/queries/filters/strategies/list.rb @@ -30,6 +30,9 @@ module Queries::Filters::Strategies class List < BaseStrategy + delegate :allowed_values, + to: :filter + self.supported_operators = ["=", "!"] self.default_operator = "=" @@ -50,16 +53,12 @@ def validate end end - def allowed_values - filter.allowed_values.map { |_, v| v.to_s } - end - def valid_values! - filter.values &= (allowed_values + ["-1"]) + filter.values &= (allowed_values.map { |_, v| v.to_s } + ["-1"]) end def non_valid_values? - (values.compact_blank & (allowed_values + ["-1"])) != values.compact_blank + (values.compact_blank & (allowed_values.map { |_, v| v.to_s } + ["-1"])) != values.compact_blank end end end diff --git a/spec/models/custom_actions/actions/custom_field_spec.rb b/spec/models/custom_actions/actions/custom_field_spec.rb index 644856e59880..d1b4d7ce2f7c 100644 --- a/spec/models/custom_actions/actions/custom_field_spec.rb +++ b/spec/models/custom_actions/actions/custom_field_spec.rb @@ -112,7 +112,7 @@ expect(described_class.all.map(&:custom_field)) .to match_array(custom_fields) - described_class.find_each do |subclass| + described_class.all.each do |subclass| expect(subclass.ancestors).to include(described_class) end end From 2fa1c8fca55e131742837d4d125a4975d8f3c688 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 25 Feb 2025 20:33:45 +0200 Subject: [PATCH 16/16] Revert the hideSelected option on the primer autocompleter. --- lib/primer/open_project/forms/autocompleter.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/primer/open_project/forms/autocompleter.rb b/lib/primer/open_project/forms/autocompleter.rb index 5e73a02a986a..43440e24ed70 100644 --- a/lib/primer/open_project/forms/autocompleter.rb +++ b/lib/primer/open_project/forms/autocompleter.rb @@ -39,7 +39,6 @@ def autocomplete_input_defaults(inputs) inputs[:inputName] ||= builder.field_name(@input.name) inputs[:labelForId] ||= builder.field_id(@input.name) inputs[:defaultData] = true unless inputs.key?(:defaultData) - inputs[:hideSelected] = true inputs end