pivotal-to-trello 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +260 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/bin/pivotal-to-trello +10 -0
- data/lib/pivotal-to-trello.rb +16 -0
- data/lib/pivotal_to_trello/core.rb +114 -0
- data/lib/pivotal_to_trello/pivotal_wrapper.rb +34 -0
- data/lib/pivotal_to_trello/runner.rb +108 -0
- data/lib/pivotal_to_trello/trello_wrapper.rb +107 -0
- data/pivotal-to-trello.gemspec +76 -0
- data/spec/pivotal_to_trello/core_spec.rb +101 -0
- data/spec/pivotal_to_trello/pivotal_wrapper_spec.rb +30 -0
- data/spec/pivotal_to_trello/trello_wrapper_spec.rb +114 -0
- data/spec/spec_helper.rb +70 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1c6a9c20609a5c73bcb501e1dfb7348887428e9f
|
4
|
+
data.tar.gz: 4de6683169e6fdfe8f0c7b91ece7e7a2d250598c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 77a57760785ef0508c3a9f4e787b6df6ebd143178e5ae1deabc56707bbfbf2023d9598bb5e65c851c0a05d9bc60f50de25a76770ab92c50c775558e672c20b1d
|
7
|
+
data.tar.gz: b8d99a1d6839f9c8792811d8317e251d830198ed71797dc2e3e512af41a134c9cb8b249a92d53161d2869d9d998f69c1ff1f7e6d8281993e4dff3245870e9223
|
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
gem 'highline'
|
4
|
+
gem 'ruby-trello', '>= 1.0.0 '
|
5
|
+
gem 'pivotal-tracker'
|
6
|
+
|
7
|
+
# Add dependencies to develop your gem here.
|
8
|
+
# Include everything needed to run rake, tests, features, etc.
|
9
|
+
group :development do
|
10
|
+
gem 'rspec'
|
11
|
+
gem 'rdoc'
|
12
|
+
gem 'bundler'
|
13
|
+
gem 'jeweler'
|
14
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2014 Dave Perrett
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
pivotal-to-trello [![Build Status](https://travis-ci.org/recurser/pivotal-to-trello.png?branch=master)](https://travis-ci.org/recurser/pivotal-to-trello)
|
2
|
+
=================
|
3
|
+
|
4
|
+
This gem provides a command for exporting a [Pivotal Tracker](https://www.pivotaltracker.com/) project to [Trello](https://trello.com/).
|
5
|
+
|
6
|
+
Getting started
|
7
|
+
---------------
|
8
|
+
|
9
|
+
1. Install the gem:
|
10
|
+
|
11
|
+
> gem install pivotal-to-trello
|
12
|
+
|
13
|
+
2. Run the importer:
|
14
|
+
|
15
|
+
> pivotal-to-trello import --trello-key TRELLO_API_KEY --trello-token TRELLO_TOKEN --pivotal-token PIVOTAL_TOKEN
|
16
|
+
|
17
|
+
See the [Obtaining API credentials](#obtaining-api-credentials) section for details on how to obtain these credentials.
|
18
|
+
|
19
|
+
The importer will ask you a series of questions to identify which Trello lists you want to import certain classes of stories into. It will then import the stories into Trello, along with any associated comments. It does not currently have the ability to import attachments.
|
20
|
+
|
21
|
+
For example :
|
22
|
+
|
23
|
+
> pivotal-to-trello import --trello-key TRELLO_API_KEY --trello-token TRELLO_TOKEN --pivotal-token PIVOTAL_TOKEN
|
24
|
+
|
25
|
+
Which Pivotal project would you like to export?
|
26
|
+
1. Android App
|
27
|
+
2. IOS App
|
28
|
+
3. Tech Support
|
29
|
+
4. Web App
|
30
|
+
Please select an option : 4
|
31
|
+
|
32
|
+
Which Trello board would you like to import into?
|
33
|
+
1. Development
|
34
|
+
2. Welcome Board
|
35
|
+
3. Wish List
|
36
|
+
Please select an option : 1
|
37
|
+
|
38
|
+
Which Trello list would you like to put 'icebox' stories into?
|
39
|
+
1. Accepted
|
40
|
+
2. Backlog
|
41
|
+
3. Bugs
|
42
|
+
4. Delivered
|
43
|
+
5. Finished
|
44
|
+
6. Icebox
|
45
|
+
7. In Progress
|
46
|
+
8. Rejected
|
47
|
+
9. Releases
|
48
|
+
10. [don't import these stories]
|
49
|
+
Please select an option : 6
|
50
|
+
|
51
|
+
Which Trello list would you like to put 'current' stories into?
|
52
|
+
1. Accepted
|
53
|
+
2. Backlog
|
54
|
+
3. Bugs
|
55
|
+
4. Delivered
|
56
|
+
5. Finished
|
57
|
+
6. Icebox
|
58
|
+
7. In Progress
|
59
|
+
8. Rejected
|
60
|
+
9. Releases
|
61
|
+
10. [don't import these stories]
|
62
|
+
Please select an option : 7
|
63
|
+
|
64
|
+
Which Trello list would you like to put 'finished' stories into?
|
65
|
+
1. Accepted
|
66
|
+
2. Backlog
|
67
|
+
3. Bugs
|
68
|
+
4. Delivered
|
69
|
+
5. Finished
|
70
|
+
6. Icebox
|
71
|
+
7. In Progress
|
72
|
+
8. Rejected
|
73
|
+
9. Releases
|
74
|
+
10. [don't import these stories]
|
75
|
+
Please select an option : 5
|
76
|
+
|
77
|
+
Which Trello list would you like to put 'delivered' stories into?
|
78
|
+
1. Accepted
|
79
|
+
2. Backlog
|
80
|
+
3. Bugs
|
81
|
+
4. Delivered
|
82
|
+
5. Finished
|
83
|
+
6. Icebox
|
84
|
+
7. In Progress
|
85
|
+
8. Rejected
|
86
|
+
9. Releases
|
87
|
+
10. [don't import these stories]
|
88
|
+
Please select an option : 4
|
89
|
+
|
90
|
+
Which Trello list would you like to put 'accepted' stories into?
|
91
|
+
1. Accepted
|
92
|
+
2. Backlog
|
93
|
+
3. Bugs
|
94
|
+
4. Delivered
|
95
|
+
5. Finished
|
96
|
+
6. Icebox
|
97
|
+
7. In Progress
|
98
|
+
8. Rejected
|
99
|
+
9. Releases
|
100
|
+
10. [don't import these stories]
|
101
|
+
Please select an option : 10
|
102
|
+
|
103
|
+
Which Trello list would you like to put 'rejected' stories into?
|
104
|
+
1. Accepted
|
105
|
+
2. Backlog
|
106
|
+
3. Bugs
|
107
|
+
4. Delivered
|
108
|
+
5. Finished
|
109
|
+
6. Icebox
|
110
|
+
7. In Progress
|
111
|
+
8. Rejected
|
112
|
+
9. Releases
|
113
|
+
10. [don't import these stories]
|
114
|
+
Please select an option : 10
|
115
|
+
|
116
|
+
Which Trello list would you like to put 'backlog' bugs into?
|
117
|
+
1. Accepted
|
118
|
+
2. Backlog
|
119
|
+
3. Bugs
|
120
|
+
4. Delivered
|
121
|
+
5. Finished
|
122
|
+
6. Icebox
|
123
|
+
7. In Progress
|
124
|
+
8. Rejected
|
125
|
+
9. Releases
|
126
|
+
10. [don't import these stories]
|
127
|
+
Please select an option : 2
|
128
|
+
|
129
|
+
Which Trello list would you like to put 'backlog' chores into?
|
130
|
+
1. Accepted
|
131
|
+
2. Backlog
|
132
|
+
3. Bugs
|
133
|
+
4. Delivered
|
134
|
+
5. Finished
|
135
|
+
6. Icebox
|
136
|
+
7. In Progress
|
137
|
+
8. Rejected
|
138
|
+
9. Releases
|
139
|
+
10. [don't import these stories]
|
140
|
+
Please select an option : 2
|
141
|
+
|
142
|
+
Which Trello list would you like to put 'backlog' features into?
|
143
|
+
1. Accepted
|
144
|
+
2. Backlog
|
145
|
+
3. Bugs
|
146
|
+
4. Delivered
|
147
|
+
5. Finished
|
148
|
+
6. Icebox
|
149
|
+
7. In Progress
|
150
|
+
8. Rejected
|
151
|
+
9. Releases
|
152
|
+
10. [don't import these stories]
|
153
|
+
Please select an option : 2
|
154
|
+
|
155
|
+
Which Trello list would you like to put 'backlog' releases into?
|
156
|
+
1. Accepted
|
157
|
+
2. Backlog
|
158
|
+
3. Bugs
|
159
|
+
4. Delivered
|
160
|
+
5. Finished
|
161
|
+
6. Icebox
|
162
|
+
7. In Progress
|
163
|
+
8. Rejected
|
164
|
+
9. Releases
|
165
|
+
10. [don't import these stories]
|
166
|
+
Please select an option : 2
|
167
|
+
|
168
|
+
What color would you like to label bugs with?
|
169
|
+
1. Blue
|
170
|
+
2. Green
|
171
|
+
3. Orange
|
172
|
+
4. Purple
|
173
|
+
5. Red
|
174
|
+
6. Yellow
|
175
|
+
7. [none]
|
176
|
+
Please select an option : 5
|
177
|
+
|
178
|
+
What color would you like to label features with?
|
179
|
+
1. Blue
|
180
|
+
2. Green
|
181
|
+
3. Orange
|
182
|
+
4. Purple
|
183
|
+
5. Red
|
184
|
+
6. Yellow
|
185
|
+
7. [none]
|
186
|
+
Please select an option : 2
|
187
|
+
|
188
|
+
What color would you like to label chores with?
|
189
|
+
1. Blue
|
190
|
+
2. Green
|
191
|
+
3. Orange
|
192
|
+
4. Purple
|
193
|
+
5. Red
|
194
|
+
6. Yellow
|
195
|
+
7. [none]
|
196
|
+
Please select an option : 6
|
197
|
+
|
198
|
+
What color would you like to label releases with?
|
199
|
+
1. Blue
|
200
|
+
2. Green
|
201
|
+
3. Orange
|
202
|
+
4. Purple
|
203
|
+
5. Red
|
204
|
+
6. Yellow
|
205
|
+
7. [none]
|
206
|
+
Please select an option : 4
|
207
|
+
|
208
|
+
Beginning import...
|
209
|
+
Creating a card for chore 'My example chore'.
|
210
|
+
...
|
211
|
+
|
212
|
+
Obtaining API credentials
|
213
|
+
-------------------------
|
214
|
+
|
215
|
+
You can get your Trello application key by logging into Trello, and then visiting [https://trello.com/1/appKey/generate](https://trello.com/1/appKey/generate)
|
216
|
+
|
217
|
+
Your 32-character application key will be listed in the first box.
|
218
|
+
|
219
|
+
To obtain your Trello member token, visit the following URL, substuting your Trello application key for *APP_KEY*:
|
220
|
+
|
221
|
+
[https://trello.com/1/authorize?key=APP_KEY&name=Pivotal%20To%20Trello&response_type=token&scope=read,write](https://trello.com/1/authorize?key=APP_KEY&name=Pivotal%20To%20Trello&response_type=token&scope=read,write)
|
222
|
+
|
223
|
+
Click the *Allow* button, and you will be presented with a 64-character token.
|
224
|
+
|
225
|
+
See the [Trello documentation](https://trello.com/docs/gettingstarted/index.html#getting-an-application-key) for more details.
|
226
|
+
|
227
|
+
The Pivotal Tracker token can be found at the bottom of your [Pivotal profile page](https://www.pivotaltracker.com/profile).
|
228
|
+
|
229
|
+
Change history
|
230
|
+
--------------
|
231
|
+
|
232
|
+
* **Version 0.1.0 (2014-01-13)** : Initial version.
|
233
|
+
|
234
|
+
Bug Reports
|
235
|
+
-----------
|
236
|
+
|
237
|
+
If you come across any problems, please [create a ticket](https://github.com/recurser/pivotal-to-trello/issues) and I'll try to get it fixed as soon as possible.
|
238
|
+
|
239
|
+
Contributing
|
240
|
+
------------
|
241
|
+
|
242
|
+
Once you've made your changes:
|
243
|
+
|
244
|
+
1. [Fork](http://help.github.com/fork-a-repo/) pivotal-to-trello
|
245
|
+
2. Create a topic branch - `git checkout -b my_branch`
|
246
|
+
3. Push to your branch - `git push origin my_branch`
|
247
|
+
4. Create a [Pull Request](http://help.github.com/pull-requests/) from your branch
|
248
|
+
5. That's it!
|
249
|
+
|
250
|
+
|
251
|
+
Author
|
252
|
+
------
|
253
|
+
|
254
|
+
Dave Perrett :: hello@daveperrett.com :: [@daveperrett](http://twitter.com/daveperrett)
|
255
|
+
|
256
|
+
|
257
|
+
Copyright
|
258
|
+
---------
|
259
|
+
|
260
|
+
Copyright (c) 2010 Dave Perrett. See [License](https://github.com/recurser/jquery-i18n/blob/master/LICENSE) for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "pivotal-to-trello"
|
18
|
+
gem.license = "MIT"
|
19
|
+
gem.homepage = "http://github.com/recurser/pivotal-to-trello"
|
20
|
+
gem.summary = %Q{Pivotal Tracker to Trello exporter}
|
21
|
+
gem.description = %Q{Pulls stories from Pivotal Tracker and imports them into Trello}
|
22
|
+
gem.email = "hello@daveperrett.com"
|
23
|
+
gem.authors = ["Dave Perrett"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rdoc/task'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "pivotal-to-trello #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,16 @@
|
|
1
|
+
unless defined? P2T_ROOT
|
2
|
+
$LOAD_PATH << File.expand_path(File.dirname(__FILE__))
|
3
|
+
P2T_ROOT = File.expand_path(File.dirname(__FILE__) + '/../')
|
4
|
+
P2T_LIB = File.join(P2T_ROOT, 'lib', 'pivotal_to_trello')
|
5
|
+
end
|
6
|
+
|
7
|
+
module PivotalToTrello
|
8
|
+
def self.version
|
9
|
+
File.exist?(File.join(P2T_ROOT, 'VERSION')) ? File.read(File.join(P2T_ROOT, 'VERSION')) : 'Unknown'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'pivotal_to_trello/core'
|
13
|
+
require 'pivotal_to_trello/pivotal_wrapper'
|
14
|
+
require 'pivotal_to_trello/runner'
|
15
|
+
require 'pivotal_to_trello/trello_wrapper'
|
16
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'highline/import'
|
3
|
+
|
4
|
+
module PivotalToTrello
|
5
|
+
# The core entry point of the gem, which handles the import process.
|
6
|
+
class Core
|
7
|
+
|
8
|
+
# Constructor
|
9
|
+
def initialize(options = OpenStruct.new)
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
# Generates a film clip based on the given input audio file, and saves it
|
14
|
+
# to the given output path.
|
15
|
+
def import!
|
16
|
+
prompt_for_details
|
17
|
+
|
18
|
+
puts "\nBeginning import..."
|
19
|
+
pivotal.stories(options.pivotal_project_id).each do |story|
|
20
|
+
if story.current_state == 'accepted'
|
21
|
+
list_id = options.accepted_list_id
|
22
|
+
elsif story.current_state == 'rejected'
|
23
|
+
list_id = options.rejected_list_id
|
24
|
+
elsif story.current_state == 'finished'
|
25
|
+
list_id = options.finished_list_id
|
26
|
+
elsif story.current_state == 'delivered'
|
27
|
+
list_id = options.delivered_list_id
|
28
|
+
elsif story.current_state == 'started'
|
29
|
+
list_id = options.current_list_id
|
30
|
+
elsif story.current_state == 'unscheduled'
|
31
|
+
list_id = options.icebox_list_id
|
32
|
+
elsif story.current_state == 'unstarted' && story.story_type == 'feature'
|
33
|
+
list_id = options.feature_list_id
|
34
|
+
elsif story.current_state == 'unstarted' && story.story_type == 'chore'
|
35
|
+
list_id = options.chore_list_id
|
36
|
+
elsif story.current_state == 'unstarted' && story.story_type == 'bug'
|
37
|
+
list_id = options.bug_list_id
|
38
|
+
elsif story.current_state == 'unstarted' && story.story_type == 'release'
|
39
|
+
list_id = options.release_list_id
|
40
|
+
else
|
41
|
+
puts "Ignoring story #{story.id} - type is '#{story.story_type}', state is '#{story.current_state}'"
|
42
|
+
end
|
43
|
+
|
44
|
+
if story.story_type == 'bug' && options.bug_label
|
45
|
+
label = options.bug_label
|
46
|
+
elsif story.story_type == 'chore' && options.feature_label
|
47
|
+
label = options.feature_label
|
48
|
+
elsif story.story_type == 'feature' && options.chore_label
|
49
|
+
label = options.chore_label
|
50
|
+
elsif story.story_type == 'release' && options.release_label
|
51
|
+
label = options.release_label
|
52
|
+
end
|
53
|
+
|
54
|
+
if list_id
|
55
|
+
card = trello.create_card(list_id, story)
|
56
|
+
trello.add_label(card, label)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the options struct.
|
62
|
+
def options
|
63
|
+
@options
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# Prompts the user for details about the imort/export.
|
69
|
+
def prompt_for_details
|
70
|
+
options.pivotal_project_id = prompt_selection('Which Pivotal project would you like to export?', pivotal.project_choices)
|
71
|
+
options.trello_board_id = prompt_selection('Which Trello board would you like to import into?', trello.board_choices)
|
72
|
+
options.icebox_list_id = prompt_selection("Which Trello list would you like to put 'icebox' stories into?", trello.list_choices(options.trello_board_id))
|
73
|
+
options.current_list_id = prompt_selection("Which Trello list would you like to put 'current' stories into?", trello.list_choices(options.trello_board_id))
|
74
|
+
options.finished_list_id = prompt_selection("Which Trello list would you like to put 'finished' stories into?", trello.list_choices(options.trello_board_id))
|
75
|
+
options.delivered_list_id = prompt_selection("Which Trello list would you like to put 'delivered' stories into?", trello.list_choices(options.trello_board_id))
|
76
|
+
options.accepted_list_id = prompt_selection("Which Trello list would you like to put 'accepted' stories into?", trello.list_choices(options.trello_board_id))
|
77
|
+
options.rejected_list_id = prompt_selection("Which Trello list would you like to put 'rejected' stories into?", trello.list_choices(options.trello_board_id))
|
78
|
+
options.bug_list_id = prompt_selection("Which Trello list would you like to put 'backlog' bugs into?", trello.list_choices(options.trello_board_id))
|
79
|
+
options.chore_list_id = prompt_selection("Which Trello list would you like to put 'backlog' chores into?", trello.list_choices(options.trello_board_id))
|
80
|
+
options.feature_list_id = prompt_selection("Which Trello list would you like to put 'backlog' features into?", trello.list_choices(options.trello_board_id))
|
81
|
+
options.release_list_id = prompt_selection("Which Trello list would you like to put 'backlog' releases into?", trello.list_choices(options.trello_board_id))
|
82
|
+
options.bug_label = prompt_selection('What color would you like to label bugs with?', trello.label_choices)
|
83
|
+
options.feature_label = prompt_selection('What color would you like to label features with?', trello.label_choices)
|
84
|
+
options.chore_label = prompt_selection('What color would you like to label chores with?', trello.label_choices)
|
85
|
+
options.release_label = prompt_selection('What color would you like to label releases with?', trello.label_choices)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Prompts the user to select an option from the given list of choices.
|
89
|
+
def prompt_selection(question, choices)
|
90
|
+
say("\n#{question}")
|
91
|
+
choose do |menu|
|
92
|
+
menu.prompt = "Please select an option : "
|
93
|
+
|
94
|
+
choices.each do |key, value|
|
95
|
+
menu.choice value do
|
96
|
+
return key
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns an instance of the pivotal wrapper.
|
104
|
+
def pivotal
|
105
|
+
@pivotal ||= PivotalToTrello::PivotalWrapper.new(options.pivotal_token)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns an instance of the trello wrapper.
|
109
|
+
def trello
|
110
|
+
@trello ||= PivotalToTrello::TrelloWrapper.new(options.trello_key, options.trello_token)
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'pivotal-tracker'
|
2
|
+
|
3
|
+
module PivotalToTrello
|
4
|
+
# Interface to the Pivotal Tracker API.
|
5
|
+
class PivotalWrapper
|
6
|
+
|
7
|
+
# Constructor
|
8
|
+
def initialize(token)
|
9
|
+
::PivotalTracker::Client.token = token
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns a hash of available projects keyed on project ID.
|
13
|
+
def project_choices
|
14
|
+
::PivotalTracker::Project.all.inject({}) do |hash, project|
|
15
|
+
hash[project.id] = project.name
|
16
|
+
hash
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns all stories for the given project.
|
21
|
+
def stories(project_id)
|
22
|
+
project(project_id).stories.all
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Returns the Pivotal project that we're exporting.
|
28
|
+
def project(project_id)
|
29
|
+
@projects ||= {}
|
30
|
+
@projects[project_id] ||= ::PivotalTracker::Project.find(project_id)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module PivotalToTrello
|
5
|
+
# Utility class to handle the different commands that the 'pivotal-to-trello'
|
6
|
+
# command offers.
|
7
|
+
class Runner
|
8
|
+
# Start running a pivotal-to-trello command from the passed-in arguments.
|
9
|
+
def self.execute
|
10
|
+
runner = new
|
11
|
+
options = runner.parse_options(ARGV)
|
12
|
+
runner.execute!(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Dispatch central.
|
16
|
+
def execute!(options)
|
17
|
+
case options.action
|
18
|
+
when :import then import(options)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Parses the options, and displays help messages if the options given are
|
23
|
+
# incorrect.
|
24
|
+
def parse_options(args)
|
25
|
+
options = OpenStruct.new
|
26
|
+
|
27
|
+
commands = {
|
28
|
+
'import' => OptionParser.new do |opts|
|
29
|
+
opts.banner = 'Usage: pivotal-to-trello import [options]'
|
30
|
+
opts.separator ''
|
31
|
+
opts.separator 'All arguments except for -v and -h are required.'
|
32
|
+
opts.separator ''
|
33
|
+
opts.separator 'Options:'
|
34
|
+
|
35
|
+
opts.on('-k', '--trello-key KEY', 'Trello application key') do |trello_key|
|
36
|
+
options.trello_key = trello_key
|
37
|
+
end
|
38
|
+
opts.on('-t', '--trello-token TOKEN', 'Trello member token') do |trello_token|
|
39
|
+
options.trello_token = trello_token
|
40
|
+
end
|
41
|
+
opts.on('-p', '--pivotal-token TOKEN', 'Pivotal Tracker API token') do |pivotal_token|
|
42
|
+
options.pivotal_token = pivotal_token
|
43
|
+
end
|
44
|
+
|
45
|
+
# Miscellaneous.
|
46
|
+
opts.on_tail('-v', '--version', 'Show version information') { show_version }
|
47
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
48
|
+
STDOUT.write opts
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on do
|
53
|
+
if options.trello_key && \
|
54
|
+
options.trello_token && \
|
55
|
+
options.pivotal_token
|
56
|
+
options.action = :import
|
57
|
+
else
|
58
|
+
# Output a help message unless the required options have been specified.
|
59
|
+
options.action = :error
|
60
|
+
STDOUT.write commands['import']
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end,
|
64
|
+
}
|
65
|
+
|
66
|
+
global = OptionParser.new do |opts|
|
67
|
+
opts.banner = "Usage: pivotal-to-trello [#{commands.keys.join(', ')}] [options]"
|
68
|
+
|
69
|
+
opts.separator ''
|
70
|
+
opts.separator 'pivotal-to-trello is a library for importing stories from Pivotal Tracker into Trello.'
|
71
|
+
opts.separator ''
|
72
|
+
opts.separator "Use pivotal-to-trello [#{commands.keys.join(', ')}] -h to find out more about a specific command"
|
73
|
+
opts.separator ''
|
74
|
+
opts.separator 'Other options:'
|
75
|
+
|
76
|
+
opts.on_tail('-v', '--version', 'Show version information') do
|
77
|
+
show_version
|
78
|
+
exit
|
79
|
+
end
|
80
|
+
|
81
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
82
|
+
STDOUT.write opts
|
83
|
+
exit
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
global.order!
|
88
|
+
command = args.shift
|
89
|
+
commands[command].order! if commands[command]
|
90
|
+
|
91
|
+
# Output a help message unless a command has been specified.
|
92
|
+
STDOUT.write global unless options.action
|
93
|
+
|
94
|
+
options
|
95
|
+
end
|
96
|
+
|
97
|
+
# Generates a film clip for the given input file, and saves it to the given
|
98
|
+
# output path.
|
99
|
+
def import(options)
|
100
|
+
PivotalToTrello::Core.new(options).import!
|
101
|
+
end
|
102
|
+
|
103
|
+
# Display the current version of Ruby-Processing.
|
104
|
+
def show_version
|
105
|
+
STDOUT.write "pivotal-to-trello version #{PivotalToTrello.version}"
|
106
|
+
end
|
107
|
+
end # class Runner
|
108
|
+
end # module PivotalToTrello
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'trello'
|
2
|
+
|
3
|
+
module PivotalToTrello
|
4
|
+
# Interface to the Trello API.
|
5
|
+
class TrelloWrapper
|
6
|
+
|
7
|
+
# Constructor
|
8
|
+
def initialize(key, token)
|
9
|
+
Trello.configure do |config|
|
10
|
+
config.developer_public_key = key
|
11
|
+
config.member_token = token
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Creates a card in the given list if one with the same name doesn't already exist.
|
16
|
+
def create_card(list_id, pivotal_story)
|
17
|
+
card = get_card(list_id, pivotal_story.name, pivotal_story.description)
|
18
|
+
card ||= begin
|
19
|
+
puts "Creating a card for #{pivotal_story.story_type} '#{pivotal_story.name}'."
|
20
|
+
card = Trello::Card.create(
|
21
|
+
:name => pivotal_story.name,
|
22
|
+
:desc => pivotal_story.description,
|
23
|
+
:list_id => list_id
|
24
|
+
)
|
25
|
+
|
26
|
+
pivotal_story.notes.all.each do |note|
|
27
|
+
card.add_comment("[#{note.author}] #{note.text.to_s.strip}") unless note.text.to_s.strip.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
card
|
31
|
+
end
|
32
|
+
|
33
|
+
key = card_hash(card.name, card.desc)
|
34
|
+
@cards ||= {}
|
35
|
+
@cards[list_id] ||= {}
|
36
|
+
@cards[list_id][key] = card
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a hash of available boards, keyed on board ID.
|
40
|
+
def board_choices
|
41
|
+
Trello::Board.all.inject({}) do |hash, board|
|
42
|
+
hash[board.id] = board.name
|
43
|
+
hash
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns a hash of available lists for the given board, keyed on board ID.
|
48
|
+
def list_choices(board_id)
|
49
|
+
# Cache the list to improve performance.
|
50
|
+
@lists ||= {}
|
51
|
+
@lists[board_id] ||= begin
|
52
|
+
choices = Trello::Board.find(board_id).lists.inject({}) do |hash, list|
|
53
|
+
hash[list.id] = list.name
|
54
|
+
hash
|
55
|
+
end
|
56
|
+
choices = Hash[choices.sort_by { |_, v| v }]
|
57
|
+
choices[false] = "[don't import these stories]"
|
58
|
+
choices
|
59
|
+
end
|
60
|
+
|
61
|
+
@lists[board_id]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns a list of all cards in the given list, keyed on name.
|
65
|
+
def cards_for_list(list_id)
|
66
|
+
@cards ||= {}
|
67
|
+
@cards[list_id] ||= Trello::List.find(list_id).cards.inject({}) do |hash, card|
|
68
|
+
hash[card_hash(card.name, card.desc)] = card
|
69
|
+
hash
|
70
|
+
end
|
71
|
+
|
72
|
+
@cards[list_id]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Adds the given label to the card.
|
76
|
+
def add_label(card, label)
|
77
|
+
card.add_label(label) unless card.labels.collect { |label| label.color }.include?(label)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a list of colors that can be used to label cards.
|
81
|
+
def label_choices
|
82
|
+
{
|
83
|
+
'blue' => 'Blue',
|
84
|
+
'green' => 'Green',
|
85
|
+
'orange' => 'Orange',
|
86
|
+
'purple' => 'Purple',
|
87
|
+
'red' => 'Red',
|
88
|
+
'yellow' => 'Yellow',
|
89
|
+
false => '[none]'
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# Returns a unique identifier for this list/name/description combination.
|
96
|
+
def card_hash(name, description)
|
97
|
+
Digest::SHA1.hexdigest("#{name}_#{description}")
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns a card with the given name and description if it exists in the given list, nil otherwise.
|
101
|
+
def get_card(list_id, name, description)
|
102
|
+
key = card_hash(name, description)
|
103
|
+
cards_for_list(list_id)[key] if !cards_for_list(list_id)[key].nil?
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: pivotal-to-trello 0.1.0 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "pivotal-to-trello"
|
9
|
+
s.version = "0.1.0"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.authors = ["Dave Perrett"]
|
14
|
+
s.date = "2014-01-13"
|
15
|
+
s.description = "Pulls stories from Pivotal Tracker and imports them into Trello"
|
16
|
+
s.email = "hello@daveperrett.com"
|
17
|
+
s.executables = ["pivotal-to-trello"]
|
18
|
+
s.extra_rdoc_files = [
|
19
|
+
"LICENSE.txt",
|
20
|
+
"README.markdown"
|
21
|
+
]
|
22
|
+
s.files = [
|
23
|
+
".document",
|
24
|
+
"Gemfile",
|
25
|
+
"LICENSE.txt",
|
26
|
+
"README.markdown",
|
27
|
+
"Rakefile",
|
28
|
+
"VERSION",
|
29
|
+
"bin/pivotal-to-trello",
|
30
|
+
"lib/pivotal-to-trello.rb",
|
31
|
+
"lib/pivotal_to_trello/core.rb",
|
32
|
+
"lib/pivotal_to_trello/pivotal_wrapper.rb",
|
33
|
+
"lib/pivotal_to_trello/runner.rb",
|
34
|
+
"lib/pivotal_to_trello/trello_wrapper.rb",
|
35
|
+
"pivotal-to-trello.gemspec",
|
36
|
+
"spec/pivotal_to_trello/core_spec.rb",
|
37
|
+
"spec/pivotal_to_trello/pivotal_wrapper_spec.rb",
|
38
|
+
"spec/pivotal_to_trello/trello_wrapper_spec.rb",
|
39
|
+
"spec/spec_helper.rb"
|
40
|
+
]
|
41
|
+
s.homepage = "http://github.com/recurser/pivotal-to-trello"
|
42
|
+
s.licenses = ["MIT"]
|
43
|
+
s.rubygems_version = "2.2.1"
|
44
|
+
s.summary = "Pivotal Tracker to Trello exporter"
|
45
|
+
|
46
|
+
if s.respond_to? :specification_version then
|
47
|
+
s.specification_version = 4
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
50
|
+
s.add_runtime_dependency(%q<highline>, [">= 0"])
|
51
|
+
s.add_runtime_dependency(%q<ruby-trello>, [">= 1.0.0"])
|
52
|
+
s.add_runtime_dependency(%q<pivotal-tracker>, [">= 0"])
|
53
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
54
|
+
s.add_development_dependency(%q<rdoc>, [">= 0"])
|
55
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
56
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
57
|
+
else
|
58
|
+
s.add_dependency(%q<highline>, [">= 0"])
|
59
|
+
s.add_dependency(%q<ruby-trello>, [">= 1.0.0"])
|
60
|
+
s.add_dependency(%q<pivotal-tracker>, [">= 0"])
|
61
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
62
|
+
s.add_dependency(%q<rdoc>, [">= 0"])
|
63
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
64
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
65
|
+
end
|
66
|
+
else
|
67
|
+
s.add_dependency(%q<highline>, [">= 0"])
|
68
|
+
s.add_dependency(%q<ruby-trello>, [">= 1.0.0"])
|
69
|
+
s.add_dependency(%q<pivotal-tracker>, [">= 0"])
|
70
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
71
|
+
s.add_dependency(%q<rdoc>, [">= 0"])
|
72
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
73
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.expand_path(File.dirname(File.dirname(__FILE__)) + '/spec_helper')
|
2
|
+
|
3
|
+
describe 'Core' do
|
4
|
+
let(:core) { PivotalToTrello::Core.new(mock_options) }
|
5
|
+
let(:pivotal) { mock_pivotal_wrapper }
|
6
|
+
let(:trello) { mock_trello_wrapper }
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
IO.any_instance.stub(:puts)
|
10
|
+
core.stub(:pivotal => pivotal)
|
11
|
+
core.stub(:trello => trello)
|
12
|
+
|
13
|
+
core.stub(:prompt_selection).with('Which Pivotal project would you like to export?', pivotal.project_choices).and_return('pivotal_project_id')
|
14
|
+
core.stub(:prompt_selection).with('Which Trello board would you like to import into?', trello.board_choices).and_return('trello_board_id')
|
15
|
+
core.stub(:prompt_selection).with("Which Trello list would you like to put 'icebox' stories into?", trello.list_choices).and_return('icebox_list_id')
|
16
|
+
core.stub(:prompt_selection).with("Which Trello list would you like to put 'current' stories into?", trello.list_choices).and_return('current_list_id')
|
17
|
+
core.stub(:prompt_selection).with("Which Trello list would you like to put 'finished' stories into?", trello.list_choices).and_return('finished_list_id')
|
18
|
+
core.stub(:prompt_selection).with("Which Trello list would you like to put 'delivered' stories into?", trello.list_choices).and_return('delivered_list_id')
|
19
|
+
core.stub(:prompt_selection).with("Which Trello list would you like to put 'accepted' stories into?", trello.list_choices).and_return('accepted_list_id')
|
20
|
+
core.stub(:prompt_selection).with("Which Trello list would you like to put 'rejected' stories into?", trello.list_choices).and_return('rejected_list_id')
|
21
|
+
core.stub(:prompt_selection).with("Which Trello list would you like to put 'backlog' bugs into?", trello.list_choices).and_return('bug_list_id')
|
22
|
+
core.stub(:prompt_selection).with("Which Trello list would you like to put 'backlog' chores into?", trello.list_choices).and_return('chore_list_id')
|
23
|
+
core.stub(:prompt_selection).with("Which Trello list would you like to put 'backlog' features into?", trello.list_choices).and_return('feature_list_id')
|
24
|
+
core.stub(:prompt_selection).with("Which Trello list would you like to put 'backlog' releases into?", trello.list_choices).and_return('release_list_id')
|
25
|
+
core.stub(:prompt_selection).with('What color would you like to label bugs with?', trello.label_choices).and_return('bug_label')
|
26
|
+
core.stub(:prompt_selection).with('What color would you like to label features with?', trello.label_choices).and_return('feature_label')
|
27
|
+
core.stub(:prompt_selection).with('What color would you like to label chores with?', trello.label_choices).and_return('chore_label')
|
28
|
+
core.stub(:prompt_selection).with('What color would you like to label releases with?', trello.label_choices).and_return('release_label')
|
29
|
+
end
|
30
|
+
|
31
|
+
context '#import!' do
|
32
|
+
it 'prompts the user for details' do
|
33
|
+
core.import!
|
34
|
+
core.options.should == mock_options
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'story handling' do
|
38
|
+
let(:card) { mock_trello_card }
|
39
|
+
let(:story) { mock_pivotal_story }
|
40
|
+
|
41
|
+
before(:each) do
|
42
|
+
pivotal.should_receive(:stories).and_return([story])
|
43
|
+
trello.stub(:add_label => true, :create_card => card)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'handles accepted stories' do
|
47
|
+
story.stub(:current_state => 'accepted')
|
48
|
+
trello.should_receive(:create_card).with(core.options.accepted_list_id, story).and_return(card)
|
49
|
+
core.import!
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'handles rejected stories' do
|
53
|
+
story.stub(:current_state => 'rejected')
|
54
|
+
trello.should_receive(:create_card).with(core.options.rejected_list_id, story).and_return(card)
|
55
|
+
core.import!
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'handles finished stories' do
|
59
|
+
story.stub(:current_state => 'finished')
|
60
|
+
trello.should_receive(:create_card).with(core.options.finished_list_id, story).and_return(card)
|
61
|
+
core.import!
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'handles delivered stories' do
|
65
|
+
story.stub(:current_state => 'delivered')
|
66
|
+
trello.should_receive(:create_card).with(core.options.delivered_list_id, story).and_return(card)
|
67
|
+
core.import!
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'handles unstarted features' do
|
71
|
+
story.stub(:current_state => 'unstarted', :story_type => 'feature')
|
72
|
+
trello.should_receive(:create_card).with(core.options.feature_list_id, story).and_return(card)
|
73
|
+
core.import!
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'handles unstarted chores' do
|
77
|
+
story.stub(:current_state => 'unstarted', :story_type => 'chore')
|
78
|
+
trello.should_receive(:create_card).with(core.options.chore_list_id, story).and_return(card)
|
79
|
+
core.import!
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'handles unstarted bugs' do
|
83
|
+
story.stub(:current_state => 'unstarted', :story_type => 'bug')
|
84
|
+
trello.should_receive(:create_card).with(core.options.bug_list_id, story).and_return(card)
|
85
|
+
core.import!
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'handles unstarted releases' do
|
89
|
+
story.stub(:current_state => 'unstarted', :story_type => 'release')
|
90
|
+
trello.should_receive(:create_card).with(core.options.release_list_id, story).and_return(card)
|
91
|
+
core.import!
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'labels stories' do
|
95
|
+
story.stub(:story_type => 'bug')
|
96
|
+
trello.should_receive(:add_label).with(card, core.options.bug_label)
|
97
|
+
core.import!
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path(File.dirname(File.dirname(__FILE__)) + '/spec_helper')
|
2
|
+
|
3
|
+
describe 'PivotalWrapper' do
|
4
|
+
let(:wrapper) { PivotalToTrello::PivotalWrapper.new('token') }
|
5
|
+
|
6
|
+
context '#initialize' do
|
7
|
+
it 'sets the pivotal token' do
|
8
|
+
::PivotalTracker::Client.should_receive(:token=).with('token')
|
9
|
+
PivotalToTrello::PivotalWrapper.new('token')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context '#project_choices' do
|
14
|
+
it 'returns a hash of pivotal projects' do
|
15
|
+
project = OpenStruct.new(:id => 'id', :name => 'My Project')
|
16
|
+
::PivotalTracker::Project.should_receive(:all).and_return([project])
|
17
|
+
wrapper.project_choices.should == { 'id' => 'My Project'}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context '#stories' do
|
22
|
+
it 'returns an array of pivotal stories' do
|
23
|
+
story = mock_pivotal_story
|
24
|
+
project = double(PivotalTracker::Project)
|
25
|
+
::PivotalTracker::Project.should_receive(:find).with('project_id').and_return(project)
|
26
|
+
project.stub_chain(:stories, :all).and_return([story])
|
27
|
+
wrapper.stories('project_id').should == [story]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require File.expand_path(File.dirname(File.dirname(__FILE__)) + '/spec_helper')
|
2
|
+
|
3
|
+
describe 'TrelloWrapper' do
|
4
|
+
let(:wrapper) { PivotalToTrello::TrelloWrapper.new('key', 'token') }
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
IO.any_instance.stub(:puts)
|
8
|
+
end
|
9
|
+
|
10
|
+
context '#initialize' do
|
11
|
+
it 'sets the auth credentials' do
|
12
|
+
config = double
|
13
|
+
Trello.should_receive(:configure).and_yield(config)
|
14
|
+
config.should_receive(:developer_public_key=).with('key')
|
15
|
+
config.should_receive(:member_token=).with('token')
|
16
|
+
PivotalToTrello::TrelloWrapper.new('key', 'token')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context '#create_card' do
|
21
|
+
let(:card) { mock_trello_card }
|
22
|
+
|
23
|
+
it 'creates a new card if none exists' do
|
24
|
+
story = mock_pivotal_story
|
25
|
+
wrapper.should_receive(:get_card).and_return(nil)
|
26
|
+
Trello::Card.should_receive(:create).with(
|
27
|
+
:name => story.name,
|
28
|
+
:desc => story.description,
|
29
|
+
:list_id => 'list_id'
|
30
|
+
).and_return(card)
|
31
|
+
wrapper.create_card('list_id', story).should == card
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'does not create a new card if one exists with the same name' do
|
35
|
+
story = mock_pivotal_story(:name => 'My Card')
|
36
|
+
Trello::List.stub_chain(:find, :cards).and_return([card])
|
37
|
+
Trello::Card.should_not_receive(:create)
|
38
|
+
wrapper.create_card('list_id', story).should == card
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'creates a new card if one exists with a different name' do
|
42
|
+
story = mock_pivotal_story(:name => 'My Foo')
|
43
|
+
Trello::List.stub_chain(:find, :cards).and_return([card])
|
44
|
+
Trello::Card.should_receive(:create).with(
|
45
|
+
:name => story.name,
|
46
|
+
:desc => story.description,
|
47
|
+
:list_id => 'list_id'
|
48
|
+
).and_return(card)
|
49
|
+
wrapper.create_card('list_id', story).should == card
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'adds comments' do
|
53
|
+
note = OpenStruct.new(:text => 'My Note', :author => 'John Smith')
|
54
|
+
story = mock_pivotal_story
|
55
|
+
story.stub_chain(:notes, :all).and_return([note])
|
56
|
+
wrapper.should_receive(:get_card).and_return(nil)
|
57
|
+
Trello::Card.should_receive(:create).and_return(card)
|
58
|
+
card.should_receive(:add_comment).with('[John Smith] My Note')
|
59
|
+
wrapper.create_card('list_id', story).should == card
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context '#board_choices' do
|
64
|
+
it 'returns a hash of Trello boards' do
|
65
|
+
board = OpenStruct.new(:id => 'id', :name => 'My Board')
|
66
|
+
Trello::Board.should_receive(:all).and_return([board])
|
67
|
+
wrapper.board_choices.should == { 'id' => 'My Board'}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context '#list_choices' do
|
72
|
+
it 'returns a hash of Trello lists' do
|
73
|
+
board = double(Trello::Board)
|
74
|
+
list = OpenStruct.new(:id => 'id', :name => 'My List')
|
75
|
+
Trello::Board.should_receive(:find).with('board_id').and_return(board)
|
76
|
+
board.should_receive(:lists).and_return([list])
|
77
|
+
wrapper.list_choices('board_id').should == {
|
78
|
+
'id' => 'My List',
|
79
|
+
false => "[don't import these stories]",
|
80
|
+
}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context '#cards_for_list' do
|
85
|
+
it 'returns a hash of Trello lists' do
|
86
|
+
list = double(Trello::List)
|
87
|
+
card = OpenStruct.new(:name => 'My Card', :desc => 'My Description')
|
88
|
+
Trello::List.should_receive(:find).with('list_id').and_return(list)
|
89
|
+
list.should_receive(:cards).and_return([card])
|
90
|
+
expected = {'193060beddd00d64259bdc1271d6c5a330e92e7d' => card}
|
91
|
+
wrapper.cards_for_list('list_id').should == expected
|
92
|
+
# Test caching.
|
93
|
+
Trello::List.should_not_receive(:find)
|
94
|
+
wrapper.cards_for_list('list_id').should == expected
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context '#add_label' do
|
99
|
+
it 'adds a label if it does not already exist' do
|
100
|
+
card = mock_trello_card
|
101
|
+
card.stub(:labels => [])
|
102
|
+
card.should_receive(:add_label).with('red')
|
103
|
+
wrapper.add_label(card, 'red')
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'does not add a label if it already exists' do
|
107
|
+
card = mock_trello_card
|
108
|
+
card.stub(:labels => [OpenStruct.new(:color => 'red')])
|
109
|
+
card.should_not_receive(:add_label)
|
110
|
+
wrapper.add_label(card, 'red')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'pivotal-to-trello'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
end
|
12
|
+
|
13
|
+
def mock_pivotal_wrapper
|
14
|
+
pivotal = double(PivotalToTrello::PivotalWrapper)
|
15
|
+
pivotal.stub(:project_choices => {})
|
16
|
+
pivotal.stub(:stories => [])
|
17
|
+
pivotal
|
18
|
+
end
|
19
|
+
|
20
|
+
def mock_trello_wrapper
|
21
|
+
trello = double(PivotalToTrello::TrelloWrapper)
|
22
|
+
trello.stub(:board_choices => {})
|
23
|
+
trello.stub(:list_choices => {})
|
24
|
+
trello.stub(:label_choices => {})
|
25
|
+
trello
|
26
|
+
end
|
27
|
+
|
28
|
+
def mock_pivotal_story(options = {})
|
29
|
+
options = {
|
30
|
+
:name => 'My Story',
|
31
|
+
:description => 'My Description',
|
32
|
+
:current_state => 'unstarted',
|
33
|
+
:story_type => 'feature',
|
34
|
+
}.merge(options)
|
35
|
+
story = double(PivotalTracker::Story)
|
36
|
+
story.stub_chain(:notes, :all).and_return([])
|
37
|
+
options.each { |k, v| story.stub(k => v) }
|
38
|
+
story
|
39
|
+
end
|
40
|
+
|
41
|
+
def mock_trello_card(options = {})
|
42
|
+
options = {
|
43
|
+
:name => 'My Card',
|
44
|
+
:desc => 'My Description',
|
45
|
+
}.merge(options)
|
46
|
+
card = double(Trello::Card)
|
47
|
+
options.each { |k, v| card.stub(k => v) }
|
48
|
+
card
|
49
|
+
end
|
50
|
+
|
51
|
+
def mock_options
|
52
|
+
OpenStruct.new({
|
53
|
+
:pivotal_project_id => 'pivotal_project_id',
|
54
|
+
:trello_board_id => 'trello_board_id',
|
55
|
+
:icebox_list_id => 'icebox_list_id',
|
56
|
+
:current_list_id => 'current_list_id',
|
57
|
+
:finished_list_id => 'finished_list_id',
|
58
|
+
:delivered_list_id => 'delivered_list_id',
|
59
|
+
:accepted_list_id => 'accepted_list_id',
|
60
|
+
:rejected_list_id => 'rejected_list_id',
|
61
|
+
:bug_list_id => 'bug_list_id',
|
62
|
+
:chore_list_id => 'chore_list_id',
|
63
|
+
:feature_list_id => 'feature_list_id',
|
64
|
+
:release_list_id => 'release_list_id',
|
65
|
+
:bug_label => 'bug_label',
|
66
|
+
:feature_label => 'feature_label',
|
67
|
+
:chore_label => 'chore_label',
|
68
|
+
:release_label => 'release_label',
|
69
|
+
})
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pivotal-to-trello
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dave Perrett
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: highline
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ruby-trello
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pivotal-tracker
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rdoc
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: jeweler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Pulls stories from Pivotal Tracker and imports them into Trello
|
112
|
+
email: hello@daveperrett.com
|
113
|
+
executables:
|
114
|
+
- pivotal-to-trello
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files:
|
117
|
+
- LICENSE.txt
|
118
|
+
- README.markdown
|
119
|
+
files:
|
120
|
+
- ".document"
|
121
|
+
- Gemfile
|
122
|
+
- LICENSE.txt
|
123
|
+
- README.markdown
|
124
|
+
- Rakefile
|
125
|
+
- VERSION
|
126
|
+
- bin/pivotal-to-trello
|
127
|
+
- lib/pivotal-to-trello.rb
|
128
|
+
- lib/pivotal_to_trello/core.rb
|
129
|
+
- lib/pivotal_to_trello/pivotal_wrapper.rb
|
130
|
+
- lib/pivotal_to_trello/runner.rb
|
131
|
+
- lib/pivotal_to_trello/trello_wrapper.rb
|
132
|
+
- pivotal-to-trello.gemspec
|
133
|
+
- spec/pivotal_to_trello/core_spec.rb
|
134
|
+
- spec/pivotal_to_trello/pivotal_wrapper_spec.rb
|
135
|
+
- spec/pivotal_to_trello/trello_wrapper_spec.rb
|
136
|
+
- spec/spec_helper.rb
|
137
|
+
homepage: http://github.com/recurser/pivotal-to-trello
|
138
|
+
licenses:
|
139
|
+
- MIT
|
140
|
+
metadata: {}
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project:
|
157
|
+
rubygems_version: 2.2.1
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: Pivotal Tracker to Trello exporter
|
161
|
+
test_files: []
|