payping-gitlab-triage 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +19 -0
- data/.gitignore +15 -0
- data/.gitlab/CODEOWNERS +2 -0
- data/.gitlab/changelog_config.yml +13 -0
- data/.gitlab/issue_templates/Default.md +13 -0
- data/.gitlab/merge_request_templates/Default.md +11 -0
- data/.gitlab/merge_request_templates/Release.md +13 -0
- data/.gitlab-ci.yml +146 -0
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +145 -0
- data/.ruby-version +1 -0
- data/.tool-versions +1 -0
- data/.yardopts +4 -0
- data/CONTRIBUTING.md +31 -0
- data/Dangerfile +5 -0
- data/Gemfile +15 -0
- data/Guardfile +70 -0
- data/LICENSE.md +25 -0
- data/README.md +1480 -0
- data/Rakefile +6 -0
- data/bin/gitlab-triage +19 -0
- data/gitlab-triage.gemspec +41 -0
- data/lib/gitlab/triage/action/base.rb +14 -0
- data/lib/gitlab/triage/action/comment.rb +104 -0
- data/lib/gitlab/triage/action/comment_on_summary.rb +83 -0
- data/lib/gitlab/triage/action/delete.rb +56 -0
- data/lib/gitlab/triage/action/issue.rb +64 -0
- data/lib/gitlab/triage/action/summarize.rb +82 -0
- data/lib/gitlab/triage/action.rb +36 -0
- data/lib/gitlab/triage/api_query_builders/base_query_param_builder.rb +27 -0
- data/lib/gitlab/triage/api_query_builders/date_query_param_builder.rb +42 -0
- data/lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb +28 -0
- data/lib/gitlab/triage/api_query_builders/single_query_param_builder.rb +13 -0
- data/lib/gitlab/triage/command_builders/base_command_builder.rb +40 -0
- data/lib/gitlab/triage/command_builders/cc_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/comment_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/label_command_builder.rb +40 -0
- data/lib/gitlab/triage/command_builders/move_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/remove_label_command_builder.rb +15 -0
- data/lib/gitlab/triage/command_builders/status_command_builder.rb +23 -0
- data/lib/gitlab/triage/command_builders/text_content_builder.rb +138 -0
- data/lib/gitlab/triage/engine.rb +635 -0
- data/lib/gitlab/triage/entity_builders/issue_builder.rb +54 -0
- data/lib/gitlab/triage/entity_builders/summary_builder.rb +82 -0
- data/lib/gitlab/triage/errors/network.rb +11 -0
- data/lib/gitlab/triage/errors.rb +1 -0
- data/lib/gitlab/triage/expand_condition/expansion.rb +203 -0
- data/lib/gitlab/triage/expand_condition/list.rb +25 -0
- data/lib/gitlab/triage/expand_condition/sequence.rb +25 -0
- data/lib/gitlab/triage/expand_condition.rb +23 -0
- data/lib/gitlab/triage/filters/assignee_member_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/author_member_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/base_conditions_filter.rb +58 -0
- data/lib/gitlab/triage/filters/branch_date_filter.rb +73 -0
- data/lib/gitlab/triage/filters/branch_protected_filter.rb +26 -0
- data/lib/gitlab/triage/filters/discussions_conditions_filter.rb +58 -0
- data/lib/gitlab/triage/filters/issue_date_conditions_filter.rb +78 -0
- data/lib/gitlab/triage/filters/member_conditions_filter.rb +84 -0
- data/lib/gitlab/triage/filters/merge_request_date_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/name_conditions_filter.rb +26 -0
- data/lib/gitlab/triage/filters/no_additional_labels_conditions_filter.rb +30 -0
- data/lib/gitlab/triage/filters/ruby_conditions_filter.rb +33 -0
- data/lib/gitlab/triage/filters/votes_conditions_filter.rb +54 -0
- data/lib/gitlab/triage/graphql_network.rb +81 -0
- data/lib/gitlab/triage/graphql_queries/query_builder.rb +158 -0
- data/lib/gitlab/triage/graphql_queries/query_param_builders/base_param_builder.rb +30 -0
- data/lib/gitlab/triage/graphql_queries/query_param_builders/date_param_builder.rb +35 -0
- data/lib/gitlab/triage/graphql_queries/query_param_builders/labels_param_builder.rb +18 -0
- data/lib/gitlab/triage/limiters/base_limiter.rb +35 -0
- data/lib/gitlab/triage/limiters/date_field_limiter.rb +45 -0
- data/lib/gitlab/triage/network.rb +39 -0
- data/lib/gitlab/triage/network_adapters/base_adapter.rb +17 -0
- data/lib/gitlab/triage/network_adapters/graphql_adapter.rb +92 -0
- data/lib/gitlab/triage/network_adapters/httparty_adapter.rb +116 -0
- data/lib/gitlab/triage/network_adapters/test_adapter.rb +39 -0
- data/lib/gitlab/triage/option_parser.rb +105 -0
- data/lib/gitlab/triage/options.rb +30 -0
- data/lib/gitlab/triage/param_builders/date_param_builder.rb +64 -0
- data/lib/gitlab/triage/policies/base_policy.rb +80 -0
- data/lib/gitlab/triage/policies/rule_policy.rb +36 -0
- data/lib/gitlab/triage/policies/summary_policy.rb +29 -0
- data/lib/gitlab/triage/policies_resources/rule_resources.rb +11 -0
- data/lib/gitlab/triage/policies_resources/summary_resources.rb +11 -0
- data/lib/gitlab/triage/resource/base.rb +102 -0
- data/lib/gitlab/triage/resource/branch.rb +13 -0
- data/lib/gitlab/triage/resource/context.rb +47 -0
- data/lib/gitlab/triage/resource/epic.rb +20 -0
- data/lib/gitlab/triage/resource/instance_version.rb +35 -0
- data/lib/gitlab/triage/resource/issue.rb +52 -0
- data/lib/gitlab/triage/resource/label.rb +56 -0
- data/lib/gitlab/triage/resource/label_event.rb +48 -0
- data/lib/gitlab/triage/resource/linked_issue.rb +15 -0
- data/lib/gitlab/triage/resource/merge_request.rb +23 -0
- data/lib/gitlab/triage/resource/milestone.rb +98 -0
- data/lib/gitlab/triage/resource/shared/issuable.rb +119 -0
- data/lib/gitlab/triage/rest_api_network.rb +125 -0
- data/lib/gitlab/triage/retryable.rb +33 -0
- data/lib/gitlab/triage/ui.rb +23 -0
- data/lib/gitlab/triage/url_builders/url_builder.rb +54 -0
- data/lib/gitlab/triage/utils.rb +13 -0
- data/lib/gitlab/triage/validators/limiter_validator.rb +21 -0
- data/lib/gitlab/triage/validators/params_validator.rb +43 -0
- data/lib/gitlab/triage/version.rb +7 -0
- data/lib/gitlab/triage.rb +6 -0
- data/support/.gitlab-ci.example.yml +22 -0
- data/support/.triage-policies.example.yml +51 -0
- metadata +280 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../url_builders/url_builder'
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Triage
|
7
|
+
module Resource
|
8
|
+
class Base
|
9
|
+
attr_reader :resource, :parent
|
10
|
+
|
11
|
+
CONFIDENTIAL_TEXT = '(confidential)'
|
12
|
+
|
13
|
+
def self.define_field(name, &block)
|
14
|
+
define_method(name) do
|
15
|
+
if redact_confidential_attributes?
|
16
|
+
CONFIDENTIAL_TEXT
|
17
|
+
else
|
18
|
+
instance_eval(&block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(
|
24
|
+
resource, parent: nil, network: nil, redact_confidentials: true)
|
25
|
+
@resource = resource
|
26
|
+
@parent = parent
|
27
|
+
@network = network
|
28
|
+
@redact_confidentials = redact_confidentials
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def redact_confidential_attributes?
|
34
|
+
parent&.redact_confidential_attributes? ||
|
35
|
+
(@redact_confidentials && resource[:confidential])
|
36
|
+
end
|
37
|
+
|
38
|
+
def network
|
39
|
+
parent&.network || @network
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def expand_resource!(params: {})
|
45
|
+
resource.merge!(
|
46
|
+
network.query_api_cached(resource_url(params: params)).first)
|
47
|
+
end
|
48
|
+
|
49
|
+
def source_resource
|
50
|
+
@source_resource ||= network.query_api_cached(source_url).first
|
51
|
+
end
|
52
|
+
|
53
|
+
def source_url
|
54
|
+
build_url(options: { resource_type: nil })
|
55
|
+
end
|
56
|
+
|
57
|
+
def url(params = {})
|
58
|
+
build_url(params: params)
|
59
|
+
end
|
60
|
+
|
61
|
+
def resource_id
|
62
|
+
resource[:iid]
|
63
|
+
end
|
64
|
+
|
65
|
+
def resource_url(params: {}, sub_resource_type: nil)
|
66
|
+
build_url(
|
67
|
+
params: params,
|
68
|
+
options: {
|
69
|
+
resource_id: resource_id,
|
70
|
+
sub_resource_type: sub_resource_type
|
71
|
+
}
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_url(params: {}, options: {})
|
76
|
+
UrlBuilders::UrlBuilder.new(
|
77
|
+
url_opts
|
78
|
+
.merge(options)
|
79
|
+
.merge(params: { per_page: 100 }.merge(params))
|
80
|
+
).build
|
81
|
+
end
|
82
|
+
|
83
|
+
def url_opts
|
84
|
+
{
|
85
|
+
network_options: network.options,
|
86
|
+
resource_type: self.class.name.demodulize.underscore.pluralize,
|
87
|
+
source: source,
|
88
|
+
source_id: resource[:"#{source.singularize}_id"]
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def source
|
93
|
+
if resource[:project_id]
|
94
|
+
'projects'
|
95
|
+
elsif resource[:group_id]
|
96
|
+
'groups'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require_relative 'epic'
|
5
|
+
require_relative 'issue'
|
6
|
+
require_relative 'linked_issue'
|
7
|
+
require_relative 'merge_request'
|
8
|
+
require_relative 'instance_version'
|
9
|
+
require_relative 'branch'
|
10
|
+
|
11
|
+
module Gitlab
|
12
|
+
module Triage
|
13
|
+
module Resource
|
14
|
+
module Context
|
15
|
+
EvaluationError = Class.new(RuntimeError)
|
16
|
+
|
17
|
+
def self.build(resource, **options)
|
18
|
+
const_name = (resource[:type] || 'Base')
|
19
|
+
.to_s.singularize.camelcase
|
20
|
+
|
21
|
+
Resource.const_get(const_name).new(resource, **options).extend(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def eval(ruby)
|
25
|
+
instance_eval <<~RUBY, __FILE__, __LINE__ + 1
|
26
|
+
begin
|
27
|
+
#{ruby}
|
28
|
+
rescue StandardError, ScriptError => e
|
29
|
+
raise EvaluationError.new(e.message)
|
30
|
+
end
|
31
|
+
RUBY
|
32
|
+
rescue EvaluationError => e
|
33
|
+
# This way we could obtain the original backtrace and error
|
34
|
+
# If we just let instance_eval raise an error, the backtrace
|
35
|
+
# won't contain the actual line where it's giving an error.
|
36
|
+
raise e.cause
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def instance_version
|
42
|
+
@instance_version ||= InstanceVersion.new(parent: self)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require_relative 'shared/issuable'
|
5
|
+
|
6
|
+
module Gitlab
|
7
|
+
module Triage
|
8
|
+
module Resource
|
9
|
+
class Epic < Base
|
10
|
+
include Shared::Issuable
|
11
|
+
|
12
|
+
def project_path
|
13
|
+
@project_path ||=
|
14
|
+
request_group(resource[:group_id])[:full_path]
|
15
|
+
end
|
16
|
+
alias_method :group_path, :project_path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Triage
|
5
|
+
module Resource
|
6
|
+
class InstanceVersion < Base
|
7
|
+
def initialize(**options)
|
8
|
+
super({}, **options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def version
|
12
|
+
response[:version]
|
13
|
+
end
|
14
|
+
|
15
|
+
def version_short
|
16
|
+
version[/^\d+\.\d+/]
|
17
|
+
end
|
18
|
+
|
19
|
+
def revision
|
20
|
+
response[:revision]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# See https://gitlab.com/api/v4/version
|
26
|
+
def response
|
27
|
+
@response ||=
|
28
|
+
network.query_api_cached(
|
29
|
+
"#{network.options.host_url}/api/#{network.options.api_version}/version")
|
30
|
+
.first
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require_relative 'shared/issuable'
|
5
|
+
|
6
|
+
module Gitlab
|
7
|
+
module Triage
|
8
|
+
module Resource
|
9
|
+
class Issue < Base
|
10
|
+
include Shared::Issuable
|
11
|
+
|
12
|
+
DATE_FIELDS = %i[
|
13
|
+
due_date
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
DATE_FIELDS.each do |field|
|
17
|
+
define_field(field) do
|
18
|
+
value = resource[field]
|
19
|
+
|
20
|
+
Date.parse(value) if value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def merge_requests_count
|
25
|
+
@merge_requests_count ||= resource.dig(:merge_requests_count)
|
26
|
+
end
|
27
|
+
|
28
|
+
def related_merge_requests
|
29
|
+
@related_merge_requests ||= network.query_api_cached(
|
30
|
+
resource_url(sub_resource_type: 'related_merge_requests'))
|
31
|
+
.map { |merge_request| MergeRequest.new(merge_request, parent: self) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def closed_by
|
35
|
+
@closed_by ||= network.query_api_cached(
|
36
|
+
resource_url(sub_resource_type: 'closed_by'))
|
37
|
+
.map { |merge_request| MergeRequest.new(merge_request, parent: self) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def linked_issues
|
41
|
+
@linked_issues ||= network.query_api_cached(
|
42
|
+
resource_url(sub_resource_type: 'links'))
|
43
|
+
.map { |issue| LinkedIssue.new(issue, parent: self) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def expired?(today = Date.today)
|
47
|
+
due_date && due_date < today
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require 'date'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
module Gitlab
|
8
|
+
module Triage
|
9
|
+
module Resource
|
10
|
+
class Label < Base
|
11
|
+
LabelDoesntExistError = Class.new(StandardError)
|
12
|
+
|
13
|
+
FIELDS = %i[
|
14
|
+
id
|
15
|
+
project_id
|
16
|
+
group_id
|
17
|
+
name
|
18
|
+
description
|
19
|
+
color
|
20
|
+
priority
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
TIME_FIELDS = %i[
|
24
|
+
added_at
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
FIELDS.each do |field|
|
28
|
+
define_field(field) do
|
29
|
+
resource[field]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
TIME_FIELDS.each do |field|
|
34
|
+
define_field(field) do
|
35
|
+
value = resource[field]
|
36
|
+
|
37
|
+
Time.parse(value) if value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def exist?
|
42
|
+
label = network.query_api_cached(resource_url).first
|
43
|
+
return false unless label
|
44
|
+
|
45
|
+
label[:name] == name
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def resource_id
|
51
|
+
name
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require_relative 'label'
|
5
|
+
require 'date'
|
6
|
+
require 'time'
|
7
|
+
|
8
|
+
module Gitlab
|
9
|
+
module Triage
|
10
|
+
module Resource
|
11
|
+
class LabelEvent < Base
|
12
|
+
FIELDS = %i[
|
13
|
+
id
|
14
|
+
user
|
15
|
+
resource_type
|
16
|
+
resource_id
|
17
|
+
action
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
TIME_FIELDS = %i[
|
21
|
+
created_at
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
FIELDS.each do |field|
|
25
|
+
define_field(field) do
|
26
|
+
resource[field]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
TIME_FIELDS.each do |field|
|
31
|
+
define_field(field) do
|
32
|
+
value = resource[field]
|
33
|
+
|
34
|
+
Time.parse(value) if value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def label
|
39
|
+
return unless resource[:label]
|
40
|
+
|
41
|
+
@label ||= Label.new(
|
42
|
+
resource[:label].reverse_merge(added_at: resource[:created_at]),
|
43
|
+
parent: self)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require_relative 'shared/issuable'
|
5
|
+
|
6
|
+
module Gitlab
|
7
|
+
module Triage
|
8
|
+
module Resource
|
9
|
+
class MergeRequest < Base
|
10
|
+
include Shared::Issuable
|
11
|
+
|
12
|
+
def first_contribution?
|
13
|
+
if resource.key?(:first_contribution)
|
14
|
+
resource[:first_contribution]
|
15
|
+
else
|
16
|
+
expanded = expand_resource!
|
17
|
+
expanded[:first_contribution]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require 'date'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Triage
|
7
|
+
module Resource
|
8
|
+
class Milestone < Base
|
9
|
+
FIELDS = %i[
|
10
|
+
id
|
11
|
+
iid
|
12
|
+
project_id
|
13
|
+
group_id
|
14
|
+
title
|
15
|
+
description
|
16
|
+
state
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
DATE_FIELDS = %i[
|
20
|
+
due_date
|
21
|
+
start_date
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
TIME_FIELDS = %i[
|
25
|
+
updated_at
|
26
|
+
created_at
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
FIELDS.each do |field|
|
30
|
+
define_field(field) do
|
31
|
+
resource[field]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
DATE_FIELDS.each do |field|
|
36
|
+
define_field(field) do
|
37
|
+
value = resource[field]
|
38
|
+
|
39
|
+
Date.parse(value) if value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
TIME_FIELDS.each do |field|
|
44
|
+
define_field(field) do
|
45
|
+
value = resource[field]
|
46
|
+
|
47
|
+
Time.parse(value) if value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def succ
|
52
|
+
index = current_index
|
53
|
+
|
54
|
+
all_active_with_start_date[index.succ] if index
|
55
|
+
end
|
56
|
+
|
57
|
+
def active?
|
58
|
+
state == 'active'
|
59
|
+
end
|
60
|
+
|
61
|
+
def closed?
|
62
|
+
state == 'closed'
|
63
|
+
end
|
64
|
+
|
65
|
+
def started?(today = Date.today)
|
66
|
+
start_date && start_date <= today
|
67
|
+
end
|
68
|
+
|
69
|
+
def expired?(today = Date.today)
|
70
|
+
due_date && due_date < today
|
71
|
+
end
|
72
|
+
|
73
|
+
def in_progress?(today = Date.today)
|
74
|
+
started?(today) && !expired?(today)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def current_index
|
80
|
+
all_active_with_start_date
|
81
|
+
.index { |milestone| milestone.id == id }
|
82
|
+
end
|
83
|
+
|
84
|
+
def all_active_with_start_date
|
85
|
+
@all_active_with_start_date ||=
|
86
|
+
all_active.select(&:start_date).sort_by(&:start_date)
|
87
|
+
end
|
88
|
+
|
89
|
+
def all_active
|
90
|
+
@all_active ||=
|
91
|
+
network
|
92
|
+
.query_api_cached(url(state: 'active'))
|
93
|
+
.map { |milestone| self.class.new(milestone, parent: self) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../label'
|
4
|
+
require_relative '../label_event'
|
5
|
+
require_relative '../milestone'
|
6
|
+
|
7
|
+
module Gitlab
|
8
|
+
module Triage
|
9
|
+
module Resource
|
10
|
+
module Shared
|
11
|
+
module Issuable
|
12
|
+
SourceTooDeep = Class.new(RuntimeError)
|
13
|
+
MAX_PARENT_LOOKUP = 10
|
14
|
+
|
15
|
+
def milestone
|
16
|
+
@milestone ||=
|
17
|
+
resource[:milestone] &&
|
18
|
+
Milestone.new(resource[:milestone], parent: self)
|
19
|
+
end
|
20
|
+
|
21
|
+
# This will be more useful when we have:
|
22
|
+
# https://gitlab.com/gitlab-org/gitlab-ce/issues/51011
|
23
|
+
def labels
|
24
|
+
@labels ||= resource[:labels] # an array of label names
|
25
|
+
.map { |label| Label.new({ name: label }, parent: self) }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Make this an alias of `labels` when we have:
|
29
|
+
# https://gitlab.com/gitlab-org/gitlab-ce/issues/51011
|
30
|
+
def labels_with_details
|
31
|
+
# Labels can be deleted thus event.label can be nil
|
32
|
+
@labels_with_details ||= label_events
|
33
|
+
.select { |event| event.action == 'add' && event.label }
|
34
|
+
.map(&:label)
|
35
|
+
.sort_by(&:added_at)
|
36
|
+
.reverse
|
37
|
+
.uniq(&:name)
|
38
|
+
.select { |label| resource[:labels].include?(label.name) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def label_events
|
42
|
+
@label_events ||= query_label_events
|
43
|
+
.map { |label_event| LabelEvent.new(label_event, parent: self) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def labels_chronologically
|
47
|
+
@labels_chronologically ||= labels_with_details.sort_by(&:added_at)
|
48
|
+
end
|
49
|
+
|
50
|
+
def state
|
51
|
+
@state ||= resource.dig(:state)
|
52
|
+
end
|
53
|
+
|
54
|
+
def author
|
55
|
+
@author ||= resource.dig(:author, :username)
|
56
|
+
end
|
57
|
+
|
58
|
+
def project_path
|
59
|
+
@project_path ||=
|
60
|
+
request_project(resource[:project_id])[:path_with_namespace]
|
61
|
+
end
|
62
|
+
|
63
|
+
def full_resource_reference
|
64
|
+
@full_resource_reference ||= resource.dig(:references, :full)
|
65
|
+
end
|
66
|
+
|
67
|
+
def root_id(
|
68
|
+
resource: source_resource,
|
69
|
+
max_levels: MAX_PARENT_LOOKUP)
|
70
|
+
raise SourceTooDeep if max_levels <= 0
|
71
|
+
|
72
|
+
# In projects, the reference to the namespace's parent ID is `namespace.parent_id`
|
73
|
+
# but in groups, the reference is directly in `parent_id`
|
74
|
+
parent_id = resource.dig(:namespace, :parent_id) || resource.dig(:parent_id)
|
75
|
+
|
76
|
+
if parent_id
|
77
|
+
root_id(
|
78
|
+
resource: request_group(parent_id),
|
79
|
+
max_levels: max_levels - 1)
|
80
|
+
else
|
81
|
+
resource.dig(:namespace, :id) || resource[:id]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def query_label_events
|
88
|
+
network.query_api_cached(
|
89
|
+
resource_url(sub_resource_type: 'resource_label_events'))
|
90
|
+
end
|
91
|
+
|
92
|
+
def request_project(project_id)
|
93
|
+
network.query_api_cached(project_url(project_id)).first
|
94
|
+
end
|
95
|
+
|
96
|
+
def request_group(group_id)
|
97
|
+
network.query_api_cached(group_url(group_id)).first
|
98
|
+
end
|
99
|
+
|
100
|
+
def group_url(group_id)
|
101
|
+
Gitlab::Triage::UrlBuilders::UrlBuilder.new(
|
102
|
+
network_options: network.options,
|
103
|
+
source: 'groups',
|
104
|
+
source_id: group_id
|
105
|
+
).build
|
106
|
+
end
|
107
|
+
|
108
|
+
def project_url(project_id)
|
109
|
+
Gitlab::Triage::UrlBuilders::UrlBuilder.new(
|
110
|
+
network_options: network.options,
|
111
|
+
source: 'projects',
|
112
|
+
source_id: project_id
|
113
|
+
).build
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|