trackinator 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/.rvmrc +6 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +89 -0
- data/Rakefile +2 -0
- data/bin/trackinate +25 -0
- data/lib/trackinator/importer.rb +176 -0
- data/lib/trackinator/version.rb +3 -0
- data/lib/trackinator.rb +5 -0
- data/trackinator.gemspec +26 -0
- metadata +92 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Justin Beck
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# Trackinator
|
2
|
+
|
3
|
+
This gem, when used correctly, will import a Google spreadsheet
|
4
|
+
into YouTrack as tickets. The spreadsheet is a test plan which
|
5
|
+
defines features (and how to use them). There are numerous
|
6
|
+
benefits to this approach:
|
7
|
+
|
8
|
+
- Scope is defined up front which reduces scope creep
|
9
|
+
- Creating the test plan forces developers to be appropriately familiar
|
10
|
+
with the project
|
11
|
+
- A test plan is written up front (TDD at a macro level)
|
12
|
+
|
13
|
+
The format of the spreadsheet is as follows (columns can be in any order):
|
14
|
+
|
15
|
+
####project
|
16
|
+
The abbreviation for the project in YouTrack. The project must
|
17
|
+
exist in YouTrack already, this gem will not create the project for you
|
18
|
+
|
19
|
+
####id
|
20
|
+
Similar to a version number. Must be unique in this test plan. Follows
|
21
|
+
the format x.y.z (this example is 3 levels deep) and there is no limit
|
22
|
+
to nesting depth. Each level denotes sub-tickets that will be created
|
23
|
+
and associated to the level above.
|
24
|
+
|
25
|
+
####type
|
26
|
+
"Story" for top level items, "Features" and "Tasks" for items one or more
|
27
|
+
levels below "Story". “Features” are user facing and "Tasks" are
|
28
|
+
implementation details. "Tasks" will not be acceptance tested.
|
29
|
+
|
30
|
+
####summary
|
31
|
+
A one-line summarizing of the feature.
|
32
|
+
|
33
|
+
####description
|
34
|
+
A description of the feature including steps to use the feature.
|
35
|
+
|
36
|
+
####outcome
|
37
|
+
The expected outcome if the feature is used per the steps in the description.
|
38
|
+
|
39
|
+
####notes
|
40
|
+
Any additional notes (which will show up as a comment) that might be useful
|
41
|
+
for the developer or tester.
|
42
|
+
|
43
|
+
####references
|
44
|
+
A reference to the design document. The format should be wf/c-<page>-<screen
|
45
|
+
(or range)>. E.g. wf-12-5 or c-9-2-3 (where "wf" refers to wireframe and "c"
|
46
|
+
refers to composition)
|
47
|
+
|
48
|
+
####platform
|
49
|
+
This is usually one of iOS, iPhone, iPad, Android, Desktop Web, Mobile Web
|
50
|
+
|
51
|
+
####priority
|
52
|
+
One of Low, Normal, High, Show-stopper
|
53
|
+
|
54
|
+
## Installation
|
55
|
+
|
56
|
+
Add this line to your application's Gemfile:
|
57
|
+
|
58
|
+
gem 'trackinator'
|
59
|
+
|
60
|
+
And then execute:
|
61
|
+
|
62
|
+
$ bundle
|
63
|
+
|
64
|
+
Or install it yourself as:
|
65
|
+
|
66
|
+
$ gem install trackinator
|
67
|
+
|
68
|
+
## Usage
|
69
|
+
|
70
|
+
<pre>
|
71
|
+
Options:
|
72
|
+
--youtrack-username, -y <s>: Your YouTrack username
|
73
|
+
--youtrack-password, -p <s>: Your YouTrack password
|
74
|
+
--google-username, -g <s>: Your Google username
|
75
|
+
--google-password, -a <s>: Your Google password
|
76
|
+
--youtrack-host, -o <s>: YouTrack host
|
77
|
+
--youtrack-port, -r <i>: YouTrack port (default: 80)
|
78
|
+
--youtrack-path-prefix, -e <s>: YouTrack path prefix (e.g. '/youtrack/') (default: /)
|
79
|
+
--help, -h: Show this message
|
80
|
+
filename: File name in Google Docs
|
81
|
+
</pre>
|
82
|
+
|
83
|
+
## Contributing
|
84
|
+
|
85
|
+
1. Fork it
|
86
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
87
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
88
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
89
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/trackinate
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'trackinator/importer'
|
4
|
+
require 'trollop'
|
5
|
+
|
6
|
+
opts = Trollop::options do
|
7
|
+
opt :youtrack_username, "Your YouTrack username", :type => :string, :short => "y"
|
8
|
+
opt :youtrack_password, "Your YouTrack password", :type => :string, :short => "p"
|
9
|
+
opt :google_username, "Your Google username", :type => :string, :short => "g"
|
10
|
+
opt :google_password, "Your Google password", :type => :string, :short => "a"
|
11
|
+
opt :youtrack_host, "YouTrack host", :type => :string, :short => "o"
|
12
|
+
opt :youtrack_port, "YouTrack port", :type => :int, :default => 80, :short => "r"
|
13
|
+
opt :youtrack_path_prefix, "YouTrack path prefix (e.g. '/youtrack/')", :type => :string, :default => "/", :short =>"e"
|
14
|
+
end
|
15
|
+
|
16
|
+
p opts
|
17
|
+
|
18
|
+
Trollop::die :youtrack_username, "is required" if opts[:youtrack_username].empty?
|
19
|
+
Trollop::die :youtrack_password, "is required" if opts[:youtrack_password].empty?
|
20
|
+
Trollop::die :google_username, "is required" if opts[:google_username].empty?
|
21
|
+
Trollop::die :google_password, "is required" if opts[:google_password].empty?
|
22
|
+
Trollop::die :youtrack_host, "is required" if opts[:youtrack_host].empty?
|
23
|
+
|
24
|
+
importer = Trackinator::Importer.new opts
|
25
|
+
importer.import ARGV[0]
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'CSV'
|
3
|
+
|
4
|
+
require 'gdata'
|
5
|
+
|
6
|
+
module Trackinator
|
7
|
+
class Importer
|
8
|
+
@youtrack_cookie
|
9
|
+
@youtrack_connection
|
10
|
+
|
11
|
+
@google_headers
|
12
|
+
@stack
|
13
|
+
|
14
|
+
@col_count
|
15
|
+
|
16
|
+
@youtrack_host
|
17
|
+
@youtrack_port
|
18
|
+
@youtrack_path_prefix
|
19
|
+
|
20
|
+
def initialize opts
|
21
|
+
@stack = []
|
22
|
+
@google_connection = Net::HTTP
|
23
|
+
@google_client = GData::Client::Spreadsheets.new
|
24
|
+
|
25
|
+
google_login = opts[:google_username]
|
26
|
+
youtrack_login = opts[:youtrack_username]
|
27
|
+
|
28
|
+
@youtrack_host = opts[:youtrack_host]
|
29
|
+
@youtrack_port = opts[:youtrack_port]
|
30
|
+
@youtrack_path_prefix = opts[:youtrack_path_prefix]
|
31
|
+
|
32
|
+
google_password = params[:google_password]
|
33
|
+
youtrack_password = params[:youtrack_password]
|
34
|
+
|
35
|
+
login_youtrack youtrack_login, youtrack_password
|
36
|
+
|
37
|
+
@google_client.clientlogin(google_login, google_password)
|
38
|
+
end
|
39
|
+
|
40
|
+
def import file_name
|
41
|
+
spreadsheet_feed = @google_client.get("http://spreadsheets.google.com/feeds/worksheets/#{get_spreadsheet_key(file_name)}/private/full").to_xml
|
42
|
+
spreadsheet_list_data = @google_client.get(spreadsheet_feed.elements[1, 'entry'].elements[1, 'content'].attributes['src']).to_xml
|
43
|
+
|
44
|
+
spreadsheet_list_data.elements.each('entry') do |entry|
|
45
|
+
ticket_data = get_ticket_data entry
|
46
|
+
issue_id = is_issue_exists? ticket_data
|
47
|
+
false unless !issue_id.nil? ? update_ticket(issue_id, ticket_data) : create_ticket(ticket_data)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Google API methods
|
54
|
+
|
55
|
+
def get_spreadsheet_key file_name
|
56
|
+
doc_feed = @google_client.get("http://spreadsheets.google.com/feeds/spreadsheets/private/full").to_xml
|
57
|
+
|
58
|
+
doc_feed.elements.each ('entry') do |entry|
|
59
|
+
if entry.elements['title'].text.eql? file_name
|
60
|
+
return entry.elements[1].text[/spreadsheets\/(.*)/, 1]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_ticket_data entry
|
66
|
+
data = {}
|
67
|
+
|
68
|
+
REXML::XPath.match(entry, 'gsx:*').each do |col|
|
69
|
+
data[col.name] = URI.escape(col.text) unless col.text.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
data
|
73
|
+
end
|
74
|
+
|
75
|
+
# YouTrack API methods
|
76
|
+
|
77
|
+
def login_youtrack user_name, password
|
78
|
+
@youtrack_connection = Net::HTTP.new @youtrack_host, @youtrack_port
|
79
|
+
response = @youtrack_connection.post "#{@youtrack_path_prefix}rest/user/login", "login=#{user_name}&password=#{password}"
|
80
|
+
@youtrack_cookie = response.response['Set-Cookie'].split('; ')[0]
|
81
|
+
end
|
82
|
+
|
83
|
+
def create_ticket data
|
84
|
+
issue_id, create_response = create_youtrack_ticket data
|
85
|
+
success = create_response.eql? "Created"
|
86
|
+
|
87
|
+
success ? (success = update_ticket(issue_id, data)) : (return success)
|
88
|
+
success ? update_dependencies([issue_id, data['id']]) : success
|
89
|
+
end
|
90
|
+
|
91
|
+
def update_ticket issue_id, data
|
92
|
+
success = set_platform(issue_id, data['platform'])
|
93
|
+
success ? (success = set_summary_and_description(issue_id, data['summary'], data['description'])) : (return success)
|
94
|
+
success ? (success = set_type(issue_id, data['type'])) : (return success)
|
95
|
+
success ? (success = set_import_identifier(issue_id, "#{data['project']}-#{data['id']}")) : (return success)
|
96
|
+
success ? (success = set_design_reference(issue_id, "#{data['references']}")) : (return success) unless data['references'].nil?
|
97
|
+
|
98
|
+
success
|
99
|
+
end
|
100
|
+
|
101
|
+
def update_dependencies issue
|
102
|
+
issue_id = issue[0]
|
103
|
+
issue_level = issue[1]
|
104
|
+
|
105
|
+
if @stack.empty?
|
106
|
+
@stack.push [issue_id, issue_level]
|
107
|
+
else
|
108
|
+
last_issue = @stack.last
|
109
|
+
last_issue_id = last_issue[0]
|
110
|
+
last_issue_level = last_issue[1]
|
111
|
+
|
112
|
+
if issue_level.length <= last_issue_level.length
|
113
|
+
@stack.pop
|
114
|
+
update_dependencies issue
|
115
|
+
else
|
116
|
+
success = create_dependency last_issue_id, issue_id
|
117
|
+
@stack.push issue
|
118
|
+
|
119
|
+
success
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# YouTrack API calls
|
125
|
+
|
126
|
+
def is_issue_exists? data
|
127
|
+
find_response_xml = REXML::Document.new(@youtrack_connection.get("#{@youtrack_path_prefix}rest/issue/byproject/#{data['project']}?filter=Import+Identifier:+#{data['project']}-#{data['id']}", { 'Cookie' => @youtrack_cookie, 'Content-Type' => 'text/plain; charset=utf-8' }).body)
|
128
|
+
find_response_xml.elements['issues'].length > 0 ? find_response_xml.elements['issues/issue'].attributes['id'] : nil
|
129
|
+
end
|
130
|
+
|
131
|
+
def create_youtrack_ticket data
|
132
|
+
response = @youtrack_connection.put("#{@youtrack_path_prefix}rest/issue?project=#{data['project']}&summary=#{data['summary']}&description=#{data['description']}&priority=#{data['priority']}", nil, { 'Cookie' => @youtrack_cookie, 'Content-Type' => 'text/plain; charset=utf-8' })
|
133
|
+
return response.header["Location"].split("/").last, response.header.msg
|
134
|
+
end
|
135
|
+
|
136
|
+
def create_dependency parent_id, child_id
|
137
|
+
response = @youtrack_connection.post("#{@youtrack_path_prefix}rest/issue/#{parent_id}/execute?command=parent+for+#{child_id}&disableNotifications=true", nil, { 'Cookie' => @youtrack_cookie })
|
138
|
+
response.header.msg.eql? "OK"
|
139
|
+
end
|
140
|
+
|
141
|
+
def set_summary_and_description issue_id, summary, description
|
142
|
+
return true if summary.nil?
|
143
|
+
|
144
|
+
response = @youtrack_connection.post("#{@youtrack_path_prefix}rest/issue/#{issue_id}/?summary=#{summary}&description=#{description}&disableNotifications=true", nil, { 'Cookie' => @youtrack_cookie })
|
145
|
+
response.header.msg.eql? "OK"
|
146
|
+
end
|
147
|
+
|
148
|
+
def set_platform issue_id, platform
|
149
|
+
return true if platform.nil?
|
150
|
+
|
151
|
+
response = @youtrack_connection.post("#{@youtrack_path_prefix}rest/issue/#{issue_id}/execute?command=Platform+#{platform}&disableNotifications=true", nil, { 'Cookie' => @youtrack_cookie })
|
152
|
+
response.header.msg.eql? "OK"
|
153
|
+
end
|
154
|
+
|
155
|
+
def set_type issue_id, type
|
156
|
+
return true if type.nil?
|
157
|
+
|
158
|
+
response = @youtrack_connection.post("#{@youtrack_path_prefix}rest/issue/#{issue_id}/execute?command=Type+#{type}&disableNotifications=true", nil, { 'Cookie' => @youtrack_cookie })
|
159
|
+
response.header.msg.eql? "OK"
|
160
|
+
end
|
161
|
+
|
162
|
+
def set_import_identifier issue_id, import_id
|
163
|
+
return true if import_id.nil?
|
164
|
+
|
165
|
+
response = @youtrack_connection.post("#{@youtrack_path_prefix}rest/issue/#{issue_id}/execute?command=Import+Identifier+#{import_id}&disableNotifications=true", nil, { 'Cookie' => @youtrack_cookie })
|
166
|
+
response.header.msg.eql? "OK"
|
167
|
+
end
|
168
|
+
|
169
|
+
def set_design_reference issue_id, reference
|
170
|
+
return true if reference.nil?
|
171
|
+
|
172
|
+
response = @youtrack_connection.post("#{@youtrack_path_prefix}rest/issue/#{issue_id}/execute?command=Design+Reference+#{reference}&disableNotifications=true", nil, { 'Cookie' => @youtrack_cookie })
|
173
|
+
response.header.msg.eql? "OK"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/lib/trackinator.rb
ADDED
data/trackinator.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/trackinator/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Justin Beck"]
|
6
|
+
gem.email = %w{justinbeck@mac.com}
|
7
|
+
gem.description = <<-EOF
|
8
|
+
In order to introduce in to my main development process
|
9
|
+
I decided that creating a test plan prior to development
|
10
|
+
and then using that as the basis for my YouTrack tickets
|
11
|
+
would be a good approach. Think of it as TDD at a macro
|
12
|
+
level.
|
13
|
+
EOF
|
14
|
+
gem.summary = %q{Imports a spreadsheet in to YouTrack}
|
15
|
+
gem.homepage = "https://github.com/justincbeck/trackinator"
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($\)
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
|
+
gem.name = "trackinator"
|
21
|
+
gem.require_paths = %w{lib}
|
22
|
+
gem.version = Trackinator::VERSION
|
23
|
+
|
24
|
+
gem.add_dependency "gdata_19"
|
25
|
+
gem.add_dependency "trollop"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trackinator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Justin Beck
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: gdata_19
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: trollop
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: ! " In order to introduce in to my main development process\n I
|
47
|
+
decided that creating a test plan prior to development\n and then using that
|
48
|
+
as the basis for my YouTrack tickets\n would be a good approach. Think of it
|
49
|
+
as TDD at a macro\n level.\n"
|
50
|
+
email:
|
51
|
+
- justinbeck@mac.com
|
52
|
+
executables:
|
53
|
+
- trackinate
|
54
|
+
extensions: []
|
55
|
+
extra_rdoc_files: []
|
56
|
+
files:
|
57
|
+
- .gitignore
|
58
|
+
- .rvmrc
|
59
|
+
- Gemfile
|
60
|
+
- LICENSE
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- bin/trackinate
|
64
|
+
- lib/trackinator.rb
|
65
|
+
- lib/trackinator/importer.rb
|
66
|
+
- lib/trackinator/version.rb
|
67
|
+
- trackinator.gemspec
|
68
|
+
homepage: https://github.com/justincbeck/trackinator
|
69
|
+
licenses: []
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.8.24
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Imports a spreadsheet in to YouTrack
|
92
|
+
test_files: []
|