xplanner 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +6 -1
- data/README.md +23 -0
- data/lib/config/attr_accessor_dirty_bit.rb +4 -4
- data/lib/config/default_config.rb +5 -5
- data/lib/config/savon_config.rb +36 -36
- data/lib/config/version.rb +1 -1
- data/lib/helpers/xplanner_soap_client.rb +19 -19
- data/lib/models/iteration.rb +47 -47
- data/lib/models/person.rb +1 -1
- data/lib/models/project.rb +18 -18
- data/lib/models/story.rb +4 -4
- data/lib/models/task.rb +4 -4
- data/lib/xplanner.rb +19 -19
- data/test/tests.rb +3 -15
- metadata +2 -2
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
# XPlanner+ Webservice Wrapper
|
2
|
+
|
3
|
+
This gem provides a wrapper for manipulating [XPlanner+](http://xplanner-plus.sourceforge.net/) via the SOAP interface.
|
4
|
+
|
5
|
+
There are models for Projects, Iterations, User Storys, Tasks and People.
|
6
|
+
|
7
|
+
To get started, you need to provide a config hash to the XPlanner module, like so:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
config = {
|
11
|
+
:url => 'http://127.0.0.1:8080/xplanner-plus/'
|
12
|
+
:user => 'sysadmin'
|
13
|
+
:pass => 'admin'
|
14
|
+
}
|
15
|
+
|
16
|
+
XPlanner::setup( config )
|
17
|
+
```
|
18
|
+
|
19
|
+
Start creating XPlanner objects like so:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
@myProject = XPlanner::Project.create( 'delete me', 'delete me: test project' )
|
23
|
+
```
|
@@ -4,10 +4,10 @@ module AttrAccessorWithDirtyBit
|
|
4
4
|
|
5
5
|
names.each do |name|
|
6
6
|
define_method :"#{name}=" do |v|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
if instance_variable_get(:"@#{name}") != v
|
8
|
+
@dirty_bit = true
|
9
|
+
instance_variable_set(:"@#{name}", v)
|
10
|
+
end
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module XPlanner
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
@@default_config = {
|
3
|
+
:xplanner_url => "http://127.0.0.1:8080/xplanner-plus/",
|
4
|
+
:username => 'sysadmin',
|
5
|
+
:password => 'admin'
|
6
|
+
}
|
7
7
|
end
|
data/lib/config/savon_config.rb
CHANGED
@@ -12,45 +12,45 @@ HTTPI.log = false
|
|
12
12
|
|
13
13
|
# Helper for decoding multi-ref style SOAP responses
|
14
14
|
module Savon
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
15
|
+
module SOAP
|
16
|
+
class Response
|
17
|
+
def decode_soap_response( path = [] )
|
18
|
+
hash = SoapResponseDecoder.decode( self.to_xml )
|
19
|
+
path.each { |p| hash = hash[p] }
|
20
|
+
hash
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
24
|
end
|
25
25
|
|
26
26
|
# Decode multi-ref soap references.
|
27
27
|
class SoapResponseDecoder
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
28
|
+
def self.decode( xml_doc )
|
29
|
+
doc = Nokogiri::XML xml_doc
|
30
|
+
|
31
|
+
while( el = doc.xpath('//soapenv:Body//*[@href]').first )
|
32
|
+
id = el.attribute('href').value.gsub('#', '')
|
33
|
+
el.children = doc.xpath("//soapenv:Body//multiRef[@id='#{id}']").children()
|
34
|
+
el.remove_attribute('href')
|
35
|
+
end
|
36
|
+
|
37
|
+
hash = convert_hash_keys( Hash.from_xml( doc.to_s ) )[:envelope][:body]
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def self.underscore_key(k)
|
42
|
+
k.to_s.underscore.to_sym
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.convert_hash_keys(value)
|
46
|
+
case value
|
47
|
+
when Array
|
48
|
+
value.map { |v| convert_hash_keys(v) }
|
49
|
+
when Hash
|
50
|
+
Hash[value.map { |k, v| [underscore_key(k), convert_hash_keys(v)] if k != 'xsi:type' }]
|
51
|
+
else
|
52
|
+
value
|
53
|
+
end
|
54
|
+
end
|
55
55
|
|
56
56
|
end
|
data/lib/config/version.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
module XPlanner
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
class SoapClient
|
3
|
+
def initialize( params )
|
4
|
+
@client = Savon.client do |wsdl, http|
|
5
|
+
wsdl.document = params[:wsdl]
|
6
|
+
http.auth.basic params[:username], params[:password]
|
7
|
+
end
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
def confirm_connection
|
11
|
+
begin
|
12
|
+
return @client.wsdl.soap_actions.include? :get_user_story
|
13
|
+
rescue
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
# throws exception if xplanner doesn't like it.
|
19
|
+
def call( rpc, payload=nil)
|
20
|
+
@client.request( rpc ) { soap.body = payload unless payload.nil? }
|
21
|
+
end
|
22
|
+
end
|
23
23
|
end
|
data/lib/models/iteration.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module XPlanner
|
2
2
|
class Iteration < ModelHelper
|
3
3
|
extend AttrAccessorWithDirtyBit
|
4
|
-
|
4
|
+
dirty_accessor :name,
|
5
5
|
:description,
|
6
6
|
:project_id,
|
7
7
|
:status_key,
|
@@ -21,73 +21,73 @@ module XPlanner
|
|
21
21
|
:last_update_time
|
22
22
|
|
23
23
|
|
24
|
-
|
24
|
+
def initialize( iter )
|
25
25
|
@dirty_bit = false
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
27
|
+
@id = iter[:id]
|
28
|
+
@name = iter[:name]
|
29
|
+
@description = iter[:description]
|
30
|
+
@project_id = iter[:project_id]
|
31
|
+
@status_key = iter[:status_key]
|
32
|
+
@actual_hours = iter[:actual_hours]
|
33
|
+
@added_hours = iter[:added_hours]
|
34
|
+
@adjusted_estimated_hours = iter[:adjusted_estimated_hours]
|
35
|
+
@days_worked = iter[:days_worked]
|
36
|
+
@estimated_hours = iter[:estimated_hours]
|
37
|
+
@overestimated_hours = iter[:overestimated_hours]
|
38
|
+
@postponed_hours = iter[:postponed_hours]
|
39
|
+
@remaining_hours = iter[:remaining_hours]
|
40
|
+
@start_date = Iteration.parse_date iter[:start_date]
|
41
|
+
@end_date = Iteration.parse_date iter[:end_date]
|
42
|
+
@last_update_time = Iteration.parse_date iter[:last_update_time]
|
43
|
+
@underestimated_hours = iter[:underestimated_hours]
|
44
|
+
end
|
45
45
|
|
46
46
|
def self.all( project_id )
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
rpc = :get_iterations
|
48
|
+
payload = { :projectId => project_id }
|
49
|
+
path = [:get_iterations_response, :get_iterations_return, :get_iterations_return]
|
50
|
+
fetch_from_remote( rpc, payload, path, Iteration )
|
51
51
|
end
|
52
52
|
|
53
53
|
def self.retrieve( iteration_id )
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
rpc = :get_iteration
|
55
|
+
payload = { :iterationId => iteration_id }
|
56
|
+
path = [:get_iteration_response, :get_iteration_return]
|
57
|
+
fetch_from_remote( rpc, payload, path, Iteration )
|
58
58
|
end
|
59
59
|
|
60
60
|
# needs status setting
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
61
|
+
def self.create( project_id, name, description, start_date, end_date )
|
62
|
+
rpc = :add_iteration
|
63
|
+
payload = {
|
64
|
+
:iterationData => {
|
65
|
+
:project_id => project_id,
|
66
|
+
:name => name,
|
67
|
+
:description => description,
|
68
|
+
:start_date => start_date,
|
69
|
+
:end_date => end_date,
|
70
70
|
:status_key => 'inactive'
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
71
|
+
}
|
72
|
+
}
|
73
|
+
path = [:add_iteration_response, :add_iteration_return]
|
74
|
+
fetch_from_remote( rpc, payload, path, Iteration )
|
75
75
|
end
|
76
76
|
|
77
77
|
def self.delete( id )
|
78
|
-
|
79
|
-
|
78
|
+
rpc = :remove_iteration
|
79
|
+
payload = { :id => id }
|
80
80
|
remote_delete( rpc, payload )
|
81
81
|
end
|
82
82
|
|
83
83
|
def delete
|
84
|
-
|
84
|
+
Iteration.delete( @id )
|
85
85
|
end
|
86
86
|
|
87
87
|
def save
|
88
|
-
|
89
|
-
|
90
|
-
|
88
|
+
rpc = :update
|
89
|
+
payload = { :iterationData => create_payload }
|
90
|
+
remote_update( rpc, payload )
|
91
91
|
end
|
92
92
|
|
93
93
|
end
|
data/lib/models/person.rb
CHANGED
data/lib/models/project.rb
CHANGED
@@ -10,45 +10,45 @@ module XPlanner
|
|
10
10
|
def initialize( p )
|
11
11
|
@dirty_bit = false
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
@id = p[:id]
|
14
|
+
@name = p[:name]
|
15
|
+
@last_update_time = Project.parse_date p[:last_update_time]
|
16
|
+
@description = p[:description]
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.all
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
rpc = :get_projects
|
21
|
+
path = [:get_projects_response, :get_projects_return, :get_projects_return]
|
22
|
+
fetch_from_remote( rpc, {}, path, Project )
|
23
23
|
end
|
24
24
|
|
25
25
|
def self.retrieve( id )
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
rpc = :get_project
|
27
|
+
payload = { :id => id }
|
28
|
+
path = [:get_project_response, :get_project_return]
|
29
29
|
fetch_from_remote( rpc, payload, path, Project )
|
30
30
|
end
|
31
31
|
|
32
32
|
def self.create( name, description )
|
33
|
-
|
34
|
-
|
33
|
+
rpc = :add_project
|
34
|
+
payload = { :projectData => { :name => name, :description => description } }
|
35
35
|
path = [:add_project_response, :add_project_return]
|
36
|
-
|
36
|
+
fetch_from_remote( rpc, payload, path, Project )
|
37
37
|
end
|
38
38
|
|
39
39
|
def self.delete( id )
|
40
|
-
|
41
|
-
|
40
|
+
rpc = :remove_project
|
41
|
+
payload = { :id => id }
|
42
42
|
remote_delete( rpc, payload )
|
43
43
|
end
|
44
44
|
|
45
45
|
def delete
|
46
|
-
|
46
|
+
Project.delete @id
|
47
47
|
end
|
48
48
|
|
49
49
|
def save
|
50
|
-
|
51
|
-
|
50
|
+
rpc = :update
|
51
|
+
payload = { :projectData => create_payload }
|
52
52
|
remote_update( rpc, payload )
|
53
53
|
end
|
54
54
|
|
data/lib/models/story.rb
CHANGED
@@ -43,10 +43,10 @@ module XPlanner
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def self.all( iteration_id )
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
rpc = :get_user_stories
|
47
|
+
payload = { :containerId => iteration_id }
|
48
|
+
path = [:get_user_stories_response, :get_user_stories_return, :get_user_stories_return]
|
49
|
+
fetch_from_remote( rpc, payload, path, Story )
|
50
50
|
end
|
51
51
|
|
52
52
|
def self.retrieve( story_id )
|
data/lib/models/task.rb
CHANGED
@@ -41,10 +41,10 @@ module XPlanner
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def self.all( story_id )
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
rpc = :get_tasks
|
45
|
+
payload = { :containerId => story_id }
|
46
|
+
path = [:get_taskss_response, :get_tasks_return, :get_tasks_return]
|
47
|
+
fetch_from_remote( rpc, payload, path, Task )
|
48
48
|
end
|
49
49
|
|
50
50
|
def self.retrieve( task_id )
|
data/lib/xplanner.rb
CHANGED
@@ -2,33 +2,33 @@ project_root = File.expand_path('../', __FILE__)
|
|
2
2
|
project_dirs = ['config', 'helpers', 'models']
|
3
3
|
|
4
4
|
project_dirs.each do |d|
|
5
|
-
|
5
|
+
Dir.glob(project_root + '/' + d + '/*.rb') {|file| require file}
|
6
6
|
end
|
7
7
|
|
8
8
|
require 'uri'
|
9
9
|
|
10
10
|
module XPlanner
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
def self.setup( config = {} )
|
12
|
+
url = config[:url] ||= @@default_config[:xplanner_url]
|
13
|
+
user = config[:user] ||= @@default_config[:username]
|
14
|
+
pass = config[:pass] ||= @@default_config[:password]
|
15
15
|
|
16
|
-
|
16
|
+
wsdl = URI::join(url, 'soap/XPlanner?wsdl').to_s
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
client_config = {
|
19
|
+
:wsdl => wsdl,
|
20
|
+
:username => user,
|
21
|
+
:password => pass
|
22
|
+
}
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
soap_client = SoapClient.new( client_config )
|
25
|
+
ModelHelper.soap_client= soap_client
|
26
|
+
@@connection_successful = soap_client.confirm_connection
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
ModelHelper.base_url= url
|
29
|
+
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
def self.connection?
|
32
|
+
@@connection_successful
|
33
|
+
end
|
34
34
|
end
|
data/test/tests.rb
CHANGED
@@ -46,11 +46,7 @@ describe 'XPlanner+ SOAP interface' do
|
|
46
46
|
describe 'Model: Iteration' do
|
47
47
|
before(:all) do
|
48
48
|
@myProject = XPlanner::Project.create( 'delete me', 'delete me: test project' )
|
49
|
-
@myIteration = XPlanner::Iteration.create(
|
50
|
-
'delete me',
|
51
|
-
'delete me: test iteration',
|
52
|
-
DateTime.now,
|
53
|
-
DateTime.now+5 )
|
49
|
+
@myIteration = XPlanner::Iteration.create( @myProject.id, 'delete me', 'delete me: test iteration', DateTime.now, DateTime.now+5 )
|
54
50
|
end
|
55
51
|
|
56
52
|
after(:all) do
|
@@ -90,11 +86,7 @@ describe 'XPlanner+ SOAP interface' do
|
|
90
86
|
describe 'Model: User Story' do
|
91
87
|
before(:all) do
|
92
88
|
@myProject = XPlanner::Project.create( 'delete me', 'delete me: test project' )
|
93
|
-
@myIteration = XPlanner::Iteration.create(
|
94
|
-
'delete me',
|
95
|
-
'delete me: test iteration',
|
96
|
-
DateTime.now,
|
97
|
-
DateTime.now+5 )
|
89
|
+
@myIteration = XPlanner::Iteration.create( @myProject.id, 'delete me', 'delete me: test iteration', DateTime.now, DateTime.now+5 )
|
98
90
|
@myStory = XPlanner::Story.create( @myIteration.id, 'delete me', 'delete me: test story')
|
99
91
|
end
|
100
92
|
|
@@ -134,11 +126,7 @@ describe 'XPlanner+ SOAP interface' do
|
|
134
126
|
describe 'Model: Task' do
|
135
127
|
before(:all) do
|
136
128
|
@myProject = XPlanner::Project.create( 'delete me', 'delete me: test project' )
|
137
|
-
@myIteration = XPlanner::Iteration.create(
|
138
|
-
'delete me',
|
139
|
-
'delete me: test iteration',
|
140
|
-
DateTime.now,
|
141
|
-
DateTime.now+5 )
|
129
|
+
@myIteration = XPlanner::Iteration.create( @myProject.id, 'delete me', 'delete me: test iteration', DateTime.now, DateTime.now+5 )
|
142
130
|
@myStory = XPlanner::Story.create( @myIteration.id, 'delete me', 'delete me: test story')
|
143
131
|
@myTask = XPlanner::Task.create( @myStory.id, 'delete me', 'delete me: test task')
|
144
132
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xplanner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: savon
|