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.
@@ -27,10 +27,12 @@ class Issue
27
27
  end
28
28
 
29
29
  # Receive an issue and all it's fields from jira
30
+ # @todo This method does not work yet
30
31
  # @param [Connection] connection
31
32
  def receive(connection)
32
33
  uritail = "issue/#{@issueid}"
33
34
  result = connection.execute("Get",uritail,{"expand" => "metadata"}) # get the issue AND the metadata because we already know how to parse that.
35
+ return result
34
36
  # TODO Many and more fields
35
37
  end
36
38
 
@@ -61,5 +63,10 @@ class Issue
61
63
  set_assignee(connection, nil)
62
64
  end
63
65
 
66
+ # Interpret the result of createmeta for one issuetype
67
+ # @attr [Hash] issuetype one issutype resulting from createmeta
68
+ def createmeta(issuetype)
69
+ @name = issuetype
70
+ end
64
71
 
65
72
  end # Issue
@@ -0,0 +1,256 @@
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_relative "field"
18
+
19
+ # Keep track of one Issuetype
20
+ class Issuetype
21
+ # @return [String] The name of the issuetype
22
+ attr_reader :name
23
+ # @return [String] The description for this issuetype
24
+ attr_reader :description
25
+ # @return [Array<Hash>] All the fields in this issuetype
26
+ attr_reader :fields
27
+ # @return [Array] All the required fields in this issuetype
28
+ attr_reader :required_fields
29
+
30
+ #Get the correct Fieldtype based on the schema from createmeta
31
+ # @attr [Hash] schema The type description we get
32
+ # @return [String] Name of the Fieldtype to use
33
+ # @todo timetracking will probably not work
34
+ # @todo attachment is not realy good either
35
+ # @todo hiddenjobswitch is unsure
36
+ # @todo jobcheckbox is unsure
37
+ # @todo check priority type
38
+ # @todo how to handle readonlyfield?
39
+ def decipher_schema(schema)
40
+ case schema["type"]
41
+ when "number"
42
+ return "NumberField"
43
+ when "user"
44
+ return "UserField"
45
+ when "version"
46
+ return "VersionField"
47
+ when "project"
48
+ return "ProjectField"
49
+ when "issuetype"
50
+ return "HashField"
51
+ when "datetime"
52
+ return "DateTimeField"
53
+ when "date"
54
+ return "DateField"
55
+ when "priority"
56
+ return "HashField"
57
+ when "group"
58
+ return "UserField"
59
+ when "resolution"
60
+ return "HashField"
61
+ when "timetracking"
62
+ return "TimetrackingField"
63
+ when "array"
64
+ case schema["items"]
65
+ when "version"
66
+ return "MultiVersionField"
67
+ when "group"
68
+ return "MultiUserField"
69
+ when "attachment"
70
+ return "MultiStringField"
71
+ when "user"
72
+ return "MultiUserField"
73
+ when "component"
74
+ return "MultiHashField"
75
+ when "string"
76
+ return "MultiStringField" if schema["system"] && schema["system"] == "labels" # This is the only field with the "system" attribute
77
+ case schema["custom"]
78
+ when /.*:multicheckboxes$/
79
+ return "MultiHashField"
80
+ when /.*:multiselect$/
81
+ return "MultiHashField"
82
+ when /.*:cascadingselect$/
83
+ return "CascadingField"
84
+ else
85
+ raise Jirarest2::CouldNotDetermineFieldtypeException schema
86
+ end
87
+ else
88
+ raise Jirarest2::CouldNotDetermineFieldtypeException schema
89
+ end
90
+ when "string"
91
+ return "TextField" if schema["system"]
92
+ schema["custom"] =~ /.*:(\w*)$/
93
+ case $1
94
+ when "url"
95
+ return "TextField"
96
+ when "textfield"
97
+ return "TextField"
98
+ when "textarea"
99
+ return "TextField"
100
+ when "radiobuttons"
101
+ return "HashField"
102
+ when "select"
103
+ return "HashField"
104
+ when "hiddenjobswitch"
105
+ return "TextField"
106
+ when "readonlyfield"
107
+ return "TextField"
108
+ when "jobcheckbox"
109
+ return "TextField"
110
+ else
111
+ raise Jirarest2::CouldNotDetermineFieldtypeException schema
112
+ end
113
+ else
114
+ raise Jirarest2::CouldNotDetermineFieldtypeException schema
115
+ end
116
+
117
+ end # def decipher_schema
118
+
119
+ # Interpret the result of createmeta for one issuetype
120
+ # @attr [Hash] issuetype The JSON result for one issuetype
121
+ # @raise [Jirarest2::WrongIssuetypeException] Raised if the issuetype is not found in the answer
122
+ # @todo As the name and not the id of the field is used here some fields might get lost. There should be some way around while still enabling the use of names for external calls
123
+ def createmeta(issuetype)
124
+ if issuetype.nil? || issuetype["name"].nil? then
125
+ raise Jirarest2::WrongIssuetypeException
126
+ else
127
+ @name = issuetype["name"]
128
+ end
129
+ @description = issuetype["description"]
130
+ @fields = Hash.new
131
+ @required_fields = Array.new
132
+ @field_name_id = Hash.new
133
+ issuetype["fields"].each { |id,structure|
134
+ name = structure["name"]
135
+ required = structure["required"]
136
+ type = decipher_schema(structure["schema"])
137
+ field = Jirarest2Field::const_get(type).new(id,name,{:required => required, :createmeta => structure})
138
+ @fields[id] = field
139
+ @field_name_id[name] = id
140
+ @required_fields << field if required
141
+ }
142
+ end
143
+
144
+
145
+ # Interpret the result to the request for an existing issue
146
+ # @attr [Hash] json The hashed json body of the request
147
+ # @raise [Jirarest2::FieldsUnknownError] Raised if the fields to this issuetype is unknown
148
+ # @todo Is this not really Issue instead of Issuetype?
149
+ # @todo I need name of the issuetype if possible
150
+ def decode_issue(json)
151
+ if (@fields.nil? or @fields == {}) then
152
+ # Prepare the fields
153
+ if json.has_key?("schema") then
154
+ createmeta(json["schema"])
155
+ elsif json.has_key?("editmeta") then
156
+ createmeta(json["editmeta"])
157
+ else
158
+ # We need to know what to fill and how
159
+ raise Jirearesr2::FieldsUnknownError json["self"]
160
+ end
161
+ end
162
+ @issuekey = json["key"]
163
+ json["fields"].each{ |field_id,content|
164
+ @fields["field_id"].parse_value(content)
165
+ }
166
+ end # def decode_issue
167
+
168
+
169
+ #Set the value of a field
170
+ # @param [String] id The name of the field
171
+ # @param [String,Array] value The value of the field
172
+ # @param [Symbol] denom Field identifier to use (:name or :id)
173
+ def set_value(id,value,denom = :name)
174
+ field = get_field(id,denom)
175
+ field.value = value
176
+ end
177
+
178
+ # Get the value of a field
179
+ # @param [String] id The name of the field
180
+ # @return [String,Array] value The value of the field
181
+ # @param [Symbol] denom Field identifier to use (:name or :id)
182
+ def get_value(id,denom = :name)
183
+ field = get_field(id,denom)
184
+ return field.value
185
+ end
186
+
187
+
188
+
189
+ #check if all the required fields have values
190
+ # The following fields are not seen as required in this method because JIRA (tm) sets it's own defaults: project, issuetype, reporter
191
+ # @param [Boolean] only_empty If set to true will only return those Names where the value is empty
192
+ # @return [Array] Names of all the required_fields that have no value assigned, empty if all fields have a value
193
+ def required_by_name(only_empty = false)
194
+ empty = Array.new
195
+ @required_fields.each{ |field|
196
+ empty << field.name if (field.value.nil? || ! only_empty ) && !field.name.nil? && field.id != "issuetype" && field.id != "reporter"
197
+ }
198
+ return empty
199
+ end
200
+
201
+ #Build up a hash to give to jira to create a new ticket
202
+ # @return [Hash] Hash to be sent to the server
203
+ # @raise [Jirarest2::RequiredFieldNotSetException] Raised if a required field is not set
204
+ # @todo NOT finished
205
+ def new_ticket_hash
206
+ missing_fields = required_by_name(true)
207
+ if missing_fields == [] then
208
+ fields = Hash.new
209
+ @fields.each { |id,field|
210
+ fields = fields.merge!(field.to_j) if ! field.to_j.nil? #Sending empty fields with a new ticket will not work
211
+ }
212
+ h = {"fields" => fields}
213
+ return h
214
+ else
215
+ raise Jirarest2::RequiredFieldNotSetException, missing_fields
216
+ end
217
+ end
218
+
219
+ # Return the fieldtype (Multitype as "array" nostly for backwards compability)
220
+ # @attr [String] fieldname The Name of the field
221
+ # @return [String] The fieldtype as String. MultiField types and CascadingField are returned as "array"
222
+ def fieldtype(fieldname)
223
+ ftype = get_field(fieldname,:name).class.to_s
224
+ ftype =~ /^.*::(\w+)Field$/
225
+ ftshort = $1
226
+ case ftshort
227
+ when /Multi.*/
228
+ return "array"
229
+ when "Cascading"
230
+ return "array"
231
+ else
232
+ return ftshort
233
+ end
234
+ end
235
+
236
+
237
+
238
+ private
239
+ # Get the field based on the id and the denominator (:id or :name)
240
+ # @param [String] id The name of the field
241
+ # @param [Symbol] denom Field identifier to use (:name or :id)
242
+ def get_field(id,denom = :name)
243
+ working_id = id
244
+ if denom == :name then
245
+ working_id = @field_name_id[id]
246
+ end
247
+ field = @fields[working_id]
248
+ if field.nil? then # Try if it's has been the id all along
249
+ field = @fields[id]
250
+ end
251
+ raise Jirarest2::WrongFieldnameException, id if field.nil?
252
+ return field
253
+ end
254
+
255
+
256
+ end # class Issuetype
@@ -30,8 +30,9 @@ require "json"
30
30
  # The config file can either contain key = value pairs or JSON data
31
31
  # JSON and simple "key = value\n" lines work
32
32
  # TODO make it work with "key = [value,value]\n" or even "key = value1 \n value2 \n ..."
33
- # @param [String] configfile
33
+ # @param [String] config_file
34
34
  # @param [Boolean] whitespace Keep whitespace characters?
35
+ # @raise [IOError] Raised if the file indicated by config_file is not found
35
36
  # @return [Hash] Hash configparameters
36
37
  def self.read_configfile(config_file,whitespace = false)
37
38
  if whitespace then
@@ -82,6 +83,7 @@ require "json"
82
83
  # @param [String] config_file Name (and path) of the config file
83
84
  # @param [Hash] configoptions Hash of "option" => "value" pairs
84
85
  # @param [Symbol] save Determines if an existing file is to be kept. :force replaces an existing file
86
+ # @raise [FileExistsException] Raised if the file that is to be written already exists and the overwrite flag (save) is not set to :force
85
87
  def self.write_configfile(config_file, configoptions, save = :noforce)
86
88
  config_file = File.expand_path(config_file) # Be save
87
89
 
@@ -15,10 +15,10 @@
15
15
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  #
17
17
 
18
- =begin
19
- # An Issue object contains all the data of an issue
20
- =end
21
- class NewIssue
18
+ require_relative "issuetype"
19
+
20
+ # class to handle new issues (building them up, changing fields, persistence)
21
+ class NewIssue
22
22
 
23
23
  # issue type of the issue
24
24
  attr_reader :issuetype
@@ -30,197 +30,61 @@ class NewIssue
30
30
  # @param [String] project Key of the JIRA(tm) project the issue belongs to
31
31
  # @param [String] type Issuetype the issue belongs to
32
32
  # @param [Connection] connection
33
+ # @raise [Jirarest2::WrongProjectException] Raised of the project type is not found in the answer
33
34
  def initialize (project,type,connection)
34
35
  query = {:projectKeys => project, :issuetypeNames => type, :expand => "projects.issuetypes.fields" }
35
36
  answer = connection.execute("Get","issue/createmeta/",query)
36
37
  jhash = answer.result
37
- parse_json(jhash)
38
- raise Jirarest2::WrongProjectException, project if @project == ""
39
- raise Jirarest2::WrongIssuetypeException, type if @issuetype == ""
40
- end
41
-
42
- # produces an instance-variable @issuefields that can be
43
- # @param [Hash] jhash Hashed version of the json-snippet JIRA(tm) returns
44
- def parse_json (jhash)
45
- @issuefields = Hash.new
46
- @project = ""
47
- @issuetype = ""
48
- jhash["projects"].each { |value|
49
- @project = value["key"]
50
- value["issuetypes"].each { |value1|
51
- @issuetype = value1["name"]
52
- value1["fields"].delete("project") #The project key is duplicate and will make our live harder afterwards. It is marked as required but nothing happens if this key is not set.
53
- value1["fields"].each { |key,value2|
54
- fields = Hash.new
55
- fields["id"] = key
56
- if value2["name"] then
57
- name = value2["name"]
58
- else
59
- name = key
60
- end
61
- # If the allowed reponses are limited we want to know them.
62
- if value2["allowedValues"] then
63
- # With custom fields the identifier is "value" with the built in ones it's "name"
64
- identifier = "name"
65
- if value2["schema"]["custom"] then
66
- identifier = "value"
67
- end
68
- allowedValues = Array.new
69
- value2["allowedValues"].each { |value3|
70
- allowedValues << value3[identifier]
71
- } # value3
72
- fields["allowedValuesIdentifier"] = identifier
73
- fields["allowedValues"] = allowedValues
74
- end
75
- fields["required"] = value2["required"]
76
- fields["type"] = value2["schema"]["type"]
77
- @issuefields[name] = fields if name != "Issue Type" # "Issue Type" is not really a required field as we have to assign it at another place anyway
78
- } # value1
79
- } # value
80
- } # jhash
81
- end
82
-
83
- # @param [String] field Name of the field
84
- # @return [String] type of the Field
85
- def fieldtype(field)
86
- # If the fieldname is wrong we want to tell this and stop execution (or maybe let the caller fix it)
87
- if @issuefields[field].nil? then
88
- raise Jirarest2::WrongFieldnameException, field
89
- else
90
- return @issuefields[field]["type"]
38
+ begin
39
+ @project = jhash["projects"][0]["key"]
40
+ rescue NoMethodError
41
+ raise Jirarest2::WrongProjectException, project
91
42
  end
43
+ @issue = Issuetype.new
44
+ @issue.createmeta(jhash["projects"][0]["issuetypes"][0])
45
+ @issuetype = @issue.name
92
46
  end
93
-
47
+
94
48
  # @return [Array] Names of required fields
95
49
  def get_requireds
96
- names = Array.new
97
- @issuefields.each {|key,value|
98
- if value["required"] then
99
- names << key
100
- end
101
- }
102
- return names
50
+ return @issue.required_by_name
103
51
  end
104
52
 
105
53
  # @return [Array] Names of all fields
106
54
  def get_fieldnames
107
55
  names = Array.new
108
- @issuefields.each {|key,value|
109
- names << key
56
+ @issue.fields.each {|id,field|
57
+ names << field.name
110
58
  }
111
59
  return names
112
60
  end
113
61
 
114
- # @param [String] name Name of a field
115
- # @return [String] id of the field
116
- protected
117
- def get_id(name)
118
- return @issuefields["name"]["id"]
119
- end
120
-
121
- =begin
122
- # query=
123
- # {"fields"=>
124
- # { "project"=>{"key"=>"MFTP"},
125
- # "environment"=>"REST ye merry gentlemen.",
126
- # "My own text"=>"Creating of an issue using project keys and issue type names using the REST API",
127
- # "issuetype"=> {"name"=>"My own type"}
128
- # }
129
- # }
130
- =end
131
-
62
+ # take this classes representation of an issue and make it presentable to JIRA(tm)
132
63
  # @return [Hash] Hash to be sent to JIRA(tm) in a JSON representation
133
- public
64
+ # @deprecated @see Issuetype#new_ticket_hash does the same now
134
65
  def jirahash
135
- h = Hash.new
136
- issuetype = {"issuetype" => {"name" => @issuetype}}
137
- project = {"key" => @project}
138
- fields = Hash.new
139
- fields["project"] = project
140
- # here we need to enter the relevant fields and their values
141
- @issuefields.each { |key,value|
142
- if key != "project" then
143
- id = value["id"]
144
- if ! value["value"].nil? then
145
- fields[id] = value["value"]
146
- end
147
- end
148
- }
149
- fields = fields.merge!(issuetype)
150
- h = {"fields" => fields}
151
- return h
66
+ return @issue.new_ticket_hash
152
67
  end
153
68
 
154
- # check if the value is allowed for this field
155
- # @param [String] key Name of the field
156
- # @param [String] value Value to be checked
157
- # @return [Boolean, Jirarest2::ValueNotAllowedException]
158
- protected
159
- def value_allowed?(key,value)
160
- if @issuefields[key]["allowedValues"].include?(value)
161
- return true
162
- else
163
- # puts "Value #{value} not allowed for field #{key}."
164
- raise Jirarest2::ValueNotAllowedException.new(key, @issuefields[key]["allowedValues"]), value
165
- end
166
- end
167
-
168
-
169
- # Special setter for fields that have a limited numer of allowed values.
170
- #
171
- # This setter might be included in set_field at a later date.
172
- # @param [String] key Name of the field
173
- # @param [String] value Value to be checked
174
- def set_allowed_value(key,value)
175
- if @issuefields[key]["type"] == "array" && value.instance_of?(Array) then
176
- array = Array.new
177
- value.each {|item|
178
- if value_allowed?(key,item) then
179
- array << {@issuefields[key]["allowedValuesIdentifier"] => item}
180
- end
181
- }
182
- @issuefields[key]["value"] = array
183
- else
184
- if value_allowed?(key,value) then
185
- @issuefields[key]["value"] = {@issuefields[key]["allowedValuesIdentifier"] => value}
186
- end
187
- end
188
- end
189
-
190
-
191
- # TODO We are not yet able to work with "Cascading Select" fields ( "custom": "com.atlassian.jira.plugin.system.customfieldtypes:cascadingselect")
192
69
  # @param [String] key Name of the field
193
70
  # @param [String] value Value the field should be set to, this is either a String or an Array (don't know if numbers work too)
194
- public
71
+ # @raise [Jirarest2::WrongFieldnameException] Raised if the name of the field is not found
72
+ # @todo check if the allowed Values are working now too and if they might throw an exception
195
73
  def set_field(key, value)
196
- if @issuefields.include?(key) then
197
- if @issuefields[key].include?("allowedValues") then
198
- set_allowed_value(key,value)
199
- else
200
- @issuefields[key]["value"] = value
201
- end
202
- else
203
- raise Jirarest2::WrongFieldnameException, key
204
- puts "Unknown Field: #{key}"
205
- end
74
+ @issue.set_value(key,value,:name)
206
75
  end
207
76
 
208
77
  # @param [String] field Name of the field
209
78
  # @return [String] value of the field
210
79
  def get_field(field)
211
- @issuefields[field]["value"]
80
+ @issue.get_value(field,:name)
212
81
  end
213
82
 
214
83
  # persitence of this Issue object instance
215
84
  # @param [Connection] connection
216
85
  # @return [Jirarest2::Result]
217
86
  def persist(connection)
218
- get_requireds.each { |fieldname|
219
- if @issuefields[fieldname]["value"].nil? then
220
- raise Jirarest2::RequiredFieldNotSetException, fieldname
221
- end
222
- }
223
- hash = jirahash
87
+ hash = @issue.new_ticket_hash
224
88
  ret = connection.execute("Post","issue/",hash)
225
89
  if ret.code == "201" then # Ticket sucessfully created
226
90
  @issuekey = ret.result["key"]
@@ -228,8 +92,6 @@ class NewIssue
228
92
  return ret
229
93
  end
230
94
 
231
-
232
-
233
95
  # Set the watchers for this Ticket
234
96
  # @param [Connection] connection
235
97
  # @param [Array] watchers Watchers to be added
@@ -243,5 +105,11 @@ class NewIssue
243
105
  return success
244
106
  end
245
107
 
108
+ # Return the fieldtype (Multitype as "array" nostly for backwards compability)
109
+ # @attr [String] fieldname The Name of the field
110
+ # @return [String] The fieldtype as String. MultiField types and CascadingField are returned as "array"
111
+ def fieldtype(fieldname)
112
+ return @issue.fieldtype(fieldname)
113
+ end
246
114
 
247
115
  end # class