1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
# frozen_string_literal: true
# Note: initial thinking behind `icon_name` is for it to do triple duty:
# 1. one of our svg icon names, such as `external-link` or a new one `bug`
# 2. if it's an absolute url, then url to a user uploaded icon/image
# 3. an emoji, with the format of `:smile:`
module WorkItems
class Type < ApplicationRecord
self.table_name = 'work_item_types'
include CacheMarkdownField
include ReactiveCaching
self.reactive_cache_work_type = :no_dependency
self.reactive_cache_refresh_interval = 10.minutes
self.reactive_cache_lifetime = 1.hour
# type name is used in restrictions DB seeder to assure restrictions for
# default types are pre-filled
TYPE_NAMES = {
issue: 'Issue',
incident: 'Incident',
test_case: 'Test Case',
requirement: 'Requirement',
task: 'Task',
objective: 'Objective',
key_result: 'Key Result',
epic: 'Epic',
ticket: 'Ticket'
}.freeze
# Base types need to exist on the DB on app startup
# This constant is used by the DB seeder
# TODO - where to add new icon names created?
BASE_TYPES = {
issue: { name: TYPE_NAMES[:issue], icon_name: 'issue-type-issue', enum_value: 0 },
incident: { name: TYPE_NAMES[:incident], icon_name: 'issue-type-incident', enum_value: 1 },
test_case: { name: TYPE_NAMES[:test_case], icon_name: 'issue-type-test-case', enum_value: 2 }, ## EE-only
requirement: { name: TYPE_NAMES[:requirement], icon_name: 'issue-type-requirements', enum_value: 3 }, ## EE-only
task: { name: TYPE_NAMES[:task], icon_name: 'issue-type-task', enum_value: 4 },
objective: { name: TYPE_NAMES[:objective], icon_name: 'issue-type-objective', enum_value: 5 }, ## EE-only
key_result: { name: TYPE_NAMES[:key_result], icon_name: 'issue-type-keyresult', enum_value: 6 }, ## EE-only
epic: { name: TYPE_NAMES[:epic], icon_name: 'issue-type-epic', enum_value: 7 }, ## EE-only
ticket: { name: TYPE_NAMES[:ticket], icon_name: 'issue-type-issue', enum_value: 8 }
}.freeze
# A list of types user can change between - both original and new
# type must be included in this list. This is needed for legacy issues
# where it's possible to switch between issue and incident.
CHANGEABLE_BASE_TYPES = %w[issue incident test_case].freeze
cache_markdown_field :description, pipeline: :single_line
enum base_type: BASE_TYPES.transform_values { |value| value[:enum_value] }
belongs_to :namespace, optional: true
has_many :work_items, class_name: 'Issue', foreign_key: :work_item_type_id, inverse_of: :work_item_type
has_many :widget_definitions, foreign_key: :work_item_type_id, inverse_of: :work_item_type
has_many :enabled_widget_definitions, -> { where(disabled: false) }, foreign_key: :work_item_type_id,
inverse_of: :work_item_type, class_name: 'WorkItems::WidgetDefinition'
has_many :child_restrictions, class_name: 'WorkItems::HierarchyRestriction', foreign_key: :parent_type_id,
inverse_of: :parent_type
has_many :allowed_child_types_by_name, -> { order_by_name_asc },
through: :child_restrictions, class_name: 'WorkItems::Type',
foreign_key: :child_type_id, source: :child_type
before_validation :strip_whitespace
after_save :clear_reactive_cache!
# TODO: review validation rules
# https://gitlab.com/gitlab-org/gitlab/-/issues/336919
validates :name, presence: true
validates :name, uniqueness: { case_sensitive: false, scope: [:namespace_id] }
validates :name, length: { maximum: 255 }
validates :icon_name, length: { maximum: 255 }
scope :default, -> { where(namespace: nil) }
scope :order_by_name_asc, -> { order(arel_table[:name].lower.asc) }
scope :by_type, ->(base_type) { where(base_type: base_type) }
def self.default_by_type(type)
found_type = find_by(namespace_id: nil, base_type: type)
return found_type if found_type
Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.upsert_types
Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions
Gitlab::DatabaseImporters::WorkItems::RelatedLinksRestrictionsImporter.upsert_restrictions
find_by(namespace_id: nil, base_type: type)
end
def self.default_issue_type
default_by_type(:issue)
end
def self.allowed_types_for_issues
base_types.keys.excluding('objective', 'key_result', 'epic', 'ticket')
end
def default?
namespace.blank?
end
def widgets
enabled_widget_definitions.filter_map(&:widget_class)
end
def supports_assignee?
widgets.include? ::WorkItems::Widgets::Assignees
end
def default_issue?
name == WorkItems::Type::TYPE_NAMES[:issue]
end
def calculate_reactive_cache
allowed_child_types_by_name
end
def allowed_child_types(cache: false)
cached_data = cache ? with_reactive_cache { |query_data| query_data } : nil
cached_data || allowed_child_types_by_name
end
private
def strip_whitespace
name&.strip!
end
end
end
|