rally_api 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|