highway 0.0.1 → 1.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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/lib/highway.rb +8 -4
  3. data/lib/highway/compiler/analyze/analyzer.rb +249 -0
  4. data/lib/highway/compiler/analyze/tree/root.rb +95 -0
  5. data/lib/highway/compiler/analyze/tree/segments/text.rb +36 -0
  6. data/lib/highway/compiler/analyze/tree/segments/variable.rb +43 -0
  7. data/lib/highway/compiler/analyze/tree/stage.rb +48 -0
  8. data/lib/highway/compiler/analyze/tree/step.rb +69 -0
  9. data/lib/highway/compiler/analyze/tree/values/array.rb +45 -0
  10. data/lib/highway/compiler/analyze/tree/values/base.rb +67 -0
  11. data/lib/highway/compiler/analyze/tree/values/hash.rb +45 -0
  12. data/lib/highway/compiler/analyze/tree/values/primitive.rb +43 -0
  13. data/lib/highway/compiler/analyze/tree/variable.rb +48 -0
  14. data/lib/highway/compiler/build/builder.rb +154 -0
  15. data/lib/highway/compiler/build/output/invocation.rb +70 -0
  16. data/lib/highway/compiler/build/output/manifest.rb +52 -0
  17. data/lib/highway/compiler/parse/parser.rb +92 -0
  18. data/lib/highway/compiler/parse/tree/root.rb +73 -0
  19. data/lib/highway/compiler/parse/tree/step.rb +62 -0
  20. data/lib/highway/compiler/parse/tree/variable.rb +48 -0
  21. data/lib/highway/compiler/parse/versions/v1.rb +110 -0
  22. data/lib/highway/compiler/suite.rb +56 -0
  23. data/lib/highway/environment.rb +282 -0
  24. data/lib/highway/fastlane/action.rb +67 -0
  25. data/lib/highway/interface.rb +135 -0
  26. data/lib/highway/main.rb +173 -0
  27. data/lib/highway/runtime/context.rb +229 -0
  28. data/lib/highway/runtime/report.rb +80 -0
  29. data/lib/highway/runtime/runner.rb +286 -0
  30. data/lib/highway/steps/infrastructure.rb +20 -0
  31. data/lib/highway/steps/library/action.rb +42 -0
  32. data/lib/highway/steps/library/appcenter.rb +106 -0
  33. data/lib/highway/steps/library/appstore.rb +137 -0
  34. data/lib/highway/steps/library/carthage.rb +67 -0
  35. data/lib/highway/steps/library/cocoapods.rb +76 -0
  36. data/lib/highway/steps/library/copy_artifacts.rb +36 -0
  37. data/lib/highway/steps/library/lane.rb +42 -0
  38. data/lib/highway/steps/library/sh.rb +36 -0
  39. data/lib/highway/steps/library/slack.rb +381 -0
  40. data/lib/highway/steps/library/testflight.rb +105 -0
  41. data/lib/highway/steps/library/xcode_archive.rb +162 -0
  42. data/lib/highway/steps/library/xcode_test.rb +264 -0
  43. data/lib/highway/steps/parameters/base.rb +52 -0
  44. data/lib/highway/steps/parameters/compound.rb +141 -0
  45. data/lib/highway/steps/parameters/single.rb +74 -0
  46. data/lib/highway/steps/registry.rb +92 -0
  47. data/lib/highway/steps/step.rb +52 -0
  48. data/lib/highway/steps/types/any.rb +65 -0
  49. data/lib/highway/steps/types/anyof.rb +64 -0
  50. data/lib/highway/steps/types/array.rb +44 -0
  51. data/lib/highway/steps/types/bool.rb +36 -0
  52. data/lib/highway/steps/types/enum.rb +44 -0
  53. data/lib/highway/steps/types/hash.rb +45 -0
  54. data/lib/highway/steps/types/number.rb +38 -0
  55. data/lib/highway/steps/types/set.rb +39 -0
  56. data/lib/highway/steps/types/string.rb +47 -0
  57. data/lib/highway/steps/types/url.rb +35 -0
  58. data/lib/highway/utilities.rb +51 -0
  59. data/lib/highway/version.rb +9 -1
  60. metadata +194 -22
  61. data/.gitignore +0 -4
  62. data/Gemfile +0 -4
  63. data/Rakefile +0 -1
  64. data/highway.gemspec +0 -24
@@ -0,0 +1,137 @@
1
+ #
2
+ # appstore.rb
3
+ # Copyright © 2019 Netguru S.A. All rights reserved.
4
+ #
5
+
6
+ require "fastlane"
7
+
8
+ require "highway/steps/infrastructure"
9
+
10
+ module Highway
11
+ module Steps
12
+ module Library
13
+
14
+ class AppStoreStep < Step
15
+
16
+ def self.name
17
+ "appstore"
18
+ end
19
+
20
+ def self.parameters
21
+ [
22
+ Parameters::Single.new(
23
+ name: "app_identifier",
24
+ required: true,
25
+ type: Types::String.new()
26
+ ),
27
+ Parameters::Single.new(
28
+ name: "build_number",
29
+ required: false,
30
+ type: Types::String.new()
31
+ ),
32
+ Parameters::Single.new(
33
+ name: "force",
34
+ required: false,
35
+ default: true,
36
+ type: Types::Bool.new()
37
+ ),
38
+ Parameters::Single.new(
39
+ name: "metadata_path",
40
+ required: false,
41
+ type: Types::String.new()
42
+ ),
43
+ Parameters::Single.new(
44
+ name: "password",
45
+ required: true,
46
+ type: Types::String.new()
47
+ ),
48
+ Parameters::Single.new(
49
+ name: "session",
50
+ required: false,
51
+ type: Types::String.new()
52
+ ),
53
+ Parameters::Single.new(
54
+ name: "screenshots_path",
55
+ required: false,
56
+ type: Types::String.new()
57
+ ),
58
+ Parameters::Single.new(
59
+ name: "skip_app_version_update",
60
+ required: false,
61
+ default: false,
62
+ type: Types::Bool.new()
63
+ ),
64
+ Parameters::Single.new(
65
+ name: "skip_binary_upload",
66
+ required: false,
67
+ default: false,
68
+ type: Types::Bool.new()
69
+ ),
70
+ Parameters::Single.new(
71
+ name: "submit_for_review",
72
+ required: false,
73
+ default: false,
74
+ type: Types::Bool.new()
75
+ ),
76
+ Parameters::Single.new(
77
+ name: "team_id",
78
+ required: false,
79
+ type: Types::String.new()
80
+ ),
81
+ Parameters::Single.new(
82
+ name: "team_name",
83
+ required: false,
84
+ type: Types::String.new()
85
+ ),
86
+ Parameters::Single.new(
87
+ name: "username",
88
+ required: true,
89
+ type: Types::String.regex(/^\S+@\S+\.\S+$/)
90
+ )
91
+ ]
92
+ end
93
+
94
+ def self.run(parameters:, context:, report:)
95
+
96
+ username = parameters["username"]
97
+ password = parameters["password"]
98
+ session = parameters["session"]
99
+ app_identifier = parameters["app_identifier"]
100
+ metadata_path = parameters["metadata_path"]
101
+ screenshots_path = parameters["screenshots_path"]
102
+ skip_app_version_update = parameters["skip_app_version_update"]
103
+ submit_for_review = parameters["submit_for_review"]
104
+ team_id = parameters["team_id"]
105
+ team_name = parameters["team_name"]
106
+ skip_binary_upload = parameters["skip_binary_upload"]
107
+ force = parameters["force"]
108
+ build_number = parameters["build_number"]
109
+
110
+ env = {
111
+ "FASTLANE_PASSWORD" => password,
112
+ "FASTLANE_SESSION" => session
113
+ }
114
+
115
+ context.with_modified_env(env) {
116
+ context.run_action("upload_to_app_store", options: {
117
+ username: username,
118
+ app_identifier: app_identifier,
119
+ metadata_path: metadata_path,
120
+ screenshots_path: screenshots_path,
121
+ skip_app_version_update: skip_app_version_update,
122
+ submit_for_review: submit_for_review,
123
+ team_id: team_id,
124
+ skip_binary_upload: skip_binary_upload,
125
+ build_number: build_number,
126
+ team_name: team_name,
127
+ force: force
128
+ })
129
+ }
130
+
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,67 @@
1
+ #
2
+ # carthage.rb
3
+ # Copyright © 2019 Netguru S.A. All rights reserved.
4
+ #
5
+
6
+ require "highway/steps/infrastructure"
7
+
8
+ module Highway
9
+ module Steps
10
+ module Library
11
+
12
+ class CarthageStep < Step
13
+
14
+ def self.name
15
+ "carthage"
16
+ end
17
+
18
+ def self.parameters
19
+ [
20
+ Parameters::Single.new(
21
+ name: "command",
22
+ required: false,
23
+ type: Types::Enum.new("bootstrap", "update"),
24
+ default: "bootstrap",
25
+ ),
26
+ Parameters::Single.new(
27
+ name: "github_token",
28
+ required: false,
29
+ type: Types::String.new(),
30
+ ),
31
+ Parameters::Single.new(
32
+ name: "platforms",
33
+ required: true,
34
+ type: Types::Set.new(Types::Enum.new("macos", "ios", "tvos", "watchos")),
35
+ ),
36
+ ]
37
+ end
38
+
39
+ def self.run(parameters:, context:, report:)
40
+
41
+ context.assert_executable_available!("carthage")
42
+
43
+ command = parameters["command"]
44
+ token = parameters["github_token"]
45
+
46
+ platform_map = {macos: "Mac", ios: "iOS", tvos: "tvOS", watchos: "watchOS"}
47
+ platform = parameters["platforms"].to_a.map { |p| platform_map[p.to_sym] }.join(",")
48
+
49
+ env = {
50
+ "GITHUB_ACCESS_TOKEN" => token
51
+ }
52
+
53
+ context.with_modified_env(env) {
54
+ context.run_action("carthage", options: {
55
+ cache_builds: true,
56
+ command: command,
57
+ platform: platform,
58
+ })
59
+ }
60
+
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,76 @@
1
+ #
2
+ # cocoapods.rb
3
+ # Copyright © 2019 Netguru S.A. All rights reserved.
4
+ #
5
+
6
+ require "highway/steps/infrastructure"
7
+
8
+ module Highway
9
+ module Steps
10
+ module Library
11
+
12
+ class CocoaPodsStep < Step
13
+
14
+ def self.name
15
+ "cocoapods"
16
+ end
17
+
18
+ def self.parameters
19
+ [
20
+ Parameters::Single.new(
21
+ name: "command",
22
+ required: false,
23
+ type: Types::Enum.new("install", "update"),
24
+ default: "install",
25
+ ),
26
+ Parameters::Single.new(
27
+ name: "update_specs_repo",
28
+ required: false,
29
+ type: Types::Enum.new("always", "never", "on-error"),
30
+ default: "never",
31
+ ),
32
+ ]
33
+ end
34
+
35
+ def self.run(parameters:, context:, report:)
36
+
37
+ context.assert_gem_available!("cocoapods")
38
+
39
+ command = parameters["command"]
40
+ update_specs_repo = parameters["update_specs_repo"]
41
+
42
+ repo_update_always = update_specs_repo == "always"
43
+ repo_update_on_error = update_specs_repo == "on-error"
44
+
45
+ if command == "install"
46
+
47
+ context.run_action("cocoapods", options: {
48
+ repo_update: repo_update_always,
49
+ try_repo_update_on_error: repo_update_on_error,
50
+ use_bundle_exec: context.should_use_bundle_exec?,
51
+ })
52
+
53
+ elsif command == "update"
54
+
55
+ invocation = []
56
+ invocation << "bundle exec" if context.should_use_bundle_exec?
57
+ invocation << "pod update"
58
+ invocation << "--repo-update" if repo_update_always
59
+
60
+ context.run_sh(invocation.join(" "), on_error: lambda { |error|
61
+ if repo_update_on_error
62
+ context.run_sh((invocation + ["--repo-update"]).join(" "))
63
+ else
64
+ context.reporter.fatal!(error)
65
+ end
66
+ })
67
+
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,36 @@
1
+ #
2
+ # copy_artifacts.rb
3
+ # Copyright © 2019 Netguru S.A. All rights reserved.
4
+ #
5
+
6
+ require "highway/steps/infrastructure"
7
+
8
+ module Highway
9
+ module Steps
10
+ module Library
11
+
12
+ class CopyArtifactsStep < Step
13
+
14
+ def self.name
15
+ "copy_artifacts"
16
+ end
17
+
18
+ def self.parameters
19
+ [
20
+ Parameters::Single.new(
21
+ name: "path",
22
+ required: true,
23
+ type: Types::String.new(),
24
+ )
25
+ ]
26
+ end
27
+
28
+ def self.run(parameters:, context:, report:)
29
+ FileUtils.copy_entry(context.artifacts_dir, parameters["path"], false, false, false)
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ #
2
+ # lane.rb
3
+ # Copyright © 2019 Netguru S.A. All rights reserved.
4
+ #
5
+
6
+ require "highway/steps/infrastructure"
7
+
8
+ module Highway
9
+ module Steps
10
+ module Library
11
+
12
+ class LaneStep < Step
13
+
14
+ def self.name
15
+ "lane"
16
+ end
17
+
18
+ def self.parameters
19
+ [
20
+ Parameters::Single.new(
21
+ name: "name",
22
+ required: true,
23
+ type: Types::String.new(),
24
+ ),
25
+ Parameters::Single.new(
26
+ name: "options",
27
+ required: false,
28
+ type: Types::Hash.new(Types::Any.new()),
29
+ default: {},
30
+ ),
31
+ ]
32
+ end
33
+
34
+ def self.run(parameters:, context:, report:)
35
+ context.run_lane(parameters["name"], options: parameters["options"])
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ #
2
+ # sh.rb
3
+ # Copyright © 2019 Netguru S.A. All rights reserved.
4
+ #
5
+
6
+ require "highway/steps/infrastructure"
7
+
8
+ module Highway
9
+ module Steps
10
+ module Library
11
+
12
+ class ShStep < Step
13
+
14
+ def self.name
15
+ "sh"
16
+ end
17
+
18
+ def self.parameters
19
+ [
20
+ Parameters::Single.new(
21
+ name: "command",
22
+ required: true,
23
+ type: Types::String.new(),
24
+ ),
25
+ ]
26
+ end
27
+
28
+ def self.run(parameters:, context:, report:)
29
+ context.run_sh(parameters["command"])
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,381 @@
1
+ #
2
+ # slack.rb
3
+ # Copyright © 2019 Netguru S.A. All rights reserved.
4
+ #
5
+
6
+ require "fastlane"
7
+ require "slack-notifier"
8
+
9
+ require "highway/steps/infrastructure"
10
+
11
+ module Highway
12
+ module Steps
13
+ module Library
14
+
15
+ class SlackStep < Step
16
+
17
+ def self.name
18
+ "slack"
19
+ end
20
+
21
+ def self.parameters
22
+ [
23
+ Parameters::Single.new(
24
+ name: "avatar",
25
+ required: false,
26
+ type: Types::AnyOf.new(
27
+ url: Types::Url.new(),
28
+ emoji: Types::String.regex(/\:[a-z0-9_-]+\:/),
29
+ ),
30
+ ),
31
+ Parameters::Single.new(
32
+ name: "channel",
33
+ required: true,
34
+ type: Types::String.regex(/#[a-z0-9_-]+/)
35
+ ),
36
+ Parameters::Single.new(
37
+ name: "username",
38
+ required: false,
39
+ type: Types::String.new(),
40
+ default: "Highway",
41
+ ),
42
+ Parameters::Single.new(
43
+ name: "webhook",
44
+ required: true,
45
+ type: Types::Url.new(),
46
+ ),
47
+ ]
48
+ end
49
+
50
+ def self.run(parameters:, context:, report:)
51
+
52
+ username = parameters["username"]
53
+ webhook = parameters["webhook"].to_s
54
+ avatar_emoji = parameters["avatar"][:value] if parameters["avatar"]&[:tag] == :emoji
55
+ avatar_url = parameters["avatar"][:value].to_s if parameters["avatar"]&[:tag] == :url
56
+
57
+ attachments = [
58
+ generate_build_attachments(context),
59
+ generate_tests_attachments(context),
60
+ generate_deployment_attachments(context),
61
+ ]
62
+
63
+ attachments = attachments.flatten.compact
64
+
65
+ attachments.each { |attachment|
66
+ attachment[:mrkdwn_in] = [:text, :fields]
67
+ attachment[:fallback] ||= attachment.fetch(:fields, []).reduce([]) { |memo, field| memo + ["#{field[:title]}: #{field[:value]}."] }.join(" ")
68
+ }
69
+
70
+ attachments.each { |attachment|
71
+ attachment[:fields].each { |field| field[:value] = Slack::Notifier::Util::LinkFormatter.format(field[:value]) }
72
+ attachment[:fallback] = Slack::Notifier::Util::LinkFormatter.format(attachment[:fallback])
73
+ }
74
+
75
+ notifier = Slack::Notifier.new(webhook)
76
+
77
+ notifier.post({
78
+ username: username,
79
+ icon_emoji: avatar_emoji,
80
+ icon_url: avatar_url,
81
+ attachments: attachments,
82
+ })
83
+
84
+ context.interface.success("Successfully posted a report message on Slack.")
85
+
86
+ end
87
+
88
+ private
89
+
90
+ def self.generate_build_attachments(context)
91
+
92
+ # Generate main field containing information about build status
93
+ # and its identifier.
94
+
95
+ main_title = context.reports_any_failed? ? "Build failed" : "Build succeeded"
96
+
97
+ if context.env.ci?
98
+ if context.env.ci_build_number != nil
99
+ if context.env.ci_build_url != nil
100
+ main_value = "[#{context.env.ci_build_number}](#{context.env.ci_build_url})"
101
+ else
102
+ main_value = "#{context.env.ci_build_number}"
103
+ end
104
+ else
105
+ main_value = "(unknown build)"
106
+ end
107
+ else
108
+ main_value = "(local build)"
109
+ end
110
+
111
+ # Generate duration field, rounding and ceiling it to minutes, if
112
+ # possible.
113
+
114
+ duration_title = "Duration"
115
+
116
+ if context.duration_so_far > 60
117
+ duration_value = "#{(context.duration_so_far.to_f / 60).ceil} minutes"
118
+ else
119
+ duration_value = "#{context.duration_so_far} seconds"
120
+ end
121
+
122
+ # Generate pull request field, containing number, title and a URL,
123
+ # if possible.
124
+
125
+ pr_title = "Pull Request"
126
+
127
+ if context.env.ci_trigger == :pr && context.env.git_pr_number != nil
128
+ if context.env.git_pr_url != nil
129
+ if context.env.git_pr_title != nil
130
+ pr_value = "[##{context.env.git_pr_number}: #{context.env.git_pr_title}](#{context.env.git_pr_url})"
131
+ else
132
+ pr_value = "[##{context.env.git_pr_number}](#{context.env.git_pr_url})"
133
+ end
134
+ else
135
+ if context.env.git_pr_title != nil
136
+ pr_value = "##{context.env.git_pr_number}: #{context.env.git_pr_title}"
137
+ else
138
+ pr_value = "##{context.env.git_pr_number}"
139
+ end
140
+ end
141
+ end
142
+
143
+ # Generate commit field, containing hash, message and a URL, if
144
+ # possible.
145
+
146
+ commit_title = "Commit"
147
+
148
+ if context.env.git_commit_hash != nil
149
+ if context.env.git_commit_message != nil
150
+ commit_value = "#{context.env.git_commit_hash[0,7]}: #{context.env.git_commit_message}"
151
+ else
152
+ commit_value = "#{context.env.git_commit_hash}"
153
+ end
154
+ end
155
+
156
+ # Infer the attachment color.
157
+
158
+ attachment_color = context.reports_any_failed? ? "danger" : "good"
159
+
160
+ # Assemble the attachment.
161
+
162
+ attachment_fields = []
163
+ attachment_fields << {title: main_title, value: main_value, short: true} if main_value != nil
164
+ attachment_fields << {title: duration_title, value: duration_value, short: true} if duration_value != nil
165
+ attachment_fields << {title: pr_title, value: pr_value, short: false} if pr_value != nil
166
+ attachment_fields << {title: commit_title, value: commit_value, short: false} if commit_value != nil
167
+
168
+ {
169
+ color: attachment_color,
170
+ fields: attachment_fields,
171
+ }
172
+
173
+ end
174
+
175
+ def self.generate_tests_attachments(context)
176
+
177
+ # Skip if there are no test reports.
178
+
179
+ report = prepare_tests_report(context)
180
+ return nil unless report != nil
181
+
182
+ # Prepare variables.
183
+
184
+ result = :error if !report[:errors].empty?
185
+ result ||= :failure if !report[:failures].empty?
186
+ result ||= :success
187
+
188
+ errors = report[:errors]
189
+ failures = report[:failures]
190
+ count = report[:count]
191
+
192
+ # Generate main field containing the information about the result and
193
+ # counts of all, successful and failed tests.
194
+
195
+ main_title = case result
196
+ when :success then "Tests succeeded"
197
+ when :failure, :error then "Tests failed"
198
+ end
199
+
200
+ main_value = "Executed #{count[:all]} tests: #{count[:succeeded]} succeeded, #{count[:failed]} failed."
201
+
202
+ # Generate errors field containing first three error locations and
203
+ # reasons.
204
+
205
+ unless errors.empty?
206
+
207
+ errors_title = "Compile errors"
208
+
209
+ errors_messages = errors.first(3).map { |error|
210
+ if error[:location]
211
+ "```#{error[:location]}: #{error[:reason]}```"
212
+ else
213
+ "```#{error[:reason]}```"
214
+ end
215
+ }
216
+
217
+ errors_value = errors_messages.join("\n")
218
+ errors_value << "\nand #{errors.count - 3} more..." if errors.count > 3
219
+
220
+ end
221
+
222
+ # Generate failures field containing first three failure locations and
223
+ # reasons.
224
+
225
+ unless failures.empty?
226
+
227
+ failures_title = "Failing test cases"
228
+
229
+ failures_messages = failures.first(3).map { |failure|
230
+ "```#{failure[:location]}: #{failure[:reason]}```"
231
+ }
232
+
233
+ failures_value = failures_messages.join("\n")
234
+ failures_value << "\nand #{failures.count - 3} more..." if failures.count > 3
235
+
236
+ end
237
+
238
+ # Generate the fallback value.
239
+
240
+ attachment_fallback = case result
241
+ when :success then "Tests succeeded. #{main_value}"
242
+ when :failure then "Tests failed. #{main_value}"
243
+ when :error then "Tests failed. One or more compiler errors occured."
244
+ end
245
+
246
+ # Infer the attachment color.
247
+
248
+ attachment_color = case result
249
+ when :success then "good"
250
+ when :failure, :error then "danger"
251
+ end
252
+
253
+ # Assemble the attachment.
254
+
255
+ attachment_fields = []
256
+ attachment_fields << {title: main_title, value: main_value, short: false} if main_value != nil
257
+ attachment_fields << {title: errors_title, value: errors_value, short: false} if errors_value != nil
258
+ attachment_fields << {title: failures_title, value: failures_value, short: false} if failures_value != nil
259
+
260
+ {
261
+ color: attachment_color,
262
+ fields: attachment_fields,
263
+ fallback: attachment_fallback,
264
+ }
265
+
266
+ end
267
+
268
+ def self.prepare_tests_report(context)
269
+
270
+ return nil if context.test_reports.empty?
271
+
272
+ zero_report = {
273
+ errors: [],
274
+ failures: [],
275
+ count: {all: 0, failed: 0, succeeded: 0}
276
+ }
277
+
278
+ merged_results = context.test_reports.map { |report|
279
+ report[:result]
280
+ }
281
+
282
+ merged_report = context.test_reports.reduce(zero_report) { |memo, report|
283
+ {
284
+ errors: memo[:errors] + report[:test][:errors],
285
+ failures: memo[:failures] + report[:test][:failures],
286
+ count: {
287
+ all: memo[:count][:all] + report[:test][:count][:all],
288
+ failed: memo[:count][:failed] + report[:test][:count][:failed],
289
+ succeeded: memo[:count][:succeeded] + report[:test][:count][:succeeded],
290
+ }
291
+ }
292
+ }
293
+
294
+ merged_report[:result] = merged_results.each_cons(2) { |lhs, rhs|
295
+ if lhs == :error || rhs == :error
296
+ :error
297
+ elsif lhs == :failure || rhs == :failure
298
+ :failure
299
+ elsif lhs == :succeess && rhs == :success
300
+ :success
301
+ else
302
+ :failure
303
+ end
304
+ }
305
+
306
+ merged_report
307
+
308
+ end
309
+
310
+ def self.generate_deployment_attachments(context)
311
+
312
+ # Skip if there are no deployment reports.
313
+
314
+ reports = context.deployment_reports
315
+ return nil if reports.empty?
316
+
317
+ # Map reports into attachments.
318
+
319
+ attachments = reports.map { |report|
320
+ prepare_deployment_attachment(report[:deployment])
321
+ }
322
+
323
+ attachments
324
+
325
+ end
326
+
327
+ def self.prepare_deployment_attachment(report)
328
+
329
+ # Generate main field containing the information about deployment
330
+ # result and information.
331
+
332
+ main_title = "Deployment succeeded"
333
+
334
+ package_comps = []
335
+ package_comps << report[:package][:name]
336
+ package_comps << report[:package][:version]
337
+ package_comps << "(#{report[:package][:build]})" if report[:package][:build] != nil
338
+
339
+ package_value = package_comps.compact.join(" ").strip
340
+ package_value = "(unknown package)" if package_value.empty?
341
+
342
+ main_value = "Successfully deployed #{package_value} to #{report[:service]}."
343
+
344
+ # Generate install button pointing to the installation page.
345
+
346
+ install_title = "Install"
347
+ install_url = report[:urls][:install]
348
+
349
+ # Generate manage button pointing to the view page.
350
+
351
+ view_title = "View"
352
+ view_url = report[:urls][:view]
353
+
354
+ # Generate the attachment fallback value and color.
355
+
356
+ attachment_fallback = "Deployment succeeded. #{main_value}"
357
+ attachment_color = "good"
358
+
359
+ # Assemble the attachment.
360
+
361
+ attachment_fields = []
362
+ attachment_fields << {title: main_title, value: main_value, short: false} if main_value != nil
363
+
364
+ attachment_actions = []
365
+ attachment_actions << {type: "button", style: "primary", text: install_title, url: install_url} if install_url != nil
366
+ attachment_actions << {type: "button", style: "default", text: view_title, url: view_url} if view_url != nil
367
+
368
+ {
369
+ color: attachment_color,
370
+ fields: attachment_fields,
371
+ actions: attachment_actions,
372
+ fallback: attachment_fallback,
373
+ }
374
+
375
+ end
376
+
377
+ end
378
+
379
+ end
380
+ end
381
+ end