danger-additional-logging 0.0.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.
Files changed (127) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +93 -0
  4. data/bin/danger +5 -0
  5. data/lib/assets/DangerfileTemplate +13 -0
  6. data/lib/danger/ci_source/appcenter.rb +55 -0
  7. data/lib/danger/ci_source/appcircle.rb +83 -0
  8. data/lib/danger/ci_source/appveyor.rb +64 -0
  9. data/lib/danger/ci_source/azure_pipelines.rb +61 -0
  10. data/lib/danger/ci_source/bamboo.rb +41 -0
  11. data/lib/danger/ci_source/bitbucket_pipelines.rb +37 -0
  12. data/lib/danger/ci_source/bitrise.rb +78 -0
  13. data/lib/danger/ci_source/buddybuild.rb +62 -0
  14. data/lib/danger/ci_source/buildkite.rb +51 -0
  15. data/lib/danger/ci_source/ci_source.rb +37 -0
  16. data/lib/danger/ci_source/circle.rb +94 -0
  17. data/lib/danger/ci_source/circle_api.rb +51 -0
  18. data/lib/danger/ci_source/cirrus.rb +31 -0
  19. data/lib/danger/ci_source/code_build.rb +71 -0
  20. data/lib/danger/ci_source/codefresh.rb +47 -0
  21. data/lib/danger/ci_source/codemagic.rb +58 -0
  22. data/lib/danger/ci_source/codeship.rb +44 -0
  23. data/lib/danger/ci_source/concourse.rb +60 -0
  24. data/lib/danger/ci_source/custom_ci_with_github.rb +49 -0
  25. data/lib/danger/ci_source/dotci.rb +50 -0
  26. data/lib/danger/ci_source/drone.rb +71 -0
  27. data/lib/danger/ci_source/github_actions.rb +44 -0
  28. data/lib/danger/ci_source/gitlab_ci.rb +89 -0
  29. data/lib/danger/ci_source/jenkins.rb +148 -0
  30. data/lib/danger/ci_source/local_git_repo.rb +117 -0
  31. data/lib/danger/ci_source/local_only_git_repo.rb +44 -0
  32. data/lib/danger/ci_source/screwdriver.rb +48 -0
  33. data/lib/danger/ci_source/semaphore.rb +37 -0
  34. data/lib/danger/ci_source/support/commits.rb +19 -0
  35. data/lib/danger/ci_source/support/find_repo_info_from_logs.rb +35 -0
  36. data/lib/danger/ci_source/support/find_repo_info_from_url.rb +43 -0
  37. data/lib/danger/ci_source/support/local_pull_request.rb +14 -0
  38. data/lib/danger/ci_source/support/no_pull_request.rb +7 -0
  39. data/lib/danger/ci_source/support/no_repo_info.rb +5 -0
  40. data/lib/danger/ci_source/support/pull_request_finder.rb +190 -0
  41. data/lib/danger/ci_source/support/remote_pull_request.rb +15 -0
  42. data/lib/danger/ci_source/support/repo_info.rb +10 -0
  43. data/lib/danger/ci_source/surf.rb +37 -0
  44. data/lib/danger/ci_source/teamcity.rb +163 -0
  45. data/lib/danger/ci_source/travis.rb +51 -0
  46. data/lib/danger/ci_source/xcode_cloud.rb +38 -0
  47. data/lib/danger/ci_source/xcode_server.rb +48 -0
  48. data/lib/danger/clients/rubygems_client.rb +14 -0
  49. data/lib/danger/commands/dangerfile/gem.rb +43 -0
  50. data/lib/danger/commands/dangerfile/init.rb +30 -0
  51. data/lib/danger/commands/dry_run.rb +54 -0
  52. data/lib/danger/commands/init.rb +297 -0
  53. data/lib/danger/commands/init_helpers/interviewer.rb +92 -0
  54. data/lib/danger/commands/local.rb +83 -0
  55. data/lib/danger/commands/local_helpers/http_cache.rb +38 -0
  56. data/lib/danger/commands/local_helpers/local_setup.rb +48 -0
  57. data/lib/danger/commands/local_helpers/pry_setup.rb +32 -0
  58. data/lib/danger/commands/plugins/plugin_json.rb +44 -0
  59. data/lib/danger/commands/plugins/plugin_lint.rb +52 -0
  60. data/lib/danger/commands/plugins/plugin_readme.rb +42 -0
  61. data/lib/danger/commands/pr.rb +93 -0
  62. data/lib/danger/commands/runner.rb +94 -0
  63. data/lib/danger/commands/staging.rb +53 -0
  64. data/lib/danger/commands/systems.rb +41 -0
  65. data/lib/danger/comment_generators/bitbucket_server.md.erb +20 -0
  66. data/lib/danger/comment_generators/bitbucket_server_inline.md.erb +15 -0
  67. data/lib/danger/comment_generators/bitbucket_server_message_group.md.erb +12 -0
  68. data/lib/danger/comment_generators/github.md.erb +55 -0
  69. data/lib/danger/comment_generators/github_inline.md.erb +26 -0
  70. data/lib/danger/comment_generators/gitlab.md.erb +40 -0
  71. data/lib/danger/comment_generators/gitlab_inline.md.erb +21 -0
  72. data/lib/danger/comment_generators/vsts.md.erb +20 -0
  73. data/lib/danger/comment_generators/vsts_inline.md.erb +17 -0
  74. data/lib/danger/core_ext/file_list.rb +18 -0
  75. data/lib/danger/core_ext/string.rb +20 -0
  76. data/lib/danger/danger_core/dangerfile.rb +348 -0
  77. data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
  78. data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
  79. data/lib/danger/danger_core/environment_manager.rb +126 -0
  80. data/lib/danger/danger_core/executor.rb +91 -0
  81. data/lib/danger/danger_core/message_aggregator.rb +50 -0
  82. data/lib/danger/danger_core/message_group.rb +68 -0
  83. data/lib/danger/danger_core/messages/base.rb +57 -0
  84. data/lib/danger/danger_core/messages/markdown.rb +41 -0
  85. data/lib/danger/danger_core/messages/violation.rb +53 -0
  86. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +142 -0
  87. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +211 -0
  88. data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +274 -0
  89. data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +159 -0
  90. data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +264 -0
  91. data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +275 -0
  92. data/lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb +43 -0
  93. data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +220 -0
  94. data/lib/danger/danger_core/plugins/dangerfile_vsts_plugin.rb +191 -0
  95. data/lib/danger/danger_core/standard_error.rb +142 -0
  96. data/lib/danger/helpers/array_subclass.rb +61 -0
  97. data/lib/danger/helpers/comment.rb +32 -0
  98. data/lib/danger/helpers/comments_helper.rb +179 -0
  99. data/lib/danger/helpers/comments_parsing_helper.rb +71 -0
  100. data/lib/danger/helpers/emoji_mapper.rb +41 -0
  101. data/lib/danger/helpers/find_max_num_violations.rb +31 -0
  102. data/lib/danger/helpers/message_groups_array_helper.rb +31 -0
  103. data/lib/danger/plugin_support/gems_resolver.rb +77 -0
  104. data/lib/danger/plugin_support/plugin.rb +52 -0
  105. data/lib/danger/plugin_support/plugin_file_resolver.rb +30 -0
  106. data/lib/danger/plugin_support/plugin_linter.rb +162 -0
  107. data/lib/danger/plugin_support/plugin_parser.rb +199 -0
  108. data/lib/danger/plugin_support/templates/readme_table.html.erb +26 -0
  109. data/lib/danger/request_sources/bitbucket_cloud.rb +169 -0
  110. data/lib/danger/request_sources/bitbucket_cloud_api.rb +181 -0
  111. data/lib/danger/request_sources/bitbucket_server.rb +210 -0
  112. data/lib/danger/request_sources/bitbucket_server_api.rb +129 -0
  113. data/lib/danger/request_sources/code_insights_api.rb +142 -0
  114. data/lib/danger/request_sources/github/github.rb +535 -0
  115. data/lib/danger/request_sources/github/github_review.rb +127 -0
  116. data/lib/danger/request_sources/github/github_review_resolver.rb +17 -0
  117. data/lib/danger/request_sources/github/github_review_unsupported.rb +23 -0
  118. data/lib/danger/request_sources/gitlab.rb +557 -0
  119. data/lib/danger/request_sources/local_only.rb +50 -0
  120. data/lib/danger/request_sources/request_source.rb +97 -0
  121. data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
  122. data/lib/danger/request_sources/vsts.rb +278 -0
  123. data/lib/danger/request_sources/vsts_api.rb +172 -0
  124. data/lib/danger/scm_source/git_repo.rb +198 -0
  125. data/lib/danger/version.rb +4 -0
  126. data/lib/danger.rb +45 -0
  127. metadata +351 -0
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Danger
4
+ module RequestSources
5
+ class RequestSource
6
+ DANGER_REPO_NAME = "danger"
7
+
8
+ attr_accessor :ci_source, :scm, :host, :ignored_violations
9
+
10
+ def self.env_vars
11
+ raise "Subclass and overwrite self.env_vars"
12
+ end
13
+
14
+ def self.optional_env_vars
15
+ []
16
+ end
17
+
18
+ def self.inherited(child_class)
19
+ available_request_sources.add child_class
20
+ super
21
+ end
22
+
23
+ def self.available_request_sources
24
+ @available_request_sources ||= Set.new
25
+ end
26
+
27
+ def self.source_name
28
+ to_s.sub("Danger::RequestSources::", "")
29
+ end
30
+
31
+ def self.available_source_names_and_envs
32
+ available_request_sources.map do |klass|
33
+ " - #{klass.source_name}: #{klass.env_vars.join(', ').yellow}"
34
+ end
35
+ end
36
+
37
+ def initialize(_ci_source, _environment)
38
+ raise "Subclass and overwrite initialize"
39
+ end
40
+
41
+ def inspect
42
+ inspected = super
43
+
44
+ inspected.gsub!(@token, "********") if @token
45
+ inspected.gsub!(@access_token, "********") if @access_token
46
+ inspected.gsub!(@bearer_token, "********") if @bearer_token
47
+
48
+ inspected
49
+ end
50
+
51
+ # @return [Boolean] whether scm.origins is a valid git repository or not
52
+ def validates_as_ci?
53
+ !!self.scm.origins.match(%r{#{Regexp.escape self.host}(:|/)(.+/.+?)(?:\.git)?$})
54
+ end
55
+
56
+ def validates_as_api_source?
57
+ raise "Subclass and overwrite validates_as_api_source?"
58
+ end
59
+
60
+ def scm
61
+ @scm ||= nil
62
+ end
63
+
64
+ def host
65
+ @host ||= nil
66
+ end
67
+
68
+ def ignored_violations
69
+ @ignored_violations ||= []
70
+ end
71
+
72
+ def update_pull_request!(_warnings: [], _errors: [], _messages: [], _markdowns: [])
73
+ raise "Subclass and overwrite update_pull_request!"
74
+ end
75
+
76
+ def setup_danger_branches
77
+ raise "Subclass and overwrite setup_danger_branches"
78
+ end
79
+
80
+ def fetch_details
81
+ raise "Subclass and overwrite initialize"
82
+ end
83
+
84
+ def organisation
85
+ raise "Subclass and overwrite organisation"
86
+ end
87
+
88
+ def file_url(_organisation: nil, _repository: nil, _ref: nil, _branch: nil, _path: nil)
89
+ raise "Subclass and overwrite file_url"
90
+ end
91
+
92
+ def update_build_status(_status)
93
+ raise "Subclass and overwrite update_build_status"
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,17 @@
1
+ class GetIgnoredViolation
2
+ IGNORE_REGEXP = />*\s*danger\s*:\s*ignore\s*"(?<error>[^"]*)"/i.freeze
3
+
4
+ def initialize(body)
5
+ @body = body
6
+ end
7
+
8
+ def call
9
+ return [] unless body
10
+
11
+ body.chomp.scan(IGNORE_REGEXP).flatten
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :body
17
+ end
@@ -0,0 +1,278 @@
1
+ require "danger/helpers/comments_helper"
2
+ require "danger/request_sources/vsts_api"
3
+
4
+ module Danger
5
+ module RequestSources
6
+ class VSTS < RequestSource
7
+ include Danger::Helpers::CommentsHelper
8
+ attr_accessor :pr_json
9
+
10
+ def self.env_vars
11
+ [
12
+ "DANGER_VSTS_API_TOKEN",
13
+ "DANGER_VSTS_HOST"
14
+ ]
15
+ end
16
+
17
+ def self.optional_env_vars
18
+ [
19
+ "DANGER_VSTS_API_VERSION"
20
+ ]
21
+ end
22
+
23
+ def initialize(ci_source, environment)
24
+ self.ci_source = ci_source
25
+
26
+ @is_vsts_ci = environment.key? "DANGER_VSTS_HOST"
27
+
28
+ @api = VSTSAPI.new(ci_source.repo_slug, ci_source.pull_request_id, environment)
29
+ end
30
+
31
+ def validates_as_ci?
32
+ @is_vsts_ci
33
+ end
34
+
35
+ def validates_as_api_source?
36
+ @api.credentials_given?
37
+ end
38
+
39
+ def scm
40
+ @scm ||= GitRepo.new
41
+ end
42
+
43
+ def client
44
+ @api
45
+ end
46
+
47
+ def host
48
+ @host ||= @api.host
49
+ end
50
+
51
+ def fetch_details
52
+ self.pr_json = @api.fetch_pr_json
53
+ end
54
+
55
+ def setup_danger_branches
56
+ base_branch = self.pr_json[:targetRefName].sub("refs/heads/", "")
57
+ base_commit = self.pr_json[:lastMergeTargetCommit][:commitId]
58
+ head_branch = self.pr_json[:sourceRefName].sub("refs/heads/", "")
59
+ head_commit = self.pr_json[:lastMergeSourceCommit][:commitId]
60
+
61
+ # Next, we want to ensure that we have a version of the current branch at a known location
62
+ scm.ensure_commitish_exists_on_branch! base_branch, base_commit
63
+ self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{base_commit}"
64
+
65
+ # OK, so we want to ensure that we have a known head branch, this will always represent
66
+ # the head of the PR ( e.g. the most recent commit that will be merged. )
67
+ scm.ensure_commitish_exists_on_branch! head_branch, head_commit
68
+ self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{head_commit}"
69
+ end
70
+
71
+ def organisation
72
+ nil
73
+ end
74
+
75
+ def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
76
+ unless @api.supports_comments?
77
+ return
78
+ end
79
+
80
+ regular_violations = regular_violations_group(
81
+ warnings: warnings,
82
+ errors: errors,
83
+ messages: messages,
84
+ markdowns: markdowns
85
+ )
86
+
87
+ inline_violations = inline_violations_group(
88
+ warnings: warnings,
89
+ errors: errors,
90
+ messages: messages,
91
+ markdowns: markdowns
92
+ )
93
+
94
+ rest_inline_violations = submit_inline_comments!(**{
95
+ danger_id: danger_id,
96
+ previous_violations: {}
97
+ }.merge(inline_violations))
98
+
99
+ main_violations = merge_violations(
100
+ regular_violations, rest_inline_violations
101
+ )
102
+
103
+ comment = generate_description(warnings: main_violations[:warnings], errors: main_violations[:errors])
104
+ comment += "\n\n"
105
+ comment += generate_comment(**{
106
+ previous_violations: {},
107
+ danger_id: danger_id,
108
+ template: "vsts"
109
+ }.merge(main_violations))
110
+ if new_comment || remove_previous_comments
111
+ post_new_comment(comment)
112
+ else
113
+ update_old_comment(comment, danger_id: danger_id)
114
+ end
115
+ end
116
+
117
+ def post_new_comment(comment)
118
+ @api.post_comment(comment)
119
+ end
120
+
121
+ def update_old_comment(new_comment, danger_id: "danger")
122
+ comment_updated = false
123
+ @api.fetch_last_comments.each do |c|
124
+ thread_id = c[:id]
125
+ comment = c[:comments].first
126
+ comment_id = comment[:id]
127
+ comment_content = comment[:content].nil? ? "" : comment[:content]
128
+ # Skip the comment if it wasn't posted by danger
129
+ next unless comment_content.include?("generated_by_#{danger_id}")
130
+ # Skip the comment if it's an inline comment
131
+ next unless c[:threadContext].nil?
132
+
133
+ # Updated the danger posted comment
134
+ @api.update_comment(thread_id, comment_id, new_comment)
135
+ comment_updated = true
136
+ end
137
+ # If no comment was updated, post a new one
138
+ post_new_comment(new_comment) unless comment_updated
139
+ end
140
+
141
+ def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
142
+ # Avoid doing any fetchs if there's no inline comments
143
+ return {} if (warnings + errors + messages + markdowns).select(&:inline?).empty?
144
+
145
+ pr_threads = @api.fetch_last_comments
146
+ danger_threads = pr_threads.select do |thread|
147
+ comment = thread[:comments].first
148
+ comment_content = comment[:content].nil? ? "" : comment[:content]
149
+
150
+ next comment_content.include?("generated_by_#{danger_id}")
151
+ end
152
+ non_danger_threads = pr_threads - danger_threads
153
+
154
+ warnings = submit_inline_comments_for_kind!(:warning, warnings, danger_threads, previous_violations["warning"], danger_id: danger_id)
155
+ errors = submit_inline_comments_for_kind!(:error, errors, danger_threads, previous_violations["error"], danger_id: danger_id)
156
+ messages = submit_inline_comments_for_kind!(:message, messages, danger_threads, previous_violations["message"], danger_id: danger_id)
157
+ markdowns = submit_inline_comments_for_kind!(:markdown, markdowns, danger_threads, [], danger_id: danger_id)
158
+
159
+ # submit removes from the array all comments that are still in force
160
+ # so we strike out all remaining ones
161
+ danger_threads.each do |thread|
162
+ violation = violations_from_table(thread[:comments].first[:content]).first
163
+ if !violation.nil? && violation.sticky
164
+ body = generate_inline_comment_body("white_check_mark", violation, danger_id: danger_id, resolved: true, template: "github")
165
+ @api.update_comment(thread[:id], thread[:comments].first[:id], body)
166
+ end
167
+ end
168
+
169
+ {
170
+ warnings: warnings,
171
+ errors: errors,
172
+ messages: messages,
173
+ markdowns: markdowns
174
+ }
175
+ end
176
+
177
+ def messages_are_equivalent(m1, m2)
178
+ blob_regexp = %r{blob/[0-9a-z]+/}
179
+ m1.file == m2.file && m1.line == m2.line &&
180
+ m1.message.sub(blob_regexp, "") == m2.message.sub(blob_regexp, "")
181
+ end
182
+
183
+ def submit_inline_comments_for_kind!(kind, messages, danger_threads, previous_violations, danger_id: "danger")
184
+ previous_violations ||= []
185
+ is_markdown_content = kind == :markdown
186
+ emoji = { warning: "warning", error: "no_entry_sign", message: "book" }[kind]
187
+
188
+ messages.reject do |m|
189
+ next false unless m.file && m.line
190
+
191
+ # Once we know we're gonna submit it, we format it
192
+ if is_markdown_content
193
+ body = generate_inline_markdown_body(m, danger_id: danger_id, template: "vsts")
194
+ else
195
+ # Hide the inline link behind a span
196
+ m.message = m.message.gsub("\n", "<br />")
197
+ m = process_markdown(m, true)
198
+ body = generate_inline_comment_body(emoji, m, danger_id: danger_id, template: "vsts")
199
+ # A comment might be in previous_violations because only now it's part of the unified diff
200
+ # We remove from the array since it won't have a place in the table anymore
201
+ previous_violations.reject! { |v| messages_are_equivalent(v, m) }
202
+ end
203
+
204
+ matching_threads = danger_threads.select do |comment_data|
205
+ if comment_data.key?(:threadContext) && !comment_data[:threadContext].nil? &&
206
+ comment_data[:threadContext][:filePath] == m.file &&
207
+ comment_data[:threadContext].key?(:rightFileStart) &&
208
+ comment_data[:threadContext][:rightFileStart][:line] == m.line
209
+ # Parse it to avoid problems with strikethrough
210
+ violation = violations_from_table(comment_data[:comments].first[:content]).first
211
+ if violation
212
+ messages_are_equivalent(violation, m)
213
+ else
214
+ blob_regexp = %r{blob/[0-9a-z]+/}
215
+ comment_data[:comments].first[:content].sub(blob_regexp, "") == body.sub(blob_regexp, "")
216
+ end
217
+ else
218
+ false
219
+ end
220
+ end
221
+
222
+ if matching_threads.empty?
223
+ @api.post_inline_comment(body, m.file, m.line)
224
+
225
+ # Not reject because this comment has not completed
226
+ next false
227
+ else
228
+ # Remove the surviving comment so we don't strike it out
229
+ danger_threads.reject! { |c| matching_threads.include? c }
230
+
231
+ # Update the comment to remove the strikethrough if present
232
+ thread = matching_threads.first
233
+ @api.update_comment(thread[:id], thread[:comments].first[:id], body)
234
+ end
235
+
236
+ # Remove this element from the array
237
+ next true
238
+ end
239
+ end
240
+
241
+ private
242
+
243
+ def regular_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
244
+ {
245
+ warnings: warnings.reject(&:inline?),
246
+ errors: errors.reject(&:inline?),
247
+ messages: messages.reject(&:inline?),
248
+ markdowns: markdowns.reject(&:inline?)
249
+ }
250
+ end
251
+
252
+ def inline_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
253
+ cmp = proc do |a, b|
254
+ next -1 unless a.file && a.line
255
+ next 1 unless b.file && b.line
256
+
257
+ next a.line <=> b.line if a.file == b.file
258
+
259
+ next a.file <=> b.file
260
+ end
261
+
262
+ # Sort to group inline comments by file
263
+ {
264
+ warnings: warnings.select(&:inline?).sort(&cmp),
265
+ errors: errors.select(&:inline?).sort(&cmp),
266
+ messages: messages.select(&:inline?).sort(&cmp),
267
+ markdowns: markdowns.select(&:inline?).sort(&cmp)
268
+ }
269
+ end
270
+
271
+ def merge_violations(*violation_groups)
272
+ violation_groups.inject({}) do |accumulator, group|
273
+ accumulator.merge(group) { |_, old, fresh| old + fresh }
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "danger/helpers/comments_helper"
5
+
6
+ module Danger
7
+ module RequestSources
8
+ class VSTSAPI
9
+ attr_accessor :host, :pr_api_endpoint, :min_api_version_for_comments
10
+
11
+ def initialize(slug, pull_request_id, environment)
12
+ self.min_api_version_for_comments = "3.0"
13
+
14
+ user_name = ""
15
+ personal_access_token = environment["DANGER_VSTS_API_TOKEN"]
16
+
17
+ @token = Base64.strict_encode64("#{user_name}:#{personal_access_token}")
18
+ @api_version = environment["DANGER_VSTS_API_VERSION"] ||= self.min_api_version_for_comments
19
+
20
+ self.host = environment["DANGER_VSTS_HOST"]
21
+ if self.host && !(self.host.include? "http://") && !(self.host.include? "https://")
22
+ self.host = "https://" + self.host
23
+ end
24
+
25
+ self.pr_api_endpoint = "#{host}/_apis/git/repositories/#{slug}/pullRequests/#{pull_request_id}"
26
+ end
27
+
28
+ def supports_comments?
29
+ major_version = @api_version.split(".").first.to_i
30
+ minimum_version_for_comments = self.min_api_version_for_comments.split(".").first.to_i
31
+
32
+ major_version >= minimum_version_for_comments
33
+ end
34
+
35
+ def inspect
36
+ inspected = super
37
+
38
+ inspected.gsub!(@token, "********") if @token
39
+
40
+ inspected
41
+ end
42
+
43
+ def credentials_given?
44
+ @token && !@token.empty?
45
+ end
46
+
47
+ def pull_request(*)
48
+ fetch_pr_json
49
+ end
50
+
51
+ def fetch_pr_json
52
+ uri = URI("#{pr_api_endpoint}?api-version=#{@api_version}")
53
+ fetch_json(uri)
54
+ end
55
+
56
+ def fetch_last_comments
57
+ uri = URI("#{pr_api_endpoint}/threads?api-version=#{@api_version}")
58
+ fetch_json(uri)[:value]
59
+ end
60
+
61
+ def post_comment(text)
62
+ uri = URI("#{pr_api_endpoint}/threads?api-version=#{@api_version}")
63
+ body = {
64
+ "comments" => [
65
+ {
66
+ "parentCommentId" => 0,
67
+ "content" => text,
68
+ "commentType" => 1
69
+ }
70
+ ],
71
+ "properties" => {
72
+ "Microsoft.TeamFoundation.Discussion.SupportsMarkdown" => {
73
+ "type" => "System.Int32",
74
+ "value" => 1
75
+ }
76
+ },
77
+ "status" => 1
78
+ }.to_json
79
+ post(uri, body)
80
+ end
81
+
82
+ def post_inline_comment(text, file, line)
83
+ uri = URI("#{pr_api_endpoint}/threads?api-version=#{@api_version}")
84
+ body = {
85
+ "comments" => [
86
+ {
87
+ "parentCommentId" => 0,
88
+ "content" => text,
89
+ "commentType" => 1
90
+ }
91
+ ],
92
+ "properties" => {
93
+ "Microsoft.TeamFoundation.Discussion.SupportsMarkdown" => {
94
+ "type" => "System.Int32",
95
+ "value" => 1
96
+ }
97
+ },
98
+ "status" => 1,
99
+ "threadContext" => {
100
+ "filePath" => file,
101
+ "rightFileEnd" => {
102
+ "line" => line + 1,
103
+ "offset" => 1
104
+ },
105
+ "rightFileStart" => {
106
+ "line" => line,
107
+ "offset" => 1
108
+ }
109
+ }
110
+ }.to_json
111
+ post(uri, body)
112
+ end
113
+
114
+ def update_comment(thread, id, new_comment)
115
+ uri = URI("#{pr_api_endpoint}/threads/#{thread}/comments/#{id}?api-version=#{@api_version}")
116
+ body = {
117
+ "content" => new_comment
118
+ }.to_json
119
+ patch(uri, body)
120
+ end
121
+
122
+ private
123
+
124
+ def use_ssl
125
+ return self.pr_api_endpoint.include? "https://"
126
+ end
127
+
128
+ def fetch_json(uri)
129
+ req = Net::HTTP::Get.new(uri.request_uri, { "Content-Type" => "application/json", "Authorization" => "Basic #{@token}" })
130
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
131
+ http.request(req)
132
+ end
133
+ JSON.parse(res.body, symbolize_names: true)
134
+ end
135
+
136
+ def post(uri, body)
137
+ req = Net::HTTP::Post.new(uri.request_uri, { "Content-Type" => "application/json", "Authorization" => "Basic #{@token}" })
138
+ req.body = body
139
+
140
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
141
+ http.request(req)
142
+ end
143
+
144
+ # show error to the user when VSTS returned an error
145
+ case res
146
+ when Net::HTTPClientError, Net::HTTPServerError
147
+ # HTTP 4xx - 5xx
148
+ abort "\nError posting comment to VSTS: #{res.code} (#{res.message})\n\n"
149
+ end
150
+ end
151
+
152
+ def patch(uri, body)
153
+ puts uri
154
+ puts body
155
+
156
+ req = Net::HTTP::Patch.new(uri.request_uri, { "Content-Type" => "application/json", "Authorization" => "Basic #{@token}" })
157
+ req.body = body
158
+
159
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
160
+ http.request(req)
161
+ end
162
+
163
+ # show error to the user when VSTS returned an error
164
+ case res
165
+ when Net::HTTPClientError, Net::HTTPServerError
166
+ # HTTP 4xx - 5xx
167
+ abort "\nError updating comment on VSTS: #{res.code} (#{res.message})\n\n"
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end