jirarest2 0.0.12 → 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
data.tar.gz.sig CHANGED
Binary file
@@ -1,3 +1,21 @@
1
+ === 0.0.13 / 2012-09-18
2
+
3
+ * 1 major enhancement:
4
+
5
+ * Field types are now somewhat mirrored from the server - This should enable working with different fieldtypes without the "expected an object" error. Hopefu
6
+
7
+ * 2 minor enhancements:
8
+
9
+ * Added feature to see if we are really authenticated. Had to change credentials around a bit for that.
10
+ * Added fieldtypes for the known fields with support for allowed values
11
+
12
+ * 4 unknowns:
13
+
14
+ * Added reading of values for fields from the json results
15
+ * Fixed a bug with authentication but found a new bug that is not fixed yet ( #25 )
16
+ * Some minor work on tests and warnings
17
+ * Some work on documentation
18
+
1
19
  === 0.0.12 / 2012-08-07
2
20
 
3
21
  * 1 major enhancement:
@@ -14,7 +14,9 @@ lib/jirarest2/connect.rb
14
14
  lib/jirarest2/cookie_credentials.rb
15
15
  lib/jirarest2/credentials.rb
16
16
  lib/jirarest2/exceptions.rb
17
+ lib/jirarest2/field.rb
17
18
  lib/jirarest2/issue.rb
19
+ lib/jirarest2/issuetype.rb
18
20
  lib/jirarest2/madbitconfig.rb
19
21
  lib/jirarest2/newissue.rb
20
22
  lib/jirarest2/password_credentials.rb
@@ -26,6 +28,7 @@ lib/jirarest2/services/issuelinktype.rb
26
28
  lib/jirarest2/services/watcher.rb
27
29
  lib/jirarest2bin.rb
28
30
  test/data/cookiejar
31
+ test/data/createmeta
29
32
  test/data/get-comments.txt
30
33
  test/data/issuespec.txt
31
34
  test/data/test.config.data
@@ -37,9 +40,12 @@ test/test_comment.rb
37
40
  test/test_connect.rb
38
41
  test/test_cookie_credentials.rb
39
42
  test/test_credentials.rb
43
+ test/test_fieldcreatemeta.rb
44
+ test/test_fields.rb
40
45
  test/test_issue.rb
41
46
  test/test_issuelink.rb
42
47
  test/test_issuelinktype.rb
48
+ test/test_issuetype.rb
43
49
  test/test_madbitconfig.rb
44
50
  test/test_newissue.rb
45
51
  test/test_password_credentials.rb
@@ -130,7 +130,7 @@ end # class ParseOptions
130
130
 
131
131
 
132
132
  def no_issue(type,issue)
133
- puts "The #{type}type you entered (\"#{issue}\") does no exist."
133
+ puts "The #{type}type you entered (\"#{issue}\") does not exist."
134
134
  puts "Maybe you entered the wrong type or made a typo? (Case is relevant!)"
135
135
  exit 1
136
136
  end
@@ -148,9 +148,9 @@ def open_issue
148
148
  end
149
149
  @connection,issue = Jirarest2Bin::command(@scriptopts,@connection,:issue,@issueopts.project,@issueopts.issue)
150
150
  rescue Jirarest2::WrongProjectException => e
151
- no_issue("project",e)
151
+ no_issue("project",@issueopts.project)
152
152
  rescue Jirarest2::WrongIssuetypeException => e
153
- no_issue("project",e)
153
+ no_issue("issue",@issueopts.issue)
154
154
  end
155
155
  return issue
156
156
  end
@@ -172,6 +172,7 @@ def show_scheme
172
172
  end
173
173
 
174
174
  # Split the content from the command line parameter "-c"
175
+ # TODO coupling is way to strong here. Fucks up.
175
176
  def split_content(issue)
176
177
  fields = Hash.new
177
178
  @issueopts.content.each { |value|
@@ -224,8 +225,7 @@ def prepare_new_ticket
224
225
  rescue Jirarest2::WrongFieldnameException => e
225
226
  no_issue("field",e)
226
227
  rescue Jirarest2::ValueNotAllowedException => e
227
- puts "Value #{e} not allowed for field #{e.fieldname}."
228
- puts "Please use one of: \"" + e.allowed.join("\", \"") + "\""
228
+ puts "Problem with #{e.fieldname}: Value #{e}"
229
229
  valueNotAllowedRaised = true
230
230
  end
231
231
 
@@ -17,7 +17,9 @@
17
17
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
18
  #
19
19
 
20
- VERSION = "0.0.12"
20
+ # Version Parameter all CAPS
21
+ VERSION = "0.0.13"
22
+ # Version Parameter
21
23
  Version = VERSION
22
24
 
23
25
  require_relative "jirarest2/connect"
@@ -40,6 +40,14 @@ class Connect
40
40
  # @param [String, "Get", "Post", "Delete", "Put"] operation HTTP method: GET, POST, DELETE, PUT
41
41
  # @param [String] uritail The last part of the REST URI
42
42
  # @param [Hash] data Data to be sent.
43
+ # @raise [Jirarest2::BadRequestError] Raised if the servers returns statuscode 400 (bad request)
44
+ # @raise [Jirarest2::PasswordAuthenticationError] Raised if authentication failed (status code 401) and the credentials were username/password based
45
+ # @raise [Jirarest2::CookieAuthenticationError] Raised if authentication failed (status code 401) and the credentials were cookie based
46
+ # @raise [Jirarest2::AuthenticationError] Raised if authentication failed (status code 401) and the credentials were neither cookie or username/password based
47
+ # @raise [Jirarest2::AuthentificationCaptchaError] Raised if the server sends a forbidden status (status code 403) and an login url which means the user needs to answer a captcha
48
+ # @raise [Jirarest2::ForbiddenError] Raised if the server sends a forbidden status (status code 403) and no login url
49
+ # @raise [Jirarest2::NotFoundError] Raised if the server returns statuscode 404 (Not found)
50
+ # @raise [Jirarest2::MethodNotAllowedError] Raised if the server returns statuscode 405 (Method not allowed)
43
51
  # @return [Jirarest2::Result]
44
52
  def execute(operation,uritail,data)
45
53
  uri = nil
@@ -112,7 +120,7 @@ class Connect
112
120
  # @return [Boolean]
113
121
  def check_uri
114
122
  begin
115
- ret = (execute("Get","dashboard","").code == "200")
123
+ return execute("Get","dashboard","").code == "200"
116
124
  # TODO is the 404 really possible?
117
125
  rescue Jirarest2::NotFoundError
118
126
  return false
@@ -141,7 +149,8 @@ class Connect
141
149
 
142
150
 
143
151
  # try to fix the connecturl of this instance
144
- # @return [String,Jirarest2::CouldNotHealURIError] Fixed URL or Exception
152
+ # @raise [Jirarest2::CouldNotHealURIError] Raised if the url can not be healed automatically
153
+ # @return [String] Fixed URL
145
154
  def heal_uri!
146
155
  if ! check_uri then
147
156
  @credentials.connecturl = heal_uri(@credentials.connecturl)
@@ -157,7 +166,7 @@ class Connect
157
166
  # @return [Boolean] true if the authentication seems to be valid (actually it checks if there is a session)
158
167
  def verify_auth
159
168
  ret = execute("Get","auth/latest/session","")
160
- store_cookiejar if @credentials.instance_of?(CookieCredentials) && @credentials.autosave
169
+ @credentials.store_cookiejar if @credentials.instance_of?(CookieCredentials) && @credentials.autosave
161
170
  return ret.code == "200"
162
171
  end
163
172
 
@@ -25,9 +25,12 @@ require "pstore"
25
25
  class CookieCredentials < Credentials
26
26
 
27
27
  # Location of the file the cookie is persited on a harddrive. Default is "~/.jirarest2.cookie
28
+ # @return [String] Location of the cookie on the harddrive
28
29
  attr_accessor :cookiestore
30
+ attr_reader :autosave
29
31
 
30
- # @param [String] url URL to JIRA(tm) instance
32
+ # @param [String] connecturl URL to JIRA(tm) instance
33
+ # @param [String] username Username to connect to the server
31
34
  # @param [Boolean] autosave Save the cookie on the harddisk whenever something happens?
32
35
  def initialize(connecturl, username, autosave = false )
33
36
  super(connecturl,username)
@@ -29,6 +29,8 @@ class Credentials
29
29
  attr_reader :baseurl
30
30
 
31
31
  # @param [String] url URL to JIRA(tm) instance
32
+ # @param [String] username Username of the user who connects to jira
33
+ # @raise [Jirarest2::NotAnURLError] Raised if the given URL is not recogniced as an url
32
34
  def initialize(url,username)
33
35
  uri = URI(url)
34
36
  if uri.instance_of?(URI::HTTP) || uri.instance_of?(URI::HTTPS) then
@@ -40,8 +42,9 @@ class Credentials
40
42
  end
41
43
  end
42
44
 
43
- # Throws an Jirarest2::NotAnURLError if the given String is not an URI.
45
+ # Set the connect url a later date in the life of the instance
44
46
  # @param [String] url
47
+ # @raise [Jirarest2::NotAnURLError] Raised if the given URL is not recogniced as an url
45
48
  def connecturl=(url)
46
49
  uri = URI(url)
47
50
  if uri.instance_of?(URI::HTTP) || uri.instance_of?(URI::HTTPS) then
@@ -67,4 +67,9 @@ module Jirarest2
67
67
  # A field that is defined as "required" has not been given a value
68
68
  class RequiredFieldNotSetException < ArgumentError; end
69
69
 
70
+ #Could not determine the right fieldtype as this seems to be new
71
+ class CouldNotDetermineFieldtypeException < ArgumentError; end
72
+ #Hashfiels always have to have a key assigned to them
73
+ class HashKeyMissingException < ArgumentError; end
74
+
70
75
  end
@@ -0,0 +1,645 @@
1
+ # Copyright (C) 2012 Cyril Bitterich
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+ #
16
+
17
+ require "jirarest2/exceptions"
18
+ # All the fieldtypes in their own namespace (hopefully easier in the documentation)
19
+ # @todo operations "add","set","remove" are ignored right now.
20
+ module Jirarest2Field
21
+ # Superclass for all fields
22
+ class Field
23
+ # Is this field mandatory?
24
+ # @return [Boolean] Default: false. True if your field has to be set for the issuetype
25
+ attr_reader :required
26
+ # Is this field readonly?
27
+ # @return [Boolean] Default: false
28
+ attr_reader :readonly
29
+ # The field id in JIRA(tm)
30
+ # @return [String] The id in your JIRA(tm) instance
31
+ attr_reader :id
32
+ # The name given to the field (not unique in jira!)
33
+ # @return [String] The name in your JIRA(tm) instance
34
+ attr_reader :name
35
+ # The raw value
36
+ # @return [Hash] The value in it's raw form
37
+ attr_reader :raw_value
38
+ # Allowed values for the fields
39
+ # @return [Array] The values allowed for this kind of field
40
+ attr_accessor :allowed_values
41
+
42
+ # @attr [String] id The fields identifier in JIRA(tm)
43
+ # @attr [String] name The fields name in JIRA(tm)
44
+ # @attr [Hash] args, :required if this is a mandatory field
45
+ def initialize(id,name,args)
46
+ @id = id
47
+ @name = name
48
+ if args[:required] then
49
+ @required = true
50
+ else
51
+ @required = false
52
+ end
53
+ @allowed_values = []
54
+ if args[:allowed_values] then
55
+ allowed_values = args[:allowed_values]
56
+ end
57
+ @value = nil
58
+ @readonly = false
59
+ if args[:createmeta] then
60
+ createmeta(args[:createmeta])
61
+ end
62
+ end
63
+
64
+ # Get the value of the field
65
+ # @param [Boolean] raw true returns a date Object, false a String
66
+ # @return [String] if raw is false (default)
67
+ # @return [Object] if raw is true
68
+ def value(raw = false)
69
+ return @value
70
+ end
71
+
72
+ # Checks if the value is in the list of allowed values. If the list is empty every value is allowed
73
+ # @param [Object] value The value to check for
74
+ # @raise [Jirarest2::ValueNotAllowedException] Raised if the value is not allowed
75
+ # @return [Boolean] true if the value is allowed, false if not
76
+ def value_allowed?(value)
77
+ return true if @allowed_values == [] # If there is no list get out of here fast
78
+ if @allowed_values.include?(value) then
79
+ return true
80
+ else
81
+ raise Jirarest2::ValueNotAllowedException.new(@name,@allowed_values), "#{value} is not a valid value. Please use one of #{@allowed_values.join("; ").to_s}"
82
+ end
83
+ end
84
+
85
+ # Set the value of the field
86
+ # @param [Object] content The value of this field
87
+ def value=(content)
88
+ @value = content if value_allowed?(content)
89
+ end
90
+
91
+ # Representation to be used for json and jira
92
+ # @param [String,Hash] value the to be put into the representation.
93
+ # @return [Hash] if the value is set
94
+ # @return [Nil] if the value is not set
95
+ def to_j(value = @value)
96
+ if value.nil? then
97
+ return nil
98
+ else
99
+ return {@id => value}
100
+ end
101
+ end
102
+
103
+ #Interpret the result of createmeta for one field
104
+ # If there is only one value allowed this value will be set
105
+ # @attr [Hash] structure The JSON result for one field
106
+ # @todo change @allowed_values here. -> suggestion has to go to and build the correct type
107
+ def createmeta(structure)
108
+ @readonly = true if structure["operations"] == []
109
+ if structure["allowedValues"] then
110
+ structure["allowedValues"].flatten!(1)
111
+ if ! structure["allowedValues"][0].nil? then
112
+ if structure["allowedValues"][0].has_key?("value") then
113
+ @key = "value"
114
+ elsif structure["allowedValues"][0].has_key?("key") then
115
+ @key = "key"
116
+ elsif structure["allowedValues"][0].has_key?("name") then
117
+ @key = "name"
118
+ else
119
+ @key = "id"
120
+ end
121
+ structure["allowedValues"].each{ |suggestion|
122
+ @allowed_values << suggestion[@key]
123
+ }
124
+ if structure["allowedValues"].size == 1 && !structure["allowedValues"][0].instance_of?(Array) then # If there is only one value allowed it might as well be set at the earliest convenience
125
+ @value = structure["allowedValues"][0][@key]
126
+ end
127
+ else
128
+ @key = ""
129
+ @allowed_values == []
130
+ end
131
+ end
132
+ end
133
+
134
+ # Parse the value of this field as sent by the server
135
+ # @attr [String,Hash,Array] jvalue The part of the response that is connected to this instance
136
+ def parse_value(jvalue)
137
+ @rawvalue = jvalue
138
+ @value = jvalue
139
+ end
140
+
141
+ protected
142
+ # Representation to be used for json and jira - don't return the fieldid
143
+ # @param [String,Hash] value the to be put into the representation.
144
+ # @return [String,Hash] if the value is set
145
+ # @return [Nil] if the value is not set
146
+ def to_j_inner(value = @value)
147
+ if value.nil? then
148
+ return nil
149
+ else
150
+ return value
151
+ end
152
+ end
153
+
154
+ end # class Field
155
+
156
+
157
+
158
+ # A simple text field. JSON representation will be "Fieldid" : "Value"
159
+ class TextField < Field
160
+ end # TextField
161
+
162
+ # A simple Date field.
163
+ class DateField < Field
164
+ require "date"
165
+
166
+ # Set the value
167
+ # @param [String] content The date in a string representation (Either [YY]YY-[M]M-[D]D or [D]D.[M]M.YYYY or YY.[M]M.[D]D See Date.parse)
168
+ def value=(content)
169
+ value = Date.parse(content)
170
+ @value = value if value_allowed?(value)
171
+ end
172
+
173
+ # Get the value
174
+ # @param [Boolean] raw true returns a date Object, false a String
175
+ # @return [String] if raw is false (default)
176
+ # @return [Date] if raw is true
177
+ def value(raw = false)
178
+ if raw then
179
+ super
180
+ else
181
+ return @value.to_s
182
+ end
183
+ end
184
+
185
+ # Representation to be used for json and jira
186
+ # @return [Hash]
187
+ def to_j(value = @value)
188
+ if value.nil? then
189
+ super(nil)
190
+ else
191
+ super(value.to_s)
192
+ end
193
+ end
194
+
195
+ # Parse the value of this field as sent by the server
196
+ # @attr [String] jvalue The part of the response that is connected to this instance
197
+ def parse_value(jvalue)
198
+ super
199
+ @value = Date.parse(jvalue)
200
+ end
201
+
202
+ # Representation to be used for json and jira without the fieldId
203
+ # @return [Hash]
204
+ def to_j_inner
205
+ if @value.nil? then
206
+ super(nil)
207
+ else
208
+ super(@value.to_s)
209
+ end
210
+ end
211
+
212
+ end # end class DateField
213
+
214
+ # A field resembling a DateTime
215
+ class DateTimeField < DateField
216
+ # require "date"
217
+
218
+ # Set the value
219
+ # @param [String] content The DateTime in a string representation (Use "YYYY-MM-DD HH:MM:SS" although others like "HH:MM:SS YYYY-MM-DD" or "HH:MM:SS DD.MM.YYYY" work too. See DateTime.parse )
220
+ def value=(content)
221
+ value = DateTime.parse(content)
222
+ @value = value if value_allowed?(value)
223
+ end
224
+
225
+ # Parse the value of this field as sent by the server
226
+ # @attr [String] jvalue The part of the response that is connected to this instance
227
+ def parse_value(jvalue)
228
+ super
229
+ @value = DateTime.parse(jvalue)
230
+ end
231
+
232
+ # Representation to be used for json and JIRA(tm)
233
+ # JIRA(tm) expects certain format which we try to accomodate here
234
+ # @return [Hash]
235
+ def to_j(value = @value)
236
+ if value.nil? then
237
+ super(nil)
238
+ else
239
+ strvalue = value.strftime("%FT%T.%L%z")
240
+ super(strvalue)
241
+ end
242
+ end
243
+ end #class DateTimeField
244
+
245
+ # A field representing Numbers (not Strings with Numbers)
246
+ # @todo See to recognize allowed - might hide in schema
247
+ class NumberField < TextField
248
+ # Set the value
249
+ # @param [String,Fixnum] content A number
250
+ def value=(content)
251
+ if content.instance_of?(String) then
252
+ value = content.to_f
253
+ else
254
+ value = content
255
+ end
256
+ @value = value if value_allowed?(value)
257
+ end
258
+ end # class NumberField
259
+
260
+ # A Field that presents its value in an hash that has additional information ("name","id","key","value")
261
+ class HashField < TextField
262
+ # The key element for the answers - It should not be needed - but it's easer on the checks if it's exposed
263
+ # @return [String] The key element for the way to Jira
264
+ attr_reader :key
265
+
266
+ # @attr [String] id The fields identifier in JIRA(tm)
267
+ # @attr [String] name The fields name in JIRA(tm)
268
+ # @attr [Hash] args :key ist mandatory and a String, :required (a Boolean if this is a mandatory field)
269
+ # (key should be one of "id", "key", "name", "value" )
270
+ # @raise [Jirarest2::HashKeyMissingException] If the key determining the base value of this field in it's hash is not given.
271
+ def initialize(id,name,args)
272
+ @key ||= nil # Trying to initialize without overwriting something that might come from the subclass
273
+ @key = args[:key].downcase if ( ! args[:createmeta] && args[:key])
274
+ super
275
+ raise Jirarest2::HashKeyMissingException, "HashTypes like in #{id} alway require a key!" if @key.nil?
276
+ end
277
+
278
+ # Representation to be used for json and jira
279
+ # @return [Hash] if value is set
280
+ def to_j(value = @value)
281
+ if value.nil? then
282
+ super(nil)
283
+ else
284
+ valuehash = {@key => value}
285
+ super(valuehash)
286
+ end
287
+ end
288
+
289
+ # Parse the value of this field as sent by the server
290
+ # @attr [String,Hash,Array] jvalue The part of the response that is connected to this instance
291
+ def parse_value(jvalue)
292
+ super
293
+ if jvalue.nil? then
294
+ @value = nil
295
+ else
296
+ @value = jvalue[key]
297
+ end
298
+ end
299
+
300
+ # Representation to be used for json and jira without the fieldID
301
+ # @return [Hash] if value is set
302
+ def to_j_inner(value = @value)
303
+ if value.nil? then
304
+ super(nil)
305
+ else
306
+ valuehash = {@key => value}
307
+ super(valuehash)
308
+ end
309
+ end
310
+
311
+ end # class HashField
312
+
313
+ # A field containing one or more other fields (usually only TextField or HashField)
314
+ class MultiField < Field
315
+ # @attr [String] id The fields identifier in JIRA(tm)
316
+ # @attr [String] name The fields name in JIRA(tm)
317
+ # @attr [Hash] args, :required if this is a mandatory field
318
+ def initialize(id,name,args)
319
+ super(id,name,args)
320
+ @value = []
321
+ @delete = false
322
+ end
323
+
324
+ # Checks if the value is in the list of allowed values. If the list is empty every value is allowed
325
+ # @param [Object] value The value to check for
326
+ # @raise [Jirarest2::ValueNotAllowedException] Raised if the value is not allowed
327
+ # @return [Boolean] true if the value is allowed, false if not
328
+ def value_allowed?(value)
329
+ return true if @allowed_values == [] # If there is no list get out of here fast
330
+ if value.instance_of?(Array) then
331
+ value.each { |entry|
332
+ value_allowed?(entry)
333
+ }
334
+ else
335
+ if @allowed_values.include?(value) then
336
+ return true
337
+ else
338
+ raise Jirarest2::ValueNotAllowedException.new(@name,@allowed_values), "#{value} is not a valid value. Please use one of #{@allowed_values.join("; ").to_s}"
339
+ end
340
+ end
341
+ end
342
+
343
+ # Set the value of the field
344
+ # @attribute [w] value
345
+ # @param [Object] content The value of this field
346
+ # @return [Array] All the contained fields
347
+ def value=(content)
348
+ if ! content.instance_of?(Array) then
349
+ content = [content]
350
+ end
351
+ super(content)
352
+ end
353
+
354
+
355
+ # Return for JSON representation
356
+ # if @value == [] or nil and @delete is false set super will return nil
357
+ def to_j(value = @value)
358
+ if ((value == [] || value.nil?) and ! @delete) then
359
+ super(nil)
360
+ else
361
+ value.compact!
362
+ fields = Array.new
363
+ if value[0].class < Jirarest2Field::Field then
364
+ value.each {|field|
365
+ fields << field.to_j_inner
366
+ }
367
+ else
368
+ fields = value
369
+ end
370
+ super(fields)
371
+ end
372
+ end
373
+
374
+ # Delete items
375
+ # @param [Object] object The object to delete (If the object is self it all fields and sets @delete)
376
+ # @return The deleted object
377
+ def delete(object)
378
+ if object == self then
379
+ @delete = true
380
+ @value = []
381
+ else
382
+ @value.delete(object)
383
+ end
384
+ end
385
+
386
+ # Delete items based on their value attribute
387
+ # @param [Object] ovalue The value that defines the objects to delete from this MultiField
388
+ # @return [Array] The remaining Objects
389
+ def delete_by_value(ovalue)
390
+ @value.delete_if {|x| x.value == ovalue}
391
+ end
392
+
393
+ # Add another field to the MultiField
394
+ # @param [Field,String] content the content to add to the hash
395
+ def <<(content)
396
+
397
+ if @value.length > 0 then
398
+ raise Jirarest2::ValueNotAllowedException.new(@name,@value[0].class), "#{@value[0].class} vs #{content.class}" if @value[0].class != content.class
399
+ end
400
+
401
+ @value << content if value_allowed?(content)
402
+ end
403
+
404
+ # One field from the MultiField
405
+ # @param [Integer] index Position of the field
406
+ def [](index)
407
+ @value[index]
408
+ end
409
+
410
+ # Set the content of one special field
411
+ # @param [Integer] index Position of the field
412
+ # @param [Object] content Value to put at the place marked by index
413
+ # @raise [Jirarest2::ValueNotAllowedException] Raised if Classes of the fields are to be mixed
414
+ def []=(index,content)
415
+ raise Jirarest2::ValueNotAllowedException.new(@name,@value[0].class), "#{@value[0].class} vs #{content.class}" if @value[0].class != content.class
416
+ @value[index] = content if value_allowed?(content)
417
+ end
418
+ end # class MultiField
419
+
420
+ # The class to represent CascadingSelectFields
421
+ class CascadingField < Field
422
+ # The key element for the answers - It should not be needed - but it's easer on the checks if it's exposed
423
+ # @return [String] The key element for the way to Jira
424
+ attr_reader :key
425
+ # @!attribute [w] allowed_values
426
+ # @attr [Hash<Array>] value The Hashes with the allowed values
427
+ def allowed_values=(value)
428
+ @allowed_values = value
429
+ end
430
+
431
+ # Checks if the value is in the list of allowed values. If the list is empty every value is allowed
432
+ # @param [Object] value The value to check for
433
+ # @raise [Jirarest2::ValueNotAllowedException] Raised if the value is not allowed
434
+ # @return [Boolean] true if the value is allowed, false if not
435
+ def value_allowed?(value)
436
+ return true if @allowed_values == [] # If there is no list get out of here fast
437
+ if @allowed_values[0].has_key?(value[0]) && @allowed_values[0][value[0]].include?(value[1]) then
438
+ return true
439
+ else
440
+ raise Jirarest2::ValueNotAllowedException.new(@name,@allowed_values), "#{value.to_s} is not a valid value. Please use one of #{@allowed_values}"
441
+ end
442
+ end
443
+
444
+ # Set the value of the field
445
+ # @!attribute [w] value
446
+ # @param [Array(String,String)] content The value of this field
447
+ # @raise [Jirarest2::ValueNotAllowedException] Raised if Classes of the fields are to be mixed
448
+ def value=(content)
449
+ if ! content.instance_of?(Array) or content.size != 2 then
450
+ raise Jirarest2::ValueNotAllowedException.new(@name,"Array"), "needs to be an Array with exactly 2 parameters. Was #{content.class}."
451
+ end
452
+ super
453
+ end
454
+
455
+
456
+ # Representation to be used for json and jira
457
+ # @return [Hash] if the value is set
458
+ # @return [Nil] if the value is not set
459
+ def to_j
460
+ if @value.nil? then
461
+ super(nil)
462
+ else
463
+ super({"value" => @value[0], "child" => {"value" => @value[1]}})
464
+ end
465
+ end
466
+
467
+ # Parse the value of this field as sent by the server
468
+ # @attr [Array] jvalue The part of the response that is connected to this instance
469
+ def parse_value(jvalue)
470
+ super
471
+ @value = [jvalue["value"],jvalue["child"]["value"]]
472
+ end
473
+
474
+ #Interpret the result of createmeta for one field
475
+ # @attr [Hash](structure)
476
+ # @note fills allowed_values with a straight list of allowed values
477
+ # @todo Nothing is done here yet!
478
+ def createmeta(structure)
479
+ @readonly = true if structure["operations"] == []
480
+ @key = "value"
481
+ if structure["allowedValues"] then
482
+ structure["allowedValues"].each{ |suggestion|
483
+ subentries = Array.new
484
+ suggestion["children"].each{ |entry|
485
+ subentries << entry["value"]
486
+ }
487
+ @allowed_values << {suggestion[@key] => subentries}
488
+ }
489
+ end
490
+ end
491
+ end # class CascadingField
492
+
493
+ # At the moment it's only there to keep the HashField company
494
+ class MultiStringField < MultiField ; end
495
+
496
+ # Hash Fields are always somewhat different to normal fields
497
+ # @todo to_j is shot
498
+ class MultiHashField < MultiField
499
+ # The key element for the answers - It should not be needed - but it's easer on the checks if it's exposed
500
+ # @return [String] The key element for the way to Jira
501
+ attr_reader :key
502
+
503
+ # @attr [String] id The fields identifier in JIRA(tm)
504
+ # @attr [String] name The fields name in JIRA(tm)
505
+ # @attr [Hash] args, :required if this is a mandatory field
506
+ # @raise [Jirarest2::HashKeyMissingException] If the key determining the base value of this field in it's hash is not given.
507
+ # @todo Test to not really work for to_j
508
+ # @todo Hash_identifier is not alway "name", we should have some "value" fields as well. Whole thing needs to be rewritten work with HashFields as parts
509
+ def initialize(id,name,args)
510
+ @key ||= nil # Trying to initialize without overwriting something that might come from the subclass
511
+ @key = args[:key].downcase if ( ! args[:createmeta] && args[:key])
512
+ super
513
+ raise Jirarest2::HashKeyMissingException, "HashTypes like in #{id} always require a key!" if @key.nil?
514
+ end
515
+
516
+ # Parse the value of this field as sent by the server
517
+ # @attr [Array] jvalue The part of the response that is connected to this instance
518
+ def parse_value(jvalue)
519
+ @rawvalue = jvalue
520
+ jvalue.each{ |item|
521
+ @value << item[@key]
522
+ }
523
+ end
524
+
525
+ # Return for JSON representation
526
+ # if @value == [] or nil and @delete is false set super will return nil
527
+ def to_j(value = @value)
528
+ if ((value == [] || value.nil?) and ! @delete) then
529
+ super(nil)
530
+ else
531
+ value.compact!
532
+ fields = Array.new
533
+ if value[0].class < Jirarest2Field::Field then # This is how it should be
534
+ value.each {|field|
535
+ fields << field.to_j_inner
536
+ }
537
+ else
538
+ value.each{ |field|
539
+ f = HashField.new("10000a",field,{:createmeta => {"operations" => ["set"], "allowedValues" => [{@key => field}] } })
540
+ fields << f.to_j_inner
541
+ }
542
+ end
543
+ super(fields)
544
+ end
545
+ end
546
+
547
+ # Representation to be used for json and jira without the fieldID
548
+ # @return [Hash] if value is set
549
+ def to_j_inner(value = @value)
550
+ if value.nil? then
551
+ super(nil)
552
+ else
553
+ valuehash = {@key => value}
554
+ super(valuehash)
555
+ end
556
+ end
557
+
558
+ end
559
+
560
+ # Versions might behave in another way somewhere
561
+ class MultiVersionField < MultiHashField ; end
562
+
563
+ # Unfortunately Users and Groups don't give us any clue as to how to set their "key" element. Therefore this own class
564
+ class MultiUserField < MultiHashField
565
+ def initialize(id,name,args)
566
+ @key = "name"
567
+ super
568
+ end
569
+ end
570
+
571
+ # Unfortunately Users and Groups don't give us any clue as to how to set their "key" element. Therefore this own class
572
+ class UserField < HashField
573
+ def initialize(id,name,args)
574
+ @key = "name"
575
+ super
576
+ end
577
+ end
578
+
579
+ class VersionField < HashField ; end
580
+
581
+ # Projects are a little bit special
582
+ class ProjectField < VersionField
583
+ =begin
584
+ def createmeta(structure)
585
+ super
586
+ if ! structure["allowedValues"][0].instance_of?(Array) then # If there is only one value allowed it might as well be set at the earliest convenience
587
+ @value = structure["allowedValues"][0]["key"]
588
+ end
589
+ end
590
+ =end
591
+ end
592
+
593
+ # Timetracking is very special
594
+ # @todo This class is not really doing anything usefull
595
+ class TimetrackingField < Field; end
596
+
597
+ =begin
598
+ class CascadingSelect < CascadingField ; end # Look for "custom" Key
599
+ class DateTime < DateTimeField ; end
600
+ class GroupPicker < HashField ; end
601
+ class ImportId ; end
602
+ class Labels < MultiStringField ; end
603
+ class MultiGroupPicker < MultiHashField ; end
604
+ class MultiUserPicker < MultiHashField ; end
605
+ class ProjectPicker < HashField; end
606
+ class ReadOnlyTextField < TextField ; end
607
+ class SingleVersionPicker < HashField ; end
608
+ class URLField < TextField ; end
609
+ class VersionPicker < MultiHashField ; end
610
+ class DatePicker < DateField ; end
611
+ class FreeTextField < TextField ; end
612
+ class HiddenJobSwitch ; end
613
+ class JobCheckbox ; end
614
+ class MultiCheckboxes < MultiField ; end #unsure
615
+ class MultiSelect < MultiHashField ; end
616
+ # class NumberField < NumberField ; end
617
+ class RadioButtons < HashField ; end
618
+ class SelectList < HashField ; end
619
+ # class TextField < TextField ; end
620
+ class UserPicker < HashField ; end
621
+
622
+ class String < TextField ; end
623
+ class Progress ; end
624
+ # class Timetracking < TimeTrackingEntry ; end # "timetracking" : { "originalEstimate" : "1w2h", "remainingEstimate" : "3h23m" }
625
+ class Issuetype < HashField ; end
626
+ class Number < NumberField ; end
627
+ class User < UserPicker ; end
628
+ # class Datetime ; end # See above
629
+ class Priority < TextField ; end # Not HashField?
630
+ class Date < DateField ; end
631
+ class Array < MultiField ; end # Never alone always with an items parameter
632
+ class Status ; end
633
+ class Project < HashField ; end
634
+ class Component ; end
635
+ class Comment ; end
636
+ class Votes ; end
637
+ class Resolution < TextField ; end
638
+ class Version < HashField ; end
639
+ class Watches ; end
640
+ class Worklog ; end
641
+ class Attachment ; end # readonly
642
+ =end
643
+ end
644
+
645
+