rally_rest_api 0.5.9 → 0.6.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/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: []
|