From c58834ac920b32f4a46a77635fdda38821c18a76 Mon Sep 17 00:00:00 2001 From: Marc Saleiko Date: Tue, 2 Dec 2025 18:00:00 +0100 Subject: [PATCH] Introduce types provider abstraction layer --- app/finders/issues/issue_types_filter.rb | 3 +- app/finders/issues_finder.rb | 5 +- app/finders/work_items/types_finder.rb | 11 +- app/graphql/mutations/work_items/convert.rb | 12 +- app/graphql/mutations/work_items/create.rb | 10 +- app/models/issue.rb | 27 ++-- app/models/namespace.rb | 2 +- app/models/work_items/types_filter.rb | 28 ++-- .../work_items/types_framework/provider.rb | 124 ++++++++++++++++++ app/serializers/issue_board_entity.rb | 2 +- app/serializers/issue_entity.rb | 4 +- app/serializers/linked_issue_entity.rb | 4 +- .../concerns/issues/issue_type_helpers.rb | 6 +- app/services/issues/base_service.rb | 11 +- app/services/issues/build_service.rb | 8 +- app/services/issues/update_service.rb | 2 +- app/services/quick_actions/target_service.rb | 4 +- .../work_item_system_defined_type.yml | 10 ++ ee/lib/gitlab/elastic/search_results.rb | 3 + locale/gitlab.pot | 2 +- .../mutations/work_items/convert_spec.rb | 2 +- spec/support/finder_collection_allowlist.yml | 1 + 22 files changed, 225 insertions(+), 56 deletions(-) create mode 100644 app/models/work_items/types_framework/provider.rb create mode 100644 config/feature_flags/gitlab_com_derisk/work_item_system_defined_type.yml diff --git a/app/finders/issues/issue_types_filter.rb b/app/finders/issues/issue_types_filter.rb index ad5b54e4c50c02..563904b174b475 100644 --- a/app/finders/issues/issue_types_filter.rb +++ b/app/finders/issues/issue_types_filter.rb @@ -16,7 +16,8 @@ def by_issue_types(issues) end def valid_param_types? - (::WorkItems::Type.base_types.keys & param_types).sort == param_types.sort + provider = ::WorkItems::TypesFramework::Provider.new(parent) + (provider.base_types & param_types).sort == param_types.sort end def param_types diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index addb14cebf0aa5..7b31941df4313a 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -28,7 +28,7 @@ # updated_after: datetime # updated_before: datetime # confidential: boolean -# issue_types: array of strings (one of WorkItems::Type.base_types) +# issue_types: array of strings (one of WorkItems::TypesFramework::Provider.new(root_ancestor).base_types) # class IssuesFinder < IssuableFinder extend ::Gitlab::Utils::Override @@ -137,7 +137,8 @@ def by_service_desk(items) end def by_negated_issue_types(items) - issue_type_params = Array(not_params[:issue_types]).map(&:to_s) & WorkItems::Type.base_types.keys + provider = WorkItems::TypesFramework::Provider.new(params.parent) + issue_type_params = Array(not_params[:issue_types]).map(&:to_s) & provider.base_types return items if issue_type_params.blank? items.without_issue_type(issue_type_params) diff --git a/app/finders/work_items/types_finder.rb b/app/finders/work_items/types_finder.rb index 643d7f84309b11..822a3570c6d79f 100644 --- a/app/finders/work_items/types_finder.rb +++ b/app/finders/work_items/types_finder.rb @@ -7,16 +7,17 @@ def initialize(container:) end def execute(name: nil, only_available: false) - return WorkItems::Type.none if unavailable_container? - return order(WorkItems::Type.by_type(name)) if name.present? && !only_available - return order(WorkItems::Type) unless only_available + return [] if unavailable_container? + + provider = ::WorkItems::TypesFramework::Provider.new(@container.root_ancestor) + return Array.wrap(provider.find_by_base_type(name)) if name.present? && !only_available + return provider.all_ordered_by_name unless only_available ::WorkItems::TypesFilter .new(container: @container) .allowed_types .then { |types| name.present? ? types.intersection(Array.wrap(name)) : types } - .then { |types| WorkItems::Type.by_type(types) } - .then { |scope| order(scope) } + .then { |types| provider.by_base_types_ordered_by_name(types) } end private diff --git a/app/graphql/mutations/work_items/convert.rb b/app/graphql/mutations/work_items/convert.rb index 737bb90c4112e2..334cba92f2c1c9 100644 --- a/app/graphql/mutations/work_items/convert.rb +++ b/app/graphql/mutations/work_items/convert.rb @@ -24,7 +24,7 @@ class Convert < BaseMutation def resolve(attributes) work_item = authorized_find!(id: attributes[:id]) - work_item_type = find_work_item_type!(attributes[:work_item_type_id]) + work_item_type = find_work_item_type!(attributes[:work_item_type_id], work_item.namespace.root_ancestor) authorize_work_item_type!(work_item, work_item_type) update_result = ::WorkItems::UpdateService.new( @@ -44,12 +44,10 @@ def resolve(attributes) private - def find_work_item_type!(gid) - work_item_type = ::WorkItems::Type.find_by_id(gid.model_id) - - return work_item_type if work_item_type.present? - - message = format(_('Work Item type with id %{id} was not found'), id: gid.model_id) + def find_work_item_type!(gid, root_ancestor) + ::WorkItems::TypesFramework::Provider.new(root_ancestor).find_by_gid(gid) + rescue ActiveRecord::RecordNotFound + message = format(_('Work Item type with %{gid} was not found'), gid: gid) raise_resource_not_available_error! message end diff --git a/app/graphql/mutations/work_items/create.rb b/app/graphql/mutations/work_items/create.rb index 31a8ba2af7f4e1..73ef7a104ec8d4 100644 --- a/app/graphql/mutations/work_items/create.rb +++ b/app/graphql/mutations/work_items/create.rb @@ -107,7 +107,7 @@ def resolve(project_path: nil, namespace_path: nil, **attributes) container_path = project_path || namespace_path container = authorized_find!(container_path) - params = params_with_work_item_type(attributes).merge(author_id: current_user.id, + params = params_with_work_item_type(attributes, container).merge(author_id: current_user.id, scope_validator: context[:scope_validator]) params = params_with_resolve_discussion_params(params) type = params[:work_item_type] @@ -169,11 +169,11 @@ def params_with_resolve_discussion_params(attributes) attributes end - def params_with_work_item_type(attributes) - work_item_type_id = attributes.delete(:work_item_type_id)&.model_id - work_item_type = ::WorkItems::Type.find_by_id(work_item_type_id) + def params_with_work_item_type(attributes, container) + work_item_type_gid = attributes.delete(:work_item_type_id) + provider = ::WorkItems::TypesFramework::Provider.new(container.root_ancestor) - attributes[:work_item_type] = work_item_type + attributes[:work_item_type] = provider.find_by_gid(work_item_type_gid) attributes end diff --git a/app/models/issue.rb b/app/models/issue.rb index b459712130a5f6..fd68a0bfad30f5 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -53,9 +53,6 @@ class Issue < ApplicationRecord # Types of issues that should be displayed on issue board lists TYPES_FOR_BOARD_LIST = %w[issue incident].freeze - # This default came from the enum `issue_type` column. Defined as default in the DB - DEFAULT_ISSUE_TYPE = :issue - # Interim columns to convert integer IDs to bigint ignore_column :author_id_convert_to_bigint, remove_with: '17.8', remove_after: '2024-12-13' ignore_column :closed_by_id_convert_to_bigint, remove_with: '17.8', remove_after: '2024-12-13' @@ -275,12 +272,14 @@ def most_recent scope :counts_by_state, -> { reorder(nil).group(:state_id).count } scope :service_desk, -> { + provider = WorkItems::TypesFramework::Provider.new + where( author: User.support_bot, - work_item_type: WorkItems::Type.default_issue_type + work_item_type: provider.default_issue_type.id ) .or( - where(work_item_type: WorkItems::Type.default_by_type(:ticket)) + where(work_item_type: provider.find_by_base_type(:ticket).id) ) } @@ -829,7 +828,7 @@ def issuing_parent_id # Persisted records will always have a work_item_type. This method is useful # in places where we use a non persisted issue to perform feature checks def work_item_type_with_default - work_item_type || WorkItems::Type.default_by_type(DEFAULT_ISSUE_TYPE) + work_item_type || work_item_type_provider.default_issue_type end def issue_type @@ -991,7 +990,7 @@ def ensure_namespace_id def ensure_work_item_type return if work_item_type.present? || work_item_type_id.present? || work_item_type_id_change&.last.present? - self.work_item_type = WorkItems::Type.default_by_type(DEFAULT_ISSUE_TYPE) + self.work_item_type = work_item_type_provider.default_issue_type end def ensure_namespace_traversal_ids @@ -1001,9 +1000,12 @@ def ensure_namespace_traversal_ids def allowed_work_item_type_change return unless changes[:work_item_type_id] - involved_types = WorkItems::Type.where( - id: changes[:work_item_type_id].compact - ).pluck(:base_type).uniq + # With system-defined and custom types changes might happen in both work_item_type_id and custom_type_id. + # We'll need to change this accordingly to support both cases. + involved_types = work_item_type_provider.by_ids( + changes[:work_item_type_id].compact + ).map(&:base_type).uniq + disallowed_types = involved_types - WorkItems::Type::CHANGEABLE_BASE_TYPES return if disallowed_types.empty? @@ -1022,6 +1024,11 @@ def linked_issues_select def validate_due_date? true end + + def work_item_type_provider + ::WorkItems::TypesFramework::Provider.new(namespace&.root_ancestor) + end + strong_memoize_attr :work_item_type_provider end Issue.prepend_mod_with('Issue') diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 15e757a6e14ccb..87bde62d94b978 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -881,7 +881,7 @@ def allowed_work_item_types def allowed_work_item_type?(type) type = type.to_s - unless ::WorkItems::TypesFilter.base_types.include?(type) + unless ::WorkItems::TypesFilter.new.base_types.include?(type) raise ArgumentError, %("#{type}" is not a valid WorkItems::Type.base_types) end diff --git a/app/models/work_items/types_filter.rb b/app/models/work_items/types_filter.rb index f5af39efa0c4c2..d9fde82b0a407c 100644 --- a/app/models/work_items/types_filter.rb +++ b/app/models/work_items/types_filter.rb @@ -8,20 +8,24 @@ class TypesFilter DISABLED_WORKFLOW_TYPES = %w[requirement test_case].freeze class << self - include ::Gitlab::Utils::StrongMemoize + # include ::Gitlab::Utils::StrongMemoize def allowed_types_for_issues - base_types.excluding('epic', *OKR_TYPES) + new(container: nil).base_types.excluding('epic', *OKR_TYPES) end - def base_types - ::WorkItems::Type.base_types.keys.map(&:to_s) - end - strong_memoize_attr :base_types + # def base_types + # # ::WorkItems::Type.base_types.keys.map(&:to_s) + # ::WorkItems::TypesFramework::Provider.new(resource_parent).base_types.map(&:to_s) + # end + # strong_memoize_attr :base_types end - def initialize(container:) + attr_reader :base_types + + def initialize(container:, base_types: []) @container = container + @base_types = base_types end # Filter types by the given resource_parent. The filters take in consideration @@ -41,13 +45,15 @@ def allowed_types .then { |types| filter_okr(types) } # overridden in EE end - private + # def base_types + # ::WorkItems::TypesFramework::Provider.new(resource_parent).base_types.map(&:to_s) + # end + # strong_memoize_attr :base_types - def base_types - self.class.base_types - end + private def resource_parent + return unless @container return if @container.owner_entity_name == :user @container.owner_entity diff --git a/app/models/work_items/types_framework/provider.rb b/app/models/work_items/types_framework/provider.rb new file mode 100644 index 00000000000000..388d1feb175f34 --- /dev/null +++ b/app/models/work_items/types_framework/provider.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +module WorkItems + module TypesFramework + # This is the single source of truth to fetch work item types. + # + # Namespaces can use system-defined and custom work item types. + # This class aims to abstract that fetching logic away so application code doesn't need to care + # about the composition of types of a given namespace. + class Provider + include Gitlab::Utils::StrongMemoize + + def initialize(namespace = nil) + # Take the namespace as is for now. + # + # For custom types we need to either + # 1. fetch types by organization_id of the namespace for Self-Managed + # 2. fetch types by the root group for Saas + @namespace = namespace + end + + # Misses that don't use this provider class yet: + # TODO: app/models/issue.rb: 976 + # TODO: ee/app/finders/work_items/widgets/rolledup_dates_finder.rb:112 + # TODO: ee/lib/gitlab/epic_work_item_sync/diff.rb:93 + # TODO: ee/lib/search/elastic/work_item_query_builder.rb:180 + + # This would likely exclude custom types or we'd need to build the base type from the name of the type. + # Usually we use the base types in cases where we know an item needs to have a certain type. + def unfiltered_base_types + return type_class.base_types.keys unless system_defined_types_available? + + type_class.all.map(&:base_type) + end + + def filtered_base_types + WorkItems::TypesFilter.new(container: @namespace, base_types: unfiltered_base_types).allowed_types.to_a + end + strong_memoize_attr :filtered_base_types + + def filtered_types + type_class.where(base_type: filtered_base_types) + end + strong_memoize_attr :filtered_types + alias_method :all, :filtered_types + + def by_base_types(names) + filtered_types.select { |type| type.base_type.in?(names) } + end + + def find_by_base_type(name) + filtered_types.find { |type| type.base_type == name } + end + + def default_issue_type + find_by_base_type(:issue) + end + + def find_by_gid(gid) + # There're a couple of cases for the future here: + # 1. New custom type which has a different GID pattern. + # Check whether GID contains `Custom` and then resolve custom type + # 2. Converted type. When the system-defined type was modified in the namespace we + # create a custom type that references back to the system-defined one. So look it up + # from the `converted_from_system_defined_type_identifier`of the custom type. + # 3. System-defined type. Simply find by model_id. + # + # For now we still use the DB-based types so we resolve normally. + return unless gid + + filtered_types.find { |type| type.id == gid.model_id } + end + + # Id is ambiguous in terms of system-defined and custom types. + # Let's try to get rid of this as fast as possible. + # + # This has some API related usages where a work item type id is passed. + # We should change these interfaces to use a GID instead so we can properly distinguish + # between system-defined and custom types. + # + # For now it looks like we can use the GID in most cases. + def find_by_id(id) + filtered_types.find { |type| type.id == id } + end + + def by_ids(ids) + filtered_types.select { |type| type.id.in?(ids) } + end + + def all_ordered_by_name + sort_by_name(filtered_types) + end + + def by_ids_ordered_by_name(ids) + sort_by_name(by_ids(ids)) + end + + def by_base_types_ordered_by_name(names) + sort_by_name(by_base_types(names)) + end + + private + + def sort_by_name(items) + items.sort_by(&:name) + end + + def type_class + return WorkItems::Type unless system_defined_types_available? + + SystemDefined::Type + end + strong_memoize_attr :type_class + + def system_defined_types_available? + # Do instance check when we use the provider without a namespace. + return Feature.enabled?(:work_item_system_defined_type, type: :gitlab_com_derisk) unless @namespace # rubocop:disable Gitlab/FeatureFlagWithoutActor -- reason above + + Feature.enabled?(:work_item_system_defined_type, @namespace, type: :gitlab_com_derisk) + end + strong_memoize_attr :system_defined_types_available? + end + end +end diff --git a/app/serializers/issue_board_entity.rb b/app/serializers/issue_board_entity.rb index b6aa421b603bee..411bc5dcc3823b 100644 --- a/app/serializers/issue_board_entity.rb +++ b/app/serializers/issue_board_entity.rb @@ -59,7 +59,7 @@ class IssueBoardEntity < Grape::Entity expose :issue_type, as: :type, format_with: :upcase, - documentation: { type: "String", desc: "One of #{::WorkItems::Type.base_types.keys.map(&:upcase)}" } + documentation: { type: "String", desc: "One of #{::WorkItems::TypesFramework::Provider.new.base_types.map(&:upcase)}" } end IssueBoardEntity.prepend_mod_with('IssueBoardEntity') diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb index dca42b6b0d6766..801f074ccbab3e 100644 --- a/app/serializers/issue_entity.rb +++ b/app/serializers/issue_entity.rb @@ -101,7 +101,9 @@ class IssueEntity < IssuableEntity expose :issue_type, as: :type, format_with: :upcase, - documentation: { type: "String", desc: "One of #{::WorkItems::Type.base_types.keys.map(&:upcase)}" } + documentation: { + type: "String", desc: "One of #{::WorkItems::TypesFramework::Provider.new.base_types.map(&:upcase)}" + } end IssueEntity.prepend_mod_with('IssueEntity') diff --git a/app/serializers/linked_issue_entity.rb b/app/serializers/linked_issue_entity.rb index 9f24b465248f67..4e4aef2180e83b 100644 --- a/app/serializers/linked_issue_entity.rb +++ b/app/serializers/linked_issue_entity.rb @@ -28,7 +28,9 @@ class LinkedIssueEntity < Grape::Entity expose :issue_type, as: :type, format_with: :upcase, - documentation: { type: "String", desc: "One of #{::WorkItems::Type.base_types.keys.map(&:upcase)}" } + documentation: { + type: "String", desc: "One of #{::WorkItems::TypesFramework::Provider.new.base_types.map(&:upcase)}" + } expose :relation_path diff --git a/app/services/concerns/issues/issue_type_helpers.rb b/app/services/concerns/issues/issue_type_helpers.rb index e6ac08a567d431..b30b0e8f3c0228 100644 --- a/app/services/concerns/issues/issue_type_helpers.rb +++ b/app/services/concerns/issues/issue_type_helpers.rb @@ -5,8 +5,10 @@ module IssueTypeHelpers # @param object [Issue, Project] # @param issue_type [String, Symbol] def create_issue_type_allowed?(object, issue_type) - WorkItems::Type.base_types.key?(issue_type.to_s) && - can?(current_user, :"create_#{issue_type}", object) + return false unless can?(current_user, :"create_#{issue_type}", object) + + root_ancestor = object.is_a?(Issue) ? object.namespace.root_ancestor : object.owner_entity.root_ancestor + ::WorkItems::TypesFramework::Provider.new(root_ancestor).base_types.include?(issue_type.to_s) end end end diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 0a5475ba18956b..79426fdbcdf868 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -58,8 +58,10 @@ def self.constructor_container_arg(value) end def find_work_item_type_id(issue_type) - work_item_type = WorkItems::Type.default_by_type(issue_type) - work_item_type ||= WorkItems::Type.default_issue_type + provider = work_item_type_provider + + work_item_type = provider.find_by_base_type(issue_type) + work_item_type ||= provider.default_issue_type work_item_type.id end @@ -164,6 +166,11 @@ def filter_timestamp_params params.delete(param) unless current_user.can?(:"set_issue_#{param}", container) end end + + # Maybe memoize? + def work_item_type_provider + ::WorkItems::TypesFramework::Provider.new(container.root_ancestor) + end end end diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb index 7025632a3dccdd..28276a0959fd4e 100644 --- a/app/services/issues/build_service.rb +++ b/app/services/issues/build_service.rb @@ -73,9 +73,11 @@ def issue_params private def set_work_item_type(issue) + provider = work_item_type_provider + work_item_type = if params[:work_item_type_id].present? params.delete(:work_item_type) - WorkItems::Type.find_by(id: params.delete(:work_item_type_id)) # rubocop: disable CodeReuse/ActiveRecord + provider.find_by_id(params.delete(:work_item_type_id)) else params.delete(:work_item_type) end @@ -85,11 +87,11 @@ def set_work_item_type(issue) base_type = work_item_type&.base_type || params[:issue_type] issue.work_item_type = if create_issue_type_allowed?(container, base_type) - work_item_type || WorkItems::Type.default_by_type(base_type) + work_item_type || provider.find_by_base_type(base_type) else # If no work item type was provided or not allowed, we need to set it to # the default issue_type - WorkItems::Type.default_by_type(::Issue::DEFAULT_ISSUE_TYPE) + provider.find_by_base_type(::Issue::DEFAULT_ISSUE_TYPE) end end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 0c2283744ae0ae..76cbda6cb3e6ab 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -248,7 +248,7 @@ def handle_issue_type_change(issue) end def do_handle_issue_type_change(issue) - old_work_item_type = ::WorkItems::Type.find_by_id(issue.work_item_type_id_before_last_save).base_type + old_work_item_type = work_item_type_provider.find_by_id(issue.work_item_type_id_before_last_save).base_type SystemNoteService.change_issue_type(issue, current_user, old_work_item_type) ::IncidentManagement::IssuableEscalationStatuses::CreateService.new(issue).execute if issue.supports_escalation? diff --git a/app/services/quick_actions/target_service.rb b/app/services/quick_actions/target_service.rb index 089b1736b65106..abc66642c70b6b 100644 --- a/app/services/quick_actions/target_service.rb +++ b/app/services/quick_actions/target_service.rb @@ -21,8 +21,10 @@ def execute(type, type_iid) def work_item(type_iid) if type_iid.blank? parent = group_container? ? { namespace: group } : { project: project, namespace: project.project_namespace } + provider = ::WorkItems::TypesFramework::Provider.new(parent.root_ancestor) + return WorkItem.new( - work_item_type_id: params[:work_item_type_id] || WorkItems::Type.default_issue_type.id, + work_item_type_id: params[:work_item_type_id] || provider.default_issue_type.id, **parent ) end diff --git a/config/feature_flags/gitlab_com_derisk/work_item_system_defined_type.yml b/config/feature_flags/gitlab_com_derisk/work_item_system_defined_type.yml new file mode 100644 index 00000000000000..efc257f6cde26c --- /dev/null +++ b/config/feature_flags/gitlab_com_derisk/work_item_system_defined_type.yml @@ -0,0 +1,10 @@ +--- +name: work_item_system_defined_type +description: Switch WorkItemType model from database to fixed item model. +feature_issue_url: +introduced_by_url: +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/583465 +milestone: '18.7' +group: group::project management +type: gitlab_com_derisk +default_enabled: false diff --git a/ee/lib/gitlab/elastic/search_results.rb b/ee/lib/gitlab/elastic/search_results.rb index 68cb7d8e59211d..82af5ab432e858 100644 --- a/ee/lib/gitlab/elastic/search_results.rb +++ b/ee/lib/gitlab/elastic/search_results.rb @@ -368,6 +368,7 @@ def scope_options(scope) work_item_scope_options.merge( not_work_item_type_ids: nil, klass: WorkItem, + # TODO: Rework so it uses base type work_item_type_ids: [::WorkItems::Type.find_by_name(::WorkItems::Type::TYPE_NAMES[:epic]).id] ).except(:fields) when :users @@ -386,12 +387,14 @@ def work_item_scope_options { klass: Issue, # For rendering the UI index_name: ::Search::Elastic::References::WorkItem.index, + # TODO: Rework so it uses base type not_work_item_type_ids: [::WorkItems::Type.find_by_name(::WorkItems::Type::TYPE_NAMES[:epic]).id] }, filters.slice(*::Search::Elastic::References::WorkItem::PERMITTED_FILTER_KEYS) ) if filters[:type].present? + # TODO: Rework so it uses base type work_item_type_id = ::WorkItems::Type.find_by_name(::WorkItems::Type::TYPE_NAMES[filters[:type].to_sym])&.id work_item_scope_options[:work_item_type_ids] = [work_item_type_id] unless work_item_type_id.nil? end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 27ff1c676bb919..0fd386cb515f76 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -76553,7 +76553,7 @@ msgstr "" msgid "Withdraw access request" msgstr "" -msgid "Work Item type with id %{id} was not found" +msgid "Work Item type with %{gid} was not found" msgstr "" msgid "Work in progress (open and unassigned)" diff --git a/spec/requests/api/graphql/mutations/work_items/convert_spec.rb b/spec/requests/api/graphql/mutations/work_items/convert_spec.rb index edc598c842ec19..658da75ce34497 100644 --- a/spec/requests/api/graphql/mutations/work_items/convert_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/convert_spec.rb @@ -39,7 +39,7 @@ post_graphql_mutation(mutation, current_user: current_user) expect(graphql_errors).to include( - a_hash_including('message' => "Work Item type with id #{non_existing_record_id} was not found") + a_hash_including('message' => "Work Item type with #{work_item_type_id} was not found") ) end end diff --git a/spec/support/finder_collection_allowlist.yml b/spec/support/finder_collection_allowlist.yml index cdb1c471f87018..548f1607faf456 100644 --- a/spec/support/finder_collection_allowlist.yml +++ b/spec/support/finder_collection_allowlist.yml @@ -19,6 +19,7 @@ - Security::AnalyzerGroupStatusFinder # Reason: To give accurate counts, return all analyzer types, even when there is no DB record - Ai::ClickHouseUsageEventsFinder # Reason: The finder's data is coming from Clickhouse and not Postgres, no ActiveRelation involved - Ai::UsageEventsFinder # Reason: The finder's data is coming from Clickhouse and not Postgres, no ActiveRelation involved +- WorkItems::TypesFinder # Reason: Types can come from system-defined or ActiveRecord, no ActiveRelation involved # Temporary excludes (aka TODOs) # For example: -- GitLab