payping-gitlab-triage 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|