jiralicious 0.1.0 → 0.2.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.
Files changed (50) hide show
  1. data/.gitignore +2 -1
  2. data/.ruby-gemset +1 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +5 -0
  5. data/README.md +32 -4
  6. data/jiralicious.gemspec +7 -5
  7. data/lib/jiralicious.rb +10 -3
  8. data/lib/jiralicious/base.rb +114 -0
  9. data/lib/jiralicious/configuration.rb +13 -2
  10. data/lib/jiralicious/cookie_session.rb +5 -5
  11. data/lib/jiralicious/custom_field_option.rb +27 -0
  12. data/lib/jiralicious/field.rb +39 -0
  13. data/lib/jiralicious/issue.rb +111 -26
  14. data/lib/jiralicious/issue/comment.rb +62 -0
  15. data/lib/jiralicious/issue/fields.rb +93 -0
  16. data/lib/jiralicious/issue/transitions.rb +92 -0
  17. data/lib/jiralicious/issue/watchers.rb +47 -0
  18. data/lib/jiralicious/parsers/field_parser.rb +2 -2
  19. data/lib/jiralicious/project.rb +44 -0
  20. data/lib/jiralicious/search.rb +4 -1
  21. data/lib/jiralicious/search_result.rb +4 -0
  22. data/lib/jiralicious/version.rb +1 -1
  23. data/spec/basic_session_spec.rb +4 -4
  24. data/spec/comment_spec.rb +64 -0
  25. data/spec/configuration_spec.rb +9 -0
  26. data/spec/fixtures/comment.json +30 -0
  27. data/spec/fixtures/comment_single.json +29 -0
  28. data/spec/fixtures/issue.json +89 -93
  29. data/spec/fixtures/issue_2.json +30 -0
  30. data/spec/fixtures/issue_create.json +5 -0
  31. data/spec/fixtures/issue_createmeta.json +34 -0
  32. data/spec/fixtures/issue_editmeta.json +22 -0
  33. data/spec/fixtures/issue_update.json +164 -0
  34. data/spec/fixtures/jira.yml +7 -0
  35. data/spec/fixtures/project.json +87 -0
  36. data/spec/fixtures/project_issue_list.json +20 -0
  37. data/spec/fixtures/projects.json +22 -0
  38. data/spec/fixtures/search.json +9 -9
  39. data/spec/fixtures/test.json +24 -0
  40. data/spec/fixtures/transitions.json +61 -61
  41. data/spec/fixtures/watchers.json +17 -0
  42. data/spec/issue_spec.rb +255 -21
  43. data/spec/project_spec.rb +55 -0
  44. data/spec/search_result_spec.rb +20 -8
  45. data/spec/search_spec.rb +6 -6
  46. data/spec/support/http.rb +55 -2
  47. data/spec/watchers_spec.rb +43 -0
  48. metadata +154 -100
  49. data/.rvmrc +0 -1
  50. data/spec/cookie_session_spec.rb +0 -268
data/.gitignore CHANGED
@@ -8,4 +8,5 @@ TAGS
8
8
  vendor/bundle
9
9
  *~
10
10
  \#*#
11
- *.#*
11
+ *.#*
12
+ /bin
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ jiralicious
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-1.9.2-p320
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+ - 1.8.7
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # jiralicious
2
2
 
3
+ [![Build Status](https://travis-ci.org/jstewart/jiralicious.png)](https://travis-ci.org/jstewart/jiralicious)
4
+
3
5
  ## Examples:
4
6
 
5
7
  Before doing anything, you must configure your session:
@@ -13,10 +15,15 @@ Before doing anything, you must configure your session:
13
15
  config.auth_type = :basic
14
16
  end
15
17
 
16
- Default auth type is now Basic auth. Cookie auth is still available with the
17
- following option:
18
+ Session configuration is also available via YAML:
19
+
20
+ jira:
21
+ username: youruser
22
+ password: yourpass
23
+ uri: https://example.com/
24
+
18
25
 
19
- config.auth_type = :cookie
26
+ Jiralicious.load_yml(File.expand_path("/path/to/jira.yml"))
20
27
 
21
28
  Search for issues:
22
29
 
@@ -28,6 +35,27 @@ Finding a single issue:
28
35
  issue = Jiralicious::Issue.find("HSP-1")
29
36
  issue.key => "HSP-1"
30
37
 
38
+
39
+ ## Deprecation Warning
40
+
41
+ Default auth type is now Basic auth. Cookie auth will be deprecated in the next version.
42
+
43
+
44
+ ## Changes from 0.1.0
45
+
46
+ * Issues can be created, updated, or deleted as needed. This includes most components such as comments, transitions, and assignees.
47
+ * Projects can now be accessed as well as related issues
48
+ * A Field class has been added to allow proper access to the meta data for create, edit, and update requests. This data is searchable via Hash or dot notation
49
+ * Some sections can now be lazy loaded
50
+ * Configuration can be loaded via yaml
51
+
52
+
53
+ ## Contributors
54
+
55
+ * Stanley Handschuh (dorack)
56
+ * Mike Fiedler (miketheman)
57
+ * Girish Sonawane (girishso)
58
+
31
59
  ## Contributing to jiralicious
32
60
 
33
61
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
@@ -40,5 +68,5 @@ Finding a single issue:
40
68
 
41
69
  ## Copyright
42
70
 
43
- Copyright (c) 2012 Jason Stewart. See LICENSE for
71
+ Copyright (c) 2013 Jason Stewart. See LICENSE for
44
72
  further details.
data/jiralicious.gemspec CHANGED
@@ -12,11 +12,13 @@ Gem::Specification.new do |s|
12
12
  s.description = %Q{A Ruby library for interacting with JIRA's REST API}
13
13
  s.email = "jstewart@fusionary.com"
14
14
  s.authors = ["Jason Stewart"]
15
- s.add_runtime_dependency 'httparty', '~>0.7.8'
16
- s.add_runtime_dependency 'hashie', '~>1.1'
17
- s.add_runtime_dependency 'json', '~>1.6.3'
18
- s.add_development_dependency 'rspec', '~>2.6.0'
19
- s.add_development_dependency 'fakeweb', '~>1.3.0'
15
+ s.add_runtime_dependency 'crack', '~> 0.1.8'
16
+ s.add_runtime_dependency 'httparty', '~> 0.11.0'
17
+ s.add_runtime_dependency 'hashie', '>= 1.1'
18
+ s.add_runtime_dependency 'json', '~> 1.7.7'
19
+ s.add_development_dependency 'rspec', '~> 2.6'
20
+ s.add_development_dependency 'rake'
21
+ s.add_development_dependency 'fakeweb', '~> 1.3.0'
20
22
 
21
23
  s.files = `git ls-files`.split("\n")
22
24
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
data/lib/jiralicious.rb CHANGED
@@ -1,19 +1,26 @@
1
1
  # encoding: utf-8
2
-
3
2
  require 'hashie'
3
+ require 'crack'
4
4
  require 'httparty'
5
5
  require 'json'
6
6
 
7
-
8
7
  require 'jiralicious/parsers/field_parser'
9
8
  require 'jiralicious/errors'
9
+ require 'jiralicious/base'
10
+ require 'jiralicious/field'
11
+ require 'jiralicious/custom_field_option'
10
12
  require 'jiralicious/issue'
13
+ require 'jiralicious/issue/fields'
14
+ require 'jiralicious/issue/comment'
15
+ require 'jiralicious/issue/watchers'
16
+ require 'jiralicious/issue/transitions'
17
+ require 'jiralicious/project'
11
18
  require 'jiralicious/search'
12
19
  require 'jiralicious/search_result'
13
20
  require 'jiralicious/session'
14
21
  require 'jiralicious/basic_session'
15
22
  require 'jiralicious/cookie_session'
16
-
23
+ require 'jiralicious/configuration'
17
24
 
18
25
  module Jiralicious
19
26
  extend Configuration
@@ -0,0 +1,114 @@
1
+ # To change this template, choose Tools | Templates
2
+ # and open the template in the editor.
3
+ require "uri"
4
+ module Jiralicious
5
+ class Base < Hashie::Trash
6
+ include Jiralicious::Parsers::FieldParser
7
+
8
+ attr_accessor :loaded
9
+
10
+ ### Trash Extention Methods ###
11
+ def properties_from_hash(hash)
12
+ hash.inject({}) do |newhash, (k, v)|
13
+ k = k.gsub("-", "_")
14
+ k = "_#{k.to_s}" if k =~ /^\d/
15
+ self.class.property :"#{k}"
16
+ newhash[k] = v
17
+ newhash
18
+ end
19
+ end
20
+
21
+ ### Class Methods ###
22
+ class << self
23
+ def find(key, options = {})
24
+ response = fetch({:key => key})
25
+ if options[:reload] == true
26
+ response
27
+ else
28
+ new(response.parsed_response)
29
+ end
30
+ end
31
+
32
+ def find_all
33
+ response = fetch()
34
+ new(response)
35
+ end
36
+
37
+ def endpoint_name
38
+ self.name.split('::').last.downcase
39
+ end
40
+
41
+ def parent_name
42
+ arr = self.name.split('::')
43
+ arr[arr.length-2].downcase
44
+ end
45
+
46
+ def fetch(options = {})
47
+ options[:method] = :get unless [:get, :post, :put, :delete].include?(options[:method])
48
+ options[:parent_uri] = "#{parent_name}/#{options[:parent_key]}/" unless options[:parent].nil?
49
+ if !options[:body_override]
50
+ options[:body_uri] = (options[:body].is_a? Hash) ? options[:body] : {:body => options[:body]}
51
+ else
52
+ options[:body_uri] = options[:body]
53
+ end
54
+ if options[:body_to_params]
55
+ options[:params_uri] = "?#{options[:body].to_params}" unless options[:body].nil? || options[:body].empty?
56
+ options[:body_uri] = nil
57
+ end
58
+ options[:url_uri] = options[:url].nil? ? "#{Jiralicious.rest_path}/#{options[:parent_uri]}#{endpoint_name}/#{options[:key]}#{options[:params_uri]}" : options[:url]
59
+ Jiralicious.session.request(options[:method], options[:url_uri], :handler => handler, :body => options[:body_uri].to_json)
60
+ end
61
+
62
+ def handler
63
+ Proc.new do |response|
64
+ case response.code
65
+ when 200..204
66
+ response
67
+ when 400
68
+ raise Jiralicious::TransitionError.new(response['errorMessages'].join('\n'))
69
+ when 404
70
+ raise Jiralicious::IssueNotFound.new(response['errorMessages'].join('\n'))
71
+ else
72
+ raise Jiralicious::JiraError.new(response['errorMessages'].join('\n'))
73
+ end
74
+ end
75
+ end
76
+
77
+ alias :all :find_all
78
+ end
79
+
80
+ ### Instance Methods ###
81
+ def endpoint_name
82
+ self.class.endpoint_name
83
+ end
84
+
85
+ def parent_name
86
+ self.class.parent_name
87
+ end
88
+
89
+ def all
90
+ self.class.all
91
+ end
92
+
93
+ def loaded?
94
+ self.loaded
95
+ end
96
+
97
+ def reload
98
+ end
99
+
100
+ def method_missing(meth, *args, &block)
101
+ if !loaded?
102
+ self.loaded = true
103
+ reload
104
+ self.send(meth, *args, &block)
105
+ else
106
+ super
107
+ end
108
+ end
109
+
110
+ def numeric?(object)
111
+ true if Float(object) rescue false
112
+ end
113
+ end
114
+ end
@@ -1,11 +1,11 @@
1
1
  # encoding: utf-8
2
-
2
+ require 'ostruct'
3
3
  module Jiralicious
4
4
  module Configuration
5
5
  VALID_OPTIONS = [:username, :password, :uri, :api_version, :auth_type]
6
6
  DEFAULT_USERNAME = nil
7
- DEFAULT_AUTH_TYPE = :basic
8
7
  DEFAULT_PASSWORD = nil
8
+ DEFAULT_AUTH_TYPE = :basic
9
9
  DEFAULT_URI = nil
10
10
  DEFAULT_API_VERSION = "latest"
11
11
 
@@ -33,5 +33,16 @@ module Jiralicious
33
33
  self.api_version = DEFAULT_API_VERSION
34
34
  self.auth_type = DEFAULT_AUTH_TYPE
35
35
  end
36
+
37
+ def load_yml(yml_file)
38
+ if File.exist?(yml_file)
39
+ yml_cfg = OpenStruct.new(YAML.load_file(yml_file))
40
+ yml_cfg.jira.each do |k, v|
41
+ instance_variable_set("@#{k}", v)
42
+ end
43
+ else
44
+ reset
45
+ end
46
+ end
36
47
  end
37
48
  end
@@ -48,9 +48,9 @@ module Jiralicious
48
48
  end
49
49
 
50
50
  self.request(:post, '/rest/auth/latest/session',
51
- :body => { :username => Jiralicious.username,
52
- :password => Jiralicious.password}.to_json,
53
- :handler => handler)
51
+ :body => { :username => Jiralicious.username,
52
+ :password => Jiralicious.password}.to_json,
53
+ :handler => handler)
54
54
 
55
55
  end
56
56
 
@@ -77,8 +77,8 @@ module Jiralicious
77
77
  def captcha_required(response)
78
78
  response.code == 401 &&
79
79
  # Fakeweb lowercases headers automatically. :(
80
- (response.headers["X-Seraph-LoginReason"] == "AUTHENTICATION_DENIED" ||
81
- response.headers["x-seraph-loginreason"] == "AUTHENTICATION_DENIED")
80
+ (response.headers["X-Seraph-LoginReason"] == "AUTHENTICATION_DENIED" ||
81
+ response.headers["x-seraph-loginreason"] == "AUTHENTICATION_DENIED")
82
82
  end
83
83
 
84
84
  def cookie_invalid(response)
@@ -0,0 +1,27 @@
1
+ # To change this template, choose Tools | Templates
2
+ # and open the template in the editor.
3
+ module Jiralicious
4
+ class CustomFieldOption < Jiralicious::Base
5
+ def initialize(decoded_json, default = nil, &blk)
6
+ @loaded = false
7
+ if decoded_json.is_a? Hash
8
+ properties_from_hash(decoded_json)
9
+ super(decoded_json)
10
+ parse!(decoded_json)
11
+ @loaded = true
12
+ end
13
+ end
14
+
15
+ class << self
16
+ def endpoint_name
17
+ "customFieldOption"
18
+ end
19
+
20
+ def find(id, options = {})
21
+ response = fetch({:key => id})
22
+ response.parsed_response['id'] = id
23
+ new(response.parsed_response)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ # To change this template, choose Tools | Templates
2
+ # and open the template in the editor.
3
+ module Jiralicious
4
+ class Field < Jiralicious::Base
5
+
6
+ def initialize(decoded_json, default = nil, &blk)
7
+ @loaded = false
8
+ if decoded_json.is_a? Hash
9
+ decoded_json = properties_from_hash(decoded_json)
10
+ super(decoded_json)
11
+ parse!(decoded_json)
12
+ self.each do |k, v|
13
+ if v.is_a? Hash
14
+ self[k] = self.class.new(v)
15
+ elsif v.is_a? Array
16
+ v.each_index do |i|
17
+ if v[i].is_a? Hash
18
+ v[i] = self.class.new(v[i])
19
+ end
20
+ end
21
+ self[k] = v
22
+ end
23
+ end
24
+ @loaded = true
25
+ else
26
+ decoded_json.each do |list|
27
+ if numeric? list['id']
28
+ id = :"id_#{list['id']}"
29
+ else
30
+ id = :"#{list['id']}"
31
+ end
32
+ self.class.property id
33
+ out self.class.new(list)
34
+ self.merge!({id => out})
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,7 +1,6 @@
1
1
  # encoding: utf-8
2
2
  module Jiralicious
3
- class Issue < Hashie::Trash
4
- include Jiralicious::Parsers::FieldParser
3
+ class Issue < Jiralicious::Base
5
4
 
6
5
  property :jira_key, :from => :key
7
6
  property :expand
@@ -9,40 +8,126 @@ module Jiralicious
9
8
  property :fields
10
9
  property :transitions
11
10
  property :id
11
+ attr_accessor :fields
12
+ attr_accessor :comments
13
+ attr_accessor :watchers
14
+ attr_accessor :createmeta
15
+ attr_accessor :editmeta
12
16
 
13
- def initialize(decoded_json, default = nil, &blk)
14
- super(decoded_json)
15
- parse!(decoded_json["fields"])
17
+ ### Initialization ###
18
+ def initialize(decoded_json = nil, default = nil, &blk)
19
+ @loaded = false
20
+ if (!decoded_json.nil?)
21
+ super(decoded_json)
22
+ parse!(decoded_json["fields"])
23
+ if default.nil?
24
+ @fields = Fields.new(self['fields']) if self['fields']
25
+ @comments = Comment.find_by_key(self.jira_key)
26
+ @watchers = Watchers.find_by_key(self.jira_key)
27
+ @loaded = true
28
+ end
29
+ end
30
+ @fields = Fields.new if @fields.nil?
31
+ @comments = Comment.new if @comments.nil?
32
+ @watchers = Watchers.new if @watchers.nil?
33
+ @createmeta = nil
34
+ @editmeta = nil
35
+ end
36
+
37
+ def load(decoded_hash, default = nil)
38
+ decoded_hash.each do |k,v|
39
+ self[:"#{k}"] = v
40
+ end
41
+ if default.nil?
42
+ parse!(self['fields'])
43
+ @fields = Fields.new(self['fields']) if self['fields']
44
+ @comments = Comment.find_by_key(self.jira_key)
45
+ @watchers = Watchers.find_by_key(self.jira_key)
46
+ @loaded = true
47
+ else
48
+ parse!(decoded_hash)
49
+ end
50
+ end
51
+
52
+ def reload
53
+ load(self.class.find(self.jira_key, {:reload => true}).parsed_response)
54
+ end
55
+
56
+
57
+ ### Class Methods ###
58
+ class << self
59
+ def assignee(name, key)
60
+ name = {"name" => name} if name.is_a? String
61
+ fetch({:method => :put, :key => "#{key}/assignee", :body => name})
62
+ end
63
+
64
+ def create(issue)
65
+ fetch({:method => :post, :body => issue})
66
+ end
67
+
68
+ def remove(key, options = {})
69
+ fetch({:method => :delete, :body_to_params => true, :key => key, :body => options})
70
+ end
71
+
72
+ def update(issue, key)
73
+ fetch({:method => :put, :key => key, :body => issue})
74
+ end
75
+
76
+ def createmeta(projectkeys, issuetypeids = nil)
77
+ response = fetch({:body_to_params => true, :key => "createmeta", :body => {:expand => "projects.issuetypes.fields.", :projectKeys => projectkeys, :issuetypeIds => issuetypeids}})
78
+ return Field.new(response.parsed_response)
79
+ end
80
+
81
+ def editmeta(key)
82
+ response = fetch({:key => "#{key}/editmeta"})
83
+ response.parsed_response["key"] = key
84
+ Field.new(response.parsed_response)
85
+ end
86
+
87
+ def get_transitions(transitions_url)
88
+ Jiralicious.session.request(:get, transitions_url, :handler => handler)
89
+ end
90
+
91
+ def transition(transitions_url, data)
92
+ Jiralicious.session.request(:post, transitions_url,
93
+ :handler => handler,
94
+ :body => data.to_json)
95
+ end
96
+ end
97
+
98
+ ### Public Classes ###
99
+
100
+ def set_assignee(name)
101
+ self.class.assignee(name, self.jira_key)
16
102
  end
17
103
 
18
- def self.find(key, options = {})
19
- response = Jiralicious.session.request(:get, "#{Jiralicious.rest_path}/issue/#{key}", :handler => handler)
20
- new(response)
104
+ def remove(options = {})
105
+ self.class.remove(self.jira_key, options)
21
106
  end
22
107
 
23
- def self.get_transitions(transitions_url)
24
- Jiralicious.session.request(:get, transitions_url, :handler => handler)
108
+ def createmeta
109
+ if @createmeta.nil?
110
+ @createmeta = self.class.createmeta(self.jira_key.split("-")[0])
111
+ end
112
+ @createmeta
25
113
  end
26
114
 
27
- def self.transition(transitions_url, data)
28
- Jiralicious.session.request(:post, transitions_url,
29
- :handler => handler,
30
- :body => data.to_json)
115
+ def editmeta
116
+ if @editmeta.nil?
117
+ @editmeta = self.class.editmeta(self.jira_key)
118
+ end
119
+ @editmeta
31
120
  end
32
121
 
33
- def self.handler
34
- Proc.new do |response|
35
- case response.code
36
- when 200..204
37
- response
38
- when 400
39
- raise Jiralicious::TransitionError.new(response['errorMessages'].join('\n'))
40
- when 404
41
- raise Jiralicious::IssueNotFound.new(response['errorMessages'].join('\n'))
42
- else
43
- raise Jiralicious::JiraError.new(response['errorMessages'].join('\n'))
44
- end
122
+ def save
123
+ if loaded?
124
+ self.class.update(@fields.format_for_update, self.jira_key)
125
+ key = self.jira_key
126
+ else
127
+ response = self.class.create(@fields.format_for_create)
128
+ key = response.parsed_response['key']
45
129
  end
130
+ load(self.class.find(key, {:reload => true}).parsed_response)
46
131
  end
47
132
  end
48
133
  end