gitlab_quality-test_tooling 2.9.0 → 2.11.0
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 +4 -4
- data/.rspec +2 -1
- data/Gemfile.lock +9 -1
- data/README.md +44 -3
- data/exe/failed-test-issues +53 -8
- data/exe/feature-readiness-checklist +61 -0
- data/exe/feature-readiness-evaluation +62 -0
- data/lib/gitlab_quality/test_tooling/feature_readiness/analyzed_items/analyzed_epic.rb +94 -0
- data/lib/gitlab_quality/test_tooling/feature_readiness/analyzed_items/analyzed_issue.rb +92 -0
- data/lib/gitlab_quality/test_tooling/feature_readiness/analyzed_items/analyzed_merge_request.rb +139 -0
- data/lib/gitlab_quality/test_tooling/feature_readiness/concerns/issue_concern.rb +34 -0
- data/lib/gitlab_quality/test_tooling/feature_readiness/concerns/work_item_concern.rb +128 -0
- data/lib/gitlab_quality/test_tooling/feature_readiness/evaluation.rb +82 -0
- data/lib/gitlab_quality/test_tooling/feature_readiness/operational_readiness_check.rb +82 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/gitlab_graphql_client.rb +54 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/issues_client.rb +38 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/issues_dry_client.rb +3 -3
- data/lib/gitlab_quality/test_tooling/gitlab_client/labels_client.rb +13 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/merge_requests_client.rb +21 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/merge_requests_dry_client.rb +0 -10
- data/lib/gitlab_quality/test_tooling/gitlab_client/work_items_client.rb +277 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/work_items_dry_client.rb +25 -0
- data/lib/gitlab_quality/test_tooling/labels_inference.rb +4 -0
- data/lib/gitlab_quality/test_tooling/report/failed_test_issue.rb +6 -6
- data/lib/gitlab_quality/test_tooling/report/feature_readiness/report_on_epic.rb +174 -0
- data/lib/gitlab_quality/test_tooling/report/health_problem_reporter.rb +120 -20
- data/lib/gitlab_quality/test_tooling/report/knapsack_report_issue.rb +1 -1
- data/lib/gitlab_quality/test_tooling/report/prepare_stage_reports.rb +1 -1
- data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +1 -1
- data/lib/gitlab_quality/test_tooling/runtime/env.rb +11 -6
- data/lib/gitlab_quality/test_tooling/test_metrics_exporter/support/gcs_tools.rb +18 -5
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +32 -2
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module FeatureReadiness
|
6
|
+
module Concerns
|
7
|
+
module IssueConcern
|
8
|
+
OPERATIONAL_READINESS_CHECKLIST_LABEL = 'operational-readiness-checklist'
|
9
|
+
|
10
|
+
def create_operation_readiness_issue(work_item_title, assignee_id, issue_client, repository_files_client)
|
11
|
+
operational_readiness_issue = issue_client.create_issue(
|
12
|
+
title: "Operational Readiness Checklist for: '#{work_item_title}'",
|
13
|
+
description: repository_files_client.file_contents,
|
14
|
+
labels: [OPERATIONAL_READINESS_CHECKLIST_LABEL],
|
15
|
+
assignee_id: assignee_id
|
16
|
+
)
|
17
|
+
|
18
|
+
puts "\nCreated operational readiness issue: #{operational_readiness_issue.web_url}\n"
|
19
|
+
|
20
|
+
operational_readiness_issue
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_operational_readiness_issue_linked?(linked_issue_iids, issue_client)
|
24
|
+
linked_issues(linked_issue_iids, issue_client).any? { |issue| (issue.labels & [OPERATIONAL_READINESS_CHECKLIST_LABEL]).any? }
|
25
|
+
end
|
26
|
+
|
27
|
+
def linked_issues(linked_issue_iids, issue_client)
|
28
|
+
linked_issue_iids.flat_map { |iid| issue_client.find_issues(iid: iid) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module FeatureReadiness
|
6
|
+
module Concerns
|
7
|
+
module WorkItemConcern
|
8
|
+
OPERATIONAL_READINESS_NOTE_ID = '<!-- OPERATIONAL READINESS PRECHECK COMMENT -->'
|
9
|
+
OPERATIONAL_READINESS_TRACKING_LABEL = 'tracking operational readiness'
|
10
|
+
|
11
|
+
def add_operational_readiness_precheck_comment(work_item, work_items_client, label_client)
|
12
|
+
comment = <<~COMMENT
|
13
|
+
#{OPERATIONAL_READINESS_NOTE_ID}
|
14
|
+
## Operational Readiness Pre-Check
|
15
|
+
|
16
|
+
@#{work_item[:author][:username]} This is an automated comment to help determine if an
|
17
|
+
[operational readiness check](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Operational%20Readiness.md?ref_type=heads)
|
18
|
+
is needed for this feature.
|
19
|
+
|
20
|
+
Please respond with the ✅ emoji on this comment if your feature meets any of the below criteria. If not, please respond with the ❌ emoji.
|
21
|
+
|
22
|
+
1. Requires new infrastructure components, or significant changes to existing components that have dependencies on the GitLab application.
|
23
|
+
2. Requires changes to our application architecture that change how the infrastructure scales, GitLab is deployed or how data is processed or stored.
|
24
|
+
3. Adds new services or changes existing services that will factor into the availability of the GitLab application.
|
25
|
+
COMMENT
|
26
|
+
|
27
|
+
add_labels(ids_for_labels([OPERATIONAL_READINESS_TRACKING_LABEL], label_client), work_item[:id], work_items_client)
|
28
|
+
|
29
|
+
discussion = existing_note_containing_text(OPERATIONAL_READINESS_NOTE_ID, work_item[:iid], work_items_client)
|
30
|
+
|
31
|
+
return discussion if discussion
|
32
|
+
|
33
|
+
work_items_client.create_discussion(id: work_item[:id], note: comment)
|
34
|
+
|
35
|
+
puts "\nAdded operational readiness comment to epic work item: #{work_item[:webUrl]}\n"
|
36
|
+
|
37
|
+
existing_note_containing_text(OPERATIONAL_READINESS_NOTE_ID, work_item[:iid], work_items_client)
|
38
|
+
end
|
39
|
+
|
40
|
+
def existing_note_containing_text(text, work_item_iid, client)
|
41
|
+
work_item = fetch_work_item(work_item_iid, client, [:notes])
|
42
|
+
|
43
|
+
work_item[:widgets]
|
44
|
+
.find { |widget| widget.key?(:discussions) }
|
45
|
+
.dig(:discussions, :nodes)
|
46
|
+
.find { |node| node[:notes][:nodes].any? { |node| node[:body].include?(text) } }
|
47
|
+
&.dig(:notes, :nodes, 0)
|
48
|
+
end
|
49
|
+
|
50
|
+
def link_operation_readiness_issue(issue, work_item, link_type, client)
|
51
|
+
client.create_linked_items(work_item_id: work_item[:id], item_ids: ["gid://gitlab/Issue/#{issue.id}"], link_type: link_type)
|
52
|
+
end
|
53
|
+
|
54
|
+
def note_has_emoji?(note, emoji_name)
|
55
|
+
note&.dig(:awardEmoji, :nodes)&.any? { |node| node[:name] == emoji_name }
|
56
|
+
end
|
57
|
+
|
58
|
+
def post_comment_about_operation_readiness_issue_created(work_item, issue, precheck_comment, client)
|
59
|
+
comment_text = <<~COMMENT
|
60
|
+
@#{work_item[:author][:username]} Thanks for confirming that your feature requires an operational readiness check.
|
61
|
+
Based on your response, an operational readiness check issue has been created and linked to this issue: #{issue.web_url}.
|
62
|
+
COMMENT
|
63
|
+
|
64
|
+
client.create_discussion_note(work_item_id: work_item[:id], discussion_id: precheck_comment.dig(:discussion, :id), text: comment_text)
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_labels(work_item)
|
68
|
+
labels_node = work_item[:widgets]&.find { |widget| widget.key?(:labels) }
|
69
|
+
labels_node && labels_node[:labels][:nodes].map { |label| label[:title] }
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_issue_iids(work_item, project)
|
73
|
+
childern_node = work_item[:widgets]&.find { |widget| widget.key?(:children) }
|
74
|
+
childern_node && childern_node[:children][:nodes].filter_map { |issue| issue[:iid] if issue[:workItemType][:name] == "Issue" && issue[:project][:fullPath] == project }
|
75
|
+
end
|
76
|
+
|
77
|
+
def has_label?(work_item, label)
|
78
|
+
get_labels(work_item).include?(label)
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_labels(label_ids, work_item_id, client)
|
82
|
+
client.add_labels(work_item_id: work_item_id, label_ids: label_gids(label_ids))
|
83
|
+
end
|
84
|
+
|
85
|
+
def fetch_work_item(iid, client, widgets = [])
|
86
|
+
client.work_item(workitem_iid: iid, widgets: widgets)
|
87
|
+
end
|
88
|
+
|
89
|
+
def has_a_child_epic?(epic)
|
90
|
+
epic[:widgets]
|
91
|
+
.find { |widget| widget.has_key?(:children) }[:children][:nodes]
|
92
|
+
.any? { |child| child[:workItemType][:name] == "Epic" }
|
93
|
+
end
|
94
|
+
|
95
|
+
def work_item_author_id(work_item)
|
96
|
+
extract_id_from_gid(work_item[:author][:id])
|
97
|
+
end
|
98
|
+
|
99
|
+
def linked_issue_iids(work_item)
|
100
|
+
work_item[:widgets].find { |widget| widget.key?(:linkedItems) }
|
101
|
+
.dig(:linkedItems, :nodes)
|
102
|
+
.map { |node| node.dig(:workItem, :iid) }
|
103
|
+
end
|
104
|
+
|
105
|
+
def label_gids(label_ids = [])
|
106
|
+
label_ids.map { |label_id| "gid://gitlab/Label/#{label_id}" }
|
107
|
+
end
|
108
|
+
|
109
|
+
def ids_for_labels(labels, label_client)
|
110
|
+
labels.map { |label| get_id_for_label(label, label_client) }
|
111
|
+
end
|
112
|
+
|
113
|
+
def get_id_for_label(label_name, label_client)
|
114
|
+
labels = label_client.labels(options: { search: label_name })
|
115
|
+
|
116
|
+
raise "No labels found with name: '#{label_name}'" if labels.empty?
|
117
|
+
|
118
|
+
labels.first.id
|
119
|
+
end
|
120
|
+
|
121
|
+
def extract_id_from_gid(gid)
|
122
|
+
gid.to_s.split('/').last.to_i
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module GitlabQuality
|
6
|
+
module TestTooling
|
7
|
+
module FeatureReadiness
|
8
|
+
class Evaluation
|
9
|
+
include Concerns::IssueConcern
|
10
|
+
include Concerns::WorkItemConcern
|
11
|
+
|
12
|
+
BASE_LABELS_FOR_SEARCH = ['feature::addition'].freeze
|
13
|
+
FEATURE_READINESS_TRACKING_LABEL = 'tracking feature readiness'
|
14
|
+
|
15
|
+
def initialize(token:, project: nil, group: nil, limit_to_minutes: nil, epic_iid: nil, search_labels: [], dry_run: false)
|
16
|
+
@token = token
|
17
|
+
@project = "#{group}/#{project}"
|
18
|
+
@group = group
|
19
|
+
@limit_to_minutes = limit_to_minutes
|
20
|
+
@epic_iid = epic_iid
|
21
|
+
@search_labels = search_labels
|
22
|
+
@dry_run = dry_run
|
23
|
+
@analyzed_epics = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def invoke!
|
27
|
+
created_after = utc_time_minus_mins(limit_to_minutes)
|
28
|
+
|
29
|
+
epics = fetch_epics(created_after)
|
30
|
+
|
31
|
+
epics.compact.each do |epic|
|
32
|
+
@analyzed_epics << process_epic(epic)
|
33
|
+
rescue StandardError => e
|
34
|
+
puts "ERROR processing epic #{epic[:epic_web_url]} due to: #{e}"
|
35
|
+
end
|
36
|
+
|
37
|
+
report_epics(analyzed_epics)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :token, :project, :dry_run, :group, :limit_to_minutes, :epic_iid, :search_labels, :analyzed_epics
|
43
|
+
|
44
|
+
def work_items_client
|
45
|
+
@work_items_client ||= (dry_run ? GitlabClient::WorkItemsDryClient : GitlabClient::WorkItemsClient).new(token: token, project: project, group: group)
|
46
|
+
end
|
47
|
+
|
48
|
+
def fetch_epics(created_after)
|
49
|
+
return [fetch_work_item(epic_iid, work_items_client, [:hierarchy, :labels])] if epic_iid
|
50
|
+
|
51
|
+
work_items_client.paginated_call(:group_work_items,
|
52
|
+
labels: search_labels.concat(BASE_LABELS_FOR_SEARCH).uniq, state: 'opened', created_after: created_after)
|
53
|
+
end
|
54
|
+
|
55
|
+
def label_client
|
56
|
+
@label_client ||= GitlabClient::LabelsClient.new(token: token, project: project)
|
57
|
+
end
|
58
|
+
|
59
|
+
def process_epic(epic)
|
60
|
+
epic = fetch_work_item(epic[:iid], work_items_client, [:hierarchy, :labels])
|
61
|
+
|
62
|
+
AnalyzedItems::AnalyzedEpic.new(epic: epic, token: token, project: project, group: group, dry_run: dry_run)
|
63
|
+
.tap(&:analyze).result
|
64
|
+
end
|
65
|
+
|
66
|
+
def utc_time_minus_mins(mins)
|
67
|
+
(Time.now - (mins * 60)).utc.iso8601 if mins
|
68
|
+
end
|
69
|
+
|
70
|
+
def report_epics(epics)
|
71
|
+
epics.each do |epic|
|
72
|
+
add_labels(ids_for_labels([FEATURE_READINESS_TRACKING_LABEL], label_client), epic[:epic_id], work_items_client)
|
73
|
+
|
74
|
+
Report::FeatureReadiness::ReportOnEpic.report(epic, work_items_client)
|
75
|
+
rescue StandardError => e
|
76
|
+
puts "ERROR reporting epic #{epic[:epic_web_url]} due to: #{e}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module GitlabQuality
|
6
|
+
module TestTooling
|
7
|
+
module FeatureReadiness
|
8
|
+
class OperationalReadinessCheck
|
9
|
+
include Concerns::IssueConcern
|
10
|
+
include Concerns::WorkItemConcern
|
11
|
+
|
12
|
+
BASE_LABELS_FOR_SEARCH = ["feature::addition"].freeze
|
13
|
+
OPERATIONAL_READINESS_ISSUE_TEMPLATE_PATH = '.gitlab/issue_templates/Operational Readiness.md'
|
14
|
+
|
15
|
+
def initialize(token:, project: nil, group: nil, limit_to_minutes: nil, search_labels: [], issue_is_blocking: false, dry_run: false)
|
16
|
+
@token = token
|
17
|
+
@project = "#{group}/#{project}"
|
18
|
+
@group = group
|
19
|
+
@limit_to_minutes = limit_to_minutes
|
20
|
+
@search_labels = search_labels
|
21
|
+
@issue_is_blocking = issue_is_blocking
|
22
|
+
@dry_run = dry_run
|
23
|
+
end
|
24
|
+
|
25
|
+
def invoke!
|
26
|
+
created_after = utc_time_minus_mins(limit_to_minutes)
|
27
|
+
|
28
|
+
epics = work_items_client.paginated_call(:group_work_items,
|
29
|
+
labels: search_labels.concat(BASE_LABELS_FOR_SEARCH).uniq, state: 'opened', created_after: created_after, extras: [:work_item_fields])
|
30
|
+
|
31
|
+
epics.each do |epic|
|
32
|
+
process_epic(epic)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :token, :project, :dry_run, :group, :limit_to_minutes, :search_labels, :issue_is_blocking
|
39
|
+
|
40
|
+
def issue_client
|
41
|
+
@issue_client ||= (dry_run ? GitlabClient::IssuesDryClient : GitlabClient::IssuesClient).new(token: token, project: project)
|
42
|
+
end
|
43
|
+
|
44
|
+
def work_items_client
|
45
|
+
@work_items_client ||= (dry_run ? GitlabClient::WorkItemsDryClient : GitlabClient::WorkItemsClient).new(token: token, project: project, group: group)
|
46
|
+
end
|
47
|
+
|
48
|
+
def label_client
|
49
|
+
@label_client ||= GitlabClient::LabelsClient.new(token: token, project: project)
|
50
|
+
end
|
51
|
+
|
52
|
+
def repository_files_client
|
53
|
+
@repository_files_client ||= GitlabClient::RepositoryFilesClient.new(token: token, project: project, file_path: OPERATIONAL_READINESS_ISSUE_TEMPLATE_PATH, ref: 'master')
|
54
|
+
end
|
55
|
+
|
56
|
+
def utc_time_minus_mins(mins)
|
57
|
+
(Time.now - (mins * 60)).utc.iso8601 if mins
|
58
|
+
end
|
59
|
+
|
60
|
+
def process_epic(epic) # rubocop:disable Metrics/AbcSize
|
61
|
+
epic = fetch_work_item(epic[:iid], work_items_client, [:notes, :linked_items, :labels, :hierarchy])
|
62
|
+
|
63
|
+
return if has_a_child_epic?(epic)
|
64
|
+
|
65
|
+
pre_check_comment = add_operational_readiness_precheck_comment(epic, work_items_client, label_client)
|
66
|
+
|
67
|
+
return unless note_has_emoji?(pre_check_comment, 'white_check_mark') && !has_operational_readiness_issue_linked?(linked_issue_iids(epic), issue_client)
|
68
|
+
|
69
|
+
issue = create_operation_readiness_issue(epic[:title], work_item_author_id(epic), issue_client, repository_files_client)
|
70
|
+
|
71
|
+
link_operation_readiness_issue(issue, epic, link_type, work_items_client)
|
72
|
+
|
73
|
+
post_comment_about_operation_readiness_issue_created(epic, issue, pre_check_comment, work_items_client)
|
74
|
+
end
|
75
|
+
|
76
|
+
def link_type
|
77
|
+
issue_is_blocking ? 'BLOCKED_BY' : 'RELATED'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rest-client'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module GitlabQuality
|
7
|
+
module TestTooling
|
8
|
+
module GitlabClient
|
9
|
+
class GitlabGraphqlClient
|
10
|
+
def initialize(token:, project:, group:, endpoint: nil)
|
11
|
+
@token = token
|
12
|
+
@project = project
|
13
|
+
@group = group
|
14
|
+
@endpoint = endpoint || Runtime::Env.gitlab_graphql_api_base
|
15
|
+
end
|
16
|
+
|
17
|
+
def post(payload)
|
18
|
+
payload = { query: payload } if payload.is_a?(String)
|
19
|
+
request_args = {
|
20
|
+
method: :post,
|
21
|
+
url: endpoint,
|
22
|
+
payload: payload,
|
23
|
+
headers: { 'Authorization' => "Bearer #{token}" },
|
24
|
+
verify_ssl: false
|
25
|
+
}
|
26
|
+
extract_graphql_body(RestClient::Request.execute(request_args))
|
27
|
+
rescue StandardError => e
|
28
|
+
return_response_or_raise(e)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :project, :token, :endpoint, :group
|
34
|
+
|
35
|
+
def return_response_or_raise(error)
|
36
|
+
return error.response if error.respond_to?(:response) && error.response
|
37
|
+
|
38
|
+
raise error
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_body(response)
|
42
|
+
JSON.parse(response.body, symbolize_names: true)
|
43
|
+
end
|
44
|
+
|
45
|
+
def extract_graphql_body(graphql_response)
|
46
|
+
parsed_body = parse_body(graphql_response)
|
47
|
+
|
48
|
+
data = parsed_body[:data].to_h
|
49
|
+
data.values[0].to_h
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -9,6 +9,10 @@ module Gitlab
|
|
9
9
|
get("/projects/#{url_encode(project)}/members/all/#{id}")
|
10
10
|
end
|
11
11
|
|
12
|
+
def issue_note_award_emoji(project, issue_id, note_id, options = {})
|
13
|
+
get("/projects/#{url_encode(project)}/issues/#{issue_id}/notes/#{note_id}/award_emoji", query: options)
|
14
|
+
end
|
15
|
+
|
12
16
|
def issue_discussions(project, issue_id, options = {})
|
13
17
|
get("/projects/#{url_encode(project)}/issues/#{issue_id}/discussions", query: options)
|
14
18
|
end
|
@@ -17,6 +21,16 @@ module Gitlab
|
|
17
21
|
post("/projects/#{url_encode(project)}/issues/#{issue_iid}/discussions", query: options)
|
18
22
|
end
|
19
23
|
|
24
|
+
def create_issue_link(project, issue_iid, target_project_id, target_issue_iid, link_type)
|
25
|
+
post("/projects/#{url_encode(project)}/issues/#{issue_iid}/links",
|
26
|
+
query: {
|
27
|
+
target_project_id: target_project_id,
|
28
|
+
target_issue_iid: target_issue_iid,
|
29
|
+
link_type: link_type
|
30
|
+
}.compact
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
20
34
|
def add_note_to_issue_discussion_as_thread(project, issue_id, discussion_id, options = {})
|
21
35
|
post("/projects/#{url_encode(project)}/issues/#{issue_id}/discussions/#{discussion_id}/notes", query: options)
|
22
36
|
end
|
@@ -58,6 +72,12 @@ module GitlabQuality
|
|
58
72
|
end
|
59
73
|
end
|
60
74
|
|
75
|
+
def related_merge_requests(iid:)
|
76
|
+
handle_gitlab_client_exceptions do
|
77
|
+
client.related_merge_requests(project, iid).auto_paginate
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
61
81
|
def find_issue_discussions(iid:)
|
62
82
|
handle_gitlab_client_exceptions do
|
63
83
|
client.issue_discussions(project, iid, order_by: 'created_at', sort: 'asc').auto_paginate
|
@@ -79,6 +99,18 @@ module GitlabQuality
|
|
79
99
|
end
|
80
100
|
end
|
81
101
|
|
102
|
+
def create_issue_link(project:, issue_iid:, target_project_id:, target_issue_iid:, link_type: 'is_blocked_by')
|
103
|
+
handle_gitlab_client_exceptions do
|
104
|
+
client.create_issue_link(project, issue_iid, target_project_id, target_issue_iid, link_type)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def issue_links(project:, issue_iid:, options: {})
|
109
|
+
handle_gitlab_client_exceptions do
|
110
|
+
client.issue_links(project, issue_iid, options)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
82
114
|
def edit_issue(iid:, options: {})
|
83
115
|
handle_gitlab_client_exceptions do
|
84
116
|
client.edit_issue(project, iid, options)
|
@@ -97,6 +129,12 @@ module GitlabQuality
|
|
97
129
|
end
|
98
130
|
end
|
99
131
|
|
132
|
+
def get_note_award_emojis(issue_iid:, note_id:)
|
133
|
+
handle_gitlab_client_exceptions do
|
134
|
+
client.issue_note_award_emoji(project, issue_iid, note_id)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
100
138
|
def create_issue_discussion(iid:, note:)
|
101
139
|
handle_gitlab_client_exceptions do
|
102
140
|
client.create_issue_discussion(project, iid, body: note)
|
@@ -20,15 +20,15 @@ module GitlabQuality
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def edit_issue_note(issue_iid:, note_id:, note:)
|
23
|
-
puts "The following note would have been edited on #{project}##{issue_iid} (note #{note_id}) issue
|
23
|
+
puts "The following note would have been edited on #{project}##{issue_iid} (note #{note_id}) issue:\n\n #{note}"
|
24
24
|
end
|
25
25
|
|
26
26
|
def create_issue_discussion(iid:, note:)
|
27
|
-
puts "The following discussion would have been posted on #{project}##{iid} issue
|
27
|
+
puts "The following discussion would have been posted on #{project}##{iid} issue:\n\n #{note}"
|
28
28
|
end
|
29
29
|
|
30
30
|
def add_note_to_issue_discussion_as_thread(iid:, discussion_id:, note:)
|
31
|
-
puts "The following discussion note would have been posted on #{project}##{iid} (discussion #{discussion_id}) issue
|
31
|
+
puts "The following discussion note would have been posted on #{project}##{iid} (discussion #{discussion_id}) issue:\n\n #{note}"
|
32
32
|
end
|
33
33
|
|
34
34
|
def upload_file(file_fullpath:)
|
@@ -1,5 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'gitlab'
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
# Monkey patch the Gitlab client to allow passing query options
|
7
|
+
class Client
|
8
|
+
def merge_request_diffs(project, merge_request_iid, options = {})
|
9
|
+
get("/projects/#{url_encode(project)}/merge_requests/#{merge_request_iid}/diffs", query: options).auto_paginate
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
3
14
|
module GitlabQuality
|
4
15
|
module TestTooling
|
5
16
|
module GitlabClient
|
@@ -10,6 +21,12 @@ module GitlabQuality
|
|
10
21
|
end
|
11
22
|
end
|
12
23
|
|
24
|
+
def merge_request_diffs(merge_request_iid:)
|
25
|
+
handle_gitlab_client_exceptions do
|
26
|
+
client.merge_request_diffs(project, merge_request_iid, per_page: 100)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
13
30
|
def create_merge_request(title:, source_branch:, target_branch:, description:, labels:, assignee_id: nil, reviewer_ids: [])
|
14
31
|
attrs = {
|
15
32
|
source_branch: source_branch,
|
@@ -33,6 +50,10 @@ module GitlabQuality
|
|
33
50
|
merge_request
|
34
51
|
end
|
35
52
|
|
53
|
+
def merge_request(id:, options: {})
|
54
|
+
client.merge_request(project, id, options)
|
55
|
+
end
|
56
|
+
|
36
57
|
def find(iid: nil, options: {}, &select)
|
37
58
|
select ||= :itself
|
38
59
|
|
@@ -4,16 +4,6 @@ module GitlabQuality
|
|
4
4
|
module TestTooling
|
5
5
|
module GitlabClient
|
6
6
|
class MergeRequestsDryClient < MergeRequestsClient
|
7
|
-
def find_merge_request_changes(merge_request_iid:)
|
8
|
-
puts "Finding changes for merge_request_id #{merge_request_iid}"
|
9
|
-
puts "project: #{project}"
|
10
|
-
end
|
11
|
-
|
12
|
-
def merge_request_changed_files(merge_request_iid:)
|
13
|
-
puts "Changed files for #{merge_request_iid}"
|
14
|
-
[]
|
15
|
-
end
|
16
|
-
|
17
7
|
def find_note(body:, merge_request_iid:)
|
18
8
|
puts "Find note for #{merge_request_iid} with body: #{body} for mr_iid: #{merge_request_iid}"
|
19
9
|
end
|