rally_rest_api 0.5.9 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/lib/rally_rest_api/query.rb +9 -1
- data/lib/rally_rest_api/query_result.rb +2 -3
- data/lib/rally_rest_api/rally_rest.rb +1 -5
- data/lib/rally_rest_api/rest_builder.rb +1 -1
- data/lib/rally_rest_api/rest_object.rb +10 -1
- data/lib/rally_rest_api/typedef.rb +67 -20
- data/lib/rally_rest_api/version.rb +2 -2
- data/test/attribute_definition_spec.rb +54 -0
- data/test/spec_typedef.rb +83 -0
- data/test/tc_query_result.rb +2 -1
- data/test/tc_rest_api.rb +2 -1
- data/test/tc_rest_object.rb +3 -1
- data/test/tc_rest_query.rb +3 -1
- metadata +5 -2
data/Rakefile
CHANGED
@@ -39,7 +39,7 @@ hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
|
39
39
|
p.summary = DESCRIPTION
|
40
40
|
p.url = HOMEPATH
|
41
41
|
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
42
|
-
p.test_globs = ["test
|
42
|
+
p.test_globs = ["test/*.rb"]
|
43
43
|
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
44
44
|
p.extra_deps = ["builder"]
|
45
45
|
|
data/lib/rally_rest_api/query.rb
CHANGED
@@ -18,6 +18,12 @@ class Symbol # :nodoc: all
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
class NilClass
|
22
|
+
def to_q
|
23
|
+
"null"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
21
27
|
# == Generate a query string for Rally's webservice query interface.
|
22
28
|
# Arguments are:
|
23
29
|
# type - the type to query for
|
@@ -73,6 +79,8 @@ class RestQuery
|
|
73
79
|
def initialize(type, args = {}, &block)
|
74
80
|
@type = type
|
75
81
|
@query_string = "query=" << URI.escape(QueryBuilder.new("and", &block).to_q) if block_given?
|
82
|
+
@args_for_paging = {}
|
83
|
+
[:workspace, :project, :project_scope_down, :project_scope_up].each { |k| @args_for_paging[k] = args[k] if args.key?(k) }
|
76
84
|
@query_params = process_args(args)
|
77
85
|
end
|
78
86
|
|
@@ -91,7 +99,7 @@ class RestQuery
|
|
91
99
|
end
|
92
100
|
|
93
101
|
def next_page(args)
|
94
|
-
@query_params = process_args(args)
|
102
|
+
@query_params = process_args(args.merge(@args_for_paging))
|
95
103
|
self
|
96
104
|
end
|
97
105
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require File.dirname(__FILE__) + '/rest_object'
|
2
2
|
|
3
3
|
# == An interface to the paged query result
|
4
4
|
#
|
@@ -36,7 +36,6 @@ class QueryResult < RestObject
|
|
36
36
|
@start_index = elements[:start_index].to_i
|
37
37
|
end
|
38
38
|
|
39
|
-
require 'active_support/breakpoint'
|
40
39
|
# fetch the next page of results. Uses the original query to generate the query string.
|
41
40
|
def next_page
|
42
41
|
@rally_rest.query(@query.next_page(:start => self.start_index + self.page_size,
|
@@ -54,7 +53,7 @@ require 'active_support/breakpoint'
|
|
54
53
|
yield result.dup
|
55
54
|
end
|
56
55
|
current_result = current_result.next_page if current_result.more_pages?
|
57
|
-
end while last_result
|
56
|
+
end while !last_result.equal? current_result
|
58
57
|
end
|
59
58
|
|
60
59
|
# return the first element. Useful for queries that return only one result
|
@@ -3,10 +3,6 @@ require 'uri'
|
|
3
3
|
require 'rexml/document'
|
4
4
|
require 'ostruct'
|
5
5
|
|
6
|
-
require 'rally_rest_api/rest_builder'
|
7
|
-
require 'rally_rest_api/query'
|
8
|
-
require 'rally_rest_api/rest_object'
|
9
|
-
require 'rally_rest_api/query_result'
|
10
6
|
|
11
7
|
#
|
12
8
|
# RallyRestAPI - A Ruby-ized interface to Rally's REST webservice API
|
@@ -79,7 +75,7 @@ class RallyRestAPI
|
|
79
75
|
end
|
80
76
|
|
81
77
|
# find all object of a given type. Base types work also (.e.g :artifact)
|
82
|
-
def find_all(type,
|
78
|
+
def find_all(type, args = {})
|
83
79
|
find(type, args) { gt :object_i_d, "0" }
|
84
80
|
end
|
85
81
|
|
@@ -170,7 +170,7 @@ module RestBuilder # :nodoc:
|
|
170
170
|
end
|
171
171
|
|
172
172
|
|
173
|
-
COLLECTION_TYPES = [:dependents, :dependencies, :defects]
|
173
|
+
COLLECTION_TYPES = [:dependents, :dependencies, :defects, :duplicates]
|
174
174
|
def collection_type?(type)
|
175
175
|
COLLECTION_TYPES.include?(type)
|
176
176
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require File.dirname(__FILE__) + '/rest_builder'
|
2
2
|
|
3
3
|
# == An interface to a Rally resource
|
4
4
|
# RestObject is an adapter that is initialized with a resource ref (URL) that represents a Rally object. The adapter
|
@@ -145,6 +145,11 @@ class RestObject
|
|
145
145
|
@document.root.attributes["type"] || @document.root.name
|
146
146
|
end
|
147
147
|
|
148
|
+
# the type as symbol
|
149
|
+
def type_as_symbol
|
150
|
+
underscore(self.type).intern
|
151
|
+
end
|
152
|
+
|
148
153
|
# The oid of the underlying resource
|
149
154
|
def oid
|
150
155
|
self.elements[:object_id]
|
@@ -173,6 +178,10 @@ class RestObject
|
|
173
178
|
@elements
|
174
179
|
end
|
175
180
|
|
181
|
+
def ==(other)
|
182
|
+
other.ref == self.ref
|
183
|
+
end
|
184
|
+
|
176
185
|
public
|
177
186
|
|
178
187
|
# update the resource. This will re-read the resource after the update
|
@@ -1,49 +1,86 @@
|
|
1
|
+
require 'pp'
|
2
|
+
class TypeDefinition < RestObject # :nodoc:
|
1
3
|
|
2
|
-
|
4
|
+
def self.cached_type_definitions
|
5
|
+
@@cached_type_definitions ||= {}
|
6
|
+
end
|
3
7
|
|
4
|
-
|
8
|
+
def self.cached_type_definition(workspace_domain_object, type = workspace_domain_object.type)
|
9
|
+
key = make_key(workspace_domain_object.workspace, type)
|
5
10
|
|
6
|
-
|
7
|
-
|
8
|
-
typedef = workspace_domain_object.workspace.type_definitions.values.find do |typedef|
|
9
|
-
puts "#{workspace_domain_object.type} == #{typedef.element_name}"
|
10
|
-
workspace_domain_object.type == typedef.element_name
|
11
|
+
unless cached_type_definitions.key? key
|
12
|
+
cached_type_definitions[key] = get_type_definition(workspace_domain_object.workspace, type)
|
11
13
|
end
|
14
|
+
cached_type_definitions[key]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.get_type_definition(workspace, type)
|
18
|
+
mangle_type_definitions(workspace.type_definitions)
|
19
|
+
typedef = workspace.type_definitions[type]
|
12
20
|
TypeDefinition.new(typedef.rally_rest, typedef.body)
|
13
21
|
end
|
14
22
|
|
23
|
+
# This is a hack
|
24
|
+
# Strip spaces from the name, and put the values back in to the hash
|
25
|
+
def self.mangle_type_definitions(type_definitions)
|
26
|
+
type_definitions.each do |key, value|
|
27
|
+
type_definitions[key.gsub(" ", "")] = value if key.include? " "
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.make_key(workspace, type)
|
32
|
+
# Parent typedefs don't have a workspace, so key them appropriately
|
33
|
+
ref = workspace.ref rescue ""
|
34
|
+
type + ref
|
35
|
+
end
|
36
|
+
|
37
|
+
def custom_attributes
|
38
|
+
collect_attributes { |element_name, attrdef| attrdef.custom == "true" }
|
39
|
+
end
|
40
|
+
|
41
|
+
# custom and constrained, i.e. a custom dropdown
|
42
|
+
def custom_constrained_attributes
|
43
|
+
collect_attributes { |element_name, attrdef| attrdef.custom == "true" && attrdef.constrained == "true" }
|
44
|
+
end
|
45
|
+
alias custom_dropdown_attributes custom_constrained_attributes
|
46
|
+
|
47
|
+
def constrained_attributes
|
48
|
+
collect_attributes { |element_name, attrdef| attrdef.constrained == "true" }
|
49
|
+
end
|
50
|
+
|
15
51
|
def collection_attributes(include_parent = false)
|
16
|
-
|
52
|
+
collect_attributes(include_parent) { |element_name, attrdef| attrdef.attribute_type == "COLLECTION" }
|
17
53
|
end
|
18
54
|
|
19
55
|
def object_attributes(include_parent = false)
|
20
|
-
|
56
|
+
collect_attributes(include_parent) do |element_name, attrdef|
|
57
|
+
attrdef.attribute_type == "OBJECT"
|
58
|
+
end
|
21
59
|
end
|
22
60
|
|
23
61
|
def reference_attributes(include_parent = false)
|
24
62
|
collection_attributes(include_parent).merge object_attributes(include_parent)
|
25
63
|
end
|
26
64
|
|
27
|
-
def
|
28
|
-
self.attributes(include_parent).
|
29
|
-
|
30
|
-
h
|
31
|
-
end
|
65
|
+
def collect_attributes(include_parent = false)
|
66
|
+
values = self.attributes(include_parent).find_all { |element_name, attrdef| yield element_name, attrdef }
|
67
|
+
Hash[*values.flatten]
|
32
68
|
end
|
33
69
|
|
34
|
-
def symbol_keyed_attributes(
|
35
|
-
|
36
|
-
|
70
|
+
def symbol_keyed_attributes(attribute_hash)
|
71
|
+
return {} unless attribute_hash # some typedefs will have no attributes
|
72
|
+
attribute_hash.values.inject({}) do |hash, attrdef|
|
73
|
+
hash[underscore(attrdef.element_name).intern] = AttributeDefinition.new(self.rally_rest, attrdef)
|
37
74
|
hash
|
38
75
|
end
|
39
76
|
end
|
40
77
|
|
41
78
|
def attributes(include_parent = false)
|
42
|
-
return symbol_keyed_attributes(self.elements[:attributes]
|
79
|
+
return symbol_keyed_attributes(self.elements[:attributes]) unless include_parent
|
43
80
|
|
44
81
|
typedef = self
|
45
82
|
all_attributes = {}
|
46
|
-
until typedef.
|
83
|
+
until typedef.nil?
|
47
84
|
all_attributes.merge! typedef.attributes(false)
|
48
85
|
typedef = typedef.parent
|
49
86
|
end
|
@@ -52,7 +89,17 @@ class TypeDefinition < RestObject # :nodoc:
|
|
52
89
|
|
53
90
|
def parent
|
54
91
|
return nil if self.elements[:parent].nil?
|
55
|
-
|
92
|
+
cached_parent
|
93
|
+
end
|
94
|
+
|
95
|
+
def cached_parent
|
96
|
+
type_no_spaces = self.elements[:parent].name.gsub(" ", "")
|
97
|
+
key = TypeDefinition.make_key(self.workspace, type_no_spaces)
|
98
|
+
typedef = TypeDefinition.cached_type_definitions[key]
|
99
|
+
if typedef.nil?
|
100
|
+
typedef = TypeDefinition.cached_type_definitions[key] = TypeDefinition.new(self.rally_rest, self.elements[:parent].body)
|
101
|
+
end
|
102
|
+
typedef
|
56
103
|
end
|
57
104
|
|
58
105
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'builder'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'rally_rest_api'
|
6
|
+
|
7
|
+
|
8
|
+
context "An attribute definition for a custom dropdown" do
|
9
|
+
|
10
|
+
setup do
|
11
|
+
@b = Builder::XmlMarkup.new(:indent => 2)
|
12
|
+
@b.instruct!
|
13
|
+
|
14
|
+
xml = @b.AttributeDefinition(:ref => "http://bla/bla/bla",
|
15
|
+
:refObjectName => "Custom Dropdown") {
|
16
|
+
@b.Name("Custom Dropdown")
|
17
|
+
@b.AttributeType("STRING")
|
18
|
+
@b.ElementName("CustomDropdown")
|
19
|
+
@b.Constrained("true")
|
20
|
+
@b.Custom("true")
|
21
|
+
|
22
|
+
@b.AllowedValues {
|
23
|
+
@b.AllowedAttributeValue(:ref => "null") {
|
24
|
+
@b.StringValue
|
25
|
+
}
|
26
|
+
@b.AllowedAttributeValue(:ref => "null") {
|
27
|
+
@b.StringValue("Actor")
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
fake_rally_rest = OpenStruct.new(:username => "", :password => "")
|
32
|
+
@attrdef = AttributeDefinition.new(fake_rally_rest, RestObject.new(fake_rally_rest, xml).elements)
|
33
|
+
end
|
34
|
+
|
35
|
+
specify "should have 'Custom Dropdown' as the name" do
|
36
|
+
@attrdef.name.should == "Custom Dropdown"
|
37
|
+
end
|
38
|
+
|
39
|
+
specify "should return an arry of allowed values" do
|
40
|
+
@attrdef.allowed_values.should_be_instance_of Array
|
41
|
+
end
|
42
|
+
|
43
|
+
specify "should have 2 allowd values" do
|
44
|
+
@attrdef.allowed_values.length.should == 2
|
45
|
+
end
|
46
|
+
|
47
|
+
specify "should not return an array of hashes" do
|
48
|
+
@attrdef.allowed_values.each { |value| value.should_not_be_instance_of Hash }
|
49
|
+
end
|
50
|
+
|
51
|
+
specify "allowed_values should match" do
|
52
|
+
@attrdef.allowed_values.should == [nil, "Actor"]
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'rally_rest_api'
|
5
|
+
|
6
|
+
|
7
|
+
context "A Test Case type definition with 2 collection attribues, 1 object attributes, and 1 string attribute" do
|
8
|
+
|
9
|
+
setup do
|
10
|
+
@b = Builder::XmlMarkup.new(:indent => 2)
|
11
|
+
@b.instruct!
|
12
|
+
|
13
|
+
xml = @b.TypeDefinition(:ref => "https://rally1.rallydev.com:443/slm/webservice/1.0/typedefinition/31552422",
|
14
|
+
:refObjectName => "Test Case",
|
15
|
+
:type => "TypeDefinition") {
|
16
|
+
@b.ElementName("TestCase")
|
17
|
+
@b.Attributes {
|
18
|
+
{"Notes" => {:type => "TEXT", :element_name => "Notes", :constrained => "false", :custom => "false"},
|
19
|
+
"Test Case Result" => {:type => "COLLECTION", :element_name => "TestCaseResult", :constrained => "false", :custom => "false"},
|
20
|
+
"Foo Bar" => {:type => "COLLECTION", :element_name => "FooBar", :constrained => "false", :custom => "false"},
|
21
|
+
"Requirement" => {:type => "OBJECT", :element_name => "Requirement", :constrained => "false", :custom => "false"},
|
22
|
+
"Constrained" => {:type => "STRING", :element_name => "Constrained", :constrained => "true", :custom => "false"},
|
23
|
+
"Custom" => {:type => "STRING", :element_name => "Custom", :constrained => "false", :custom => "true"},
|
24
|
+
"ConstainedCustom" => {:type => "STRING", :element_name => "ConstrainedCustom", :constrained => "true", :custom => "true"}
|
25
|
+
}.each do |name, type|
|
26
|
+
@b.AttributeDefinition(:ref => "http://bla/bla/bla",
|
27
|
+
:refObjectName => name) {
|
28
|
+
@b.AttributeType(type[:type])
|
29
|
+
@b.ElementName(type[:element_name])
|
30
|
+
@b.Constrained(type[:constrained])
|
31
|
+
@b.Custom(type[:custom])
|
32
|
+
}
|
33
|
+
end
|
34
|
+
}
|
35
|
+
}
|
36
|
+
@typedef = TypeDefinition.new(OpenStruct.new(:username => "", :password => ""), xml)
|
37
|
+
end
|
38
|
+
|
39
|
+
specify "all attribute keys are symbols" do
|
40
|
+
@typedef.attributes.keys.each { |a| a.should_be_instance_of Symbol }
|
41
|
+
end
|
42
|
+
|
43
|
+
specify "there should be 2 collection attributes" do
|
44
|
+
@typedef.collection_attributes.size.should == 2
|
45
|
+
end
|
46
|
+
|
47
|
+
specify "the :test_case_result attribute should exist" do
|
48
|
+
@typedef.collection_attributes[:test_case_result].should_not_be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
specify "All the collection attributes keys should be symbols" do
|
52
|
+
@typedef.collection_attributes.keys.each { |a| a.should_be_instance_of Symbol }
|
53
|
+
end
|
54
|
+
|
55
|
+
specify "there should be 1 object attribute" do
|
56
|
+
@typedef.object_attributes.size.should == 1
|
57
|
+
end
|
58
|
+
|
59
|
+
specify "the :requirement attribute should exist" do
|
60
|
+
@typedef.object_attributes[:requirement].should_not_be_nil
|
61
|
+
end
|
62
|
+
|
63
|
+
specify "should have 2 constrained attributes" do
|
64
|
+
@typedef.constrained_attributes.size.should == 2
|
65
|
+
end
|
66
|
+
|
67
|
+
specify "should have 2 cusom attributes" do
|
68
|
+
@typedef.custom_attributes.size.should == 2
|
69
|
+
end
|
70
|
+
|
71
|
+
specify "should have 1 custom_constrained_attributes" do
|
72
|
+
@typedef.custom_constrained_attributes.size.should == 1
|
73
|
+
@typedef.custom_dropdown_attributes.size.should == 1
|
74
|
+
end
|
75
|
+
|
76
|
+
specify "#attributes should return instances of AttributeDefinitions" do
|
77
|
+
@typedef.attributes.values.each { |attrdef| attrdef.should_be_instance_of AttributeDefinition }
|
78
|
+
end
|
79
|
+
|
80
|
+
specify "parent should be nil" do
|
81
|
+
@typedef.parent.should_be_nil
|
82
|
+
end
|
83
|
+
end
|
data/test/tc_query_result.rb
CHANGED
data/test/tc_rest_api.rb
CHANGED
data/test/tc_rest_object.rb
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
require 'test/unit'
|
3
3
|
require 'test/unit/testcase'
|
4
4
|
|
5
|
-
require '
|
5
|
+
require 'rubygems'
|
6
|
+
require 'rally_rest_api'
|
7
|
+
|
6
8
|
|
7
9
|
# We need to override read_rest from RestBuilder, So redefine it here
|
8
10
|
# We need a way to inject the xml that will be read by a RestObject when
|
data/test/tc_rest_query.rb
CHANGED
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rally_rest_api
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2006-
|
6
|
+
version: 0.6.0
|
7
|
+
date: 2006-12-13 00:00:00 -07:00
|
8
8
|
summary: A ruby-ized interface to Rally's REST webservices API
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -52,7 +52,10 @@ test_files:
|
|
52
52
|
- test/tc_rest_api.rb
|
53
53
|
- test/tc_rest_object.rb
|
54
54
|
- test/tc_rest_query.rb
|
55
|
+
- test/test_helper.rb
|
55
56
|
- test/tc_query_result.rb
|
57
|
+
- test/spec_typedef.rb
|
58
|
+
- test/attribute_definition_spec.rb
|
56
59
|
rdoc_options: []
|
57
60
|
|
58
61
|
extra_rdoc_files: []
|