mingle-macro-development-toolkit 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +21 -0
- data/README.rdoc +66 -0
- data/Rakefile +36 -0
- data/bin/new_mingle_macro +107 -0
- data/example/Rakefile +3 -0
- data/example/deploy.rake +10 -0
- data/example/init.rb +10 -0
- data/example/integration_test.rb +13 -0
- data/example/integration_test_helper.rb +19 -0
- data/example/macro.rb +18 -0
- data/example/unit_test.rb +13 -0
- data/example/unit_test_helper.rb +12 -0
- data/getting_started.txt +253 -0
- data/lib/macro_development_toolkit.rb +22 -0
- data/lib/macro_development_toolkit/mingle/card_type.rb +41 -0
- data/lib/macro_development_toolkit/mingle/card_type_property_definition.rb +26 -0
- data/lib/macro_development_toolkit/mingle/project.rb +80 -0
- data/lib/macro_development_toolkit/mingle/project_variable.rb +23 -0
- data/lib/macro_development_toolkit/mingle/property_definition.rb +94 -0
- data/lib/macro_development_toolkit/mingle/property_value.rb +68 -0
- data/lib/macro_development_toolkit/mingle/user.rb +29 -0
- data/lib/macro_development_toolkit/mingle_model_loader.rb +169 -0
- data/tasks/test.rake +15 -0
- data/test/fixtures/sample/card_types.yml +29 -0
- data/test/fixtures/sample/card_types_property_definitions.yml +81 -0
- data/test/fixtures/sample/project_variables.yml +5 -0
- data/test/fixtures/sample/projects.yml +4 -0
- data/test/fixtures/sample/property_definitions.yml +41 -0
- data/test/fixtures/sample/users.yml +16 -0
- data/test/integration/integration_test_helper.rb +20 -0
- data/test/integration/rest_loader.rb +302 -0
- data/test/integration/rest_loader_test.rb +86 -0
- data/test/unit/fixture_loader.rb +180 -0
- data/test/unit/fixture_loader_test.rb +46 -0
- data/test/unit/unit_test_helper.rb +13 -0
- metadata +110 -0
data/tasks/test.rake
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
namespace :test do |ns|
|
2
|
+
|
3
|
+
Rake::TestTask.new(:units) do |t|
|
4
|
+
t.libs << "test/unit"
|
5
|
+
t.pattern = 'test/unit/*_test.rb'
|
6
|
+
t.verbose = true
|
7
|
+
end
|
8
|
+
|
9
|
+
Rake::TestTask.new(:integration) do |t|
|
10
|
+
t.libs << "test/integration"
|
11
|
+
t.pattern = 'test/integration/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
---
|
2
|
+
- name: Story
|
3
|
+
position: 3
|
4
|
+
color: 'ff3300'
|
5
|
+
id: 339
|
6
|
+
- name: Defect
|
7
|
+
position: 5
|
8
|
+
color: '000599'
|
9
|
+
id: 340
|
10
|
+
- name: Task
|
11
|
+
position: 4
|
12
|
+
color: 'ffe066'
|
13
|
+
id: 341
|
14
|
+
- name: Sprint
|
15
|
+
position: 2
|
16
|
+
color: 'ff0db6'
|
17
|
+
id: 342
|
18
|
+
- name: Release
|
19
|
+
position: 1
|
20
|
+
color: '990024'
|
21
|
+
id: 343
|
22
|
+
- name: Feature
|
23
|
+
position: 6
|
24
|
+
color: '296600'
|
25
|
+
id: 344
|
26
|
+
- name: Epic Story
|
27
|
+
position: 7
|
28
|
+
color: '00d91d'
|
29
|
+
id: 345
|
@@ -0,0 +1,81 @@
|
|
1
|
+
---
|
2
|
+
- card_type_id: 342
|
3
|
+
position: 3
|
4
|
+
property_definition_id: 1145
|
5
|
+
id: 2015
|
6
|
+
- card_type_id: 341
|
7
|
+
position: 1
|
8
|
+
property_definition_id: 1145
|
9
|
+
id: 2016
|
10
|
+
- card_type_id: 341
|
11
|
+
position: 2
|
12
|
+
property_definition_id: 1146
|
13
|
+
id: 2017
|
14
|
+
- card_type_id: 341
|
15
|
+
position: 3
|
16
|
+
property_definition_id: 1144
|
17
|
+
id: 2018
|
18
|
+
- card_type_id: 341
|
19
|
+
position: 4
|
20
|
+
property_definition_id: 1141
|
21
|
+
id: 2019
|
22
|
+
- card_type_id: 341
|
23
|
+
position: 6
|
24
|
+
property_definition_id: 1149
|
25
|
+
id: 2021
|
26
|
+
- card_type_id: 341
|
27
|
+
position: 7
|
28
|
+
property_definition_id: 1147
|
29
|
+
id: 2022
|
30
|
+
- card_type_id: 340
|
31
|
+
position: 1
|
32
|
+
property_definition_id: 1145
|
33
|
+
id: 2023
|
34
|
+
- card_type_id: 340
|
35
|
+
position: 2
|
36
|
+
property_definition_id: 1146
|
37
|
+
id: 2024
|
38
|
+
- card_type_id: 340
|
39
|
+
position: 3
|
40
|
+
property_definition_id: 1144
|
41
|
+
id: 2025
|
42
|
+
- card_type_id: 340
|
43
|
+
position: 5
|
44
|
+
property_definition_id: 1141
|
45
|
+
id: 2027
|
46
|
+
- card_type_id: 340
|
47
|
+
position: 7
|
48
|
+
property_definition_id: 1149
|
49
|
+
id: 2029
|
50
|
+
- card_type_id: 339
|
51
|
+
position: 1
|
52
|
+
property_definition_id: 1148
|
53
|
+
id: 2033
|
54
|
+
- card_type_id: 339
|
55
|
+
position: 4
|
56
|
+
property_definition_id: 1145
|
57
|
+
id: 2036
|
58
|
+
- card_type_id: 339
|
59
|
+
position: 5
|
60
|
+
property_definition_id: 1146
|
61
|
+
id: 2037
|
62
|
+
- card_type_id: 339
|
63
|
+
position: 6
|
64
|
+
property_definition_id: 1141
|
65
|
+
id: 2038
|
66
|
+
- card_type_id: 339
|
67
|
+
position: 7
|
68
|
+
property_definition_id: 1140
|
69
|
+
id: 2039
|
70
|
+
- card_type_id: 339
|
71
|
+
position: 8
|
72
|
+
property_definition_id: 1149
|
73
|
+
id: 2040
|
74
|
+
- card_type_id: 339
|
75
|
+
position: 12
|
76
|
+
property_definition_id: 1142
|
77
|
+
id: 2044
|
78
|
+
- card_type_id: 339
|
79
|
+
position: 13
|
80
|
+
property_definition_id: 1143
|
81
|
+
id: 2045
|
@@ -0,0 +1,41 @@
|
|
1
|
+
---
|
2
|
+
- id: 1140
|
3
|
+
name: Status
|
4
|
+
description: ""
|
5
|
+
type_description: Managed text list
|
6
|
+
- id: 1141
|
7
|
+
name: Priority
|
8
|
+
description: ""
|
9
|
+
type_description: Managed text list
|
10
|
+
- id: 1142
|
11
|
+
name: Development Started On
|
12
|
+
description: ""
|
13
|
+
type_description: Managed text list
|
14
|
+
- id: 1143
|
15
|
+
description: ""
|
16
|
+
name: Development Completed On
|
17
|
+
type_description: Date
|
18
|
+
- id: 1144
|
19
|
+
description: ""
|
20
|
+
name: Added On
|
21
|
+
type_description: Date
|
22
|
+
- id: 1145
|
23
|
+
description: ""
|
24
|
+
name: Estimate - Planning
|
25
|
+
type_description: Formula
|
26
|
+
- id: 1146
|
27
|
+
description: ""
|
28
|
+
name: Risk
|
29
|
+
type_description: Managed text list
|
30
|
+
- id: 1147
|
31
|
+
description: ""
|
32
|
+
name: Task Estimate
|
33
|
+
type_description: Managed text list
|
34
|
+
- id: 1148
|
35
|
+
description:
|
36
|
+
name: Estimate - Task
|
37
|
+
type_description: Managed text list
|
38
|
+
- id: 1149
|
39
|
+
description: Individual assigned to a work item
|
40
|
+
name: Owner
|
41
|
+
type_description: User
|
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
- project_id: 78
|
3
|
+
login: groucho
|
4
|
+
name: Groucho Marx
|
5
|
+
version_control_user_name: gmarx
|
6
|
+
email: gmarx@example.com
|
7
|
+
- project_id: 78
|
8
|
+
login: harpo
|
9
|
+
name: Harpo Marx
|
10
|
+
version_control_user_name:
|
11
|
+
email: kmarx@example.com
|
12
|
+
- project_id: 78
|
13
|
+
login: karl
|
14
|
+
name: Karl Marx
|
15
|
+
version_control_user_name: kmarx
|
16
|
+
email: kmarx@example.com
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#Copyright 2008 ThoughtWorks, Inc. All rights reserved.
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'macro_development_toolkit'
|
5
|
+
require File.dirname(__FILE__) + '/rest_loader'
|
6
|
+
|
7
|
+
class Test::Unit::TestCase
|
8
|
+
|
9
|
+
def project(name)
|
10
|
+
@project ||= RESTfulLoaders::ProjectLoader.new(name, nil, self).project
|
11
|
+
end
|
12
|
+
|
13
|
+
def errors
|
14
|
+
@errors ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
def alert(message)
|
18
|
+
errors << message
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
#Copyright 2008 ThoughtWorks, Inc. All rights reserved.
|
2
|
+
|
3
|
+
module RESTfulLoaders
|
4
|
+
class RemoteError < StandardError
|
5
|
+
def self.parse(response_body)
|
6
|
+
Hash.from_xml(response_body)['hash'].delete("message")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Base
|
11
|
+
def initialize(project_name)
|
12
|
+
@xml_model = if (String === project_name)
|
13
|
+
@project_name = project_name
|
14
|
+
OpenStruct.new(Hash.from_xml(get(project_name)))
|
15
|
+
else
|
16
|
+
project_name
|
17
|
+
end
|
18
|
+
raise "No such project resource available! #{@project_name}" unless (@xml_model && @xml_model.to_s != '')
|
19
|
+
end
|
20
|
+
|
21
|
+
def get(url)
|
22
|
+
url = URI.parse(url) unless url.respond_to?(:path)
|
23
|
+
get_request = Net::HTTP::Get.new(url.request_uri)
|
24
|
+
get_request.basic_auth(url.user, url.password)
|
25
|
+
response = Net::HTTP.start(url.host, url.port) { |http| http.request(get_request) }
|
26
|
+
if response.code.to_s != "200"
|
27
|
+
raise RemoteError, RemoteError.parse(response.body)
|
28
|
+
end
|
29
|
+
response.body
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_all_card_types_from_xml
|
33
|
+
extract_array_of 'card_types', :from => @xml_model.project
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_all_property_definitions_from_xml
|
37
|
+
extract_array_of 'property_definitions', :from => @xml_model.project
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_all_card_types_property_definitions_from_xml
|
41
|
+
load_all_card_types_from_xml.collect do |card_type_xml|
|
42
|
+
card_types_property_definitions = OpenStruct.new(card_type_xml).card_types_property_definitions
|
43
|
+
[card_types_property_definitions['card_type_property_definition']].flatten if card_types_property_definitions
|
44
|
+
end.compact.flatten
|
45
|
+
end
|
46
|
+
|
47
|
+
def card_types_property_definitions_by_card_type_id_loader(card_type_id)
|
48
|
+
LoadCardTypesPropertyDefinitionsByCardTypeId.new(card_type_id, @xml_model)
|
49
|
+
end
|
50
|
+
|
51
|
+
def card_types_property_definitions_by_property_definition_id_loader(property_definition_id)
|
52
|
+
LoadCardTypesPropertyDefinitionsByPropertyDefinitionId.new(property_definition_id, @xml_model)
|
53
|
+
end
|
54
|
+
|
55
|
+
def values_by_property_definition_id_loader(property_definition_id)
|
56
|
+
LoadValuesByPropertyDefinitionId.new(property_definition_id, @xml_model)
|
57
|
+
end
|
58
|
+
|
59
|
+
def card_type_by_id_loader(card_type_id)
|
60
|
+
LoadCardTypeById.new(card_type_id, @xml_model)
|
61
|
+
end
|
62
|
+
|
63
|
+
def property_definition_by_id_loader(property_definition_id)
|
64
|
+
LoadPropertyDefinitionById.new(property_definition_id, @xml_model)
|
65
|
+
end
|
66
|
+
|
67
|
+
def card_types_by_project_id_loader
|
68
|
+
LoadCardTypesByProjectId.new(@xml_model)
|
69
|
+
end
|
70
|
+
|
71
|
+
def property_definitions_by_project_id_loader
|
72
|
+
LoadPropertyDefinitionsByProjectId.new(@xml_model)
|
73
|
+
end
|
74
|
+
|
75
|
+
def team_by_project_id_loader
|
76
|
+
LoadTeamByProjectId.new(@xml_model)
|
77
|
+
end
|
78
|
+
|
79
|
+
def project_variables_by_project_id_loader
|
80
|
+
LoadProjectVariablesByProjectId.new(@xml_model)
|
81
|
+
end
|
82
|
+
|
83
|
+
def extract_array_of(container_key, options)
|
84
|
+
contents_hash = options[:from]
|
85
|
+
container = contents_hash[container_key]
|
86
|
+
container ? [container[container_key.singularize]].flatten : []
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class ProjectLoader < Base
|
91
|
+
class MqlExecutionDelegate < SimpleDelegator
|
92
|
+
def initialize(delegate, mql_executor)
|
93
|
+
@mql_executor = mql_executor
|
94
|
+
__setobj__(delegate)
|
95
|
+
end
|
96
|
+
|
97
|
+
def execute_mql(mql)
|
98
|
+
begin
|
99
|
+
@mql_executor.execute_mql(mql, self)
|
100
|
+
rescue => e
|
101
|
+
__getobj__.send(:add_alert, e.message)
|
102
|
+
[]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def format_number_with_project_precision(number)
|
107
|
+
begin
|
108
|
+
@mql_executor.format_number_with_project_precision(number, self)
|
109
|
+
rescue => e
|
110
|
+
__getobj__.send(:add_alert, e.message)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def format_date_with_project_date_format(date)
|
115
|
+
begin
|
116
|
+
@mql_executor.format_date_with_project_date_format(date, self)
|
117
|
+
rescue => e
|
118
|
+
__getobj__.send(:add_alert, e.message)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def initialize(project_name, macro_context=nil, alert_receiver=nil)
|
124
|
+
super(project_name)
|
125
|
+
@macro_context = macro_context
|
126
|
+
@alert_receiver = alert_receiver
|
127
|
+
end
|
128
|
+
|
129
|
+
def load_project_from_xml
|
130
|
+
@xml_model.project
|
131
|
+
end
|
132
|
+
|
133
|
+
def project
|
134
|
+
project = MqlExecutionDelegate.new(Mingle::Project.new(OpenStruct.new(load_project_from_xml), :alert_receiver => @alert_receiver), self)
|
135
|
+
project.card_types_loader = card_types_by_project_id_loader
|
136
|
+
project.property_definitions_loader = property_definitions_by_project_id_loader
|
137
|
+
project.team_loader = team_by_project_id_loader
|
138
|
+
project.project_variables_loader = project_variables_by_project_id_loader
|
139
|
+
project
|
140
|
+
end
|
141
|
+
|
142
|
+
def execute_mql(mql, project)
|
143
|
+
from_xml_data(
|
144
|
+
Hash.from_xml(
|
145
|
+
get(
|
146
|
+
build_request_url(:path => "/projects/#{project.identifier}/cards/execute_mql.xml", :query => "mql=#{mql}"))))
|
147
|
+
end
|
148
|
+
|
149
|
+
def format_number_with_project_precision(number, project)
|
150
|
+
from_xml_data(
|
151
|
+
Hash.from_xml(
|
152
|
+
get(
|
153
|
+
build_request_url(:path => "/projects/#{project.identifier}/cards/format_number_to_project_precision.xml", :query => "number=#{number}"))))
|
154
|
+
end
|
155
|
+
|
156
|
+
def format_date_with_project_date_format(date, project)
|
157
|
+
from_xml_data(
|
158
|
+
Hash.from_xml(
|
159
|
+
get(
|
160
|
+
build_request_url(:path => "/projects/#{project.identifier}/cards/format_string_to_date_format.xml", :query => "date=#{date}"))))
|
161
|
+
end
|
162
|
+
|
163
|
+
def build_request_url(params)
|
164
|
+
url = URI.parse(@project_name)
|
165
|
+
request_class = if url.scheme == 'http'
|
166
|
+
URI::HTTP
|
167
|
+
elsif
|
168
|
+
URI::HTTPS
|
169
|
+
else
|
170
|
+
raise "Unknown protocol used to access project resource #{@project_name}. Supported protocols are HTTP & HTTPS"
|
171
|
+
end
|
172
|
+
default_params = {:userinfo => "#{url.user}:#{url.password}", :host => url.host, :port => url.port.to_s}
|
173
|
+
request_class.build2(default_params.merge(params))
|
174
|
+
end
|
175
|
+
|
176
|
+
def from_xml_data(data)
|
177
|
+
if data.is_a?(Hash) && data.keys.size == 1
|
178
|
+
from_xml_data(data.values.first)
|
179
|
+
else
|
180
|
+
data
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
class LoadCardTypesByProjectId < Base
|
187
|
+
def load
|
188
|
+
load_all_card_types_from_xml.collect do |ct|
|
189
|
+
card_type = Mingle::CardType.new(OpenStruct.new(ct))
|
190
|
+
card_type.card_types_property_definitions_loader = card_types_property_definitions_by_card_type_id_loader(ct['id'])
|
191
|
+
card_type
|
192
|
+
end.sort_by(&:position)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
class LoadPropertyDefinitionsByProjectId < Base
|
197
|
+
def load
|
198
|
+
load_all_property_definitions_from_xml.collect do |pd|
|
199
|
+
property_definition = Mingle::PropertyDefinition.new(OpenStruct.new(pd))
|
200
|
+
property_definition.card_types_property_definitions_loader = card_types_property_definitions_by_property_definition_id_loader(pd['id'])
|
201
|
+
property_definition.values_loader = values_by_property_definition_id_loader(pd['id'])
|
202
|
+
property_definition
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
class LoadCardTypesPropertyDefinitionsByCardTypeId < Base
|
208
|
+
def initialize(card_type_id, fixture_file_name)
|
209
|
+
super(fixture_file_name)
|
210
|
+
@card_type_id = card_type_id
|
211
|
+
end
|
212
|
+
|
213
|
+
def load
|
214
|
+
load_all_card_types_property_definitions_from_xml.collect do |ctpd|
|
215
|
+
next unless ctpd['card_type_id'] == @card_type_id
|
216
|
+
|
217
|
+
card_type_property_definition = Mingle::CardTypePropertyDefinition.new(OpenStruct.new(ctpd))
|
218
|
+
card_type_property_definition.card_type_loader = card_type_by_id_loader(ctpd['card_type_id'])
|
219
|
+
card_type_property_definition.property_definition_loader = property_definition_by_id_loader(ctpd['property_definition_id'])
|
220
|
+
card_type_property_definition
|
221
|
+
end.compact.sort_by(&:position).compact
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
class LoadCardTypesPropertyDefinitionsByPropertyDefinitionId < Base
|
227
|
+
def initialize(property_definition_id, fixture_file_name)
|
228
|
+
super(fixture_file_name)
|
229
|
+
@property_definition_id = property_definition_id
|
230
|
+
end
|
231
|
+
|
232
|
+
def load
|
233
|
+
load_all_card_types_property_definitions_from_xml.collect do |ctpd|
|
234
|
+
next unless ctpd['property_definition_id'] == @property_definition_id
|
235
|
+
|
236
|
+
card_type_property_definition = Mingle::CardTypePropertyDefinition.new(OpenStruct.new(ctpd))
|
237
|
+
card_type_property_definition.card_type_loader = card_type_by_id_loader(ctpd['card_type_id'])
|
238
|
+
card_type_property_definition.property_definition_loader = property_definition_by_id_loader(ctpd['property_definition_id'])
|
239
|
+
card_type_property_definition
|
240
|
+
end.compact.sort_by(&:position).compact
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
class LoadCardTypeById < Base
|
246
|
+
def initialize(card_type_id, fixture_file_name)
|
247
|
+
super(fixture_file_name)
|
248
|
+
@card_type_id = card_type_id
|
249
|
+
end
|
250
|
+
|
251
|
+
def load
|
252
|
+
yaml = load_all_card_types_from_xml.detect { |ct| ct['id'] == @card_type_id }
|
253
|
+
ct = Mingle::CardType.new(OpenStruct.new(yaml))
|
254
|
+
ct.card_types_property_definitions_loader = card_types_property_definitions_by_card_type_id_loader(yaml['id'])
|
255
|
+
ct
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
class LoadPropertyDefinitionById < Base
|
260
|
+
def initialize(property_definition_id, fixture_file_name)
|
261
|
+
super(fixture_file_name)
|
262
|
+
@property_definition_id = property_definition_id
|
263
|
+
end
|
264
|
+
|
265
|
+
def load
|
266
|
+
yaml = load_all_property_definitions_from_xml.detect { |pd| pd['id'] == @property_definition_id }
|
267
|
+
pd = Mingle::PropertyDefinition.new(OpenStruct.new(yaml))
|
268
|
+
pd.card_types_property_definitions_loader = card_types_property_definitions_by_property_definition_id_loader(yaml['id'])
|
269
|
+
pd.values_loader = values_by_property_definition_id_loader(yaml['id'])
|
270
|
+
pd
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
class LoadValuesByPropertyDefinitionId < Base
|
275
|
+
def initialize(property_definition_id, fixture_file_name)
|
276
|
+
super(fixture_file_name)
|
277
|
+
@property_definition_id = property_definition_id
|
278
|
+
end
|
279
|
+
|
280
|
+
def load
|
281
|
+
property_definition = load_all_property_definitions_from_xml.detect { |property_definition_xml| property_definition_xml['id'] == @property_definition_id }
|
282
|
+
return unless property_definition
|
283
|
+
extract_array_of('values', :from => property_definition).collect do |property_value_xml|
|
284
|
+
property_value = Mingle::PropertyValue.new(OpenStruct.new(property_value_xml))
|
285
|
+
property_value.property_definition_loader = property_definition_by_id_loader(property_value_xml['property_definition_id'])
|
286
|
+
property_value
|
287
|
+
end.compact
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
class LoadTeamByProjectId < Base
|
292
|
+
def load
|
293
|
+
extract_array_of('users', :from => @xml_model.project).collect { |user| Mingle::User.new(OpenStruct.new(user)) }
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
class LoadProjectVariablesByProjectId < Base
|
298
|
+
def load
|
299
|
+
extract_array_of('project_variables', :from => @xml_model.project).collect { |pv| Mingle::ProjectVariable.new(OpenStruct.new(pv)) }
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|