mingle-macro-development-toolkit 1.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/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
|