jirarest2 0.0.12 → 0.0.13

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.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
+