speedflow-plugin-jira 0.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +98 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/lib/speedflow-plugin-jira.rb +1 -0
- data/lib/speedflow/plugin/jira.rb +47 -0
- data/lib/speedflow/plugin/jira/client.rb +154 -0
- data/lib/speedflow/plugin/jira/configuration.rb +84 -0
- data/lib/speedflow/plugin/jira/formatter/issue_formatter.rb +43 -0
- data/lib/speedflow/plugin/jira/plugin_core.rb +119 -0
- data/lib/speedflow/plugin/jira/prompt.rb +111 -0
- data/lib/speedflow/plugin/jira/version.rb +7 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/speedflow/plugin/jira/client_spec.rb +111 -0
- data/spec/speedflow/plugin/jira/configuration_spec.rb +53 -0
- data/spec/speedflow/plugin/jira/formatter/issue_formatter_spec.rb +29 -0
- data/spec/speedflow/plugin/jira/plugin_core_spec.rb +115 -0
- data/spec/speedflow/plugin/jira/prompt_spec.rb +79 -0
- data/spec/speedflow/plugin/jira_spec.rb +19 -0
- metadata +217 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'tty-prompt'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Speedflow
|
5
|
+
module Plugin
|
6
|
+
module Jira
|
7
|
+
# Plugin core
|
8
|
+
class PluginCore
|
9
|
+
# @return [Prompt] Plugin prompt.
|
10
|
+
attr_writer :prompt
|
11
|
+
|
12
|
+
# @return [Client] Plugin client.
|
13
|
+
attr_writer :client
|
14
|
+
|
15
|
+
# @return [Config] Plugin config.
|
16
|
+
attr_writer :config
|
17
|
+
|
18
|
+
# Initialize.
|
19
|
+
#
|
20
|
+
# arguments - Hash of arguments.
|
21
|
+
#
|
22
|
+
# Examples
|
23
|
+
#
|
24
|
+
# Plugin.new({})
|
25
|
+
# # => <Speedflow::Plugin::Jira::PluginCore>
|
26
|
+
#
|
27
|
+
# Returns nothing.
|
28
|
+
def initialize(arguments)
|
29
|
+
@arguments = arguments
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Create issue.
|
33
|
+
#
|
34
|
+
# Returns Hash of issue.
|
35
|
+
def action_create_issue
|
36
|
+
title = config.by_required_input('title')
|
37
|
+
|
38
|
+
project_key = config.by_input('project')
|
39
|
+
project_key = prompt.project(client.projects) if project_key.empty?
|
40
|
+
|
41
|
+
issue_type_id = config.by_input('type')
|
42
|
+
if issue_type_id.empty?
|
43
|
+
issue_type_id = prompt.issue_type(client.issue_types)
|
44
|
+
end
|
45
|
+
|
46
|
+
client.create_issue(project_key, title, issue_type_id)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: Search issue.
|
50
|
+
#
|
51
|
+
# Returns Hash of issue.
|
52
|
+
def action_search_issue
|
53
|
+
limit = config.by_input('limit', 20)
|
54
|
+
project = config.by_input('project')
|
55
|
+
project = prompt.project(client.projects) if project.empty?
|
56
|
+
|
57
|
+
issue_id = prompt.issue do |title|
|
58
|
+
client.search_issue(project, title).take(limit.to_i)
|
59
|
+
end
|
60
|
+
|
61
|
+
issue_id
|
62
|
+
end
|
63
|
+
|
64
|
+
# Public: Change issue assignee.
|
65
|
+
#
|
66
|
+
# Returns Hash of assignee.
|
67
|
+
def action_change_issue_assignee
|
68
|
+
issue_key = config.by_input('issue')
|
69
|
+
assignee_name = config.by_input('assignee')
|
70
|
+
|
71
|
+
issue_key = action_search_issue if issue_key.empty?
|
72
|
+
|
73
|
+
client.update_issue_assignee(issue_key, assignee_name)
|
74
|
+
|
75
|
+
prompt.ok "[JIRA] Issue assignee changed to: #{assignee_name}"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Public: Change issue transition.
|
79
|
+
#
|
80
|
+
# Returns Hash of assignee.
|
81
|
+
def action_change_issue_transition
|
82
|
+
issue_key = config.by_input('issue')
|
83
|
+
transition_id = config.by_input('transition')
|
84
|
+
|
85
|
+
issue_key = action_search_issue if issue_key.empty?
|
86
|
+
|
87
|
+
if transition_id.empty?
|
88
|
+
transition_id = prompt.transition(client.issue_trans(issue_key))
|
89
|
+
end
|
90
|
+
|
91
|
+
client.update_issue_transition(issue_key, transition_id)
|
92
|
+
|
93
|
+
prompt.ok "[JIRA] Issue transition changed to: #{transition_id}"
|
94
|
+
end
|
95
|
+
|
96
|
+
# Public: Plugin configuration.
|
97
|
+
#
|
98
|
+
# Returns Configuration instance.
|
99
|
+
def config
|
100
|
+
@config ||= Configuration.new(@arguments)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Public: Plugin client.
|
104
|
+
#
|
105
|
+
# Returns Client instance.
|
106
|
+
def client
|
107
|
+
@client ||= Client.new(@config, @prompt)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Public: Plugin prompt.
|
111
|
+
#
|
112
|
+
# Returns Prompt instance.
|
113
|
+
def prompt
|
114
|
+
@prompt ||= Prompt.new
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'tty-prompt'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Speedflow
|
5
|
+
module Plugin
|
6
|
+
module Jira
|
7
|
+
# Plugin prompt
|
8
|
+
class Prompt # < TTY::Prompt
|
9
|
+
# @return [TTY::Prompt] TTY::Prompt.
|
10
|
+
attr_writer :prompt
|
11
|
+
|
12
|
+
# Public: Prompt title.
|
13
|
+
#
|
14
|
+
# Returns String of title.
|
15
|
+
def title
|
16
|
+
ask('What is the title of issue?', required: true)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Prompt project.
|
20
|
+
#
|
21
|
+
# projects - List of projects.
|
22
|
+
#
|
23
|
+
# Returns String of project key.
|
24
|
+
def project(projects)
|
25
|
+
prompt.select('Choose the project:') do |menu|
|
26
|
+
projects.each do |project|
|
27
|
+
menu.choice "#{project.name} (#{project.key})", project.key
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Prompt issue type.
|
33
|
+
#
|
34
|
+
# issue_types - List of issue types.
|
35
|
+
#
|
36
|
+
# Returns ID of issue type.
|
37
|
+
def issue_type(issue_types)
|
38
|
+
prompt.select('Choose the issue type:') do |menu|
|
39
|
+
issue_types.each do |issue_type|
|
40
|
+
menu.choice "#{issue_type.name} (#{issue_type.id})", issue_type.id
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: Prompt transition.
|
46
|
+
#
|
47
|
+
# projects - List of transitions.
|
48
|
+
#
|
49
|
+
# Returns String of status key.
|
50
|
+
def transition(transitions)
|
51
|
+
prompt.select('Choose the transition:') do |menu|
|
52
|
+
transitions.each do |transition|
|
53
|
+
menu.choice(
|
54
|
+
"#{transition.name} (#{transition.id})", transition.id)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Public: Prompt issue.
|
60
|
+
#
|
61
|
+
# issues - Hash of issues from block.
|
62
|
+
#
|
63
|
+
# Returns String key of issue.
|
64
|
+
def issue(&issues)
|
65
|
+
sel_issue = prompt.select('Choose the issue:') do |menu|
|
66
|
+
menu.choice 'Retry search!', :retry
|
67
|
+
yield(title).each do |v|
|
68
|
+
menu.choice "#{v.summary} (#{v.key})", v.key
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
issue(&issues) if sel_issue == :retry
|
73
|
+
ok "[JIRA] Issue found: #{sel_issue}" unless sel_issue == :retry
|
74
|
+
|
75
|
+
sel_issue
|
76
|
+
end
|
77
|
+
|
78
|
+
# Public: Errors from JIRA exception.
|
79
|
+
#
|
80
|
+
# exception - JIRA::HTTPError.
|
81
|
+
#
|
82
|
+
# Returns nothing.
|
83
|
+
def errors(exception)
|
84
|
+
if exception.response.respond_to?('body')
|
85
|
+
response = ::JSON.parse(exception.response.body)
|
86
|
+
response['errors'].each { |k, v| prompt.warn "- #{k}: #{v}" }
|
87
|
+
response['errorMessages'].each { |v| prompt.warn "- #{v}" }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Delegate
|
92
|
+
#
|
93
|
+
# method - Method.
|
94
|
+
# args - Arguments.
|
95
|
+
# block - Block.
|
96
|
+
#
|
97
|
+
# Returns wathever.
|
98
|
+
def method_missing(method, *args, &block)
|
99
|
+
prompt.send(method, *args, &block)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Public: TTY prompt.
|
103
|
+
#
|
104
|
+
# Returns ::TTY::Prompt instance.
|
105
|
+
def prompt
|
106
|
+
@prompt ||= ::TTY::Prompt.new
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'simplecov'
|
4
|
+
require 'codeclimate-test-reporter'
|
5
|
+
|
6
|
+
CodeClimate::TestReporter.start if ENV['CODECLIMATE_REPO_TOKEN']
|
7
|
+
|
8
|
+
SimpleCov.start do
|
9
|
+
formatter SimpleCov::Formatter::MultiFormatter.new(
|
10
|
+
[SimpleCov::Formatter::HTMLFormatter, CodeClimate::TestReporter::Formatter])
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'speedflow/plugin/jira'
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Speedflow::Plugin::Jira::Client do
|
4
|
+
let(:args) do
|
5
|
+
title = { 'value' => 'test' }
|
6
|
+
assignee = { 'default' => 'julien.breux' }
|
7
|
+
{ 'title' => title, 'assignee' => assignee }
|
8
|
+
end
|
9
|
+
let(:config) do
|
10
|
+
::Speedflow::Plugin::Jira::Configuration.new(args)
|
11
|
+
end
|
12
|
+
let(:prompt) do
|
13
|
+
prompt = ::Speedflow::Plugin::Jira::Prompt.new
|
14
|
+
prompt.prompt = double
|
15
|
+
prompt
|
16
|
+
end
|
17
|
+
let(:jira_client) { double }
|
18
|
+
let(:client) do
|
19
|
+
client = ::Speedflow::Plugin::Jira::Client.new(config, prompt)
|
20
|
+
client.jira_client = jira_client
|
21
|
+
client
|
22
|
+
end
|
23
|
+
let(:exception_response) do
|
24
|
+
double(body: '{"errors": {"k": "v"}, "errorMessages": ["v"]}')
|
25
|
+
end
|
26
|
+
|
27
|
+
it '.search_issue' do
|
28
|
+
allow(jira_client).to receive_message_chain('Issue.jql')
|
29
|
+
|
30
|
+
client.search_issue('test', 'test')
|
31
|
+
end
|
32
|
+
|
33
|
+
it '.create_issue' do
|
34
|
+
receive = receive_message_chain('Issue.build') do
|
35
|
+
double(save!: nil, fetch: nil)
|
36
|
+
end
|
37
|
+
allow(jira_client)
|
38
|
+
.to receive
|
39
|
+
|
40
|
+
client.create_issue('1', 'test', '1')
|
41
|
+
end
|
42
|
+
|
43
|
+
it '.update_issue_assignee' do
|
44
|
+
receive = receive_message_chain('Issue.find') do
|
45
|
+
double(save!: nil)
|
46
|
+
end
|
47
|
+
allow(jira_client)
|
48
|
+
.to receive
|
49
|
+
|
50
|
+
client.update_issue_assignee('1', 'julien.breux')
|
51
|
+
end
|
52
|
+
|
53
|
+
it '.update_issue_transition' do
|
54
|
+
receive = receive_message_chain('Issue.find') do
|
55
|
+
double(transitions: double(build: double(save!: nil)))
|
56
|
+
end
|
57
|
+
allow(jira_client)
|
58
|
+
.to receive
|
59
|
+
|
60
|
+
client.update_issue_transition('T-1', 1)
|
61
|
+
end
|
62
|
+
|
63
|
+
it '.project' do
|
64
|
+
allow(jira_client).to receive_message_chain('Project.find')
|
65
|
+
|
66
|
+
client.project('T')
|
67
|
+
end
|
68
|
+
|
69
|
+
it '.projects' do
|
70
|
+
allow(jira_client).to receive_message_chain('Project.all')
|
71
|
+
|
72
|
+
client.projects
|
73
|
+
end
|
74
|
+
|
75
|
+
it '.issue_types' do
|
76
|
+
allow(jira_client).to receive_message_chain('Issuetype.all')
|
77
|
+
|
78
|
+
client.issue_types
|
79
|
+
end
|
80
|
+
|
81
|
+
it '.issue_trans' do
|
82
|
+
receive = receive_message_chain('Issue.find') do
|
83
|
+
double(transitions: nil)
|
84
|
+
end
|
85
|
+
allow(jira_client).to receive
|
86
|
+
|
87
|
+
client.issue_trans('T-1')
|
88
|
+
end
|
89
|
+
|
90
|
+
it '.safe' do
|
91
|
+
allow(client.prompt)
|
92
|
+
.to receive(:error).with('Invalid URL')
|
93
|
+
e = expect do
|
94
|
+
client.safe do
|
95
|
+
raise ::URI::InvalidURIError.new(''), '...'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
e.to raise_error(SystemExit)
|
99
|
+
|
100
|
+
allow(client.prompt)
|
101
|
+
.to receive(:error).with('Jira errors')
|
102
|
+
allow(client.prompt)
|
103
|
+
.to receive(:errors)
|
104
|
+
e = expect do
|
105
|
+
client.safe do
|
106
|
+
raise ::JIRA::HTTPError.new(exception_response), '...'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
e.to raise_error(SystemExit)
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Speedflow::Plugin::Jira::Configuration do
|
4
|
+
let(:config) do
|
5
|
+
title = { 'value' => 'hello' }
|
6
|
+
num = { 'value' => 3 }
|
7
|
+
jira_config = { 'username' => 'foo', 'password' => 'bar', 'site' => 'baz' }
|
8
|
+
config = { 'jira' => jira_config }
|
9
|
+
args = { 'num' => num, 'title' => title, '_config' => config }
|
10
|
+
Speedflow::Plugin::Jira::Configuration.new(args)
|
11
|
+
end
|
12
|
+
|
13
|
+
it '.by_config' do
|
14
|
+
expect(config.by_config('username', 'default'))
|
15
|
+
.to eq 'foo'
|
16
|
+
end
|
17
|
+
|
18
|
+
it '.by_input' do
|
19
|
+
expect(config.by_input('title', 'default'))
|
20
|
+
.to eq 'hello'
|
21
|
+
|
22
|
+
expect(config.by_input('num'))
|
23
|
+
.to eq '3'
|
24
|
+
|
25
|
+
expect(config.by_input('notitle', 'default'))
|
26
|
+
.to eq 'default'
|
27
|
+
end
|
28
|
+
|
29
|
+
it '.by_required_input' do
|
30
|
+
expect(config.by_required_input('title', 'default'))
|
31
|
+
.to eq 'hello'
|
32
|
+
|
33
|
+
expect { config.by_required_input('notitle') }
|
34
|
+
.to raise_error(Exception)
|
35
|
+
|
36
|
+
expect { config.by_required_input('nonotitle', 'default') }
|
37
|
+
.to raise_error(Exception)
|
38
|
+
end
|
39
|
+
|
40
|
+
it '.auth' do
|
41
|
+
returned_hash = {
|
42
|
+
auth_type: :basic,
|
43
|
+
context_path: '/',
|
44
|
+
password: 'bar',
|
45
|
+
read_timeout: 120,
|
46
|
+
site: 'baz',
|
47
|
+
username: 'foo'
|
48
|
+
}
|
49
|
+
|
50
|
+
expect(config.auth)
|
51
|
+
.to eq returned_hash
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Speedflow::Plugin::Jira::Formatter::IssueFormatter do
|
4
|
+
let(:mod) { Speedflow::Plugin::Jira::Formatter::IssueFormatter }
|
5
|
+
|
6
|
+
it '#to_create' do
|
7
|
+
returned_hash = {
|
8
|
+
'fields' => {
|
9
|
+
'project' => { 'key' => 'foo' },
|
10
|
+
'summary' => 'bar',
|
11
|
+
'issuetype' => { 'id' => 'baz' }
|
12
|
+
}
|
13
|
+
}
|
14
|
+
expect(mod.to_create('foo', 'bar', 'baz'))
|
15
|
+
.to eq returned_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
it '#to_assignee' do
|
19
|
+
returned_hash = {
|
20
|
+
'fields' => {
|
21
|
+
'assignee' => {
|
22
|
+
'name' => 'julien.breux'
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
expect(mod.to_assignee('julien.breux'))
|
27
|
+
.to eq returned_hash
|
28
|
+
end
|
29
|
+
end
|