allure-report-publisher 0.0.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,7 +4,7 @@ module Publisher
4
4
  module Providers
5
5
  # Gitlab implementation
6
6
  #
7
- class Gitlab < Base
7
+ class Gitlab < Provider
8
8
  # Get ci run ID without creating instance of ci provider
9
9
  #
10
10
  # @return [String]
@@ -12,6 +12,13 @@ module Publisher
12
12
  @run_id ||= ENV["CI_PIPELINE_ID"]
13
13
  end
14
14
 
15
+ # Pull request run
16
+ #
17
+ # @return [Boolean]
18
+ def pr?
19
+ ENV["CI_PIPELINE_SOURCE"] == "merge_request_event"
20
+ end
21
+
15
22
  # Get executor info
16
23
  #
17
24
  # @return [Hash]
@@ -28,6 +35,8 @@ module Publisher
28
35
  }
29
36
  end
30
37
 
38
+ private
39
+
31
40
  # Current pull request description
32
41
  #
33
42
  # @return [String]
@@ -37,10 +46,27 @@ module Publisher
37
46
 
38
47
  # Update pull request description
39
48
  #
40
- # @param [String] desc
41
49
  # @return [void]
42
- def update_pr_description(desc)
43
- client.update_merge_request(project, mr_iid, description: desc)
50
+ def update_pr_description
51
+ client.update_merge_request(project, mr_iid, description: report_urls.updated_pr_description(pr_description))
52
+ end
53
+
54
+ # Add comment with report url
55
+ #
56
+ # @return [void]
57
+ def add_comment
58
+ return client.create_merge_request_comment(project, mr_iid, report_urls.comment_body) unless comment
59
+
60
+ client.edit_merge_request_note(project, mr_iid, comment.id, report_urls.comment_body(comment.body))
61
+ end
62
+
63
+ # Existing comment with allure urls
64
+ #
65
+ # @return [Gitlab::ObjectifiedHash]
66
+ def comment
67
+ client.merge_request_comments(project, mr_iid).auto_paginate.detect do |comment|
68
+ UrlSectionBuilder.match?(comment.body)
69
+ end
44
70
  end
45
71
 
46
72
  # Get gitlab client
@@ -57,13 +83,6 @@ module Publisher
57
83
  end
58
84
  end
59
85
 
60
- # Pull request run
61
- #
62
- # @return [Boolean]
63
- def pr?
64
- ENV["CI_PIPELINE_SOURCE"] == "merge_request_event"
65
- end
66
-
67
86
  # Merge request iid
68
87
  #
69
88
  # @return [Integer]
@@ -89,15 +108,25 @@ module Publisher
89
108
  #
90
109
  # @return [String]
91
110
  def build_name
92
- @build_name ||= ENV["CI_JOB_NAME"]
111
+ @build_name ||= ENV[ALLURE_JOB_NAME] || ENV["CI_JOB_NAME"]
93
112
  end
94
113
 
95
- # Github repository
114
+ # Gitlab repository
96
115
  #
97
116
  # @return [String]
98
117
  def project
99
118
  @project ||= ENV["CI_PROJECT_PATH"]
100
119
  end
120
+
121
+ # Commit sha url
122
+ #
123
+ # @return [String]
124
+ def sha_url
125
+ sha = ENV["CI_MERGE_REQUEST_SOURCE_BRANCH_SHA"] || ENV["CI_COMMIT_SHA"]
126
+ short_sha = sha[0..7]
127
+
128
+ "[#{short_sha}](#{server_url}/#{project}/-/merge_requests/#{mr_iid}/diffs?commit_id=#{sha})"
129
+ end
101
130
  end
102
131
  end
103
132
  end
@@ -0,0 +1,97 @@
1
+ module Publisher
2
+ module Providers
3
+ # Urls section builder
4
+ #
5
+ class UrlSectionBuilder
6
+ DESCRIPTION_PATTERN = /<!-- allure -->[\s\S]+<!-- allurestop -->/.freeze
7
+ JOBS_PATTERN = /<!-- jobs -->\n([\s\S]+)\n<!-- jobs -->/.freeze
8
+
9
+ def initialize(report_url:, build_name:, sha_url:)
10
+ @report_url = report_url
11
+ @build_name = build_name
12
+ @sha_url = sha_url
13
+ end
14
+
15
+ # Matches url section pattern
16
+ #
17
+ # @param [String] urls_block
18
+ # @return [Boolean]
19
+ def self.match?(urls_block)
20
+ urls_block.match?(DESCRIPTION_PATTERN)
21
+ end
22
+
23
+ # Get urls for PR update
24
+ #
25
+ # @param [String] pr
26
+ # @return [String]
27
+ def updated_pr_description(pr_description)
28
+ return "#{pr_description}\n\n#{body}" unless pr_description.match?(DESCRIPTION_PATTERN)
29
+
30
+ job_entries = jobs_section(pr_description)
31
+ pr_description.gsub(DESCRIPTION_PATTERN, body(job_entries))
32
+ end
33
+
34
+ # Allure report url comment
35
+ #
36
+ # @return [String]
37
+ def comment_body(pr_comment = nil)
38
+ return body.gsub("---\n", "") unless pr_comment
39
+
40
+ job_entries = jobs_section(pr_comment)
41
+ body(job_entries).gsub("---\n", "")
42
+ end
43
+
44
+ attr_reader :report_url, :build_name, :sha_url
45
+
46
+ private
47
+
48
+ # Allure report url pr description
49
+ #
50
+ # @return [String]
51
+ def body(job_entries = "**#{build_name}**: 📝 [allure report](#{report_url})")
52
+ @body ||= <<~BODY.strip
53
+ <!-- allure -->
54
+ ---
55
+ #{heading}
56
+
57
+ <!-- jobs -->
58
+ #{job_entries}
59
+ <!-- jobs -->
60
+ <!-- allurestop -->
61
+ BODY
62
+ end
63
+
64
+ # Url section heading
65
+ #
66
+ # @return [String]
67
+ def heading
68
+ @heading ||= "# Allure report\n`allure-report-publisher` generated allure report for #{sha_url}!"
69
+ end
70
+
71
+ # Return updated jobs section
72
+ #
73
+ # @param [String] urls
74
+ # @return [String]
75
+ def jobs_section(urls_block)
76
+ jobs = urls_block.match(JOBS_PATTERN)[1]
77
+ return jobs.gsub(job_entry_pattern, job_entry) if jobs.match?(job_entry_pattern)
78
+
79
+ "#{jobs}\n#{job_entry}"
80
+ end
81
+
82
+ # Single job report URL entry
83
+ #
84
+ # @return [String]
85
+ def job_entry
86
+ @job_entry ||= "**#{build_name}**: 📝 [allure report](#{report_url})"
87
+ end
88
+
89
+ # Job entry pattern
90
+ #
91
+ # @return [RegExp]
92
+ def job_entry_pattern
93
+ @job_entry_pattern ||= /^\*\*#{build_name}\*\*:.*\[allure report\]\(.*\)$/
94
+ end
95
+ end
96
+ end
97
+ end
@@ -10,35 +10,32 @@ module Publisher
10
10
  class ReportGenerator
11
11
  include Helpers
12
12
 
13
- def initialize(results_glob, results_dir, report_dir)
13
+ def initialize(results_glob, results_path, report_path)
14
14
  @results_glob = results_glob
15
- @results_dir = results_dir
16
- @report_dir = report_dir
15
+ @results_path = results_path
16
+ @report_path = report_path
17
17
  end
18
18
 
19
19
  # Generate allure report
20
20
  #
21
21
  # @return [void]
22
22
  def generate
23
- log("Generating allure report")
24
- Helpers::Spinner.spin("generating report") do
25
- aggregate_results
26
- generate_report
27
- end
23
+ aggregate_results
24
+ generate_report
28
25
  end
29
26
 
30
27
  private
31
28
 
32
- attr_reader :results_glob, :results_dir, :report_dir
29
+ attr_reader :results_glob, :results_path, :report_path
33
30
 
34
31
  # Copy all results files to results directory
35
32
  #
36
33
  # @return [void]
37
34
  def aggregate_results
38
35
  results = Dir.glob(results_glob)
39
- error("Missing allure results") if results.empty?
36
+ raise(NoAllureResultsError, "Missing allure results") if results.empty?
40
37
 
41
- FileUtils.cp(results, results_dir)
38
+ FileUtils.cp(results, results_path)
42
39
  end
43
40
 
44
41
  # Generate allure report
@@ -46,9 +43,9 @@ module Publisher
46
43
  # @return [void]
47
44
  def generate_report
48
45
  out, _err, status = Open3.capture3(
49
- "allure generate --clean --output #{report_dir} #{results_dir}"
46
+ "allure generate --clean --output #{report_path} #{results_path}"
50
47
  )
51
- error(out) unless status.success?
48
+ raise(AllureError, out) unless status.success?
52
49
  end
53
50
  end
54
51
  end
@@ -1,10 +1,13 @@
1
1
  module Publisher
2
2
  module Uploaders
3
+ class HistoryNotFoundError < StandardError; end
4
+
3
5
  # Uploader implementation
4
6
  #
5
7
  class Uploader
6
8
  include Helpers
7
9
 
10
+ EXECUTOR_JSON = "executor.json".freeze
8
11
  HISTORY = [
9
12
  "categories-trend.json",
10
13
  "duration-trend.json",
@@ -13,38 +16,76 @@ module Publisher
13
16
  "retry-trend.json"
14
17
  ].freeze
15
18
 
16
- def initialize(results_glob:, bucket:, update_pr: false, prefix: nil, copy_latest: false)
19
+ def initialize(results_glob:, bucket:, update_pr: nil, prefix: nil, copy_latest: false)
17
20
  @results_glob = results_glob
18
- @bucket = bucket
21
+ @bucket_name = bucket
19
22
  @prefix = prefix
20
23
  @update_pr = update_pr
21
- @copy_latest = Providers.provider && copy_latest # copy latest for ci only
24
+ @copy_latest = !!(Providers.provider && copy_latest) # copy latest for ci only
22
25
  end
23
26
 
24
- # :nocov:
25
-
26
27
  # Execute allure report generation and upload
27
28
  #
28
29
  # @return [void]
29
30
  def execute
30
- check_client_configured
31
-
32
- generate
31
+ generate_report
33
32
  upload
34
33
  add_url_to_pr
35
- rescue StandardError => e
36
- error(e.message)
37
34
  end
38
35
 
39
- private
36
+ # Generate allure report
37
+ #
38
+ # @return [void]
39
+ def generate_report
40
+ add_history
41
+ add_executor_info
40
42
 
41
- attr_reader :results_glob, :bucket, :prefix, :update_pr, :copy_latest
43
+ ReportGenerator.new(results_glob, results_path, report_path).generate
44
+ end
42
45
 
43
- # Validate if client is properly configured
44
- # and raise error if it is not
46
+ # Upload report to storage provider
47
+ #
48
+ # @return [void]
49
+ def upload
50
+ run_uploads
51
+ end
52
+
53
+ # Add allure report url to pull request description
45
54
  #
46
55
  # @return [void]
47
- def check_client_configured
56
+ def add_url_to_pr
57
+ return unless update_pr && ci_provider
58
+
59
+ ci_provider.add_report_url
60
+ end
61
+
62
+ # Uploaded report urls
63
+ #
64
+ # @return [Hash<String, String>] uploaded report urls
65
+ def report_urls
66
+ urls = { "Report url" => report_url }
67
+ urls["Latest report url"] = latest_report_url if copy_latest
68
+
69
+ urls
70
+ end
71
+
72
+ # Executed in PR pipeline
73
+ #
74
+ # @return [Boolean]
75
+ def pr?
76
+ ci_provider&.pr?
77
+ end
78
+
79
+ private
80
+
81
+ attr_reader :results_glob, :bucket_name, :prefix, :update_pr, :copy_latest
82
+
83
+ # :nocov:
84
+
85
+ # Cloud provider client
86
+ #
87
+ # @return [Object]
88
+ def client
48
89
  raise("Not Implemented!")
49
90
  end
50
91
 
@@ -55,6 +96,20 @@ module Publisher
55
96
  raise("Not Implemented!")
56
97
  end
57
98
 
99
+ # Latest report url
100
+ #
101
+ # @return [String]
102
+ def latest_report_url
103
+ raise("Not Implemented!")
104
+ end
105
+
106
+ # Download allure history
107
+ #
108
+ # @return [void]
109
+ def download_history
110
+ raise("Not implemented!")
111
+ end
112
+
58
113
  # Upload history to s3
59
114
  #
60
115
  # @return [void]
@@ -81,11 +136,10 @@ module Publisher
81
136
  #
82
137
  # @return [void]
83
138
  def add_history
84
- log("Adding allure history")
85
- Helpers::Spinner.spin("adding history", exit_on_error: false) do
86
- create_history_dir
87
- yield
88
- end
139
+ create_history_dir
140
+ download_history
141
+ rescue HistoryNotFoundError
142
+ nil
89
143
  end
90
144
 
91
145
  # Add CI executor info
@@ -94,53 +148,20 @@ module Publisher
94
148
  def add_executor_info
95
149
  return unless ci_provider
96
150
 
97
- log("Adding executor info")
98
- Helpers::Spinner.spin("adding executor") do
99
- ci_provider.write_executor_info
151
+ File.open("#{results_path}/#{EXECUTOR_JSON}", "w") do |file|
152
+ file.write(ci_provider.executor_info.to_json)
100
153
  end
101
154
  end
102
155
 
103
- # Generate allure report
104
- #
105
- # @return [void]
106
- def generate
107
- add_history
108
- add_executor_info
109
-
110
- ReportGenerator.new(results_glob, results_dir, report_dir).generate
111
- end
112
-
113
- # Upload report to storage provider
114
- #
115
- # @return [void]
116
- def upload
117
- log("Uploading report")
118
- Helpers::Spinner.spin("uploading report") { run_uploads }
119
- log("Run report: #{report_url}", :green)
120
- log("Latest report: #{latest_report_url}", :green) if copy_latest
121
- end
122
-
123
156
  # Run upload commands
124
157
  #
125
158
  # @return [void]
126
159
  def run_uploads
127
- upload_history unless copy_latest # latest report will add a common history folder
160
+ upload_history unless !run_id || copy_latest
128
161
  upload_report
129
162
  upload_latest_copy if copy_latest
130
163
  end
131
164
 
132
- # Add allure report url to pull request description
133
- #
134
- # @return [void]
135
- def add_url_to_pr
136
- return unless update_pr && ci_provider
137
-
138
- log("Adding allure report link to pr description")
139
- Helpers::Spinner.spin("adding link", exit_on_error: false) do
140
- ci_provider.add_report_url
141
- end
142
- end
143
-
144
165
  # Get run id
145
166
  #
146
167
  # @return [String]
@@ -154,14 +175,14 @@ module Publisher
154
175
  def ci_provider
155
176
  return @ci_provider if defined?(@ci_provider)
156
177
 
157
- @ci_provider = Providers.provider&.new(results_dir, report_url)
178
+ @ci_provider = Providers.provider&.new(report_url: report_url, update_pr: update_pr)
158
179
  end
159
180
 
160
181
  # Fetch allure report history
161
182
  #
162
183
  # @return [void]
163
184
  def create_history_dir
164
- FileUtils.mkdir_p(path(results_dir, "history"))
185
+ FileUtils.mkdir_p(path(results_path, "history"))
165
186
  end
166
187
 
167
188
  # Report path prefix
@@ -178,15 +199,15 @@ module Publisher
178
199
  # Aggregated results directory
179
200
  #
180
201
  # @return [String]
181
- def results_dir
182
- @results_dir ||= Dir.mktmpdir("allure-results")
202
+ def results_path
203
+ @results_path ||= Dir.mktmpdir("allure-results")
183
204
  end
184
205
 
185
206
  # Allure report directory
186
207
  #
187
208
  # @return [String]
188
- def report_dir
189
- @report_dir ||= Dir.mktmpdir("allure-report")
209
+ def report_path
210
+ @report_path ||= Dir.mktmpdir("allure-report")
190
211
  end
191
212
 
192
213
  # Report files
@@ -194,7 +215,7 @@ module Publisher
194
215
  # @return [Array<Pathname>]
195
216
  def report_files
196
217
  @report_files ||= Pathname
197
- .glob("#{report_dir}/**/*")
218
+ .glob("#{report_path}/**/*")
198
219
  .reject(&:directory?)
199
220
  end
200
221
  end