rally_api 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +104 -0
- data/Rakefile +17 -0
- data/lib/rally_api.rb +11 -0
- data/lib/rally_api/custom_http_header.rb +37 -0
- data/lib/rally_api/rally_json_connection.rb +203 -0
- data/lib/rally_api/rally_object.rb +99 -0
- data/lib/rally_api/rally_query.rb +94 -0
- data/lib/rally_api/rally_query_result.rb +36 -0
- data/lib/rally_api/rally_rest_json.rb +300 -0
- data/lib/rally_api/time_machine_query.rb +89 -0
- data/lib/rally_api/version.rb +7 -0
- metadata +67 -0
data/README.rdoc
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
RallyAPI (rally_api)
|
2
|
+
|
3
|
+
==Description
|
4
|
+
|
5
|
+
RallyAPI is a wrapper of Rally's Web Service API Json endpoints
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
gem install rest-client
|
10
|
+
gem install json
|
11
|
+
gem install rally_api
|
12
|
+
|
13
|
+
** Note if you are on Windows - you will need DevKit from http://rubyinstaller.org to properly install the json gem
|
14
|
+
** - that native compiled parsing gives us some speed.
|
15
|
+
** - Follow the instructions here to install DevKit: https://github.com/oneclick/rubyinstaller/wiki/Development-Kit
|
16
|
+
|
17
|
+
== Usage
|
18
|
+
|
19
|
+
require 'rally_api'
|
20
|
+
|
21
|
+
#Setting custom headers
|
22
|
+
headers = RallyAPI::CustomHttpHeader.new()
|
23
|
+
headers.name = "My Utility"
|
24
|
+
headers.vendor = "MyCompany"
|
25
|
+
headers.version = "1.0"
|
26
|
+
|
27
|
+
#==================== Making a connection to Rally ====================
|
28
|
+
config = {:base_url => "https://rally1.rallydev.com"}
|
29
|
+
config[:username] = "user@company.com"
|
30
|
+
config[:password] = "password"
|
31
|
+
config[:workspace] = "Workspace Name"
|
32
|
+
config[:project] = "Project Name"
|
33
|
+
config[:headers] = headers #from RallyAPI::CustomHttpHeader.new()
|
34
|
+
|
35
|
+
@rally = RallyAPI::RallyRestJson.new(config)
|
36
|
+
|
37
|
+
#==================== Querying Rally ==========================
|
38
|
+
test_query = RallyAPI::RallyQuery.new()
|
39
|
+
test_query.type = :defect
|
40
|
+
test_query.fetch = "Name"
|
41
|
+
test_query.workspace = {"_ref" => "https://rally1.rallydev.com/slm/webservice/1.25/workspace/12345.js" } #optional
|
42
|
+
test_query.project = {"_ref" => "https://rally1.rallydev.com/slm/webservice/1.25/project/12345.js" } #optional
|
43
|
+
test_query.page_size = 200 #optional - default is 200
|
44
|
+
test_query.limit = 1000 #optional - default is 99999
|
45
|
+
test_query.project_scope_up = false
|
46
|
+
test_query.project_scope_down = true
|
47
|
+
test_query.order = "Name Asc"
|
48
|
+
test_query.query_string = "(Severity = \"High\")"
|
49
|
+
|
50
|
+
results = @rally.find(test_query)
|
51
|
+
|
52
|
+
#tip - set the fetch string of the query to the fields you need -
|
53
|
+
#only resort to the read method if you want your code to be slow
|
54
|
+
results.each do |defect|
|
55
|
+
puts defect.Name
|
56
|
+
defect.read #read the whole defect from Rally to get all fields (eg Severity)
|
57
|
+
puts defect.Severity
|
58
|
+
end
|
59
|
+
|
60
|
+
#==================== Reading an Artifact ====================
|
61
|
+
defect = @rally.read(:defect, 12345) #by ObjectID
|
62
|
+
#or
|
63
|
+
defect = @rally.read(:defect, "FormattedID|DE42") #by FormattedID
|
64
|
+
#or if you already have an object like from a query
|
65
|
+
results = @rally.find(RallyAPI::RallyQuery.new({:type => :defect, :query_string => "(FormattedID = DE42)"}))
|
66
|
+
defect = results.first
|
67
|
+
defect.read
|
68
|
+
|
69
|
+
puts defect["Name"]
|
70
|
+
#or - fields can be read by bracket artifact["FieldDisplayName"] or artifact.FieldDisplayName
|
71
|
+
puts defect.Name
|
72
|
+
|
73
|
+
#An Important note about reading fields and fetching:
|
74
|
+
#If you query with a specific fetch string, for example query defect and fetch Name,Severity,Description
|
75
|
+
#You will *only* get back those fields defect.Priority will be nil, but may not be null in Rally
|
76
|
+
#Use object.read or @rally.read to make sure you read the whole object if you want what is in Rally
|
77
|
+
# This is done for speed - lazy loading (going back to get a value from Rally) can be unneccessarily slow
|
78
|
+
# *Pick you fetch strings wisely* fetch everything you need and don't rely on read if you don't need it the speed is worth it.
|
79
|
+
|
80
|
+
#==================== Creating an Artifact ====================
|
81
|
+
obj = {}
|
82
|
+
obj["Name"] = "Test Defect created #{DateTime.now()}"
|
83
|
+
new_de = @rally.create(:defect, obj)
|
84
|
+
puts new_de["FormattedID"]
|
85
|
+
|
86
|
+
#==================== Updating an Artifact ====================
|
87
|
+
fields = {}
|
88
|
+
fields["Severity"] = "Critical"
|
89
|
+
fields["Description"] = "Description for the issue"
|
90
|
+
updated_defect = @rally.update(:defect, 12345, fields) #by ObjectID
|
91
|
+
#or
|
92
|
+
updated_defect = @rally.update(:defect, "FormattedID|DE42", fields) #by FormattedID
|
93
|
+
# or
|
94
|
+
defect = @rally.read(:defect, 12345) #by lookup then udpating via the object
|
95
|
+
field_updates = {"Description" => "Changed Description"}
|
96
|
+
defect.update(field_updates)
|
97
|
+
|
98
|
+
|
99
|
+
#== License
|
100
|
+
#Copyright (c) 2002-2011 Rally Software Development Corp. All Rights Reserved.
|
101
|
+
#Your use of this Software is governed by the terms and conditions
|
102
|
+
#of the applicable Subscription Agreement between your company and
|
103
|
+
#Rally Software Development Corp.
|
104
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
task :make_rally_api_gem do
|
5
|
+
system("gem build rally_api.gemspec")
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "run all api tests"
|
9
|
+
RSpec::Core::RakeTask.new('api_tests') do |t|
|
10
|
+
t.pattern = ['test/*_spec.rb']
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "run api create tests"
|
14
|
+
RSpec::Core::RakeTask.new('api_create_tests') do |t|
|
15
|
+
t.pattern = ['test/*create_spec.rb']
|
16
|
+
end
|
17
|
+
|
data/lib/rally_api.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "rally_api/version"
|
2
|
+
require "rally_api/rally_rest_json"
|
3
|
+
|
4
|
+
#Copyright (c) 2002-2012 Rally Software Development Corp. All Rights Reserved.
|
5
|
+
#Your use of this Software is governed by the terms and conditions
|
6
|
+
#of the applicable Subscription Agreement between your company and
|
7
|
+
#Rally Software Development Corp.
|
8
|
+
|
9
|
+
module RallyAPI
|
10
|
+
# Your code goes here...
|
11
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#Copyright (c) 2002-2012 Rally Software Development Corp. All Rights Reserved.
|
2
|
+
#Your use of this Software is governed by the terms and conditions
|
3
|
+
#of the applicable Subscription Agreement between your company and
|
4
|
+
#Rally Software Development Corp.
|
5
|
+
|
6
|
+
require_relative "version"
|
7
|
+
|
8
|
+
module RallyAPI
|
9
|
+
|
10
|
+
class CustomHttpHeader
|
11
|
+
attr_accessor :name, :version, :vendor
|
12
|
+
attr_reader :library, :os, :platform
|
13
|
+
|
14
|
+
HTTP_HEADER_FIELDS = [:name, :vendor, :version, :library, :platform, :os]
|
15
|
+
HTTP_HEADER_PREFIX = 'X-RallyIntegration'
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@os = RUBY_PLATFORM
|
19
|
+
@platform = "Ruby #{RUBY_VERSION}"
|
20
|
+
@library = "RallyRestJson version #{VERSION}"
|
21
|
+
@name = "RallyRestJson"
|
22
|
+
end
|
23
|
+
|
24
|
+
def headers
|
25
|
+
headers = {}
|
26
|
+
HTTP_HEADER_FIELDS.each do |field|
|
27
|
+
value = self.send(field)
|
28
|
+
next if value.nil?
|
29
|
+
header_key = "#{HTTP_HEADER_PREFIX}#{field.to_s.capitalize}"
|
30
|
+
headers[header_key.to_sym] = value
|
31
|
+
end
|
32
|
+
headers
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
#Copyright (c) 2002-2012 Rally Software Development Corp. All Rights Reserved.
|
5
|
+
#Your use of this Software is governed by the terms and conditions
|
6
|
+
#of the applicable Subscription Agreement between your company and
|
7
|
+
#Rally Software Development Corp.
|
8
|
+
module RallyAPI
|
9
|
+
|
10
|
+
|
11
|
+
class RallyJsonConnection
|
12
|
+
|
13
|
+
DEFAULT_PAGE_SIZE = 200
|
14
|
+
|
15
|
+
attr_accessor :rally_headers, :retries, :retry_list
|
16
|
+
|
17
|
+
def initialize(headers, low_debug, proxy_info)
|
18
|
+
@rally_headers = headers
|
19
|
+
@low_debug = low_debug
|
20
|
+
@retries = 0
|
21
|
+
@retry_list = {}
|
22
|
+
|
23
|
+
if (!ENV["http_proxy"].nil?) && (proxy_info.nil?)
|
24
|
+
RestClient.proxy = ENV['http_proxy']
|
25
|
+
end
|
26
|
+
|
27
|
+
if !proxy_info.nil?
|
28
|
+
RestClient.proxy = proxy_info
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def read_object(url, args)
|
34
|
+
args[:method] = :get
|
35
|
+
result = send_json_request(url, args)
|
36
|
+
puts result if @low_debug
|
37
|
+
rally_type = result.keys[0]
|
38
|
+
result[rally_type]
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_object(url, args, rally_object)
|
42
|
+
args[:method] = :post
|
43
|
+
text_json = rally_object.to_json
|
44
|
+
args[:payload] = text_json
|
45
|
+
puts "payload json: #{text_json}" if @low_debug
|
46
|
+
result = send_json_request(url, args)
|
47
|
+
puts result if @low_debug
|
48
|
+
result["CreateResult"]["Object"]
|
49
|
+
end
|
50
|
+
|
51
|
+
def update_object(url, args, rally_fields)
|
52
|
+
args[:method] = :post
|
53
|
+
text_json = rally_fields.to_json
|
54
|
+
args[:payload] = text_json
|
55
|
+
puts "payload json: #{text_json}" if @low_debug
|
56
|
+
result = send_json_request(url, args)
|
57
|
+
puts result if @low_debug
|
58
|
+
result["OperationResult"]
|
59
|
+
end
|
60
|
+
|
61
|
+
def put_object(url, args, put_params, rally_fields)
|
62
|
+
args[:method] = :put
|
63
|
+
text_json = rally_fields.to_json
|
64
|
+
args[:payload] = text_json
|
65
|
+
result = send_json_request(url, args, put_params)
|
66
|
+
result["OperationResult"]
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete_object(url,args)
|
70
|
+
args[:method] = :delete
|
71
|
+
result = send_json_request(url,args)
|
72
|
+
puts result if @low_debug
|
73
|
+
result["OperationResult"]
|
74
|
+
end
|
75
|
+
|
76
|
+
#---------------------------------------------
|
77
|
+
def get_all_json_results(url, args, query_params, limit = 99999)
|
78
|
+
all_results = []
|
79
|
+
args[:method] = :get
|
80
|
+
params = {}
|
81
|
+
params[:pagesize] = query_params[:pagesize] || DEFAULT_PAGE_SIZE
|
82
|
+
params[:start] = 1
|
83
|
+
params = params.merge(query_params)
|
84
|
+
|
85
|
+
query_result = send_json_request(url, args, params)
|
86
|
+
all_results.concat(query_result["QueryResult"]["Results"])
|
87
|
+
totals = query_result["QueryResult"]["TotalResultCount"]
|
88
|
+
|
89
|
+
limit < totals ? stop = limit : stop = totals
|
90
|
+
page = params[:pagesize] + 1
|
91
|
+
page_num = 2
|
92
|
+
query_array = []
|
93
|
+
page.step(stop, params[:pagesize]) do |new_page|
|
94
|
+
params[:start] = new_page
|
95
|
+
query_array.push({:page_num => page_num, :url => url, :args => args, :params => params.dup})
|
96
|
+
page_num = page_num + 1
|
97
|
+
end
|
98
|
+
|
99
|
+
all_res = []
|
100
|
+
all_res = run_threads(query_array) if query_array.length > 0
|
101
|
+
#stitch results back together in order
|
102
|
+
all_res.each { |page_res| all_results.concat(page_res[:results]["QueryResult"]["Results"]) }
|
103
|
+
|
104
|
+
query_result["QueryResult"]["Results"] = all_results
|
105
|
+
query_result
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def run_threads(query_array)
|
111
|
+
num_threads = 4
|
112
|
+
thr_queries = []
|
113
|
+
(0...num_threads).each { |ind| thr_queries[ind] = [] }
|
114
|
+
query_array.each { |query| thr_queries[query[:page_num] % num_threads].push(query) }
|
115
|
+
|
116
|
+
thr_array = []
|
117
|
+
thr_queries.each { |thr_query_array| thr_array.push(run_single_thread(thr_query_array)) }
|
118
|
+
|
119
|
+
all_results = []
|
120
|
+
thr_array.each do |thr|
|
121
|
+
thr.value.each { |result_val| all_results.push(result_val) }
|
122
|
+
end
|
123
|
+
all_results.sort! { |resa, resb| resa[:page_num] <=> resb[:page_num] }
|
124
|
+
end
|
125
|
+
|
126
|
+
def run_single_thread(request_array)
|
127
|
+
Thread.new do
|
128
|
+
thread_results = []
|
129
|
+
request_array.each do |req|
|
130
|
+
page_res = send_json_request(req[:url], req[:args], req[:params])
|
131
|
+
thread_results.push({:page_num => req[:page_num], :results => page_res})
|
132
|
+
end
|
133
|
+
thread_results
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
#todo support proxy stuff
|
138
|
+
def send_json_request(url, args, url_params = nil)
|
139
|
+
request_args = {}
|
140
|
+
request_args[:url] = url
|
141
|
+
request_args[:user] = args[:user]
|
142
|
+
request_args[:password] = args[:password]
|
143
|
+
request_args[:method] = args[:method]
|
144
|
+
request_args[:timeout] = 120
|
145
|
+
request_args[:open_timeout] = 120
|
146
|
+
|
147
|
+
r_headers = @rally_headers.headers
|
148
|
+
r_headers[:params] = url_params
|
149
|
+
|
150
|
+
if (args[:method] == :post) || (args[:method] == :put)
|
151
|
+
r_headers[:content_type] = :json
|
152
|
+
r_headers[:accept] = :json
|
153
|
+
request_args[:payload] = args[:payload]
|
154
|
+
end
|
155
|
+
|
156
|
+
request_args[:headers] = r_headers
|
157
|
+
|
158
|
+
begin
|
159
|
+
req = RestClient::Request.new(request_args)
|
160
|
+
puts req.url if @low_debug
|
161
|
+
response = req.execute
|
162
|
+
rescue => ex
|
163
|
+
msg = "Rally Rest Json: - rescued exception - #{ex.message} on request to #{url} with params #{url_params}"
|
164
|
+
puts msg
|
165
|
+
if !@retry_list.has_key?(req.url)
|
166
|
+
@retry_list[req.url] = 0
|
167
|
+
end
|
168
|
+
if (@retries > 0) && (retry_list[req.url] < @retries)
|
169
|
+
@retry_list[req.url] += 1
|
170
|
+
retry
|
171
|
+
end
|
172
|
+
raise msg
|
173
|
+
end
|
174
|
+
@retry_list.delete(req.url)
|
175
|
+
puts response if @low_debug
|
176
|
+
json_obj = JSON.parse(response.body) #todo handle null post error
|
177
|
+
errs = check_for_errors(json_obj)
|
178
|
+
raise "\nError on request - #{url} - \n#{errs}" if errs[:errors].length > 0
|
179
|
+
json_obj
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
def check_for_errors(result)
|
184
|
+
errors = []
|
185
|
+
warnings = []
|
186
|
+
if !result["OperationResult"].nil?
|
187
|
+
errors = result["OperationResult"]["Errors"]
|
188
|
+
warnings = result["OperationResult"]["Warnings"]
|
189
|
+
elsif !result["QueryResult"].nil?
|
190
|
+
errors = result["QueryResult"]["Errors"]
|
191
|
+
warnings = result["QueryResult"]["Warnings"]
|
192
|
+
elsif !result["CreateResult"].nil?
|
193
|
+
errors = result["CreateResult"]["Errors"]
|
194
|
+
warnings = result["CreateResult"]["Warnings"]
|
195
|
+
end
|
196
|
+
|
197
|
+
{:errors => errors, :warnings => warnings}
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#Copyright (c) 2002-2012 Rally Software Development Corp. All Rights Reserved.
|
2
|
+
#Your use of this Software is governed by the terms and conditions
|
3
|
+
#of the applicable Subscription Agreement between your company and
|
4
|
+
#Rally Software Development Corp.
|
5
|
+
module RallyAPI
|
6
|
+
|
7
|
+
class RallyObject
|
8
|
+
|
9
|
+
attr_reader :rally_object
|
10
|
+
|
11
|
+
def initialize(rally_rest, json_hash)
|
12
|
+
@type = json_hash["_type"] || json_hash["_ref"].split("/")[-2]
|
13
|
+
@rally_object = json_hash
|
14
|
+
@rally_rest = rally_rest
|
15
|
+
end
|
16
|
+
|
17
|
+
def update(fields)
|
18
|
+
oid = @rally_object["ObjectID"] || @rally_object["_ref"].split("/")[-1].split(".")[0]
|
19
|
+
@rally_object = @rally_rest.update(@type.downcase.to_sym, oid, fields).rally_object
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s(*args)
|
23
|
+
@rally_object.to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_json(*args)
|
27
|
+
@rally_object.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](field_name)
|
31
|
+
get_val(field_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def read()
|
35
|
+
@rally_object = @rally_rest.reread(@rally_object)
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def getref
|
40
|
+
@rally_object["_ref"]
|
41
|
+
end
|
42
|
+
|
43
|
+
def rank_above(relative_rally_object)
|
44
|
+
@rally_rest.rank_above(@rally_object["_ref"],relative_rally_object["_ref"])
|
45
|
+
end
|
46
|
+
|
47
|
+
def rank_below(relative_rally_object)
|
48
|
+
@rally_rest.rank_below(@rally_object["_ref"],relative_rally_object["_ref"])
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete()
|
52
|
+
@rally_rest.delete(@rally_object["_ref"])
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def method_missing(sym, *args)
|
58
|
+
get_val(sym.to_s)
|
59
|
+
end
|
60
|
+
|
61
|
+
#the magic of lazy loading is gone
|
62
|
+
#def method_missing(sym, *args)
|
63
|
+
# return_val = get_val(sym.to_s)
|
64
|
+
#
|
65
|
+
# if return_val.nil?
|
66
|
+
# @rally_object = @rally_rest.reread(@rally_object)
|
67
|
+
# return_val = get_val(sym.to_s)
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# return_val
|
71
|
+
#end
|
72
|
+
|
73
|
+
def get_val(field)
|
74
|
+
return_val = @rally_object[field]
|
75
|
+
|
76
|
+
if return_val.class == Hash
|
77
|
+
return RallyObject.new(@rally_rest, return_val)
|
78
|
+
end
|
79
|
+
|
80
|
+
if return_val.class == Array
|
81
|
+
make_object_array(field)
|
82
|
+
return_val = @rally_object[field]
|
83
|
+
end
|
84
|
+
|
85
|
+
return_val
|
86
|
+
end
|
87
|
+
|
88
|
+
def make_object_array(field)
|
89
|
+
object_array = []
|
90
|
+
@rally_object[field].each do |rally_obj|
|
91
|
+
object_array.push(RallyObject.new(@rally_rest, rally_obj))
|
92
|
+
end
|
93
|
+
@rally_object[field] = object_array
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
#Copyright (c) 2002-2012 Rally Software Development Corp. All Rights Reserved.
|
2
|
+
#Your use of this Software is governed by the terms and conditions
|
3
|
+
#of the applicable Subscription Agreement between your company and
|
4
|
+
#Rally Software Development Corp.
|
5
|
+
module RallyAPI
|
6
|
+
# query is like:
|
7
|
+
# query_hash = {}
|
8
|
+
# query_hash[:type] = Defect, Story, etc
|
9
|
+
# query_hash[:query_string] = "(State = \"Closed\")"
|
10
|
+
# query_hash[:fetch] = "Name,State,etc"
|
11
|
+
# query_hash[:workspace] = workspace json object or ref #defaults to workspace passed in RallyRestJson.new if nil
|
12
|
+
# query_hash[:project] = project json object or ref #defaults to project passed in RallyRestJson.new if nil
|
13
|
+
# query_hash[:project_scope_up] = true/false
|
14
|
+
# query_hash[:project_scope_down] = true/false
|
15
|
+
# query_hash[:order] = "ObjectID asc"
|
16
|
+
# query_hash[:page_size]
|
17
|
+
# query_hash[:stopafter]
|
18
|
+
|
19
|
+
class RallyQuery
|
20
|
+
attr_accessor :type, :query_string, :fetch, :workspace, :project, :project_scope_up, :project_scope_down
|
21
|
+
attr_accessor :order, :page_size, :limit
|
22
|
+
|
23
|
+
def initialize(query_hash = nil)
|
24
|
+
parse_query_hash(query_hash) if !query_hash.nil?
|
25
|
+
@page_size = 200 if @page_size.nil?
|
26
|
+
@limit = 99999 if @limit.nil?
|
27
|
+
@project_scope_up = false if @project_scope_up.nil?
|
28
|
+
@project_scope_down = false if @project_scope_down.nil?
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def make_query_params
|
33
|
+
query_params = {}
|
34
|
+
query_params[:query] = @query_string
|
35
|
+
query_params[:fetch] = @fetch
|
36
|
+
query_params[:workspace] = @workspace["_ref"] if !@workspace.nil?
|
37
|
+
query_params[:project] = @project["_ref"] if !@project.nil?
|
38
|
+
query_params[:projectScopeUp] = @project_scope_up
|
39
|
+
query_params[:projectScopeDown] = @project_scope_down
|
40
|
+
query_params[:order] = @order
|
41
|
+
query_params[:pagesize] = @page_size
|
42
|
+
|
43
|
+
query_params
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate(allowed_objects)
|
47
|
+
errors = []
|
48
|
+
|
49
|
+
if @type.nil?
|
50
|
+
errors.push("Object type for query cannot be nil")
|
51
|
+
end
|
52
|
+
|
53
|
+
if @limit < 0
|
54
|
+
errors.push("Stop after - #{@stop_after} - must be a number")
|
55
|
+
end
|
56
|
+
|
57
|
+
if @page_size < 0
|
58
|
+
errors.push("Page size - #{@page_size} - must be a number")
|
59
|
+
end
|
60
|
+
|
61
|
+
if !@workspace.nil?
|
62
|
+
errors.push("Workspace - #{@workspace} - must have a ref") if @workspace["_ref"].nil?
|
63
|
+
end
|
64
|
+
|
65
|
+
if !@project.nil?
|
66
|
+
errors.push("Project - #{@project} - must have a ref") if @project["_ref"].nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
if (allowed_objects[@type].nil?)
|
70
|
+
errors.push( "Object Type #{@type} is not query-able: inspect RallyRestJson.rally_objects for allowed types" )
|
71
|
+
end
|
72
|
+
|
73
|
+
errors
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def parse_query_hash(query_hash)
|
79
|
+
@type = query_hash[:type]
|
80
|
+
@query_string = query_hash[:query_string]
|
81
|
+
@fetch = query_hash[:fetch]
|
82
|
+
@project_scope_down = query_hash[:project_scope_down]
|
83
|
+
@project_scope_up = query_hash[:project_scope_up]
|
84
|
+
@order = query_hash[:order]
|
85
|
+
@page_size = query_hash[:page_size]
|
86
|
+
@stop_after = query_hash[:limit]
|
87
|
+
@workspace = query_hash[:workspace]
|
88
|
+
@project = query_hash[:project]
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#Copyright (c) 2002-2012 Rally Software Development Corp. All Rights Reserved.
|
2
|
+
#Your use of this Software is governed by the terms and conditions
|
3
|
+
#of the applicable Subscription Agreement between your company and
|
4
|
+
#Rally Software Development Corp.
|
5
|
+
module RallyAPI
|
6
|
+
class RallyQueryResult
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
attr_accessor :results, :total_result_count
|
10
|
+
|
11
|
+
def initialize(rally_rest, json_results)
|
12
|
+
@results = json_results["QueryResult"]["Results"]
|
13
|
+
@total_result_count = json_results["QueryResult"]["TotalResultCount"]
|
14
|
+
@rally_rest = rally_rest
|
15
|
+
end
|
16
|
+
|
17
|
+
def each
|
18
|
+
@results.each do |result|
|
19
|
+
yield RallyObject.new(@rally_rest, result)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](index)
|
24
|
+
RallyObject.new(@rally_rest, @results[index])
|
25
|
+
end
|
26
|
+
|
27
|
+
def total_results
|
28
|
+
@total_result_count
|
29
|
+
end
|
30
|
+
|
31
|
+
def length
|
32
|
+
@results.length
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
require_relative "custom_http_header"
|
2
|
+
require_relative "rally_json_connection"
|
3
|
+
require_relative "rally_object"
|
4
|
+
require_relative "rally_query"
|
5
|
+
require_relative "rally_query_result"
|
6
|
+
|
7
|
+
#Copyright (c) 2002-2012 Rally Software Development Corp. All Rights Reserved.
|
8
|
+
#Your use of this Software is governed by the terms and conditions
|
9
|
+
#of the applicable Subscription Agreement between your company and
|
10
|
+
#Rally Software Development Corp.
|
11
|
+
module RallyAPI
|
12
|
+
|
13
|
+
#this contstant is here - a tradeoff of speed vs completeness- right now speed wins because it is so
|
14
|
+
#expensive to query typedef and read all attributes for "OBJECT" or "COLLECTION" types
|
15
|
+
RALLY_REF_FIELDS = { "Subscription" => :subscription, "Workspace" => :workspace, "Project" => :project,
|
16
|
+
"Iteration" => :iteration, "Release" => :release, "WorkProduct" => :artifact,
|
17
|
+
"Requirement" => :hierarchicalrequirement, "Owner" => :user, "Tester" => :user,
|
18
|
+
"RevisionHistory" => :revisionhistory, "Revision" => :revision, "Revisions" => :revision,
|
19
|
+
"Blocker" => :artifact, "SubmittedBy" => :user, "TestCaseResult" => :testcaseresult,
|
20
|
+
"TestSet" => :testset, "Parent" => :hierarchicalrequirement, "TestFolder"=> :testfolder }
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
#Main Class to create from when using the tool
|
25
|
+
class RallyRestJson
|
26
|
+
DEFAULT_WSAPI_VERSION = "1.25"
|
27
|
+
|
28
|
+
attr_accessor :rally_url, :rally_user, :rally_password, :rally_workspace_name, :rally_project_name, :wsapi_version
|
29
|
+
attr_accessor :rally_headers, :rally_default_workspace, :rally_default_project, :low_debug, :proxy_info, :retries
|
30
|
+
|
31
|
+
attr_reader :rally_objects
|
32
|
+
|
33
|
+
def initialize(args)
|
34
|
+
@rally_url = args[:base_url] || "https://rally1.rallydev.com/slm"
|
35
|
+
@rally_user = args[:username]
|
36
|
+
@rally_password = args[:password]
|
37
|
+
@rally_workspace_name = args[:workspace]
|
38
|
+
@rally_project_name = args[:project]
|
39
|
+
@wsapi_version = args[:version] || DEFAULT_WSAPI_VERSION
|
40
|
+
@rally_headers = args[:headers] || CustomHttpHeader.new
|
41
|
+
@proxy_info = args[:proxy]
|
42
|
+
@retries = args[:retries] || 0
|
43
|
+
|
44
|
+
@low_debug = args[:debug] || false
|
45
|
+
|
46
|
+
@rally_connection = RallyJsonConnection.new(@rally_headers, @low_debug, @proxy_info)
|
47
|
+
if @retries > 0
|
48
|
+
@rally_connection.retries = @retries
|
49
|
+
end
|
50
|
+
|
51
|
+
@rally_objects = { :typedefinition => "TypeDefinition" }
|
52
|
+
cache_rally_objects()
|
53
|
+
|
54
|
+
if !@rally_workspace_name.nil?
|
55
|
+
@rally_default_workspace = find_workspace(@rally_workspace_name)
|
56
|
+
raise "unable to find default workspace #{@rally_workspace_name}" if @rally_default_workspace.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
if !@rally_project_name.nil?
|
60
|
+
@rally_default_project = find_project(@rally_default_workspace, @rally_project_name)
|
61
|
+
raise "unable to find default project #{@rally_project_name}" if @rally_default_project.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_workspace(workspace_name)
|
68
|
+
sub = self.user.Subscription.read()
|
69
|
+
workspace = nil
|
70
|
+
sub.Workspaces.each do |ws|
|
71
|
+
ws.read
|
72
|
+
if (ws.Name == workspace_name) && (ws.State == "Open")
|
73
|
+
workspace = ws
|
74
|
+
break #doing a break for performance some customers have 100+ workspaces - no need to do the others
|
75
|
+
end
|
76
|
+
end
|
77
|
+
workspace
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_project(workspace_object, project_name)
|
81
|
+
if workspace_object.nil?
|
82
|
+
raise "A workspace must be provided to find a project"
|
83
|
+
end
|
84
|
+
|
85
|
+
query = RallyQuery.new()
|
86
|
+
query.type = :project
|
87
|
+
query.query_string = "((Name = \"#{project_name}\") AND (State = \"Open\"))"
|
88
|
+
query.limit = 20
|
89
|
+
query.fetch = true
|
90
|
+
query.workspace = workspace_object
|
91
|
+
|
92
|
+
results = find(query)
|
93
|
+
return results.first if results.length > 0
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def user
|
98
|
+
args = {:user => @rally_user, :password => @rally_password}
|
99
|
+
json_response = @rally_connection.read_object(make_get_url(@rally_objects[:user]), args)
|
100
|
+
RallyObject.new(self, json_response)
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
def create(type, fields)
|
105
|
+
rally_type = check_type(type)
|
106
|
+
|
107
|
+
if (fields["Workspace"].nil? && fields["Project"].nil?)
|
108
|
+
fields["Workspace"] = @rally_default_workspace._ref
|
109
|
+
fields["Project"] = @rally_default_project._ref
|
110
|
+
end
|
111
|
+
|
112
|
+
args = {:user => @rally_user, :password => @rally_password }
|
113
|
+
object2create = { rally_type => make_ref_fields(fields) }
|
114
|
+
json_response = @rally_connection.create_object(make_create_url(rally_type), args, object2create)
|
115
|
+
RallyObject.new(self, json_response).read()
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def read(type, obj_id)
|
120
|
+
rally_type = check_type(type)
|
121
|
+
ref = check_id(rally_type, obj_id)
|
122
|
+
args = {:user => @rally_user, :password => @rally_password}
|
123
|
+
json_response = @rally_connection.read_object(ref, args)
|
124
|
+
RallyObject.new(self, json_response)
|
125
|
+
end
|
126
|
+
|
127
|
+
def delete(ref_to_delete)
|
128
|
+
args = {:user => @rally_user, :password => @rally_password}
|
129
|
+
@rally_connection.delete_object(ref_to_delete, args)
|
130
|
+
end
|
131
|
+
|
132
|
+
def reread(json_object)
|
133
|
+
args = {:user => @rally_user, :password => @rally_password}
|
134
|
+
@rally_connection.read_object(json_object["_ref"], args)
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def update(type, obj_id, fields)
|
139
|
+
rally_type = check_type(type)
|
140
|
+
ref = check_id(rally_type, obj_id)
|
141
|
+
json_update = { rally_type => make_ref_fields(fields) }
|
142
|
+
args = {:user => @rally_user, :password => @rally_password}
|
143
|
+
@rally_connection.update_object(ref, args, json_update)
|
144
|
+
RallyObject.new(self, reread({"_ref" => ref}))
|
145
|
+
end
|
146
|
+
|
147
|
+
#query_hash is a has like:
|
148
|
+
# query_hash = {}
|
149
|
+
# query_hash[:type] = Defect, Story, etc
|
150
|
+
# query_hash[:query_string] = "(State = \"Closed\")"
|
151
|
+
# query_hash[:fetch] = "Name,State,etc"
|
152
|
+
# query_hash[:workspace] = workspace json object or ref #defaults to workspace passed in RallyRestJson.new if nil
|
153
|
+
# query_hash[:project] = project json object or ref #defaults to project passed in RallyRestJson.new if nil
|
154
|
+
# query_hash[:project_scope_up] = true/false
|
155
|
+
# query_hash[:project_scope_down] = true/false
|
156
|
+
# query_hash[:order] = "ObjectID asc"
|
157
|
+
# query_hash[:pagesize]
|
158
|
+
# query_hash[:stopafter]
|
159
|
+
#todo default workspace and project if not set?
|
160
|
+
def find(query_obj)
|
161
|
+
errs = query_obj.validate(@rally_objects)
|
162
|
+
if errs.length > 0
|
163
|
+
raise "Errors making Rally Query: #{errs.to_s}"
|
164
|
+
end
|
165
|
+
|
166
|
+
query_url = make_query_url(@rally_url, @wsapi_version, check_type(query_obj.type))
|
167
|
+
query_params = query_obj.make_query_params
|
168
|
+
args = {:user => @rally_user, :password => @rally_password}
|
169
|
+
json_response = @rally_connection.get_all_json_results(query_url, args, query_params, query_obj.limit)
|
170
|
+
RallyQueryResult.new(self, json_response)
|
171
|
+
end
|
172
|
+
|
173
|
+
#rankAbove=%2Fhierarchicalrequirement%2F4624552599
|
174
|
+
#{"hierarchicalrequirement":{"_ref":"https://rally1.rallydev.com/slm/webservice/1.27/hierarchicalrequirement/4616818613.js"}}
|
175
|
+
def rank_above(ref_to_rank, relative_ref)
|
176
|
+
ref = ref_to_rank
|
177
|
+
params = {}
|
178
|
+
params[:rankAbove] = short_ref(relative_ref)
|
179
|
+
params[:fetch] = "true"
|
180
|
+
json_update = { get_type_from_ref(ref_to_rank) => {"_ref" => ref_to_rank} }
|
181
|
+
args = {:user => @rally_user, :password => @rally_password}
|
182
|
+
update = @rally_connection.put_object(ref, args, params, json_update)
|
183
|
+
RallyObject.new(self, update["Object"])
|
184
|
+
end
|
185
|
+
|
186
|
+
#ref to object.js? rankBelow=%2Fhierarchicalrequirement%2F4624552599
|
187
|
+
def rank_below(ref_to_rank, relative_ref)
|
188
|
+
ref = ref_to_rank
|
189
|
+
params = {}
|
190
|
+
params[:rankBelow] = short_ref(relative_ref)
|
191
|
+
params[:fetch] = "true"
|
192
|
+
json_update = { get_type_from_ref(ref_to_rank) => {"_ref" => ref_to_rank} }
|
193
|
+
args = {:user => @rally_user, :password => @rally_password}
|
194
|
+
update = @rally_connection.put_object(ref, args, params, json_update)
|
195
|
+
RallyObject.new(self, update["Object"])
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
199
|
+
|
200
|
+
def make_get_url(type)
|
201
|
+
"#{@rally_url}/webservice/#{@wsapi_version}/#{type}.js"
|
202
|
+
end
|
203
|
+
|
204
|
+
def make_read_url(type,oid)
|
205
|
+
"#{@rally_url}/webservice/#{@wsapi_version}/#{type}/#{oid}.js"
|
206
|
+
end
|
207
|
+
|
208
|
+
def make_create_url(type)
|
209
|
+
"#{@rally_url}/webservice/#{@wsapi_version}/#{type}/create.js"
|
210
|
+
end
|
211
|
+
|
212
|
+
def make_query_url(rally_url, wsapi_version, type)
|
213
|
+
"#{rally_url}/webservice/#{wsapi_version}/#{type}.js"
|
214
|
+
end
|
215
|
+
|
216
|
+
def short_ref(long_ref)
|
217
|
+
ref_pieces = long_ref.split("/")
|
218
|
+
"/#{ref_pieces[-2]}/#{ref_pieces[-1].split(".js")[0]}"
|
219
|
+
end
|
220
|
+
|
221
|
+
def check_type(type_name)
|
222
|
+
if @rally_objects[type_name].nil?
|
223
|
+
raise "The object type #{type_name} is not valid for the wsapi"
|
224
|
+
end
|
225
|
+
@rally_objects[type_name]
|
226
|
+
end
|
227
|
+
|
228
|
+
#ref should be like https://rally1.rallydev.com/slm/webservice/1.25/defect/12345.js
|
229
|
+
def has_ref?(json_object)
|
230
|
+
if json_object["_ref"].nil?
|
231
|
+
return false
|
232
|
+
end
|
233
|
+
return true if json_object["_ref"] =~ /^https:\/\/\S*(\/slm\/webservice)\S*.js$/
|
234
|
+
false
|
235
|
+
end
|
236
|
+
|
237
|
+
#expecting idstring to have "FormattedID|DE45" or the objectID
|
238
|
+
def check_id(type, idstring)
|
239
|
+
if idstring.class == Fixnum
|
240
|
+
return make_read_url(type, idstring)
|
241
|
+
end
|
242
|
+
|
243
|
+
if (idstring.class == String) && (idstring.index("FormattedID") == 0)
|
244
|
+
return ref_by_formatted_id(type, idstring.split("|")[1])
|
245
|
+
end
|
246
|
+
make_read_url(type, idstring)
|
247
|
+
end
|
248
|
+
|
249
|
+
def ref_by_formatted_id(type, fid)
|
250
|
+
query = RallyQuery.new()
|
251
|
+
query.type = type.downcase.to_sym
|
252
|
+
query.query_string = "(FormattedID = #{fid})"
|
253
|
+
query.limit = 20
|
254
|
+
query.fetch = "FormattedID"
|
255
|
+
query.workspace = @rally_default_workspace
|
256
|
+
|
257
|
+
results = find(query)
|
258
|
+
return results.first["_ref"] if results.length > 0
|
259
|
+
nil
|
260
|
+
end
|
261
|
+
|
262
|
+
#eg https://rally1.rallydev.com/slm/webservice/1.25/defect/12345.js
|
263
|
+
def get_type_from_ref(ref)
|
264
|
+
ref.split("/")[-2]
|
265
|
+
end
|
266
|
+
|
267
|
+
def make_ref_fields(fields)
|
268
|
+
fields.each do |key,val|
|
269
|
+
if (val.class == RallyObject)
|
270
|
+
fields[key] = val.getref
|
271
|
+
end
|
272
|
+
end
|
273
|
+
fields
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
def cache_rally_objects()
|
278
|
+
type_defs_query = RallyQuery.new()
|
279
|
+
type_defs_query.type = :typedefinition
|
280
|
+
type_defs_query.fetch = "Name"
|
281
|
+
|
282
|
+
type_defs = find(type_defs_query)
|
283
|
+
type_defs.each do |td|
|
284
|
+
type_sym = td.Name.downcase.gsub(" ", "").to_sym
|
285
|
+
@rally_objects[type_sym] = td.Name.gsub(" ","")
|
286
|
+
end
|
287
|
+
|
288
|
+
#some convenience keys to help people - someday we'll fix the api and make HR called story
|
289
|
+
@rally_objects[:artifact] = "Artifact"
|
290
|
+
@rally_objects[:persistableobject] = "PersistableObject"
|
291
|
+
@rally_objects[:useriterationcapacity] = "UserIterationCapacity"
|
292
|
+
@rally_objects[:userpermission] = "UserPermission"
|
293
|
+
@rally_objects[:story] = "HierarchicalRequirement"
|
294
|
+
@rally_objects[:userstory] = "HierarchicalRequirement"
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
#Copyright (c) 2002-2012 Rally Software Development Corp. All Rights Reserved.
|
2
|
+
#Your use of this Software is governed by the terms and conditions
|
3
|
+
#of the applicable Subscription Agreement between your company and
|
4
|
+
#Rally Software Development Corp.
|
5
|
+
module RallyAPI
|
6
|
+
# query is like:
|
7
|
+
# query_hash = {}
|
8
|
+
# query_hash[:type] = Defect, Story, etc
|
9
|
+
# query_hash[:query_string] = "(State = \"Closed\")"
|
10
|
+
# query_hash[:fetch] = "Name,State,etc"
|
11
|
+
# query_hash[:workspace] = workspace json object or ref #defaults to workspace passed in RallyRestJson.new if nil
|
12
|
+
# query_hash[:project] = project json object or ref #defaults to project passed in RallyRestJson.new if nil
|
13
|
+
# query_hash[:project_scope_up] = true/false
|
14
|
+
# query_hash[:project_scope_down] = true/false
|
15
|
+
# query_hash[:order] = "ObjectID asc"
|
16
|
+
# query_hash[:page_size]
|
17
|
+
# query_hash[:stopafter]
|
18
|
+
|
19
|
+
class TimeMachineQuery
|
20
|
+
attr_accessor :workspace
|
21
|
+
|
22
|
+
def initialize(query_hash = nil)
|
23
|
+
parse_query_hash(query_hash) if !query_hash.nil?
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_query_params
|
28
|
+
query_params = {}
|
29
|
+
#query_params[:query] = @query_string
|
30
|
+
#query_params[:fetch] = @fetch
|
31
|
+
query_params[:workspace] = @workspace["_ref"] if !@workspace.nil?
|
32
|
+
#query_params[:project] = @project["_ref"] if !@project.nil?
|
33
|
+
#query_params[:projectScopeUp] = @project_scope_up
|
34
|
+
#query_params[:projectScopeDown] = @project_scope_down
|
35
|
+
#query_params[:order] = @order
|
36
|
+
#query_params[:pagesize] = @page_size
|
37
|
+
|
38
|
+
query_params
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate(allowed_objects)
|
42
|
+
errors = []
|
43
|
+
|
44
|
+
if @type.nil?
|
45
|
+
errors.push("Object type for query cannot be nil")
|
46
|
+
end
|
47
|
+
|
48
|
+
if @limit < 0
|
49
|
+
errors.push("Stop after - #{@stop_after} - must be a number")
|
50
|
+
end
|
51
|
+
|
52
|
+
if @page_size < 0
|
53
|
+
errors.push("Page size - #{@page_size} - must be a number")
|
54
|
+
end
|
55
|
+
|
56
|
+
if !@workspace.nil?
|
57
|
+
errors.push("Workspace - #{@workspace} - must have a ref") if @workspace["_ref"].nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
if !@project.nil?
|
61
|
+
errors.push("Project - #{@project} - must have a ref") if @project["_ref"].nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
if (allowed_objects[@type].nil?)
|
65
|
+
errors.push( "Object Type #{@type} is not query-able: inspect RallyRestJson.rally_objects for allowed types" )
|
66
|
+
end
|
67
|
+
|
68
|
+
errors
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def parse_query_hash(query_hash)
|
74
|
+
#@type = query_hash[:type]
|
75
|
+
#@query_string = query_hash[:query_string]
|
76
|
+
#@fetch = query_hash[:fetch]
|
77
|
+
#@project_scope_down = query_hash[:project_scope_down]
|
78
|
+
#@project_scope_up = query_hash[:project_scope_up]
|
79
|
+
#@order = query_hash[:order]
|
80
|
+
#@page_size = query_hash[:page_size]
|
81
|
+
#@stop_after = query_hash[:limit]
|
82
|
+
@workspace = query_hash[:workspace]
|
83
|
+
#@project = query_hash[:project]
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
#Copyright (c) 2002-2012 Rally Software Development Corp. All Rights Reserved.
|
2
|
+
#Your use of this Software is governed by the terms and conditions
|
3
|
+
#of the applicable Subscription Agreement between your company and
|
4
|
+
#Rally Software Development Corp.
|
5
|
+
module RallyAPI
|
6
|
+
VERSION = "0.4.0"
|
7
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rally_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dave Smith
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-07 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rest-client
|
16
|
+
requirement: &70332191860000 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.6.7
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70332191860000
|
25
|
+
description: API wrapper for Rally's JSON REST web services api
|
26
|
+
email:
|
27
|
+
- dsmith@rallydev.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- README.rdoc
|
33
|
+
- Rakefile
|
34
|
+
- lib/rally_api/custom_http_header.rb
|
35
|
+
- lib/rally_api/rally_json_connection.rb
|
36
|
+
- lib/rally_api/rally_object.rb
|
37
|
+
- lib/rally_api/rally_query.rb
|
38
|
+
- lib/rally_api/rally_query_result.rb
|
39
|
+
- lib/rally_api/rally_rest_json.rb
|
40
|
+
- lib/rally_api/time_machine_query.rb
|
41
|
+
- lib/rally_api/version.rb
|
42
|
+
- lib/rally_api.rb
|
43
|
+
homepage: http://developer.rallydev.com/help
|
44
|
+
licenses: []
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project: rally_api
|
63
|
+
rubygems_version: 1.8.6
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: A wrapper for the Rally Web Services API using json
|
67
|
+
test_files: []
|