trackinator 0.0.2 → 0.0.4
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/bin/trackinate +4 -4
- data/lib/trackinator/google.rb +43 -0
- data/lib/trackinator/importer.rb +10 -159
- data/lib/trackinator/version.rb +1 -1
- data/lib/trackinator/you_track.rb +122 -0
- metadata +4 -2
data/bin/trackinate
CHANGED
|
@@ -9,9 +9,9 @@ opts = Trollop::options do
|
|
|
9
9
|
opt :youtrack_password, "Your YouTrack password", :type => :string, :short => "p"
|
|
10
10
|
opt :google_username, "Your Google username", :type => :string, :short => "g"
|
|
11
11
|
opt :google_password, "Your Google password", :type => :string, :short => "a"
|
|
12
|
-
opt :
|
|
13
|
-
opt :
|
|
14
|
-
opt :
|
|
12
|
+
opt :host, "YouTrack host", :type => :string, :short => "o"
|
|
13
|
+
opt :port, "YouTrack port", :type => :int, :default => 80, :short => "r"
|
|
14
|
+
opt :path_prefix, "YouTrack path prefix (e.g. '/youtrack/')", :type => :string, :default => "/", :short =>"e"
|
|
15
15
|
opt :create_rc, "Create a .trackinatorrc file in your home dir", :default => false, :short => "c"
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -32,7 +32,7 @@ Trollop::die :youtrack_username, "is required" if opts[:youtrack_username].nil?
|
|
|
32
32
|
Trollop::die :youtrack_password, "is required" if opts[:youtrack_password].nil?
|
|
33
33
|
Trollop::die :google_username, "is required" if opts[:google_username].nil?
|
|
34
34
|
Trollop::die :google_password, "is required" if opts[:google_password].nil?
|
|
35
|
-
Trollop::die :
|
|
35
|
+
Trollop::die :host, "is required" if opts[:host].nil?
|
|
36
36
|
|
|
37
37
|
if opts[:create_rc]
|
|
38
38
|
unless File.exists?("#{Etc.getpwuid.dir}/.trackinatorrc")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Trackinator
|
|
2
|
+
class Google
|
|
3
|
+
@client
|
|
4
|
+
|
|
5
|
+
def initialize opts
|
|
6
|
+
@client = GData::Client::Spreadsheets.new
|
|
7
|
+
@client.clientlogin(opts[:google_username], opts[:google_password])
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def get_tickets file_name
|
|
11
|
+
spreadsheet_feed = @client.get("http://spreadsheets.google.com/feeds/worksheets/#{get_spreadsheet_key(file_name)}/private/full").to_xml
|
|
12
|
+
spreadsheet_list_data = @client.get(spreadsheet_feed.elements[1, 'entry'].elements[1, 'content'].attributes['src']).to_xml
|
|
13
|
+
|
|
14
|
+
tickets = []
|
|
15
|
+
|
|
16
|
+
spreadsheet_list_data.elements.each('entry') do |entry|
|
|
17
|
+
tickets << get_ticket_data(entry)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
tickets
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get_spreadsheet_key file_name
|
|
24
|
+
doc_feed = @client.get("http://spreadsheets.google.com/feeds/spreadsheets/private/full").to_xml
|
|
25
|
+
|
|
26
|
+
doc_feed.elements.each ('entry') do |entry|
|
|
27
|
+
if entry.elements['title'].text.eql? file_name
|
|
28
|
+
return entry.elements[1].text[/spreadsheets\/(.*)/, 1]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get_ticket_data entry
|
|
34
|
+
data = {}
|
|
35
|
+
|
|
36
|
+
REXML::XPath.match(entry, 'gsx:*').each do |col|
|
|
37
|
+
data[col.name] = URI.escape(col.text) unless col.text.nil?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
data
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/trackinator/importer.rb
CHANGED
|
@@ -3,174 +3,25 @@ require 'CSV'
|
|
|
3
3
|
|
|
4
4
|
require 'gdata'
|
|
5
5
|
|
|
6
|
+
require 'trackinator/google'
|
|
7
|
+
require 'trackinator/you_track'
|
|
8
|
+
|
|
6
9
|
module Trackinator
|
|
7
10
|
class Importer
|
|
8
|
-
@
|
|
9
|
-
@youtrack_connection
|
|
10
|
-
|
|
11
|
-
@google_headers
|
|
12
|
-
@stack
|
|
13
|
-
|
|
14
|
-
@col_count
|
|
15
|
-
|
|
16
|
-
@youtrack_host
|
|
17
|
-
@youtrack_port
|
|
18
|
-
@youtrack_path_prefix
|
|
11
|
+
@you_track
|
|
19
12
|
|
|
20
13
|
def initialize opts
|
|
21
|
-
@
|
|
22
|
-
@
|
|
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 = opts[:google_password]
|
|
33
|
-
youtrack_password = opts[:youtrack_password]
|
|
34
|
-
|
|
35
|
-
login_youtrack youtrack_login, youtrack_password
|
|
36
|
-
|
|
37
|
-
@google_client.clientlogin(google_login, google_password)
|
|
14
|
+
@you_track = YouTrack.new opts
|
|
15
|
+
@google = Google.new opts
|
|
38
16
|
end
|
|
39
17
|
|
|
40
18
|
def import file_name
|
|
41
|
-
|
|
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
|
|
19
|
+
ticket_data = @google.get_tickets file_name
|
|
57
20
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
end
|
|
21
|
+
ticket_data.each do |entry|
|
|
22
|
+
issue_id = @you_track.is_issue_exists? entry
|
|
23
|
+
false unless !issue_id.nil? ? @you_track.update_ticket(issue_id, entry) : @you_track.create_ticket(entry)
|
|
62
24
|
end
|
|
63
25
|
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
26
|
end
|
|
176
27
|
end
|
data/lib/trackinator/version.rb
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module Trackinator
|
|
2
|
+
class YouTrack
|
|
3
|
+
@stack
|
|
4
|
+
|
|
5
|
+
@cookie
|
|
6
|
+
@connection
|
|
7
|
+
|
|
8
|
+
@host
|
|
9
|
+
@port
|
|
10
|
+
@path_prefix
|
|
11
|
+
|
|
12
|
+
def initialize opts
|
|
13
|
+
@stack = []
|
|
14
|
+
|
|
15
|
+
@host = opts[:host]
|
|
16
|
+
@port = opts[:port]
|
|
17
|
+
@path_prefix = opts[:path_prefix]
|
|
18
|
+
|
|
19
|
+
login opts[:youtrack_username], opts[:youtrack_password]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def login username, password
|
|
23
|
+
@connection = Net::HTTP.new @host, @port
|
|
24
|
+
response = @connection.post "#{@path_prefix}rest/user/login", "login=#{username}&password=#{password}"
|
|
25
|
+
@cookie = response.response['Set-Cookie'].split('; ')[0]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create_ticket data
|
|
29
|
+
issue_id, create_response = create_youtrack_ticket data
|
|
30
|
+
success = create_response.eql? "Created"
|
|
31
|
+
|
|
32
|
+
success ? (success = update_ticket(issue_id, data)) : (return success)
|
|
33
|
+
success ? update_dependencies([issue_id, data['id']]) : success
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def update_ticket issue_id, data
|
|
37
|
+
success = set_platform(issue_id, data['platform'])
|
|
38
|
+
success ? (success = set_summary_and_description(issue_id, data['summary'], data['description'])) : (return success)
|
|
39
|
+
success ? (success = set_type(issue_id, data['type'])) : (return success)
|
|
40
|
+
success ? (success = set_import_identifier(issue_id, "#{data['project']}-#{data['id']}")) : (return success)
|
|
41
|
+
success ? (success = set_design_reference(issue_id, "#{data['references']}")) : (return success) unless data['references'].nil?
|
|
42
|
+
|
|
43
|
+
success
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def update_dependencies issue
|
|
47
|
+
issue_id = issue[0]
|
|
48
|
+
issue_level = issue[1]
|
|
49
|
+
|
|
50
|
+
if @stack.empty?
|
|
51
|
+
@stack.push [issue_id, issue_level]
|
|
52
|
+
else
|
|
53
|
+
last_issue = @stack.last
|
|
54
|
+
last_issue_id = last_issue[0]
|
|
55
|
+
last_issue_level = last_issue[1]
|
|
56
|
+
|
|
57
|
+
if issue_level.length <= last_issue_level.length
|
|
58
|
+
@stack.pop
|
|
59
|
+
update_dependencies issue
|
|
60
|
+
else
|
|
61
|
+
success = create_dependency last_issue_id, issue_id
|
|
62
|
+
@stack.push issue
|
|
63
|
+
|
|
64
|
+
success
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# YouTrack API calls
|
|
70
|
+
|
|
71
|
+
def is_issue_exists? data
|
|
72
|
+
find_response_xml = REXML::Document.new(@connection.get("#{@path_prefix}rest/issue/byproject/#{data['project']}?filter=Import+Identifier:+#{data['project']}-#{data['id']}", { 'Cookie' => @cookie, 'Content-Type' => 'text/plain; charset=utf-8' }).body)
|
|
73
|
+
find_response_xml.elements['issues'].length > 0 ? find_response_xml.elements['issues/issue'].attributes['id'] : nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def create_youtrack_ticket data
|
|
77
|
+
response = @connection.put("#{@path_prefix}rest/issue?project=#{data['project']}&summary=#{data['summary']}&description=#{data['description']}&priority=#{data['priority']}", nil, { 'Cookie' => @cookie, 'Content-Type' => 'text/plain; charset=utf-8' })
|
|
78
|
+
return response.header["Location"].split("/").last, response.header.msg
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def create_dependency parent_id, child_id
|
|
82
|
+
response = @connection.post("#{@path_prefix}rest/issue/#{parent_id}/execute?command=parent+for+#{child_id}&disableNotifications=true", nil, { 'Cookie' => @cookie })
|
|
83
|
+
response.header.msg.eql? "OK"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def set_summary_and_description issue_id, summary, description
|
|
87
|
+
return true if summary.nil? || description.nil?
|
|
88
|
+
|
|
89
|
+
response = @connection.post("#{@path_prefix}rest/issue/#{issue_id}/?summary=#{summary}&description=#{description}&disableNotifications=true", nil, { 'Cookie' => @cookie })
|
|
90
|
+
response.header.msg.eql? "OK"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def set_platform issue_id, platform
|
|
94
|
+
return true if platform.nil?
|
|
95
|
+
|
|
96
|
+
response = @connection.post("#{@path_prefix}rest/issue/#{issue_id}/execute?command=Platform+#{platform}&disableNotifications=true", nil, { 'Cookie' => @cookie })
|
|
97
|
+
response.header.msg.eql? "OK"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def set_type issue_id, type
|
|
101
|
+
return true if type.nil?
|
|
102
|
+
|
|
103
|
+
response = @connection.post("#{@path_prefix}rest/issue/#{issue_id}/execute?command=Type+#{type}&disableNotifications=true", nil, { 'Cookie' => @cookie })
|
|
104
|
+
response.header.msg.eql? "OK"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def set_import_identifier issue_id, import_id
|
|
108
|
+
return true if import_id.nil?
|
|
109
|
+
|
|
110
|
+
response = @connection.post("#{@path_prefix}rest/issue/#{issue_id}/execute?command=Import+Identifier+#{import_id}&disableNotifications=true", nil, { 'Cookie' => @cookie })
|
|
111
|
+
response.header.msg.eql? "OK"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def set_design_reference issue_id, reference
|
|
115
|
+
return true if reference.nil?
|
|
116
|
+
|
|
117
|
+
response = @connection.post("#{@path_prefix}rest/issue/#{issue_id}/execute?command=Design+Reference+#{reference}&disableNotifications=true", nil, { 'Cookie' => @cookie })
|
|
118
|
+
response.header.msg.eql? "OK"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: trackinator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2012-
|
|
12
|
+
date: 2012-08-01 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: gdata_19
|
|
@@ -62,8 +62,10 @@ files:
|
|
|
62
62
|
- Rakefile
|
|
63
63
|
- bin/trackinate
|
|
64
64
|
- lib/trackinator.rb
|
|
65
|
+
- lib/trackinator/google.rb
|
|
65
66
|
- lib/trackinator/importer.rb
|
|
66
67
|
- lib/trackinator/version.rb
|
|
68
|
+
- lib/trackinator/you_track.rb
|
|
67
69
|
- trackinator.gemspec
|
|
68
70
|
homepage: https://github.com/justincbeck/trackinator
|
|
69
71
|
licenses: []
|