trollolo 0.1.1 → 0.2.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/.rubocop.yml +21 -2
- data/.rubocop_todo.yml +105 -270
- data/CHANGELOG.md +10 -0
- data/CONTRIBUTING.md +6 -4
- data/Gemfile +1 -1
- data/README.md +10 -1
- data/Rakefile +4 -4
- data/bin/trollolo +2 -2
- data/lib/backup.rb +21 -23
- data/lib/burndown_chart.rb +46 -49
- data/lib/burndown_data.rb +21 -21
- data/lib/card.rb +23 -32
- data/lib/checklist.rb +13 -1
- data/lib/cli.rb +105 -110
- data/lib/column.rb +11 -10
- data/lib/empty_column.rb +2 -2
- data/lib/scrum/backlog_mover.rb +3 -3
- data/lib/scrum/card_type_detection.rb +1 -1
- data/lib/scrum/creator.rb +7 -7
- data/lib/scrum/prioritizer.rb +1 -1
- data/lib/scrum/sprint_board.rb +4 -4
- data/lib/scrum/sprint_cleaner.rb +3 -3
- data/lib/scrum/sprint_planning_board.rb +2 -4
- data/lib/scrum_board.rb +3 -3
- data/lib/settings.rb +29 -27
- data/lib/trello_service.rb +1 -1
- data/lib/trello_wrapper.rb +9 -9
- data/lib/version.rb +1 -1
- data/spec/data/card.json +19 -0
- data/spec/data/trollolorc +3 -0
- data/spec/integration/command_line_spec.rb +14 -14
- data/spec/integration/create_burndown_spec.rb +25 -25
- data/spec/integration/integration_spec_helper.rb +1 -1
- data/spec/integration/wrapper/credentials_input_wrapper +7 -11
- data/spec/integration/wrapper/empty_config_trollolo_wrapper +2 -2
- data/spec/integration/wrapper/trollolo_wrapper +2 -2
- data/spec/unit/backup_spec.rb +14 -14
- data/spec/unit/burndown_chart_spec.rb +176 -184
- data/spec/unit/burndown_data_spec.rb +21 -23
- data/spec/unit/card_spec.rb +38 -24
- data/spec/unit/cli_spec.rb +57 -57
- data/spec/unit/empty_column_spec.rb +1 -1
- data/spec/unit/retrieve_data_spec.rb +13 -13
- data/spec/unit/scrum/backlog_mover_spec.rb +8 -8
- data/spec/unit/scrum/card_type_detection_spec.rb +12 -12
- data/spec/unit/scrum/creator_spec.rb +8 -8
- data/spec/unit/scrum/prioritizer_spec.rb +19 -19
- data/spec/unit/scrum/priority_name_spec.rb +16 -16
- data/spec/unit/scrum/sprint_board_spec.rb +3 -3
- data/spec/unit/scrum/sprint_cleaner_spec.rb +15 -15
- data/spec/unit/scrum/sprint_planning_board_spec.rb +2 -2
- data/spec/unit/scrum_board_spec.rb +56 -56
- data/spec/unit/settings_spec.rb +23 -23
- data/spec/unit/spec_helper.rb +3 -3
- data/spec/unit/support/test_data_operations.rb +4 -0
- data/spec/unit/support/update_webmock_data +9 -11
- data/spec/unit/support/vcr.rb +3 -3
- data/spec/unit/support/webmocks.rb +21 -12
- data/spec/unit/trello_wrapper_spec.rb +40 -29
- data/trollolo.gemspec +3 -3
- metadata +8 -5
data/lib/column.rb
CHANGED
@@ -15,21 +15,22 @@
|
|
15
15
|
# To contact SUSE about this file by physical or electronic mail,
|
16
16
|
# you may find current contact information at www.suse.com
|
17
17
|
class Column
|
18
|
-
def initialize(board_data, list_id)
|
18
|
+
def initialize(board_data, list_id, settings = nil)
|
19
19
|
@board_data = board_data
|
20
|
-
@
|
20
|
+
@settings = settings
|
21
|
+
@list_data = @board_data['lists'].find{|l| l['id'] == list_id}
|
21
22
|
end
|
22
23
|
|
23
24
|
def name
|
24
|
-
@list_data[
|
25
|
+
@list_data['name']
|
25
26
|
end
|
26
27
|
|
27
28
|
def estimated_cards
|
28
|
-
cards.select
|
29
|
+
cards.select(&:estimated?)
|
29
30
|
end
|
30
31
|
|
31
32
|
def sum
|
32
|
-
estimated_cards.map
|
33
|
+
estimated_cards.map(&:story_points).sum
|
33
34
|
end
|
34
35
|
|
35
36
|
def tasks
|
@@ -41,11 +42,11 @@ class Column
|
|
41
42
|
end
|
42
43
|
|
43
44
|
def extra_cards
|
44
|
-
cards.select
|
45
|
+
cards.select(&:extra?)
|
45
46
|
end
|
46
47
|
|
47
48
|
def unplanned_cards
|
48
|
-
cards.select
|
49
|
+
cards.select(&:unplanned?)
|
49
50
|
end
|
50
51
|
|
51
52
|
def committed_cards
|
@@ -53,13 +54,13 @@ class Column
|
|
53
54
|
end
|
54
55
|
|
55
56
|
def fast_lane_cards
|
56
|
-
cards.select
|
57
|
+
cards.select(&:fast_lane?)
|
57
58
|
end
|
58
59
|
|
59
60
|
def cards
|
60
61
|
return @cards if @cards
|
61
62
|
|
62
|
-
cards = @board_data[
|
63
|
-
@cards = cards.map{|c| Card.new(@board_data, c[
|
63
|
+
cards = @board_data['cards'].select{|c| c['idList'] == @list_data['id']}
|
64
|
+
@cards = cards.map{|c| Card.new(@board_data, c['id'], @settings)}
|
64
65
|
end
|
65
66
|
end
|
data/lib/empty_column.rb
CHANGED
data/lib/scrum/backlog_mover.rb
CHANGED
@@ -12,10 +12,10 @@ module Scrum
|
|
12
12
|
|
13
13
|
def setup(planning_board_id, sprint_board_id)
|
14
14
|
@sprint_board = sprint_board(sprint_board_id)
|
15
|
-
|
15
|
+
raise "sprint board is missing #{@sprint_board.backlog_list_name} list" unless @sprint_board.backlog_list
|
16
16
|
|
17
17
|
@planning_board = planning_board(planning_board_id)
|
18
|
-
|
18
|
+
raise 'backlog list not found on planning board' unless @planning_board.backlog_list
|
19
19
|
|
20
20
|
@waterline_card = @planning_board.waterline_card
|
21
21
|
@seabed_card = @planning_board.seabed_card
|
@@ -27,7 +27,7 @@ module Scrum
|
|
27
27
|
break
|
28
28
|
elsif @waterline_card && card == @waterline_card
|
29
29
|
@sprint_board.place_waterline(@waterline_card)
|
30
|
-
puts
|
30
|
+
puts 'under the waterline'
|
31
31
|
else
|
32
32
|
move_sprint_card(card) unless @planning_board.sticky?(card)
|
33
33
|
end
|
data/lib/scrum/creator.rb
CHANGED
@@ -13,17 +13,17 @@ module Scrum
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def create_sprint_board
|
16
|
-
board = Trello::Board.create(name: @scrum.board_names[
|
17
|
-
Trello::List.create(board_id: board.id, name: @scrum.list_names[
|
18
|
-
Trello::List.create(board_id: board.id, name: @scrum.list_names[
|
19
|
-
Trello::List.create(board_id: board.id, name: @scrum.list_names[
|
16
|
+
board = Trello::Board.create(name: @scrum.board_names['sprint'])
|
17
|
+
Trello::List.create(board_id: board.id, name: @scrum.list_names['sprint_backlog'])
|
18
|
+
Trello::List.create(board_id: board.id, name: @scrum.list_names['sprint_qa'])
|
19
|
+
Trello::List.create(board_id: board.id, name: @scrum.list_names['sprint_doing'])
|
20
20
|
create_labels(board.id)
|
21
21
|
end
|
22
22
|
|
23
23
|
def create_planning_board
|
24
|
-
board = Trello::Board.create(name: @scrum.board_names[
|
25
|
-
Trello::List.create(board_id: board.id, name: @scrum.list_names[
|
26
|
-
Trello::List.create(board_id: board.id, name: @scrum.list_names[
|
24
|
+
board = Trello::Board.create(name: @scrum.board_names['planning'])
|
25
|
+
Trello::List.create(board_id: board.id, name: @scrum.list_names['planning_backlog'])
|
26
|
+
Trello::List.create(board_id: board.id, name: @scrum.list_names['planning_ready'])
|
27
27
|
create_labels(board.id)
|
28
28
|
end
|
29
29
|
end
|
data/lib/scrum/prioritizer.rb
CHANGED
@@ -4,7 +4,7 @@ module Scrum
|
|
4
4
|
|
5
5
|
def prioritize(board_id, list_name = nil)
|
6
6
|
@board = planning_board(board_id, list_name)
|
7
|
-
|
7
|
+
raise "list named '#{@board.backlog_list_name}' not found on board" unless @board.backlog_list
|
8
8
|
update_priorities
|
9
9
|
end
|
10
10
|
|
data/lib/scrum/sprint_board.rb
CHANGED
@@ -14,7 +14,7 @@ module Scrum
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def backlog_list_name
|
17
|
-
@settings.list_names[
|
17
|
+
@settings.list_names['sprint_backlog']
|
18
18
|
end
|
19
19
|
|
20
20
|
def doing_list
|
@@ -74,15 +74,15 @@ module Scrum
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def waterline_label_name
|
77
|
-
@settings.label_names[
|
77
|
+
@settings.label_names['waterline']
|
78
78
|
end
|
79
79
|
|
80
80
|
def qa_list_name
|
81
|
-
@settings.list_names[
|
81
|
+
@settings.list_names['sprint_qa']
|
82
82
|
end
|
83
83
|
|
84
84
|
def doing_list_name
|
85
|
-
@settings.list_names[
|
85
|
+
@settings.list_names['sprint_doing']
|
86
86
|
end
|
87
87
|
end
|
88
88
|
end
|
data/lib/scrum/sprint_cleaner.rb
CHANGED
@@ -4,9 +4,9 @@ module Scrum
|
|
4
4
|
|
5
5
|
def cleanup(board_id, target_board_id)
|
6
6
|
@board = sprint_board(board_id)
|
7
|
-
|
7
|
+
raise "backlog list '#{@board.backlog_list_name}' not found on sprint board" unless @board.backlog_list
|
8
8
|
@target_board = Trello::Board.find(target_board_id)
|
9
|
-
|
9
|
+
raise "ready list '#{@settings.scrum.list_names['planning_ready']}' not found on planning board" unless target_list
|
10
10
|
|
11
11
|
gen_burndown
|
12
12
|
|
@@ -18,7 +18,7 @@ module Scrum
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def target_list
|
21
|
-
@target_list ||= @target_board.lists.find { |l| l.name == @settings.scrum.list_names[
|
21
|
+
@target_list ||= @target_board.lists.find { |l| l.name == @settings.scrum.list_names['planning_ready'] }
|
22
22
|
end
|
23
23
|
|
24
24
|
def waterline_label(card)
|
@@ -6,15 +6,13 @@ module Scrum
|
|
6
6
|
|
7
7
|
def initialize(settings)
|
8
8
|
@settings = settings
|
9
|
-
@backlog_list_name = settings.list_names[
|
9
|
+
@backlog_list_name = settings.list_names['planning_backlog']
|
10
10
|
end
|
11
11
|
|
12
12
|
attr_accessor :backlog_list
|
13
13
|
|
14
14
|
def setup(id, list_name = nil)
|
15
|
-
if list_name
|
16
|
-
@backlog_list_name = list_name
|
17
|
-
end
|
15
|
+
@backlog_list_name = list_name if list_name
|
18
16
|
@board, @backlog_list = TrelloService.find_list(id, @backlog_list_name)
|
19
17
|
self
|
20
18
|
end
|
data/lib/scrum_board.rb
CHANGED
@@ -3,13 +3,13 @@ class ScrumBoard
|
|
3
3
|
class DoneColumnNotFoundError < StandardError; end
|
4
4
|
class AcceptedColumnNotFoundError < StandardError; end
|
5
5
|
|
6
|
-
def initialize(board_data, settings)
|
6
|
+
def initialize(board_data, settings = nil)
|
7
7
|
@settings = settings
|
8
8
|
@board_data = board_data
|
9
9
|
end
|
10
10
|
|
11
11
|
def columns
|
12
|
-
@columns ||= @board_data[
|
12
|
+
@columns ||= @board_data['lists'].map{|x| Column.new(@board_data, x['id'], @settings)}
|
13
13
|
end
|
14
14
|
|
15
15
|
def done_column
|
@@ -138,7 +138,7 @@ class ScrumBoard
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def id
|
141
|
-
@board_data[
|
141
|
+
@board_data['id']
|
142
142
|
end
|
143
143
|
|
144
144
|
def cards
|
data/lib/settings.rb
CHANGED
@@ -19,23 +19,25 @@ class Settings
|
|
19
19
|
|
20
20
|
attr_accessor :developer_public_key, :member_token, :board_aliases, :verbose,
|
21
21
|
:raw, :not_done_columns, :todo_column, :accepted_column_name_regex,
|
22
|
-
:done_column_name_regex, :todo_column_name_regex, :scrum
|
22
|
+
:done_column_name_regex, :todo_column_name_regex, :scrum,
|
23
|
+
:no_task_checklists
|
23
24
|
|
24
|
-
def initialize
|
25
|
+
def initialize(config_file_path)
|
25
26
|
@config_file_path = config_file_path
|
26
27
|
if File.exists? config_file_path
|
27
28
|
@config = YAML.load_file(config_file_path)
|
28
29
|
|
29
30
|
if @config
|
30
|
-
@developer_public_key = @config[
|
31
|
-
@member_token = @config[
|
32
|
-
@board_aliases = @config[
|
33
|
-
@scrum = OpenStruct.new(@config[
|
34
|
-
@not_done_columns = @config[
|
35
|
-
@
|
36
|
-
@
|
37
|
-
@
|
38
|
-
@
|
31
|
+
@developer_public_key = @config['developer_public_key']
|
32
|
+
@member_token = @config['member_token']
|
33
|
+
@board_aliases = @config['board_aliases'] || {}
|
34
|
+
@scrum = OpenStruct.new(@config['scrum'] || scrum_defaults)
|
35
|
+
@not_done_columns = @config['not_done_columns'].freeze || ['Sprint Backlog', 'Doing']
|
36
|
+
@no_task_checklists = @config['no_task_checklists'].freeze || ['Feedback']
|
37
|
+
@todo_column = @config['todo_column'].freeze
|
38
|
+
@done_column_name_regex = @config['done_column_name_regex'].freeze || /\ADone/
|
39
|
+
@accepted_column_name_regex = @config['accepted_column_name_regex'].freeze || /\AAccepted/
|
40
|
+
@todo_column_name_regex = @config['todo_column_name_regex'].freeze || /\ATo Do\Z/
|
39
41
|
else
|
40
42
|
raise "Couldn't read config data from '#{config_file_path}'"
|
41
43
|
end
|
@@ -47,10 +49,10 @@ class Settings
|
|
47
49
|
|
48
50
|
def save_config
|
49
51
|
@config = {}
|
50
|
-
@config[
|
51
|
-
@config[
|
52
|
+
@config['developer_public_key'] = @developer_public_key
|
53
|
+
@config['member_token'] = @member_token
|
52
54
|
|
53
|
-
File.open(@config_file_path,
|
55
|
+
File.open(@config_file_path, 'w') do |f|
|
54
56
|
f.write(@config.to_yaml)
|
55
57
|
end
|
56
58
|
end
|
@@ -63,21 +65,21 @@ class Settings
|
|
63
65
|
|
64
66
|
def scrum_defaults
|
65
67
|
{
|
66
|
-
|
67
|
-
|
68
|
-
|
68
|
+
'board_names' => {
|
69
|
+
'planning' => 'Planning Board',
|
70
|
+
'sprint' => 'Sprint Board'
|
69
71
|
},
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
},
|
74
|
-
"list_names" => {
|
75
|
-
"sprint_backlog" => "Sprint Backlog",
|
76
|
-
"sprint_qa" => "QA",
|
77
|
-
"sprint_doing" => "Doing",
|
78
|
-
"planning_backlog" => "Backlog",
|
79
|
-
"planning_ready" => "Ready for Estimation"
|
72
|
+
'label_names' => {
|
73
|
+
'sticky' => 'Sticky',
|
74
|
+
'waterline' => 'Under waterline'
|
80
75
|
},
|
76
|
+
'list_names' => {
|
77
|
+
'sprint_backlog' => 'Sprint Backlog',
|
78
|
+
'sprint_qa' => 'QA',
|
79
|
+
'sprint_doing' => 'Doing',
|
80
|
+
'planning_backlog' => 'Backlog',
|
81
|
+
'planning_ready' => 'Ready for Estimation'
|
82
|
+
}
|
81
83
|
}.freeze
|
82
84
|
end
|
83
85
|
end
|
data/lib/trello_service.rb
CHANGED
data/lib/trello_wrapper.rb
CHANGED
@@ -42,23 +42,23 @@ class TrelloWrapper < TrelloService
|
|
42
42
|
|
43
43
|
def add_attachment(card_id, filename)
|
44
44
|
card = Trello::Card.find(card_id)
|
45
|
-
card.add_attachment(File.open(filename,
|
45
|
+
card.add_attachment(File.open(filename, 'rb'))
|
46
46
|
end
|
47
47
|
|
48
48
|
def make_cover(card_id, image_name)
|
49
49
|
attachment_id = attachment_id_by_name(card_id, image_name)
|
50
|
-
raise("Error: The attachment with the name '#{image_name}' was not found")
|
50
|
+
raise("Error: The attachment with the name '#{image_name}' was not found") unless attachment_id
|
51
|
+
make_cover_with_id(card_id, attachment_id)
|
52
|
+
end
|
53
|
+
|
54
|
+
def make_cover_with_id(card_id, attachment_id)
|
51
55
|
client.put("/cards/#{card_id}/idAttachmentCover?value=#{attachment_id}")
|
52
56
|
end
|
53
57
|
|
54
58
|
def attachment_id_by_name(card_id, image_name)
|
55
59
|
json = JSON.parse(client.get("/cards/#{card_id}/attachments?fields=name"))
|
56
|
-
attachment = json.find{ |e| e[
|
57
|
-
if attachment
|
58
|
-
attachment["id"]
|
59
|
-
else
|
60
|
-
nil
|
61
|
-
end
|
60
|
+
attachment = json.find{ |e| e['name'] == image_name }
|
61
|
+
attachment['id'] if attachment
|
62
62
|
end
|
63
63
|
|
64
64
|
def get_description(card_id)
|
@@ -79,7 +79,7 @@ class TrelloWrapper < TrelloService
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def get_board(board_id)
|
82
|
-
raise TrolloloError
|
82
|
+
raise TrolloloError, 'Board id cannot be blank' if board_id.blank?
|
83
83
|
|
84
84
|
client.get("/boards/#{board_id}?lists=open&cards=open&card_checklists=all")
|
85
85
|
end
|
data/lib/version.rb
CHANGED
data/spec/data/card.json
CHANGED
@@ -49,6 +49,25 @@
|
|
49
49
|
"idChecklists": [
|
50
50
|
"5319c04d592a9182153db831"
|
51
51
|
],
|
52
|
+
"checklists": [
|
53
|
+
{ "id": "5319c052d86da6cb6fa2a9e0",
|
54
|
+
"name": "Tasks",
|
55
|
+
"checkItems": [
|
56
|
+
{ "name": "Task 1", "state": "complete"},
|
57
|
+
{ "name": "Task 2", "state": "incomplete"},
|
58
|
+
{ "name": "Task 3", "state": "incomplete"}
|
59
|
+
]
|
60
|
+
},
|
61
|
+
{
|
62
|
+
"id": "5319c0562fef3bc511c79279",
|
63
|
+
"name": "Definition of done",
|
64
|
+
"checkItems": [
|
65
|
+
{ "name": "DoD 1", "state": "complete"},
|
66
|
+
{ "name": "DoD 2", "state": "complete"},
|
67
|
+
{ "name": "DoD 3", "state": "inmplete"}
|
68
|
+
]
|
69
|
+
}
|
70
|
+
],
|
52
71
|
"idMembers": [
|
53
72
|
|
54
73
|
],
|
data/spec/data/trollolorc
CHANGED
@@ -15,36 +15,36 @@ def credentials_input_wrapper
|
|
15
15
|
File.expand_path('../wrapper/credentials_input_wrapper', __FILE__)
|
16
16
|
end
|
17
17
|
|
18
|
-
describe
|
18
|
+
describe 'command line' do
|
19
19
|
|
20
|
-
it
|
21
|
-
result = run_command(args: [
|
20
|
+
it 'processes help option' do
|
21
|
+
result = run_command(args: ['-h'])
|
22
22
|
expect(result).to exit_with_success(/Commands:/)
|
23
|
-
expect(result.stdout).to match(
|
24
|
-
expect(result.stdout).to match(
|
23
|
+
expect(result.stdout).to match('trollolo help')
|
24
|
+
expect(result.stdout).to match('Options:')
|
25
25
|
end
|
26
26
|
|
27
|
-
it
|
28
|
-
result = run_command(cmd: trollolo_cmd, args: [
|
27
|
+
it 'throws error on invalid command' do
|
28
|
+
result = run_command(cmd: trollolo_cmd, args: ['invalid_command'])
|
29
29
|
expect(result).to exit_with_error(1, "Could not find command \"invalid_command\".\n")
|
30
30
|
end
|
31
31
|
|
32
|
-
it
|
33
|
-
expect(run_command(cmd: credentials_input_wrapper, args: [
|
32
|
+
it 'asks for authorization data' do
|
33
|
+
expect(run_command(cmd: credentials_input_wrapper, args: ['get-cards', '--board-id=myboardid'])).to exit_with_success('')
|
34
34
|
end
|
35
35
|
|
36
|
-
describe
|
36
|
+
describe 'burndown chart' do
|
37
37
|
use_given_filesystem
|
38
38
|
|
39
|
-
it
|
39
|
+
it 'inits burndown directory' do
|
40
40
|
path = given_directory
|
41
|
-
result = run_command(cmd: trollolo_cmd, args: [
|
41
|
+
result = run_command(cmd: trollolo_cmd, args: ['burndown-init', '-o', path.to_s, '--board-id=myboardid'])
|
42
42
|
expect(result).to exit_with_success(/Preparing/)
|
43
43
|
end
|
44
44
|
|
45
|
-
it
|
45
|
+
it 'fails, if burndown data is not found' do
|
46
46
|
path = given_directory
|
47
|
-
result = run_command(cmd: trollolo_cmd, args: [
|
47
|
+
result = run_command(cmd: trollolo_cmd, args: ['burndown', '-o', path.to_s])
|
48
48
|
expect(result).to exit_with_error(1, /burndown-data-01.yaml' not found/)
|
49
49
|
end
|
50
50
|
end
|