diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index ad78f10fe096601c428aa093b097e32a546d53d7..fa9d92f8532c19590108d8529ebae7ed876a8cb8 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -109,7 +109,7 @@ def preload_for_collection @preload_for_collection ||= case collection_type when 'Issue' common_attributes + [ - :work_item_type, + # :work_item_type, :project, { project: :namespace } ] when 'MergeRequest' diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 2f55cfeca7822b1cd9ea26e4e8a3e64794113117..3dbdb30edde902d7afe9257a72421fd236355140 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -413,8 +413,9 @@ def disable_create_query_limit! def show_work_item? # Service Desk issues and incidents should not use the work item view + # TODO: service desk issue type specific !issue.from_service_desk? && - !issue.work_item_type&.incident? && + issue.work_item_type&.key != :incident && Feature.enabled?(:work_item_view_for_issues, project&.group) end diff --git a/app/finders/issues/issue_types_filter.rb b/app/finders/issues/issue_types_filter.rb index ad5b54e4c50c02853c09fa77fb47d032ced7dcd8..cc007f765ca3918a44da63c18b459cb76006ff98 100644 --- a/app/finders/issues/issue_types_filter.rb +++ b/app/finders/issues/issue_types_filter.rb @@ -16,7 +16,9 @@ def by_issue_types(issues) end def valid_param_types? - (::WorkItems::Type.base_types.keys & param_types).sort == param_types.sort + all_keys = ::WorkItems::SystemDefined::Type.all.map(&:key) + param_types_as_symbols = param_types.map(&:to_sym) + (all_keys & param_types_as_symbols).sort == param_types_as_symbols.sort end def param_types diff --git a/app/finders/work_items/work_items_finder.rb b/app/finders/work_items/work_items_finder.rb index 46148337d221018647ad48bf74d68d3ea48bbbb8..eb425f4c327c3a0d6043ceda008d9a9a55014d8a 100644 --- a/app/finders/work_items/work_items_finder.rb +++ b/app/finders/work_items/work_items_finder.rb @@ -61,7 +61,7 @@ def by_parent(items) end def by_widgets(items) - WorkItems::WidgetDefinition.available_widgets.each do |widget_class| + WorkItems::SystemDefined::WidgetDefinition.available_widgets.each do |widget_class| widget_filter = widget_filter_for(widget_class) next unless widget_filter diff --git a/app/graphql/mutations/concerns/mutations/work_items/widgetable.rb b/app/graphql/mutations/concerns/mutations/work_items/widgetable.rb index 70d4bb4865ef81a8cb7c5325fcf86855f8048241..7fe98590d1edb0c27947698952538c1cd19c487b 100644 --- a/app/graphql/mutations/concerns/mutations/work_items/widgetable.rb +++ b/app/graphql/mutations/concerns/mutations/work_items/widgetable.rb @@ -7,7 +7,8 @@ module Widgetable def extract_widget_params!(work_item_type, attributes, resource_parent) # Get the list of widgets for the work item's type to extract only the supported attributes - widget_keys = ::WorkItems::WidgetDefinition.available_widgets.map(&:api_symbol) + # widget_keys = ::WorkItems::WidgetDefinition.available_widgets.map(&:api_symbol) + widget_keys = ::WorkItems::SystemDefined::WidgetDefinition.available_widgets.map(&:api_symbol) widget_params = attributes.extract!(*widget_keys) not_supported_keys = widget_params.keys - work_item_type.widget_classes(resource_parent).map(&:api_symbol) diff --git a/app/graphql/mutations/work_items/convert.rb b/app/graphql/mutations/work_items/convert.rb index 737bb90c4112e2cce33fb150737115073bee1937..222a1a6e52d6b0de0241f33d7602ee1d45718229 100644 --- a/app/graphql/mutations/work_items/convert.rb +++ b/app/graphql/mutations/work_items/convert.rb @@ -30,7 +30,7 @@ def resolve(attributes) update_result = ::WorkItems::UpdateService.new( container: work_item.project, current_user: current_user, - params: { work_item_type: work_item_type, issue_type: work_item_type.base_type }, + params: { work_item_type: work_item_type, issue_type: work_item_type.key }, perform_spam_check: true ).execute(work_item) @@ -45,7 +45,7 @@ def resolve(attributes) private def find_work_item_type!(gid) - work_item_type = ::WorkItems::Type.find_by_id(gid.model_id) + work_item_type = ::WorkItems::SystemDefined::Type.find(gid.model_id) return work_item_type if work_item_type.present? @@ -54,7 +54,7 @@ def find_work_item_type!(gid) end def authorize_work_item_type!(work_item, work_item_type) - return if current_user.can?(:"create_#{work_item_type.base_type}", work_item) + return if current_user.can?(:"create_#{work_item_type.key}", work_item) message = format(_('You are not allowed to change the Work Item type to %{name}.'), name: work_item_type.name) raise_resource_not_available_error! message diff --git a/app/graphql/mutations/work_items/create.rb b/app/graphql/mutations/work_items/create.rb index 83543c910b72b652b544971fa132a461625ed813..f15b10d6fc50f9fb739cb5817ccc7ffd00e310dc 100644 --- a/app/graphql/mutations/work_items/create.rb +++ b/app/graphql/mutations/work_items/create.rb @@ -142,7 +142,7 @@ def check_feature_available!(container, type, params) _('Only project level work items can be created to resolve noteable discussions') end - return if ::WorkItems::Type.allowed_group_level_types(container).include?(type.base_type) + return if ::WorkItems::SystemDefined::Type.allowed_group_level_types(container).include?(type.key) raise_resource_not_available_error! end @@ -166,7 +166,7 @@ def params_with_resolve_discussion_params(attributes) 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) + work_item_type = ::WorkItems::SystemDefined::Type.find(work_item_type_id) attributes[:work_item_type] = work_item_type diff --git a/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb b/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb index 1058f1ed91f88289ac4570495fa853f9325a11c8..3d8ee4f9f819efd67899e83845c962ebec7b9d71 100644 --- a/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb +++ b/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb @@ -15,8 +15,7 @@ def unconditional_includes { project: [:project_feature, :group] }, - :author, - :work_item_type + :author ] end @@ -28,8 +27,7 @@ def preloads participants: Issue.participant_includes, timelogs: [:timelogs], customer_relations_contacts: { customer_relations_contacts: [:group] }, - escalation_status: [:incident_management_issuable_escalation_status], - type: :work_item_type + escalation_status: [:incident_management_issuable_escalation_status] } end end diff --git a/app/graphql/resolvers/concerns/work_items/look_ahead_preloads.rb b/app/graphql/resolvers/concerns/work_items/look_ahead_preloads.rb index a26812699228c1c135a8cd4c6b58face096ec549..82738506e535f188208bd152c8ca51720f6b6828 100644 --- a/app/graphql/resolvers/concerns/work_items/look_ahead_preloads.rb +++ b/app/graphql/resolvers/concerns/work_items/look_ahead_preloads.rb @@ -12,11 +12,11 @@ module LookAheadPreloads def preloads { - work_item_type: :work_item_type, + # work_item_type: :work_item_type, web_url: { namespace: :route, project: [:project_namespace, { namespace: [:route, :namespace_settings_with_ancestors_inherited_settings] }] }, - widgets: { work_item_type: :enabled_widget_definitions }, + # widgets: { work_item_type: :enabled_widget_definitions }, archived: { namespace: :namespace_settings_with_ancestors_inherited_settings, project: { namespace: :namespace_settings_with_ancestors_inherited_settings } @@ -59,7 +59,7 @@ def unconditional_includes project: [:project_feature, :group] }, :author, - :work_item_type, + # :work_item_type, *super ] end diff --git a/app/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb b/app/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb index 41e15d355725f6916198707eac04beb8e6b8b766..33f799b00f7b7fd0b5b7e2879597f111a83016bb 100644 --- a/app/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb +++ b/app/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb @@ -59,6 +59,7 @@ module SharedFilterArguments raise Gitlab::Graphql::Errors::ArgumentError, ::Types::IssuableStateEnum::INVALID_LOCKED_MESSAGE } + # TODO: Accept list of global IDs argument :types, [::Types::IssueTypeEnum], as: :issue_types, diff --git a/app/graphql/resolvers/namespaces/work_item_resolver.rb b/app/graphql/resolvers/namespaces/work_item_resolver.rb index e386dca64a9591f4c6b6d57562c27d261ef42a5e..48ce458364fccd5cd38d8296fd5f8216697e999b 100644 --- a/app/graphql/resolvers/namespaces/work_item_resolver.rb +++ b/app/graphql/resolvers/namespaces/work_item_resolver.rb @@ -30,7 +30,8 @@ def resolve(iid:) private def log_recent_view(work_item) - base_type = work_item.work_item_type.base_type + # base_type = work_item.work_item_type.base_type + base_type = work_item.work_item_type.key return unless self.class.recent_services_map.key?(base_type) diff --git a/app/graphql/resolvers/work_items/linked_items_resolver.rb b/app/graphql/resolvers/work_items/linked_items_resolver.rb index 6e912d4a44e29f54332058988f45a09cb88a9883..9d4f213c1bbdd6272472281069d5c3a4682f9b80 100644 --- a/app/graphql/resolvers/work_items/linked_items_resolver.rb +++ b/app/graphql/resolvers/work_items/linked_items_resolver.rb @@ -34,7 +34,7 @@ def bulk_load_linked_items(link_type) batch_key = "linked_items_level_#{nesting_level}" BatchLoader::GraphQL.for(work_item.id).batch(key: batch_key, cache: false) do |item_ids, loader, _args| - preloads = [:author, :work_item_type, { project: [:route, { namespace: :route }] }] + preloads = [:author, { project: [:route, { namespace: :route }] }] linked_items = apply_lookahead(WorkItem.linked_items_for(item_ids, preload: preloads, link_type: link_type)) grouped_by_source = linked_items_grouped_by_source(linked_items, item_ids) diff --git a/app/graphql/resolvers/work_items/types_resolver.rb b/app/graphql/resolvers/work_items/types_resolver.rb index b61b8c9914a5adfa1e820d3b7e3b046f91580b36..de5c63269099229454ce5862b2d2ae59ef795455 100644 --- a/app/graphql/resolvers/work_items/types_resolver.rb +++ b/app/graphql/resolvers/work_items/types_resolver.rb @@ -16,18 +16,8 @@ def resolve_with_lookahead(name: nil) # This will require a finder in the future when groups/projects get their work item types # All groups/projects use all types for now - base_scope = ::WorkItems::Type - base_scope = base_scope.by_type(name) if name - - apply_lookahead(base_scope.order_by_name_asc) - end - - private - - def preloads - { - widget_definitions: :enabled_widget_definitions - } + # TODO: Support name + apply_lookahead(::WorkItems::SystemDefined::Type.ordered_by_name) end end end diff --git a/app/graphql/types/work_items/type_type.rb b/app/graphql/types/work_items/type_type.rb index 96dfa85c0b6ac5226ac472d08bba2689d996d550..0baf6747c6048510987d55aae8dc70b462d10756 100644 --- a/app/graphql/types/work_items/type_type.rb +++ b/app/graphql/types/work_items/type_type.rb @@ -42,6 +42,9 @@ def self.authorization_scopes end def widget_definitions + puts "typetype widgets called" + pp context[:resource_parent] + pp object.widgets(context[:resource_parent]) object.widgets(context[:resource_parent]) end @@ -52,6 +55,8 @@ def supported_conversion_types def unavailable_widgets_on_conversion(target:) source_type = object target_type = GitlabSchema.find_by_gid(target).sync + puts "typetype unavailable widgets on conversion" + pp target_type return [] unless source_type && target_type diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 6423377ada7f7b87f33e2beb7d6ddb7082301e27..40fc9847cd8a1ceb3d7df2f4d9d697393cb06fff 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -98,6 +98,7 @@ def can_create_confidential_merge_request? can?(current_user, :create_merge_request_in, @project) end + # TODO: service desk issue type specific def show_moved_service_desk_issue_warning?(issue) return false unless issue.moved_from return false unless issue.from_service_desk? @@ -108,7 +109,7 @@ def show_moved_service_desk_issue_warning?(issue) def issue_header_actions_data(project, issuable, current_user, issuable_sidebar) new_issuable_params = { issue: {}, add_related_issue: issuable.iid } - if issuable.work_item_type&.incident? + if issuable.work_item_type&.key == :incident new_issuable_params[:issuable_template] = 'incident' new_issuable_params[:issue][:issue_type] = 'incident' end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 5934e43aee68a5adc9a0a54f1c610aaf89c79d22..bdc4512486d6cf74613ff46fa12a964a887d2f7b 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -101,7 +101,8 @@ def work_item_epic_page? def new_issue_look? current_controller?('issues') && Feature.enabled?(:work_item_view_for_issues, @project&.group) && - !@issue&.work_item_type&.incident? && + !@issue&.work_item_type&.key == :incident? && + # TODO: service desk issue type specific !@issue&.from_service_desk? end diff --git a/app/helpers/routing/projects_helper.rb b/app/helpers/routing/projects_helper.rb index cc1bfac24014c8ff7a6831444e00e8fa7e1e0a56..1c307b3e5df03dae7727f4bae69cb1b76772444c 100644 --- a/app/helpers/routing/projects_helper.rb +++ b/app/helpers/routing/projects_helper.rb @@ -101,7 +101,9 @@ def use_work_items_path?(issue) end def use_issue_path?(work_item) - work_item.work_item_type.issue? || work_item.work_item_type.incident? || work_item.from_service_desk? + # TODO: service desk issue type specific + # work_item.work_item_type.issue? || work_item.work_item_type.incident? || work_item.from_service_desk? + work_item.work_item_type.key == :issue || work_item.work_item_type.key == :incident || work_item.from_service_desk? end end end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 55f2f25d995d9b549c0b31e5cccfac10ee5d985e..39d0714c36ef76d213e6d8d815d43f93f4707c37 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -219,7 +219,8 @@ def supports_escalation? end def incident_type_issue? - is_a?(Issue) && work_item_type&.incident? + # is_a?(Issue) && work_item_type&.incident? + is_a?(Issue) && work_item_type&.key == :incident end def supports_issue_type? diff --git a/app/models/event.rb b/app/models/event.rb index 243dbf0e426a90301ecb63dace52cddb2a6063ae..cd30067e38186736cc4be49d5d59f732b0070a31 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -71,7 +71,7 @@ class Event < ApplicationRecord # If the association for "target" defines an "author" association we want to # eager-load this so Banzai & friends don't end up performing N+1 queries to # get the authors of notes, issues, etc. (likewise for "noteable"). - incs = %i[author noteable work_item_type].select do |a| + incs = %i[author noteable].select do |a| reflections['events'].active_record.reflect_on_association(a) end diff --git a/app/models/issue.rb b/app/models/issue.rb index 227887ca391b072cd6f41ebfca9e4997e9df9d80..6aa0fa3a651953e3772b6bd81c715f0a7a6c240a 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -25,6 +25,7 @@ class Issue < ApplicationRecord include EachBatch include PgFullTextSearchable include Gitlab::DueAtFilterable + include Gitlab::Utils::StrongMemoize extend ::Gitlab::Utils::Override @@ -48,6 +49,7 @@ class Issue < ApplicationRecord # # This should be kept consistent with the enums used for the GraphQL issue list query in # https://gitlab.com/gitlab-org/gitlab/-/blob/1379c2d7bffe2a8d809f23ac5ef9b4114f789c07/app/assets/javascripts/issues/list/constants.js#L154-158 + # # TODO: Do we need them? TYPES_FOR_LIST = %w[issue incident test_case task objective key_result].freeze # Types of issues that should be displayed on issue board lists @@ -75,10 +77,21 @@ class Issue < ApplicationRecord belongs_to :duplicated_to, class_name: 'Issue' belongs_to :closed_by, class_name: 'User' - belongs_to :work_item_type, class_name: 'WorkItems::Type' belongs_to :moved_to, class_name: 'Issue', inverse_of: :moved_from has_one :moved_from, class_name: 'Issue', foreign_key: :moved_to_id, inverse_of: :moved_to + # We cannot use the belongs_to_fixed_items association here because we'll resolve + # system-defined and custom types here. + def work_item_type + WorkItems::SystemDefined::Type.find(work_item_type_id) if work_item_type_id + end + strong_memoize_attr :work_item_type + + def work_item_type=(type) + clear_memoization(:work_item_type) + self.work_item_type_id = type&.id + end + has_internal_id :iid, scope: :namespace, track_if: -> { !importing? } has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent @@ -140,7 +153,7 @@ def most_recent validates :project, presence: true, if: -> { !namespace || namespace.is_a?(Namespaces::ProjectNamespace) } validates :namespace, presence: true - validates :work_item_type, presence: true + # validates :work_item_type, presence: true validates :confidential, inclusion: { in: [true, false], message: 'must be a boolean' } validate :allowed_work_item_type_change, on: :update, if: :work_item_type_id_changed? @@ -217,11 +230,11 @@ def most_recent scope :preload_namespace, -> { preload(:namespace) } scope :preload_routables, -> { preload(project: [:route, { namespace: :route }]) } scope :preload_namespace_routables, -> { preload(namespace: [:route, { parent: :route }]) } - scope :preload_for_rss, -> { preload(:author, :assignees, :labels, :milestone, :work_item_type, :project, { project: :namespace }) } + scope :preload_for_rss, -> { preload(:author, :assignees, :labels, :milestone, :project, { project: :namespace }) } scope :with_alert_management_alerts, -> { joins(:alert_management_alert) } scope :with_api_entity_associations, -> { - preload(:work_item_type, + preload( :timelogs, :closed_by, :assignees, :author, :issuable_severity, :labels, namespace: [{ parent: :route }, :route], milestone: { project: [:route, { namespace: :route }] }, project: [:project_namespace, :project_feature, :route, { group: :route }, { namespace: :route }], @@ -230,14 +243,16 @@ def most_recent } scope :with_issue_type, ->(types) { type_ids = Array(types).filter_map do |type| - WorkItems::Type::BASE_TYPES.dig(type.to_sym, :id) + # WorkItems::Type::BASE_TYPES.dig(type.to_sym, :id) + WorkItems::SystemDefined::Type[type]&.id end where(work_item_type_id: type_ids) } scope :without_issue_type, ->(types) { type_ids = Array(types).filter_map do |type| - WorkItems::Type::BASE_TYPES.dig(type.to_sym, :id) + # WorkItems::Type::BASE_TYPES.dig(type.to_sym, :id) + WorkItems::SystemDefined::Type[type]&.id end where.not(work_item_type_id: type_ids) @@ -259,8 +274,10 @@ def most_recent where( "(author_id = ? AND work_item_type_id = ?) OR work_item_type_id = ?", Users::Internal.support_bot.id, - WorkItems::Type.default_issue_type.id, - WorkItems::Type.default_by_type(:ticket).id + # WorkItems::Type.default_issue_type.id, + WorkItems::SystemDefined::Type.default.id, + # WorkItems::Type.default_by_type(:ticket).id + WorkItems::SystemDefined::Type[:ticket].id ) } scope :inc_relations_for_view, -> do @@ -808,11 +825,11 @@ 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 || WorkItems::SystemDefined::Type.default end def issue_type - work_item_type_with_default.base_type + work_item_type_with_default.key.to_s end def hook_attrs @@ -874,7 +891,8 @@ def autoclose_by_merged_closing_merge_request? end def epic_work_item? - work_item_type&.epic? + # work_item_type&.epic? + work_item_type&.key == :epic end def group_epic_work_item? @@ -956,7 +974,8 @@ 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 = WorkItems::Type.default_by_type(DEFAULT_ISSUE_TYPE) + self.work_item_type = WorkItems::SystemDefined::Type.default end def ensure_namespace_traversal_ids diff --git a/app/models/work_item.rb b/app/models/work_item.rb index 30d2ae7059191eb763f410fbb2fd78f8ce279461..8b623d5fc4c697e51448dc0e1e707be8b9f3ce92 100644 --- a/app/models/work_item.rb +++ b/app/models/work_item.rb @@ -31,7 +31,7 @@ class WorkItem < Issue scope :inc_relations_for_permission_check, -> { includes( - :author, :work_item_type, { project: [:project_feature, { namespace: :route }, :group] }, { namespace: [:route] } + :author, { project: [:project_feature, { namespace: :route }, :group] }, { namespace: [:route] } ) } @@ -66,8 +66,12 @@ class WorkItem < Issue end scope :with_enabled_widget_definition, ->(type) do - joins(work_item_type: :enabled_widget_definitions) - .merge(::WorkItems::WidgetDefinition.by_enabled_widget_type(type)) + where( + work_item_type_id: WorkItems::SystemDefined::Type + .all + .select { |wit| wit.widgets(nil).include?(type) } + .map(&:id) + ) end scope :with_group_level_and_project_issues_enabled, ->(include_group_level_items: true) do @@ -452,7 +456,7 @@ def record_create_action user: author, project: project, additional_properties: { - label: work_item_type.base_type + label: work_item_type.key.to_s } ) end diff --git a/app/models/work_items/system_defined/type.rb b/app/models/work_items/system_defined/type.rb new file mode 100644 index 0000000000000000000000000000000000000000..b29d1b7d2f4326d11b6abd65a470625bc9aed3b0 --- /dev/null +++ b/app/models/work_items/system_defined/type.rb @@ -0,0 +1,267 @@ +# frozen_string_literal: true + +module WorkItems + module SystemDefined + class Type + include ActiveRecord::FixedItemsModel::Model + include GlobalID::Identification + include Gitlab::Utils::StrongMemoize + + # We can calculate that from the name + # attribute :key, :symbol + attribute :name, :string + # This is how we could work with configuration/setting per type that we'll then expose in the API + # to perform checks. + # Obviously this thsi attribute is a bad example, but it shows the idea. + attribute :ee_only, :boolean, default: false + # We can calculate that from the name. But maybe we want to move that to an icon enum later? + # Because icons can change and that way we'd only need to change the definition and not the data. + # We'll also want to use an enum for custom type icons. + # attribute :icon_name, :string + + ITEMS = [ + { + id: 1, + name: 'Issue' + }, + { + id: 2, + name: 'Incident' + }, + { + id: 3, + name: 'Test Case', + ee_only: true + }, + { + id: 4, + name: 'Requirement', + ee_only: true + }, + { + id: 5, + name: 'Task' + }, + { + id: 6, + name: 'Objective', + ee_only: true + }, + { + id: 7, + name: 'Key Result', + ee_only: true + }, + { + id: 8, + name: 'Epic', + ee_only: true + }, + { + id: 9, + name: 'Ticket' + } + ].freeze + + class << self + # Could be an idea to fetch the "base type" to reduce the length of the expression. + # WorkItems::SystemDefined::Type[:issue] vs. WorkItems::SystemDefined::Type.by_base_type(:issue) + def [](key) + return if key.nil? + + # Accept several input formats + key = key.parameterize.underscore.to_sym unless key.is_a?(Symbol) + + all.find { |item| item.key == key } + end + + # Instead of default_issue_type? Because we said everything will fall back to issue? + def default + self[:issue] + end + + # Scopes are not ideal right now because we cannot chain them (scope.order...). + # I'm unsure whether we should introduce an intermediate class that handles queries better. + # For now, let's just add class methods that do what we need. + # scope :order_by_name_asc, -> { order(arel_table[:name].lower.asc) } + def by_ids_ordered_by_name(ids) + where(id: ids).sort_by { |type| type.name.downcase } + end + + # scope :by_type, ->(base_type) { where(base_type: base_type) } + def by_keys_ordered_by_name(keys) + # TODO: Also allow methods that exist in where/find_by + all.select { |item| keys.include?(item.key) }.sort_by { |type| type.name.downcase } + end + + # def by_keys(keys) + # all.select { |item| keys.include?(item.key) } + # end + + def ordered_by_name + all.sort_by { |type| type.name.downcase } + end + + def allowed_group_level_types(resource_parent) + return [:epic] if resource_parent.licensed_feature_available?(:epics) + + [] + end + end + + def key + name.parameterize.underscore.to_sym + end + + def icon_name + "issue-type-#{parameterized_name}" + end + + # USe legacy format + def to_global_id(_options = {}) + ::Gitlab::GlobalId.build(self, model_name: 'WorkItems::Type', id: id) + end + alias_method :to_gid, :to_global_id + + def widget_definitions + WorkItems::SystemDefined::WidgetDefinition.where(work_item_type_id: id) + end + strong_memoize_attr :widget_definitions + + # We don't need "enabled_widgets" any more because there's no point in disabling them for system defined ones. + def widgets(_resource_parent) + widget_definitions.filter(&:widget_class) + end + + def widget_classes(resource_parent) + widgets(resource_parent).map(&:widget_class) + end + + # The following methods are copied over from WorkItems::Type and adjusted. + # Not sure how much of it we really need, so let's keep them here for now. + + # TODO: Move to something generic like .supports_widget?(widget_type) + def supports_assignee?(resource_parent) + widget_classes(resource_parent).include?(::WorkItems::Widgets::Assignees) + end + + def supports_time_tracking?(resource_parent) + widget_classes(resource_parent).include?(::WorkItems::Widgets::TimeTracking) + end + + # I didn't care about EE and CC differences for now.... but we'd want to have CE and EE classes for sure. + # TODO: move to EE + def status_lifecycle_for(namespace_id) + custom_lifecycle_for(namespace_id) || system_defined_lifecycle + end + + # TODO: move to EE + def custom_status_enabled_for?(namespace_id) + return false unless namespace_id + + ::WorkItems::TypeCustomLifecycle.exists?( + work_item_type_id: id, + namespace_id: namespace_id + ) + end + + # TODO: move to EE + def custom_lifecycle_for(namespace_id) + return unless namespace_id + + ::WorkItems::Statuses::Custom::Lifecycle + .includes(:statuses, :default_open_status, :default_closed_status, :default_duplicate_status) + .joins(:type_custom_lifecycles) + .find_by( + namespace_id: namespace_id, + type_custom_lifecycles: { work_item_type_id: id, namespace_id: namespace_id } + ) + end + + # TODO: move to EE + def system_defined_lifecycle + ::WorkItems::Statuses::SystemDefined::Lifecycle.of_work_item_base_type(key) + end + + # TODO: + # has_many :user_preferences, + # class_name: 'WorkItems::UserPreference', + # inverse_of: :work_item_type + + def allowed_child_types_by_name + child_type_ids = WorkItems::SystemDefined::HierarchyRestriction + .where(parent_type_id: id) + .map(&:child_type_id) + + self.class.by_ids_ordered_by_name(child_type_ids) + end + + def allowed_parent_types_by_name + parent_type_ids = WorkItems::SystemDefined::HierarchyRestriction + .where(child_type_id: id) + .map(&:parent_type_id) + self.class.by_ids_ordered_by_name(parent_type_ids) + end + + def descendant_types + descendant_types = [] + next_level_child_types = allowed_child_types + + loop do + descendant_types += next_level_child_types + + # We remove types that we've already seen to avoid circular dependencies + next_level_child_types = next_level_child_types.flat_map(&:allowed_child_types) - descendant_types + + break if next_level_child_types.empty? + end + + descendant_types + end + strong_memoize_attr :descendant_types + + def supported_conversion_types(resource_parent, user) + type_names = supported_conversion_base_types(resource_parent, user) - [key] + self.class.by_keys_ordered_by_name(type_names) + end + + def unavailable_widgets_on_conversion(target_type, resource_parent) + source_widgets = widgets(resource_parent) + target_widgets = target_type.widgets(resource_parent) + target_widget_types = target_widgets.map(&:widget_type).to_set + source_widgets.reject { |widget| target_widget_types.include?(widget.widget_type) } + end + + def allowed_child_types(authorize: false, resource_parent: nil) + types = allowed_child_types_by_name + + return types unless authorize + + authorized_types(types, resource_parent, :child) + end + + def allowed_parent_types(authorize: false, resource_parent: nil) + types = allowed_parent_types_by_name + + return types unless authorize + + authorized_types(types, resource_parent, :parent) + end + + private + + def parameterized_name + name.parameterize + end + + def supported_conversion_base_types(_resource_parent, _user) + self.class.where(ee_only: false).map(&:key) + end + + # overridden in EE to check for EE-specific restrictions + def authorized_types(types, _resource_parent, _relation) + types + end + end + end +end diff --git a/app/models/work_items/system_defined/widget_configurations/epic.rb b/app/models/work_items/system_defined/widget_configurations/epic.rb new file mode 100644 index 0000000000000000000000000000000000000000..40ce1817f571932683bf9604e8041b6e0f4d631c --- /dev/null +++ b/app/models/work_items/system_defined/widget_configurations/epic.rb @@ -0,0 +1,32 @@ +module WorkItems + module SystemDefined + module WidgetConfigurations + module Epic + WIDGETS = %i[ + assignees + award_emoji + color + current_user_todos + custom_fields + description + health_status + hierarchy + labels + linked_items + milestone + notes + notifications + participants + start_and_due_date + verification_status + time_tracking + weight + ].freeze + + WIDGET_OPTIONS = { + weight: { editable: false, rollup: true } + }.freeze + end + end + end +end diff --git a/app/models/work_items/system_defined/widget_configurations/incident.rb b/app/models/work_items/system_defined/widget_configurations/incident.rb new file mode 100644 index 0000000000000000000000000000000000000000..badcdde96cdf387d181aa2fc0228442411da8b36 --- /dev/null +++ b/app/models/work_items/system_defined/widget_configurations/incident.rb @@ -0,0 +1,30 @@ +module WorkItems + module SystemDefined + module WidgetConfigurations + module Incident + WIDGETS = %i[ + assignees + award_emoji + crm_contacts + current_user_todos + custom_fields + description + development + email_participants + hierarchy + iteration + labels + linked_items + linked_resources + milestone + notes + notifications + participants + time_tracking + ].freeze + + WIDGET_OPTIONS = {}.freeze + end + end + end +end diff --git a/app/models/work_items/system_defined/widget_configurations/issue.rb b/app/models/work_items/system_defined/widget_configurations/issue.rb new file mode 100644 index 0000000000000000000000000000000000000000..227a433e9b2ebf86d39fb8c29e211e27bce9c568 --- /dev/null +++ b/app/models/work_items/system_defined/widget_configurations/issue.rb @@ -0,0 +1,39 @@ +module WorkItems + module SystemDefined + module WidgetConfigurations + module Issue + WIDGETS = %i[ + assignees + award_emoji + crm_contacts + current_user_todos + custom_fields + description + designs + development + email_participants + error_tracking + health_status + hierarchy + iteration + labels + linked_items + milestone + notes + notifications + participants + start_and_due_date + time_tracking + vulnerabilities + linked_resources + weight + status + ].freeze + + WIDGET_OPTIONS = { + weight: { editable: true, rollup: false } + }.freeze + end + end + end +end diff --git a/app/models/work_items/system_defined/widget_configurations/key_result.rb b/app/models/work_items/system_defined/widget_configurations/key_result.rb new file mode 100644 index 0000000000000000000000000000000000000000..7dbd147033c1b8b78ef3b316d4199813eb8aa785 --- /dev/null +++ b/app/models/work_items/system_defined/widget_configurations/key_result.rb @@ -0,0 +1,26 @@ +module WorkItems + module SystemDefined + module WidgetConfigurations + module KeyResult + WIDGETS = %i[ + assignees + award_emoji + current_user_todos + custom_fields + description + health_status + hierarchy + labels + linked_items + notes + notifications + participants + start_and_due_date + progress + ].freeze + + WIDGET_OPTIONS = {}.freeze + end + end + end +end diff --git a/app/models/work_items/system_defined/widget_configurations/objective.rb b/app/models/work_items/system_defined/widget_configurations/objective.rb new file mode 100644 index 0000000000000000000000000000000000000000..98a0e82f8d20010b38b875e1599467c465f7240c --- /dev/null +++ b/app/models/work_items/system_defined/widget_configurations/objective.rb @@ -0,0 +1,26 @@ +module WorkItems + module SystemDefined + module WidgetConfigurations + module Objective + WIDGETS = %i[ + assignees + award_emoji + current_user_todos + custom_fields + description + health_status + hierarchy + labels + linked_items + milestone + notes + notifications + participants + progress + ].freeze + + WIDGET_OPTIONS = {}.freeze + end + end + end +end diff --git a/app/models/work_items/system_defined/widget_configurations/requirement.rb b/app/models/work_items/system_defined/widget_configurations/requirement.rb new file mode 100644 index 0000000000000000000000000000000000000000..97175fe6cc8196c52b8bd1b6fd57811ce0c8a401 --- /dev/null +++ b/app/models/work_items/system_defined/widget_configurations/requirement.rb @@ -0,0 +1,24 @@ +module WorkItems + module SystemDefined + module WidgetConfigurations + module Requirement + WIDGETS = %i[ + award_emoji + current_user_todos + custom_fields + description + linked_items + notes + notifications + participants + requirement_legacy + verification_status + test_reports + time_tracking + ].freeze + + WIDGET_OPTIONS = {}.freeze + end + end + end +end diff --git a/app/models/work_items/system_defined/widget_configurations/task.rb b/app/models/work_items/system_defined/widget_configurations/task.rb new file mode 100644 index 0000000000000000000000000000000000000000..d99b40147269713d9f170448ce172c64e669f6ee --- /dev/null +++ b/app/models/work_items/system_defined/widget_configurations/task.rb @@ -0,0 +1,34 @@ +module WorkItems + module SystemDefined + module WidgetConfigurations + module Task + WIDGETS = %i[ + assignees + award_emoji + crm_contacts + current_user_todos + custom_fields + description + development + hierarchy + iteration + labels + linked_items + linked_resources + milestone + notes + notifications + participants + start_and_due_date + time_tracking + weight + status + ].freeze + + WIDGET_OPTIONS = { + weight: { editable: true, rollup: false } + }.freeze + end + end + end +end diff --git a/app/models/work_items/system_defined/widget_configurations/test_case.rb b/app/models/work_items/system_defined/widget_configurations/test_case.rb new file mode 100644 index 0000000000000000000000000000000000000000..d1fe02edb57ccb00001d08984cd667bfb02cc2ac --- /dev/null +++ b/app/models/work_items/system_defined/widget_configurations/test_case.rb @@ -0,0 +1,21 @@ +module WorkItems + module SystemDefined + module WidgetConfigurations + module TestCase + WIDGETS = %i[ + award_emoji + current_user_todos + custom_fields + description + linked_items + notes + notifications + participants + time_tracking + ].freeze + + WIDGET_OPTIONS = {}.freeze + end + end + end +end diff --git a/app/models/work_items/system_defined/widget_configurations/ticket.rb b/app/models/work_items/system_defined/widget_configurations/ticket.rb new file mode 100644 index 0000000000000000000000000000000000000000..f09975796d14619f5c9b464c0c119bf7ce7d6fd7 --- /dev/null +++ b/app/models/work_items/system_defined/widget_configurations/ticket.rb @@ -0,0 +1,35 @@ +module WorkItems + module SystemDefined + module WidgetConfigurations + module Ticket + WIDGETS = %i[ + assignees + award_emoji + crm_contacts + current_user_todos + custom_fields + description + designs + development + email_participants + health_status + hierarchy + iteration + labels + linked_items + milestone + notes + notifications + participants + start_and_due_date + time_tracking + weight + ].freeze + + WIDGET_OPTIONS = { + weight: { editable: true, rollup: false } + }.freeze + end + end + end +end diff --git a/app/models/work_items/system_defined/widget_definition.rb b/app/models/work_items/system_defined/widget_definition.rb new file mode 100644 index 0000000000000000000000000000000000000000..eff9bca7db2325f1e89022198c0e5cb75d1125b5 --- /dev/null +++ b/app/models/work_items/system_defined/widget_definition.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +module WorkItems + module SystemDefined + class WidgetDefinition + include ActiveRecord::FixedItemsModel::Model + + # We don't need id's here to be stable. Just remove them for easier reading. + auto_generate_ids! + + # I took teh wording from the existing classes. + # We can also change things and remove the duplication of `widget` if we want to. + attribute :widget_type, :string + attribute :widget_options, ::Gitlab::Database::Type::IndifferentJsonb.new + attribute :work_item_type_id, :integer + + # I think we should keep a SSOT list with all possible widget types. + # This way we can easily list them and test whether widgets for types are valid. + WIDGET_TYPES = %i[ + assignees + award_emoji + color + crm_contacts + current_user_todos + custom_fields + description + designs + development + email_participants + error_tracking + health_status + hierarchy + iteration + labels + linked_items + linked_resources + milestone + notes + notifications + participants + progress + requirement_legacy + start_and_due_date + status + test_reports + time_tracking + verification_status + vulnerabilities + weight + ].freeze + + # Instead of having a huge matrix here, let's build the items based on a configuration module per type. + # This makes things more readable and easier to maintain. + ITEMS = Type::ITEMS.flat_map do |type_item| + type_key = type_item[:name].parameterize.underscore.to_sym + config_module = WidgetConfigurations.const_get(type_key.to_s.camelize, false) + + config_module::WIDGETS.map do |widget_type| + { + widget_type: widget_type, + work_item_type_id: type_item[:id], + widget_options: config_module::WIDGET_OPTIONS[widget_type] + }.compact + end + end.freeze + + # This is the old behavior. Maybe we can do better? Not sure. + def self.available_widgets + WIDGET_TYPES.filter_map do |type| + new(widget_type: type).widget_class + end + end + + def work_item_type + WorkItems::SystemDefined::Type.find(work_item_type_id) + end + + def widget_class + return unless widget_type + + WorkItems::Widgets.const_get(widget_type.camelize, false) + rescue NameError + nil + end + + def build_widget(work_item) + widget_class.new(work_item, widget_definition: self) + end + end + end +end diff --git a/app/models/work_items/widget_definition.rb b/app/models/work_items/widget_definition.rb index c35ae3fd7aecf473c59a0a2d7b786ab3962f0c01..1138d6b345466fba7c4264659c3a4134ae2f3114 100644 --- a/app/models/work_items/widget_definition.rb +++ b/app/models/work_items/widget_definition.rb @@ -15,7 +15,7 @@ class WidgetDefinition < ApplicationRecord validates :widget_options, absence: true, unless: :weight? scope :enabled, -> { where(disabled: false) } - scope :by_enabled_widget_type, ->(widget_type) { enabled.where(widget_type: widget_type) } + # scope :by_enabled_widget_type, ->(widget_type) { enabled.where(widget_type: widget_type) } enum :widget_type, { assignees: 0, diff --git a/app/policies/work_items/system_defined/type_policy.rb b/app/policies/work_items/system_defined/type_policy.rb new file mode 100644 index 0000000000000000000000000000000000000000..3b0a53b79883fcae7ed54f97addc112d0e8b6d8e --- /dev/null +++ b/app/policies/work_items/system_defined/type_policy.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module WorkItems + module SystemDefined + class TypePolicy < BasePolicy + condition(:type_present) { @subject.present? } + + rule { type_present }.enable :read_work_item_type + end + end +end diff --git a/app/services/concerns/issues/issue_type_helpers.rb b/app/services/concerns/issues/issue_type_helpers.rb index e6ac08a567d431ba15030fdf97aca7793e76c90b..a1f75e0ea47a5e59da2fab70384caa70cd4f40ea 100644 --- a/app/services/concerns/issues/issue_type_helpers.rb +++ b/app/services/concerns/issues/issue_type_helpers.rb @@ -5,7 +5,8 @@ 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) && + WorkItems::SystemDefined::Type[issue_type].present? && + # WorkItems::Type.base_types.key?(issue_type.to_s) && can?(current_user, :"create_#{issue_type}", object) end end diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 0a5475ba18956b4b87f20b5d0e4545c516890ea0..665a1d5c673028a87ada20fdcafc041b5502b467 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -43,7 +43,7 @@ def execute_hooks(issue, action = 'open', old_associations: {}) issue.namespace.execute_hooks(issue_data, hooks_scope) issue.namespace.execute_integrations(issue_data, hooks_scope) - execute_incident_hooks(issue, issue_data) if issue.work_item_type&.incident? + execute_incident_hooks(issue, issue_data) if issue.work_item_type&.key == :incident execute_group_mention_hooks(issue, issue_data) if action == 'open' end @@ -58,8 +58,8 @@ 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 + work_item_type = WorkItems::SystemDefined::Type[issue_type.to_sym] + work_item_type ||= WorkItems::SystemDefined::Type.default work_item_type.id end @@ -69,7 +69,7 @@ def filter_params(issue) params.delete(:issue_type) unless create_issue_type_allowed?(issue, params[:issue_type]) - if params[:work_item_type].present? && !create_issue_type_allowed?(project, params[:work_item_type].base_type) + if params[:work_item_type].present? && !create_issue_type_allowed?(project, params[:work_item_type].key) params.delete(:work_item_type) end diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb index 7025632a3dccddaf24c8295741fe22262751ffc4..6a81dca4cae877115ca669f62b1290c0b227c9e7 100644 --- a/app/services/issues/build_service.rb +++ b/app/services/issues/build_service.rb @@ -75,21 +75,23 @@ def issue_params def set_work_item_type(issue) 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 + WorkItems::SystemDefined::Type.find(params[:work_item_type_id].to_i) + # WorkItems::Type.find_by(id: params.delete(:work_item_type_id)) # rubocop: disable CodeReuse/ActiveRecord else params.delete(:work_item_type) end # We need to support the legacy input params[:issue_type] even if we don't have the issue_type column anymore. # In the future only params[:work_item_type] should be provided - base_type = work_item_type&.base_type || params[:issue_type] + base_type = work_item_type&.key || 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 || WorkItems::SystemDefined::Type[base_type] + # work_item_type || WorkItems::Type.default_by_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) + WorkItems::SystemDefined::Type.default end end diff --git a/app/services/issues/convert_to_ticket_service.rb b/app/services/issues/convert_to_ticket_service.rb index 8c665a90c9241f31e593937b34578f1d0de2aa3c..086a3eb3b9e9d9f353abcab32650c354ee905024 100644 --- a/app/services/issues/convert_to_ticket_service.rb +++ b/app/services/issues/convert_to_ticket_service.rb @@ -57,6 +57,7 @@ def add_note end def ticket? + # TODO: service desk issue type specific target.from_service_desk? end diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index 3d0aee0518747e00c25d8c3b3ed85607e8df64f1..ef9ec89911d4ac3098aef307e0788aab85482986 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -114,7 +114,7 @@ def authorization_action attr_reader :perform_spam_check, :extra_params def create_timeline_event(issue) - return unless issue.work_item_type&.incident? + return unless issue.work_item_type&.key == :incident IncidentManagement::TimelineEvents::CreateService.create_incident(issue, current_user) end diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb index 9243ca42bbd7f2aa9661375d87e356f87048efbe..6ba354d56d94d983bf2b3fc86e80a42dda34c365 100644 --- a/app/services/system_notes/issuables_service.rb +++ b/app/services/system_notes/issuables_service.rb @@ -467,7 +467,7 @@ def auto_resolve_prometheus_alert end def change_issue_type(previous_type) - previous = previous_type.humanize(capitalize: false) + previous = previous_type.to_s.humanize(capitalize: false) new = noteable.issue_type.humanize(capitalize: false) body = "changed type from #{previous} to #{new}" @@ -521,6 +521,7 @@ def track_cross_reference_action def hierarchy_note_params(action, parent, child) return {} unless child && parent + # TODO: custom type name child_type = child.issue_type.humanize(capitalize: false) parent_type = parent.issue_type.humanize(capitalize: false) child_reference, parent_reference = if child.namespace_id == parent.namespace_id diff --git a/app/services/work_items/bulk_update_service.rb b/app/services/work_items/bulk_update_service.rb index c88e1634aee7fd74b5c9e0c84e65427c6e2b7d11..011021492b8650642e1140b59c3c1b389a183483 100644 --- a/app/services/work_items/bulk_update_service.rb +++ b/app/services/work_items/bulk_update_service.rb @@ -48,7 +48,8 @@ def scoped_work_items end def all_widget_keys - @all_widget_keys ||= ::WorkItems::WidgetDefinition.available_widgets.map(&:api_symbol) + # @all_widget_keys ||= ::WorkItems::WidgetDefinition.available_widgets.map(&:api_symbol) + @all_widget_keys ||= ::WorkItems::SystemDefined::WidgetDefinition.available_widgets.map(&:api_symbol) end def extract_supported_widget_params(work_item_type, attributes, resource_parent) diff --git a/app/services/work_items/data_sync/widgets/notifications.rb b/app/services/work_items/data_sync/widgets/notifications.rb index 756de066fe60e7c324b2d277ec1660dad8f00709..65fec83804f8725a597c83087d9311f85550d55a 100644 --- a/app/services/work_items/data_sync/widgets/notifications.rb +++ b/app/services/work_items/data_sync/widgets/notifications.rb @@ -17,6 +17,7 @@ def after_save_commit # This replicates current move Issues::MoveService behaviour. This should be changed though to # move sent_notifications for any work_item that is being moved. + # TODO: service desk issue type specific return unless work_item.from_service_desk? # When moving sent notifications for any work item this can entail updating many records in some instances. diff --git a/app/views/projects/issues/_design_management.html.haml b/app/views/projects/issues/_design_management.html.haml index 82db797efeed2d14f74db1502a2756bd277b64bf..e80643e87798106ba7db15ac397e6579da9f475e 100644 --- a/app/views/projects/issues/_design_management.html.haml +++ b/app/views/projects/issues/_design_management.html.haml @@ -1,4 +1,4 @@ -- return if @issue.work_item_type&.incident? +- return if @issue.work_item_type&.key == :incident - requirements_link_url = help_page_path('user/project/issues/design_management.md', anchor: 'prerequisites') - requirements_link_start = ''.html_safe % { url: requirements_link_url } diff --git a/db/migrate/20251112140442_remove_work_item_type_id_fk_from_issues.rb b/db/migrate/20251112140442_remove_work_item_type_id_fk_from_issues.rb new file mode 100644 index 0000000000000000000000000000000000000000..8629bf3ba4eec8d6fea0af44a3224eb8799cc956 --- /dev/null +++ b/db/migrate/20251112140442_remove_work_item_type_id_fk_from_issues.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class RemoveWorkItemTypeIdFkFromIssues < Gitlab::Database::Migration[2.3] + disable_ddl_transaction! + milestone '18.6' + + def up + remove_foreign_key :issues, :work_item_types, column: :work_item_type_id, if_exists: true + end + + def down + add_concurrent_foreign_key :issues, :work_item_types, column: :work_item_type_id, on_delete: :cascade + end +end diff --git a/db/schema_migrations/20251112140442 b/db/schema_migrations/20251112140442 new file mode 100644 index 0000000000000000000000000000000000000000..b4e6278730dac4c86b7385edd5bf2d91f55c47cc --- /dev/null +++ b/db/schema_migrations/20251112140442 @@ -0,0 +1 @@ +30af08b828dde208d224101c54401ac945f0af721556f40084793bf3aa1a41cb \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 52b05150425771bd70fa44718ca9344412bf3c0f..6eda8348ad05540fbf28ffeae6a1f87da88acd3f 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -49858,9 +49858,6 @@ ALTER TABLE ONLY projects_branch_rules_merge_request_approval_settings ALTER TABLE ONLY issue_tracker_data ADD CONSTRAINT fk_b33e816ada FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE NOT VALID; -ALTER TABLE ONLY issues - ADD CONSTRAINT fk_b37be69be6 FOREIGN KEY (work_item_type_id) REFERENCES work_item_types(id); - ALTER TABLE ONLY packages_conan_package_revisions ADD CONSTRAINT fk_b482b1a2f8 FOREIGN KEY (package_reference_id) REFERENCES packages_conan_package_references(id) ON DELETE CASCADE; diff --git a/ee/app/controllers/ee/projects/issues_controller.rb b/ee/app/controllers/ee/projects/issues_controller.rb index 3dda5c69f43ddf9fcc2e5a306382b3cdbcdb9e2e..b57b141aac8dfc7f82f23d446c5b5fdc0fc2fec8 100644 --- a/ee/app/controllers/ee/projects/issues_controller.rb +++ b/ee/app/controllers/ee/projects/issues_controller.rb @@ -182,7 +182,8 @@ def populate_vulnerability_id end def redirect_if_test_case - return unless issue.work_item_type&.test_case? + # return unless issue.work_item_type&.test_case? + return unless issue.work_item_type&.key == :test_case redirect_to project_quality_test_case_path(project, issue) end diff --git a/ee/app/finders/work_items/widgets/rolledup_dates_finder.rb b/ee/app/finders/work_items/widgets/rolledup_dates_finder.rb index 9a1e14aea69026be9bb6c988392b70e3393ef919..3c56ad81e2c21e6202e44e89dae1a03d01a11200 100644 --- a/ee/app/finders/work_items/widgets/rolledup_dates_finder.rb +++ b/ee/app/finders/work_items/widgets/rolledup_dates_finder.rb @@ -95,21 +95,22 @@ def query_work_items(field) def children_work_items @children_work_items ||= Gitlab::SQL::CTE.new( :children_work_items, + # TODO: for type cleanup # TODO: allow any types here, but for that we need to allow inherited dates on any types as well, event on # leaf node work items, e.g. Task. In which case a leaf node work item(as Task) can inherit dates from # the milestone it is assigned and perhaps also add iteration along side milestone from which leaf work item # can inherit from. So a given work item would inherit from its direct children, milestone and iteration under # min(start date), max(due date) formula. WorkItem.with_issue_type(%w[issue epic]) - .joins(:parent_link, :work_item_type) + .joins(:parent_link) .where(WorkItems::ParentLink.arel_table[:work_item_parent_id].in(@work_items_ids)) .select( - WorkItem.arel_table[:id].as("id"), - WorkItems::ParentLink.arel_table[:work_item_parent_id].as("parent_id"), - WorkItem.arel_table[:milestone_id].as("milestone_id"), - WorkItem.arel_table[:start_date].as("start_date"), - WorkItem.arel_table[:due_date].as("due_date"), - WorkItems::Type.arel_table[:name].as("work_item_type_name")) + WorkItem.arel_table[:id].as('id'), + WorkItems::ParentLink.arel_table[:work_item_parent_id].as('parent_id'), + WorkItem.arel_table[:milestone_id].as('milestone_id'), + WorkItem.arel_table[:start_date].as('start_date'), + WorkItem.arel_table[:due_date].as('due_date'), + WorkItem.arel_table[:work_item_type_id].as('work_item_type_id')) ) end end diff --git a/ee/app/graphql/ee/mutations/work_items/create.rb b/ee/app/graphql/ee/mutations/work_items/create.rb index ce10ae8e7a37fdaab074da62d392965bceac51e5..57d7770e914752d21ce8a016fce902aa547652cd 100644 --- a/ee/app/graphql/ee/mutations/work_items/create.rb +++ b/ee/app/graphql/ee/mutations/work_items/create.rb @@ -64,7 +64,7 @@ def resolve(project_path: nil, namespace_path: nil, vulnerability: nil, **attrib override :check_feature_available! def check_feature_available!(container, type, params) - return super unless container.is_a?(Project) && type.epic? + return super unless container.is_a?(Project) && type.key == :epic raise_resource_not_available_error! unless current_user.can?(:create_epic, container) end diff --git a/ee/app/graphql/ee/resolvers/namespaces/work_item_resolver.rb b/ee/app/graphql/ee/resolvers/namespaces/work_item_resolver.rb index a63390afb85335f13d78f7005010e7d15b773b40..9d75fcc326da15cdcefad4ab159daec4a173b2c8 100644 --- a/ee/app/graphql/ee/resolvers/namespaces/work_item_resolver.rb +++ b/ee/app/graphql/ee/resolvers/namespaces/work_item_resolver.rb @@ -23,10 +23,11 @@ def recent_services_map override :log_recent_view def log_recent_view(work_item) - base_type = work_item.work_item_type.base_type + # base_type = work_item.work_item_type.base_type + base_type = work_item.work_item_type.key # Only handle Epic WorkItems in EE, delegate everything else to parent - if base_type == 'epic' && work_item.synced_epic + if base_type == :epic && work_item.synced_epic service_class = self.class.recent_services_map[base_type] return unless service_class diff --git a/ee/app/helpers/ee/issues_helper.rb b/ee/app/helpers/ee/issues_helper.rb index e00c9b1e7d296144e91aa7d70c4fbb15b0c2a4e5..8fec3f632e0c5746d65dbfae483eda33b95518b0 100644 --- a/ee/app/helpers/ee/issues_helper.rb +++ b/ee/app/helpers/ee/issues_helper.rb @@ -17,7 +17,7 @@ def issue_in_subepic?(issue, epic_id) override :show_timeline_view_toggle? def show_timeline_view_toggle?(issue) - issue.work_item_type&.incident? && issue.licensed_feature_available?(:incident_timeline_view) + issue.work_item_type&.key == :incident && issue.licensed_feature_available?(:incident_timeline_view) end override :scoped_labels_available? diff --git a/ee/app/models/concerns/issue_widgets/acts_like_requirement.rb b/ee/app/models/concerns/issue_widgets/acts_like_requirement.rb index 5c833dd101c47bdab3d54a29abf67b46bcf0860f..aa1c667105bae226326b661cb882b0d907303337 100644 --- a/ee/app/models/concerns/issue_widgets/acts_like_requirement.rb +++ b/ee/app/models/concerns/issue_widgets/acts_like_requirement.rb @@ -27,7 +27,7 @@ def requirement_sync_error! end def invalidate_if_sync_error - return unless work_item_type&.requirement? # No need to invalidate if work_item_type != requirement + return unless work_item_type&.key == :requirement # No need to invalidate if work_item_type != requirement return unless requirement_sync_error return unless requirement diff --git a/ee/app/models/dora/watchers/issue_watcher.rb b/ee/app/models/dora/watchers/issue_watcher.rb index deafd3d2a8f9dd3d4cf736a24360b41049e8c8b2..3e807eefdb8b2eb8449e6476cc90b6670ebcce2c 100644 --- a/ee/app/models/dora/watchers/issue_watcher.rb +++ b/ee/app/models/dora/watchers/issue_watcher.rb @@ -27,7 +27,7 @@ def initialize(issue, event) end def process - return unless issue.work_item_type&.incident? && production_env_id + return unless issue.work_item_type&.key == :incident && production_env_id schedule_metrics_refresh_job end diff --git a/ee/app/models/ee/issue.rb b/ee/app/models/ee/issue.rb index c2398ed42a3fe5b94589f6cf472dca2694c49d26..84b07c6a67526a12c6a09b886c17f521f06b22b2 100644 --- a/ee/app/models/ee/issue.rb +++ b/ee/app/models/ee/issue.rb @@ -243,7 +243,8 @@ def elasticsearch_issue_notes_need_updating? override :supports_weight? def supports_weight? - return false if work_item_type&.incident? || work_item_type&.epic? + # return false if work_item_type&.incident? || work_item_type&.epic? + return false if work_item_type&.key == :incident || work_item_type&.key == :epic true end diff --git a/ee/app/models/ee/work_item.rb b/ee/app/models/ee/work_item.rb index 81fafc646e5432d75e73e77ec9092959ae44eee4..7dbe3c5459c244cafefcc2dc3ccc5031af929412 100644 --- a/ee/app/models/ee/work_item.rb +++ b/ee/app/models/ee/work_item.rb @@ -39,7 +39,6 @@ module WorkItem :dates_source, :author, :sync_object, - :work_item_type, :assignees, :namespace, :milestone, @@ -88,7 +87,7 @@ def work_item_children_keyset_order(work_item) return super unless work_item.epic_work_item? && !work_item.namespace.licensed_feature_available?(:subepics) non_epic_children = work_item.work_item_children.where.not( - work_item_type_id: ::WorkItems::Type.default_by_type(:epic).id + work_item_type_id: ::WorkItems::SystemDefined::Type[:epic].id ) keyset_order = ::WorkItem.work_item_children_keyset_order_config @@ -156,15 +155,15 @@ def supported_quick_action_commands override :supports_parent? def supports_parent? - return false if work_item_type.issue? && !licensed_feature_available?(:epics) - return false if work_item_type.epic? && !licensed_feature_available?(:subepics) + return false if work_item_type.key == :issue && !licensed_feature_available?(:epics) + return false if work_item_type.key == :epic && !licensed_feature_available?(:subepics) hierarchy_supports_parent? end override :custom_notification_target_name def custom_notification_target_name - return 'epic' if work_item_type.epic? + return 'epic' if work_item_type.key == :epic super end diff --git a/ee/app/models/ee/work_items/parent_link.rb b/ee/app/models/ee/work_items/parent_link.rb index 07c201cc83c1e46ef6ce2580b2b4a0cc7e0b56e3..ff507e82c622ac20b73bf8a1ec1e9962667239e2 100644 --- a/ee/app/models/ee/work_items/parent_link.rb +++ b/ee/app/models/ee/work_items/parent_link.rb @@ -19,7 +19,7 @@ module ParentLink private def validate_legacy_hierarchy - return unless work_item_parent&.work_item_type&.base_type == 'epic' && work_item&.has_epic? + return unless work_item_parent&.work_item_type&.key == :epic && work_item&.has_epic? return if work_item.epic_issue.epic.issue_id == work_item_parent.id errors.add :work_item, _('already assigned to an epic') diff --git a/ee/app/models/work_items/statuses/system_defined/lifecycle.rb b/ee/app/models/work_items/statuses/system_defined/lifecycle.rb index 43cb60d4c7ef33ac4b51f255b0538b728e18aaf3..0e4881929148ca009150e80625249ae6ccb1c597 100644 --- a/ee/app/models/work_items/statuses/system_defined/lifecycle.rb +++ b/ee/app/models/work_items/statuses/system_defined/lifecycle.rb @@ -38,7 +38,9 @@ def for_base_type?(base_type) end def work_item_types - WorkItems::Type.where(base_type: work_item_base_types) + WorkItems::SystemDefined::Type.all.select do |type| + work_item_base_types.include?(type.key) + end end def statuses diff --git a/ee/app/models/work_items/statuses/system_defined/status.rb b/ee/app/models/work_items/statuses/system_defined/status.rb index 2687b2fa52f20780d23903ed9b3255be63cfee5c..bbc2b56e243f85625397b9651595c697f21b1c2c 100644 --- a/ee/app/models/work_items/statuses/system_defined/status.rb +++ b/ee/app/models/work_items/statuses/system_defined/status.rb @@ -53,7 +53,7 @@ class Status class << self def find_by_work_item_and_name(work_item, status_name) - base_type = work_item.work_item_type.base_type.to_sym + base_type = work_item.work_item_type.key # Status is only valid if it belongs to the lifecycle of the work item type. Lifecycle.of_work_item_base_type(base_type)&.find_available_status_by_name(status_name) end @@ -77,7 +77,7 @@ def sort_order_by_id def allowed_for_work_item?(work_item) return false unless work_item.present? - lifecycle = Lifecycle.of_work_item_base_type(work_item.work_item_type.base_type.to_sym) + lifecycle = Lifecycle.of_work_item_base_type(work_item.work_item_type.key) return false unless lifecycle.present? # Doesn't rely on widget availability but on the list of supported types. diff --git a/ee/app/policies/ee/work_item_policy.rb b/ee/app/policies/ee/work_item_policy.rb index 08a4f6605c9797db20c399123dd260962382793c..0af999b5a63f07acd1d95ff3353b4939816c7330 100644 --- a/ee/app/policies/ee/work_item_policy.rb +++ b/ee/app/policies/ee/work_item_policy.rb @@ -5,7 +5,8 @@ module WorkItemPolicy extend ActiveSupport::Concern prepended do condition(:is_epic, scope: :subject) do - @subject.work_item_type&.epic? + # @subject.work_item_type&.epic? + @subject.work_item_type&.key == :epic end condition(:related_epics_available, scope: :subject) do @subject.namespace.licensed_feature_available?(:related_epics) diff --git a/ee/app/services/ee/issues/create_service.rb b/ee/app/services/ee/issues/create_service.rb index 027147d3eab94a7e1ac7f5f624cd914788e14b49..d3d5d51f6feeac824d00bcbfea2402351ee31f56 100644 --- a/ee/app/services/ee/issues/create_service.rb +++ b/ee/app/services/ee/issues/create_service.rb @@ -25,7 +25,7 @@ def filter_params(issue) override :transaction_create def transaction_create(issue) - return super unless issue.work_item_type.requirement? + return super unless issue.work_item_type.key == :requirement requirement = issue.build_requirement(project: issue.project) requirement.requirement_issue = issue diff --git a/ee/app/services/ee/issues/update_service.rb b/ee/app/services/ee/issues/update_service.rb index ffcb2ee55b280c9596456645704202642e83396e..f392c7c642dd87c66322b83c5404c26400358f40 100644 --- a/ee/app/services/ee/issues/update_service.rb +++ b/ee/app/services/ee/issues/update_service.rb @@ -100,7 +100,7 @@ def handle_promotion(issue) end def should_update_requirement_verification_status?(issuable) - issuable.work_item_type.requirement? && + issuable.work_item_type.key == :requirement && params[:last_test_report_state].present? && can?(current_user, :create_requirement_test_report, issuable.project) end diff --git a/ee/app/services/ee/work_items/parent_links/base_service.rb b/ee/app/services/ee/work_items/parent_links/base_service.rb index d8eca49b25c877ac54863d7e313db4a91dd76200..92a67cf37f93eaa701eed6251bc1f34400016819 100644 --- a/ee/app/services/ee/work_items/parent_links/base_service.rb +++ b/ee/app/services/ee/work_items/parent_links/base_service.rb @@ -17,7 +17,7 @@ def initialize(issuable, user, params) def linkable?(work_item) return true if synced_work_item return true if work_item.importing? - return false if work_item.work_item_type.epic? && !work_item.namespace.licensed_feature_available?(:subepics) + return false if work_item.work_item_type.key == :epic && !work_item.namespace.licensed_feature_available?(:subepics) super end diff --git a/ee/app/services/ee/work_items/parent_links/create_service.rb b/ee/app/services/ee/work_items/parent_links/create_service.rb index 14fc4a1d5a8075a7916792d2040af1da2c9e064a..f68c53cc8e8d3fad21804a6ffab56bd16238f6a7 100644 --- a/ee/app/services/ee/work_items/parent_links/create_service.rb +++ b/ee/app/services/ee/work_items/parent_links/create_service.rb @@ -110,7 +110,7 @@ def sync_relative_position(parent_link) legacy_epic = parent_link.work_item.synced_epic legacy_epic.relative_position = parent_link.relative_position legacy_epic.save(touch: false) - elsif parent_link.work_item.work_item_type.issue? + elsif parent_link.work_item.work_item_type.key == :issue epic_issue = EpicIssue.find_by_issue_id(parent_link.work_item.id) epic_issue.update(relative_position: parent_link.relative_position) if epic_issue end diff --git a/ee/lib/clusters/agents/auto_flow.rb b/ee/lib/clusters/agents/auto_flow.rb index 5e3a8d24b6cc629372db2738ec506a117ea37511..b81d42e536dc8cd8b04035664ff48cbc26f4a4fa 100644 --- a/ee/lib/clusters/agents/auto_flow.rb +++ b/ee/lib/clusters/agents/auto_flow.rb @@ -9,7 +9,7 @@ def issue_events_enabled?(work_item_id) work_item = ::WorkItem.find_by_id(work_item_id) return false unless work_item return false unless work_item.project.present? - return false unless work_item.work_item_type.issue? + return false unless work_item.work_item_type.key == :issue actor = work_item.project diff --git a/ee/lib/ee/gitlab/quick_actions/work_item_actions.rb b/ee/lib/ee/gitlab/quick_actions/work_item_actions.rb index 630f125f90917db0bc0ae4ed72eb899800e69ef2..a84df8c2918c1b4fbf53b79622fa4cfb242bb6f7 100644 --- a/ee/lib/ee/gitlab/quick_actions/work_item_actions.rb +++ b/ee/lib/ee/gitlab/quick_actions/work_item_actions.rb @@ -17,7 +17,7 @@ module WorkItemActions types WorkItem condition do ::Feature.enabled?(:okr_checkin_reminders, - project) && quick_action_target.work_item_type.objective? && current_user.can?(:admin_issue, + project) && quick_action_target.work_item_type.key == :objective && current_user.can?(:admin_issue, project) end parse_params do |frequency| @@ -94,7 +94,7 @@ def validate_promote_to(type) override :type_change_allowed? def type_change_allowed? - true unless quick_action_target.work_item_type.epic? + true unless quick_action_target.work_item_type.key == :epic end def find_frequency(frequency) @@ -147,7 +147,7 @@ def handle_set_epic(parent_param) def show_epic_alias? # We also check that the quick action target is an instance of an issue here, since the work_item_type # relationship is only created after save for legacy issues - (quick_action_target.instance_of?(::Issue) || quick_action_target.work_item_type&.issue?) && + (quick_action_target.instance_of?(::Issue) || quick_action_target.work_item_type&.key == :issue) && quick_action_target.licensed_feature_available?(:epics) end end diff --git a/ee/lib/gitlab/work_items/legacy_epics/widget_params_extractor.rb b/ee/lib/gitlab/work_items/legacy_epics/widget_params_extractor.rb index 55a23443deff77f34ae1d6bf83e18225ce0a983e..3826575aa85bef87665e4b465a9e4b6b08239d66 100644 --- a/ee/lib/gitlab/work_items/legacy_epics/widget_params_extractor.rb +++ b/ee/lib/gitlab/work_items/legacy_epics/widget_params_extractor.rb @@ -24,7 +24,7 @@ def initialize(params) end def extract - work_item_type = ::WorkItems::Type.default_by_type(:epic) + work_item_type = ::WorkItems::SystemDefined::Type[:epic] MAPPED_WIDGET_PARAMS.each do |widget_name, widget_param_keys| params_for_widget = params.extract!(*widget_param_keys) diff --git a/gems/activerecord-gitlab/lib/active_record/fixed_items_model/model.rb b/gems/activerecord-gitlab/lib/active_record/fixed_items_model/model.rb index 642bde3f7934ee43e94c8e187512f4a380a97930..1a3c3b3f01402805b6f0d7ff9cfd53fa5101665e 100644 --- a/gems/activerecord-gitlab/lib/active_record/fixed_items_model/model.rb +++ b/gems/activerecord-gitlab/lib/active_record/fixed_items_model/model.rb @@ -37,6 +37,14 @@ module Model include ActiveModel::Attributes class_methods do + def auto_generate_ids! + @auto_generate_ids = true + end + + def auto_generate_ids? + @auto_generate_ids || false + end + def all load_items! if storage.empty? @@ -66,7 +74,9 @@ def storage def load_items! validate_items_definition! - self::ITEMS.each do |item_definition| + items_to_load = auto_generate_ids? ? items_with_generated_ids : self::ITEMS + + items_to_load.each do |item_definition| item = new(item_definition) raise "Static definition in ITEMS is invalid! #{item.errors.full_messages.join(', ')}" unless item.valid? @@ -74,7 +84,15 @@ def load_items! end end + def items_with_generated_ids + self::ITEMS.each_with_index.map do |item_definition, index| + item_definition.merge(id: index + 1) + end + end + def validate_items_definition! + return if auto_generate_ids? + unique_ids = self::ITEMS.map { |item| item[:id] }.uniq return if unique_ids.size == self::ITEMS.size diff --git a/lib/banzai/filter/references/issue_reference_filter.rb b/lib/banzai/filter/references/issue_reference_filter.rb index bb7b48cd79e8ce7e7c04960e26a4b4393ae0e5ae..28ff94beddfa6130a3695fd671ffd69940b1a224 100644 --- a/lib/banzai/filter/references/issue_reference_filter.rb +++ b/lib/banzai/filter/references/issue_reference_filter.rb @@ -27,7 +27,7 @@ def parent_records(parent, ids) return Issue.none if parent.is_a?(Group) parent.issues.iid_in(ids.to_a) - .includes(:project, :namespace, :work_item_type) + .includes(:project, :namespace) end def object_link_text_extras(issue, matches) @@ -50,7 +50,7 @@ def data_attributes_for(original, parent, object, **data) private def additional_object_attributes(issue) - { issue_type: issue.work_item_type.base_type } + { issue_type: issue.work_item_type.key } end def issue_path(issue) diff --git a/lib/banzai/filter/references/work_item_reference_filter.rb b/lib/banzai/filter/references/work_item_reference_filter.rb index dc80c18a858a9cb4ecac1ecf1abc929deb732de9..8e206fe284c0cc754266eb7486ea18d3bad32c28 100644 --- a/lib/banzai/filter/references/work_item_reference_filter.rb +++ b/lib/banzai/filter/references/work_item_reference_filter.rb @@ -13,7 +13,7 @@ class WorkItemReferenceFilter < IssueReferenceFilter def parent_records(parent, ids) parent.work_items.iid_in(ids.to_a) - .includes(:project, :namespace, :work_item_type) + .includes(:project, :namespace) end def parent_type @@ -27,7 +27,7 @@ def parent private def additional_object_attributes(work_item) - { work_item_type: work_item.work_item_type.base_type } + { work_item_type: work_item.work_item_type.key } end end end diff --git a/lib/gitlab/data_builder/issuable.rb b/lib/gitlab/data_builder/issuable.rb index b13d7f62e2a69063e264ff715a2d31e81ff53a4c..d906c8cecb63cea05c9f9f60341c98851cc569ab 100644 --- a/lib/gitlab/data_builder/issuable.rb +++ b/lib/gitlab/data_builder/issuable.rb @@ -45,7 +45,7 @@ def safe_keys def object_kind # To prevent a breaking change, ensure we use `issue` for work items of type issue. # https://gitlab.com/gitlab-org/gitlab/-/issues/517947 - if issuable.is_a?(WorkItem) && issuable.work_item_type.issue? + if issuable.is_a?(WorkItem) && issuable.work_item_type.key == :issue "issue" else issuable.class.name.underscore diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb index 183bd665a117b81a4c6984464df5f3171b2af1a3..8d6352152610929b5806b4a1c27755c616c0f253 100644 --- a/lib/gitlab/quick_actions/issue_actions.rb +++ b/lib/gitlab/quick_actions/issue_actions.rb @@ -273,6 +273,7 @@ module IssueActions ServiceDesk.enabled?(quick_action_target.resource_parent) && current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target) && quick_action_target.respond_to?(:from_service_desk?) && + # TODO: service desk issue type specific !quick_action_target.from_service_desk? end command :convert_to_ticket do |email = ""| @@ -290,11 +291,11 @@ module IssueActions execution_message { _('Issue has been promoted to incident') } types Issue condition do - !quick_action_target.work_item_type&.incident? && + !quick_action_target.work_item_type&.key == :incident && current_user.can?(:"set_#{quick_action_target.issue_type}_metadata", quick_action_target) end command :promote_to_incident do - @updates[:work_item_type] = ::WorkItems::Type.default_by_type(:incident) + @updates[:work_item_type] = ::WorkItems::SystemDefined::Type[:incident] end desc { _('Add customer relation contacts') } @@ -334,7 +335,7 @@ module IssueActions params ' | ' types Issue condition do - quick_action_target.work_item_type&.incident? && + quick_action_target.work_item_type&.key == :incident && current_user.can?(:admin_incident_management_timeline_event, quick_action_target) end parse_params do |event_params| diff --git a/lib/gitlab/quick_actions/work_item_actions.rb b/lib/gitlab/quick_actions/work_item_actions.rb index dc5f89df35cf8a6f470a9cce53dc1a45fb83f026..4d775a3f864bab7c7bb723c47687703c545f2901 100644 --- a/lib/gitlab/quick_actions/work_item_actions.rb +++ b/lib/gitlab/quick_actions/work_item_actions.rb @@ -24,7 +24,7 @@ module WorkItemActions end types WorkItem params do - promote_to_map[current_type.base_type].join(' | ') + promote_to_map[current_type.key].join(' | ') end condition { supports_promotion? } command :promote_to do |type_name| @@ -130,7 +130,7 @@ def current_type end def supports_promotion? - current_type.base_type.in?(promote_to_map.keys) + current_type.key.in?(promote_to_map.keys) end def promotion_allowed? @@ -142,7 +142,7 @@ def type_change_allowed? end def supports_promote_to?(type_name) - promote_to_map[current_type.base_type].include?(type_name) + promote_to_map[current_type.key].include?(type_name) end def promote_to_map