apadmi_grout 1.1.0 → 2.1.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/CHANGELOG.md +11 -0
- data/lib/apadmi/grout/actions/find_tickets_to_move_action/find_tickets_to_move_action.rb +24 -0
- data/lib/apadmi/grout/actions/find_tickets_to_move_action/find_tickets_to_move_ado_action.rb +91 -0
- data/lib/apadmi/grout/actions/find_tickets_to_move_action/find_tickets_to_move_jira_action.rb +114 -0
- data/lib/apadmi/grout/actions/generate_release_notes_action/generate_release_notes_action.rb +48 -0
- data/lib/apadmi/grout/actions/generate_release_notes_action/issue_classifier.rb +51 -0
- data/lib/apadmi/grout/{release_notes/actions → actions}/issues_from_changelog_action.rb +11 -8
- data/lib/apadmi/grout/actions/move_tickets_action.rb +34 -0
- data/lib/apadmi/grout/di.rb +59 -9
- data/lib/apadmi/grout/{jira/models/find_tickets_options.rb → models/ado_config.rb} +2 -2
- data/lib/apadmi/grout/models/bitrise.rb +38 -0
- data/lib/apadmi/grout/models/find_tickets_options.rb +44 -0
- data/lib/apadmi/grout/{jira/models → models}/flag_messages.rb +2 -1
- data/lib/apadmi/grout/models/issue.rb +49 -0
- data/lib/apadmi/grout/{jira/models → models}/pull_request.rb +0 -0
- data/lib/apadmi/grout/models/release_notes_config.rb +47 -0
- data/lib/apadmi/grout/{release_notes/models → models}/release_notes_templates.rb +6 -6
- data/lib/apadmi/grout/service/bitrise_service/bitrise_service.rb +103 -0
- data/lib/apadmi/grout/service/board_service/ado_board_service.rb +199 -0
- data/lib/apadmi/grout/service/board_service/board_service.rb +59 -0
- data/lib/apadmi/grout/{jira/wrapper/jira_wrapper.rb → service/board_service/jira_board_service.rb} +65 -107
- data/lib/apadmi/grout/utils/git_utils.rb +36 -0
- data/lib/apadmi/grout/utils/network_service.rb +123 -0
- data/lib/apadmi/grout/version.rb +1 -1
- data/lib/apadmi_grout.rb +3 -21
- metadata +23 -14
- data/lib/apadmi/grout/jira/actions/find_tickets_to_move_action.rb +0 -80
- data/lib/apadmi/grout/jira/actions/move_jira_tickets_action.rb +0 -58
- data/lib/apadmi/grout/jira/models/version.rb +0 -23
- data/lib/apadmi/grout/release_notes/actions/generate_release_notes_action.rb +0 -39
- data/lib/apadmi/grout/release_notes/models/release_notes_config.rb +0 -74
File without changes
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apadmi
|
4
|
+
module Grout
|
5
|
+
# @param tasks [Array<Apadmi::Grout::Issue>]
|
6
|
+
# @param features [Array<Apadmi::Grout::Issue>] aka stories
|
7
|
+
# @param improvements [Array<Apadmi::Grout::Issue>]
|
8
|
+
# @param defects [Array<Apadmi::Grout::Issue>] aka bugs
|
9
|
+
# @param others [Array<Apadmi::Grout::Issue>] any other non-standard issue types
|
10
|
+
ClassifiedIssues = Struct.new(
|
11
|
+
:tasks,
|
12
|
+
:features,
|
13
|
+
:improvements,
|
14
|
+
:defects,
|
15
|
+
:others
|
16
|
+
) do
|
17
|
+
# @return returns true if all categories are empty
|
18
|
+
def empty
|
19
|
+
tasks.empty? && features.empty? &&
|
20
|
+
improvements.empty? && defects.empty? && others.empty?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param title [String] The title of the document
|
25
|
+
# @param app_version [String] The app version pertaining to this release
|
26
|
+
# @param min_os_version [String] The min supported os version of this release
|
27
|
+
# @param date [String] Today's date
|
28
|
+
# @param moved_issues [Array<Apadmi::Grout::Issue>] Issues moved to a new state by this build job
|
29
|
+
# @param release_issues [Array<Apadmi::Grout::Issue>] Issues considered part of this release
|
30
|
+
# @param commit_hash [String] Commit hash from which release was built
|
31
|
+
# @param ci_build_number [String] CI build number which built the release
|
32
|
+
# @param ci_build_url [String] Link to CI build job
|
33
|
+
# @param templates [Apadmi::Grout::Templates] Mustache templates to use to generate the document
|
34
|
+
ReleaseNotesConfig = Struct.new(
|
35
|
+
:title,
|
36
|
+
:app_version,
|
37
|
+
:min_os_version,
|
38
|
+
:date,
|
39
|
+
:moved_issues,
|
40
|
+
:release_issues,
|
41
|
+
:commit_hash,
|
42
|
+
:ci_build_number,
|
43
|
+
:ci_build_url,
|
44
|
+
:templates
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
@@ -9,7 +9,7 @@ module Apadmi
|
|
9
9
|
|
10
10
|
### Version
|
11
11
|
{{config.app_version}}
|
12
|
-
|
12
|
+
|
13
13
|
### Release date
|
14
14
|
{{config.date}}
|
15
15
|
|
@@ -30,35 +30,35 @@ Commit Hash: {{config.commit_hash}}
|
|
30
30
|
%{### New functionality
|
31
31
|
{{# classified_issues.features.first}}
|
32
32
|
{{# classified_issues.features}}
|
33
|
-
* [{{ key }} - {{ summary }}]({{
|
33
|
+
* [{{ key }} - {{ summary }}]({{ url }})
|
34
34
|
{{/ classified_issues.features}}
|
35
35
|
|
36
36
|
{{/ classified_issues.features.first}}
|
37
37
|
{{# classified_issues.improvements.first}}
|
38
38
|
### Improvements
|
39
39
|
{{# classified_issues.improvements}}
|
40
|
-
* [{{ key }} - {{ summary }}]({{
|
40
|
+
* [{{ key }} - {{ summary }}]({{ url }})
|
41
41
|
{{/ classified_issues.improvements}}
|
42
42
|
|
43
43
|
{{/ classified_issues.improvements.first}}
|
44
44
|
{{# classified_issues.tasks.first}}
|
45
45
|
### Completed Tasks
|
46
46
|
{{# classified_issues.tasks}}
|
47
|
-
* [{{ key }} - {{ summary }}]({{
|
47
|
+
* [{{ key }} - {{ summary }}]({{ url }})
|
48
48
|
{{/ classified_issues.tasks}}
|
49
49
|
|
50
50
|
{{/ classified_issues.tasks.first}}
|
51
51
|
{{# classified_issues.defects.first}}
|
52
52
|
### Fixed defects
|
53
53
|
{{# classified_issues.defects}}
|
54
|
-
* [{{ key }} - {{ summary }}]({{
|
54
|
+
* [{{ key }} - {{ summary }}]({{ url }})
|
55
55
|
{{/ classified_issues.defects}}
|
56
56
|
|
57
57
|
{{/ classified_issues.defects.first}}
|
58
58
|
{{# classified_issues.others.first}}
|
59
59
|
### Other issue types
|
60
60
|
{{# classified_issues.others}}
|
61
|
-
* [{{ key }} - {{ summary }}]({{
|
61
|
+
* [{{issue_type}} - {{ key }} - {{ summary }}]({{ url }})
|
62
62
|
{{/ classified_issues.others}}
|
63
63
|
|
64
64
|
{{/ classified_issues.others.first}}}
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open-uri"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module Apadmi
|
7
|
+
module Grout
|
8
|
+
# Utility class for abstracting a some of the bitrise APIs
|
9
|
+
class BitriseService
|
10
|
+
# @param [String] app_slug
|
11
|
+
# @param [Apadmi::Grout::NetworkService] network_service
|
12
|
+
def initialize(app_slug, network_service)
|
13
|
+
@app_slug = app_slug
|
14
|
+
@network_service = network_service
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [Integer] pull_request_id
|
18
|
+
# @param [String] workflow
|
19
|
+
# @return [Array<BitriseBuild>]
|
20
|
+
def list_builds(pull_request_id, workflow)
|
21
|
+
res = @network_service.do_get("/#{@app_slug}/builds?pull_request_id=#{pull_request_id}&workflow=#{workflow}")
|
22
|
+
parsed = JSON.parse(res.body)
|
23
|
+
items = parsed["data"]
|
24
|
+
|
25
|
+
return [] if items.nil? || items.empty?
|
26
|
+
|
27
|
+
items.map do |item|
|
28
|
+
BitriseBuild.new(
|
29
|
+
item["triggered_at"],
|
30
|
+
item["slug"],
|
31
|
+
item["status"],
|
32
|
+
item["commit_hash"]
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [String] build_slug
|
38
|
+
# @param [String] artifact_slug
|
39
|
+
# @return [Artifact]
|
40
|
+
def retrieve_artifact_metadata(build_slug, artifact_slug)
|
41
|
+
res = @network_service.do_get("/#{@app_slug}/builds/#{build_slug}/artifacts/#{artifact_slug}")
|
42
|
+
parsed = JSON.parse(res.body)
|
43
|
+
|
44
|
+
Artifact.new(parsed["data"]["title"], parsed["data"]["expiring_download_url"])
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [String] build_slug
|
48
|
+
# @param [String] output_dir
|
49
|
+
# Download all artifacts from a given build to a specified directory
|
50
|
+
def download_artifacts(build_slug, output_dir)
|
51
|
+
res = @network_service.do_get("/#{@app_slug}/builds/#{build_slug}/artifacts")
|
52
|
+
parsed = JSON.parse(res.body)
|
53
|
+
items = parsed["data"]
|
54
|
+
|
55
|
+
artifacts = items.map do |item|
|
56
|
+
retrieve_artifact_metadata(build_slug, item["slug"])
|
57
|
+
end
|
58
|
+
|
59
|
+
FileUtils.mkdir_p output_dir
|
60
|
+
|
61
|
+
artifacts.each do |item|
|
62
|
+
File.open("#{output_dir}/#{item.title}", "wb") do |file|
|
63
|
+
# rubocop:disable Security/Open
|
64
|
+
file.write URI.open(item.download_url).read
|
65
|
+
# rubocop:enable Security/Open
|
66
|
+
end
|
67
|
+
end
|
68
|
+
artifacts
|
69
|
+
end
|
70
|
+
|
71
|
+
# @param [String] workflow
|
72
|
+
# @param [String] branch
|
73
|
+
# @param [String] dest the destination branch of the pr
|
74
|
+
# @param [String] pull_request_id
|
75
|
+
# @param [String] pull_request_repo_url
|
76
|
+
# @param [String] commit_hash
|
77
|
+
def trigger_build(workflow, branch, dest, pull_request_id, pull_request_repo_url, commit_hash)
|
78
|
+
params = {
|
79
|
+
workflow_id: workflow,
|
80
|
+
branch: branch,
|
81
|
+
commit_hash: commit_hash
|
82
|
+
}
|
83
|
+
|
84
|
+
params[:branch_dest] = dest unless dest.strip.blank?
|
85
|
+
params[:pull_request_id] = pull_request_id unless pull_request_id.strip.blank?
|
86
|
+
params[:pull_request_repository_url] = pull_request_repo_url unless pull_request_repo_url.strip.blank?
|
87
|
+
|
88
|
+
payload = {
|
89
|
+
hook_info: {
|
90
|
+
type: "bitrise"
|
91
|
+
},
|
92
|
+
build_params: params,
|
93
|
+
triggered_by: "curl"
|
94
|
+
}
|
95
|
+
|
96
|
+
res = @network_service.do_post("/#{@app_slug}/builds", payload.to_json)
|
97
|
+
parsed = JSON.parse(res.body)
|
98
|
+
|
99
|
+
TriggeredBitriseBuild.from_json(parsed)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./board_service"
|
4
|
+
|
5
|
+
module Apadmi
|
6
|
+
module Grout
|
7
|
+
# Provides a layer of abstraction on top of the ADO api
|
8
|
+
class AdoBoardService < BoardService
|
9
|
+
API_VERSION_PARAM = "api-version=6.0"
|
10
|
+
|
11
|
+
# @param [Apadmi::Grout::NetworkService] network_service
|
12
|
+
# @param [Apadmi::Grout::AdoConfig] ado_config
|
13
|
+
# @param [Logger] logger
|
14
|
+
def initialize(network_service, ado_config, logger)
|
15
|
+
@network_service = network_service
|
16
|
+
@ado_config = ado_config
|
17
|
+
@logger = logger
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param [String[]] keys
|
21
|
+
# @return [Array<Apadmi::Grout::Issue>]
|
22
|
+
def find_issues_by_keys(keys)
|
23
|
+
return [] if keys.length <= 0
|
24
|
+
|
25
|
+
keys = keys.map { |k| sanitise_key(k) } # Allows supporting raw keys, and keys prefixed with a hash which is common ADO practice
|
26
|
+
|
27
|
+
res = @network_service.do_get("/wit/workitems?ids=#{keys.join(",")}&api-version=7.1-preview.2")
|
28
|
+
parsed = JSON.parse(res.body)
|
29
|
+
items = parsed["value"]
|
30
|
+
|
31
|
+
return if items.nil? || items.empty?
|
32
|
+
|
33
|
+
items.map { |i| Apadmi::Grout::Issue.from_ado_hash(i) }
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param component [String] Included to be consistent with JIRA, this will boil down to a tag in ADO
|
37
|
+
# @param status [String]
|
38
|
+
# @param ticket_types [String[]]
|
39
|
+
# @param options [Apadmi::Grout::AdoFindTicketsOptions] Additional options to filter by
|
40
|
+
|
41
|
+
# @return [Array<Apadmi::Grout::Issue>]
|
42
|
+
def search_unblocked_issues(component, status, ticket_types = [], options = nil)
|
43
|
+
tags = (options&.required_tags || []).push(*component).filter { |it| !it.blank? }
|
44
|
+
not_tags = (options&.not_tags || []).filter { |it| !it.blank? }
|
45
|
+
|
46
|
+
raise "Tags can either be required or not required, not both" unless (tags & not_tags).empty?
|
47
|
+
|
48
|
+
tags_filter = tags.map { |tag| " AND [System.Tags] CONTAINS '#{tag}' " }.join(" ")
|
49
|
+
not_tags_filter = not_tags.map { |tag| " AND [System.Tags] NOT CONTAINS '#{tag}'" }.join(" ")
|
50
|
+
|
51
|
+
status_filter = ("AND [System.State]='#{status}' " unless status.blank?) || ""
|
52
|
+
type_filter = ("AND [System.WorkItemType] IN (#{ticket_types.map { |t| "'#{t}'" }.join(", ")})" unless ticket_types.empty?) || ""
|
53
|
+
wiql = %(
|
54
|
+
Select [System.Id]
|
55
|
+
From WorkItems
|
56
|
+
Where [System.Id] >= 1
|
57
|
+
#{status_filter}
|
58
|
+
#{tags_filter}
|
59
|
+
#{not_tags_filter}
|
60
|
+
#{type_filter}
|
61
|
+
)
|
62
|
+
|
63
|
+
get_issues_by_wiql(wiql)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param [String] key
|
67
|
+
# @param [String] comment
|
68
|
+
def flag_ticket(key, comment)
|
69
|
+
key = sanitise_key(key)
|
70
|
+
|
71
|
+
add_tag(key, @ado_config.flag_tag)
|
72
|
+
add_comment(key, comment) unless comment.blank?
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param [String] key
|
76
|
+
def un_flag_ticket(key)
|
77
|
+
key = sanitise_key(key)
|
78
|
+
|
79
|
+
remove_tag(key, @ado_config.flag_tag)
|
80
|
+
end
|
81
|
+
|
82
|
+
# @param [String] key
|
83
|
+
# @param [String] comment
|
84
|
+
def add_comment(key, comment)
|
85
|
+
key = sanitise_key(key)
|
86
|
+
|
87
|
+
payload = {
|
88
|
+
"text" => comment
|
89
|
+
}
|
90
|
+
@network_service.do_post("/wit/workitems/#{key}/comments?#{API_VERSION_PARAM}-preview.3", payload.to_json)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param [String] key
|
94
|
+
# @param [String] tag
|
95
|
+
def add_tag(key, tag)
|
96
|
+
payload = [{
|
97
|
+
"op" => "add",
|
98
|
+
"path" => "/fields/System.Tags",
|
99
|
+
"value" => tag
|
100
|
+
}]
|
101
|
+
@network_service.do_patch("/wit/workitems/#{key}?#{API_VERSION_PARAM}", payload.to_json)
|
102
|
+
end
|
103
|
+
|
104
|
+
# @param [String] key
|
105
|
+
# @param [String] tag
|
106
|
+
def remove_tag(key, tag)
|
107
|
+
current_tags = get_work_item(key)["fields"]["System.Tags"].split("; ")
|
108
|
+
new_tags = current_tags.filter { |t| t != tag }
|
109
|
+
|
110
|
+
payload = [{
|
111
|
+
"op" => "replace",
|
112
|
+
"path" => "/fields/System.Tags",
|
113
|
+
"value" => new_tags.join("; ")
|
114
|
+
}]
|
115
|
+
@network_service.do_patch("/wit/workitems/#{key}?#{API_VERSION_PARAM}", payload.to_json)
|
116
|
+
end
|
117
|
+
|
118
|
+
# @param [String] key
|
119
|
+
def flagged?(key)
|
120
|
+
current_tags = get_work_item(key)["fields"]["System.Tags"].split("; ")
|
121
|
+
current_tags.include?(@ado_config.flag_tag)
|
122
|
+
end
|
123
|
+
|
124
|
+
# @param [String] key
|
125
|
+
# @return [RawWorkItemHash]
|
126
|
+
def get_work_item(key)
|
127
|
+
key = sanitise_key(key)
|
128
|
+
|
129
|
+
res = @network_service.do_get("/wit/workitems/#{key}?#{API_VERSION_PARAM}")
|
130
|
+
JSON.parse(res.body)
|
131
|
+
end
|
132
|
+
|
133
|
+
# @param [String] key
|
134
|
+
# @param [Array<String>] versions
|
135
|
+
def assign_fixversions(key, versions)
|
136
|
+
key = sanitise_key(key)
|
137
|
+
|
138
|
+
payload = [{
|
139
|
+
"op" => "replace",
|
140
|
+
"path" => "/fields/Microsoft.VSTS.Build.IntegrationBuild",
|
141
|
+
"value" => versions.join("; ")
|
142
|
+
}]
|
143
|
+
@network_service.do_patch("/wit/workitems/#{key}?#{API_VERSION_PARAM}", payload.to_json)
|
144
|
+
end
|
145
|
+
|
146
|
+
# @param [String] key
|
147
|
+
# @return [Array<String>] fix versions
|
148
|
+
def get_ticket_fixversions(key)
|
149
|
+
key = sanitise_key(key)
|
150
|
+
|
151
|
+
get_work_item(key)["fields"]["Microsoft.VSTS.Build.IntegrationBuild"].split("; ")
|
152
|
+
end
|
153
|
+
|
154
|
+
# @param [Apadmi::Grout::Issue] issue
|
155
|
+
# @param [String] state_name
|
156
|
+
def transition_issue(issue, state_name)
|
157
|
+
key = sanitise_key(issue.key)
|
158
|
+
|
159
|
+
payload = [{
|
160
|
+
"op" => "add",
|
161
|
+
"path" => "/fields/System.State",
|
162
|
+
"value" => state_name
|
163
|
+
}]
|
164
|
+
@network_service.do_patch("/wit/workitems/#{key}?#{API_VERSION_PARAM}", payload.to_json)
|
165
|
+
end
|
166
|
+
|
167
|
+
# @param [String] key
|
168
|
+
# @return [String]
|
169
|
+
def get_ticket_status(key)
|
170
|
+
key = sanitise_key(key)
|
171
|
+
|
172
|
+
get_work_item(key)["fields"]["System.State"]
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def sanitise_key(key)
|
178
|
+
key = key.to_s.strip.delete_prefix "#"
|
179
|
+
raise "Invalid key" if key.blank?
|
180
|
+
|
181
|
+
key
|
182
|
+
end
|
183
|
+
|
184
|
+
# @param wiql_query [String]
|
185
|
+
# @return [Array<Apadmi::Grout::Issue>]
|
186
|
+
def get_issues_by_wiql(wiql_query)
|
187
|
+
@logger.message("Executing wiql")
|
188
|
+
@logger.message(wiql_query)
|
189
|
+
|
190
|
+
payload = { "query" => wiql_query }
|
191
|
+
res = @network_service.do_post("/wit/wiql?#{API_VERSION_PARAM}", payload.to_json)
|
192
|
+
parsed = JSON.parse(res.body)
|
193
|
+
items = parsed["workItems"] || []
|
194
|
+
ids = items.map { |item| item["id"] } || []
|
195
|
+
find_issues_by_keys(ids)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apadmi
|
4
|
+
module Grout
|
5
|
+
# A generic board service to be implemented by whatever
|
6
|
+
# board providers we have e.g. JIRA and ADO
|
7
|
+
class BoardService
|
8
|
+
# @param [String[]] keys
|
9
|
+
# @return [Array<Apadmi::Grout::Issue>]
|
10
|
+
def find_issues_by_keys(_keys)
|
11
|
+
raise "Unimplemented :("
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [String] _component
|
15
|
+
# @param [String] _status
|
16
|
+
# @param [String[]] _ticket_types
|
17
|
+
# @param [Apadmi::Grout::JiraFindTicketsOptions|Apadmi::Grout::JiraFindTicketsOptions] _options
|
18
|
+
# @return [Array<Apadmi::Grout::Issue>]
|
19
|
+
def search_unblocked_issues(_component, _status, _ticket_types = [], _options = nil)
|
20
|
+
raise "Unimplemented :("
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [String] _key
|
24
|
+
# @param [String] _comment
|
25
|
+
def flag_ticket(_key, _comment)
|
26
|
+
raise "Unimplemented :("
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [String] _key
|
30
|
+
def un_flag_ticket(_key)
|
31
|
+
raise "Unimplemented :("
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param [String] _key
|
35
|
+
# @return bool
|
36
|
+
def flagged?(_key)
|
37
|
+
raise "Unimplemented :("
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [String] _key
|
41
|
+
# @param [Array<String>] _version_strings
|
42
|
+
def assign_fixversions(_key, _version_strings)
|
43
|
+
raise "Unimplemented :("
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [String] _key
|
47
|
+
# @return [Array<String>]
|
48
|
+
def get_ticket_fixversions(_key)
|
49
|
+
raise "Unimplemented :("
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param [Apadmi::Grout::Issue] _issue
|
53
|
+
# @param [String] _state_name
|
54
|
+
def transition_issue(_issue, _state_name)
|
55
|
+
raise "Unimplemented :("
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|