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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -1
  3. data/Gemfile.lock +9 -1
  4. data/README.md +44 -3
  5. data/exe/failed-test-issues +53 -8
  6. data/exe/feature-readiness-checklist +61 -0
  7. data/exe/feature-readiness-evaluation +62 -0
  8. data/lib/gitlab_quality/test_tooling/feature_readiness/analyzed_items/analyzed_epic.rb +94 -0
  9. data/lib/gitlab_quality/test_tooling/feature_readiness/analyzed_items/analyzed_issue.rb +92 -0
  10. data/lib/gitlab_quality/test_tooling/feature_readiness/analyzed_items/analyzed_merge_request.rb +139 -0
  11. data/lib/gitlab_quality/test_tooling/feature_readiness/concerns/issue_concern.rb +34 -0
  12. data/lib/gitlab_quality/test_tooling/feature_readiness/concerns/work_item_concern.rb +128 -0
  13. data/lib/gitlab_quality/test_tooling/feature_readiness/evaluation.rb +82 -0
  14. data/lib/gitlab_quality/test_tooling/feature_readiness/operational_readiness_check.rb +82 -0
  15. data/lib/gitlab_quality/test_tooling/gitlab_client/gitlab_graphql_client.rb +54 -0
  16. data/lib/gitlab_quality/test_tooling/gitlab_client/issues_client.rb +38 -0
  17. data/lib/gitlab_quality/test_tooling/gitlab_client/issues_dry_client.rb +3 -3
  18. data/lib/gitlab_quality/test_tooling/gitlab_client/labels_client.rb +13 -0
  19. data/lib/gitlab_quality/test_tooling/gitlab_client/merge_requests_client.rb +21 -0
  20. data/lib/gitlab_quality/test_tooling/gitlab_client/merge_requests_dry_client.rb +0 -10
  21. data/lib/gitlab_quality/test_tooling/gitlab_client/work_items_client.rb +277 -0
  22. data/lib/gitlab_quality/test_tooling/gitlab_client/work_items_dry_client.rb +25 -0
  23. data/lib/gitlab_quality/test_tooling/labels_inference.rb +4 -0
  24. data/lib/gitlab_quality/test_tooling/report/failed_test_issue.rb +6 -6
  25. data/lib/gitlab_quality/test_tooling/report/feature_readiness/report_on_epic.rb +174 -0
  26. data/lib/gitlab_quality/test_tooling/report/health_problem_reporter.rb +120 -20
  27. data/lib/gitlab_quality/test_tooling/report/knapsack_report_issue.rb +1 -1
  28. data/lib/gitlab_quality/test_tooling/report/prepare_stage_reports.rb +1 -1
  29. data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +1 -1
  30. data/lib/gitlab_quality/test_tooling/runtime/env.rb +11 -6
  31. data/lib/gitlab_quality/test_tooling/test_metrics_exporter/support/gcs_tools.rb +18 -5
  32. data/lib/gitlab_quality/test_tooling/version.rb +1 -1
  33. 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: #{note}"
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: #{note}"
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: #{note}"
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:)
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitlabQuality
4
+ module TestTooling
5
+ module GitlabClient
6
+ class LabelsClient < GitlabClient
7
+ def labels(options: {})
8
+ client.labels(project, options)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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