jira-ruby 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +259 -0
- data/Rakefile +9 -0
- data/example.rb +47 -4
- data/jira-ruby.gemspec +5 -3
- data/lib/jira.rb +21 -7
- data/lib/jira/base.rb +466 -0
- data/lib/jira/base_factory.rb +49 -0
- data/lib/jira/client.rb +79 -8
- data/lib/jira/has_many_proxy.rb +43 -0
- data/lib/jira/http_error.rb +16 -0
- data/lib/jira/resource/attachment.rb +12 -0
- data/lib/jira/resource/comment.rb +14 -0
- data/lib/jira/resource/component.rb +4 -9
- data/lib/jira/resource/issue.rb +49 -5
- data/lib/jira/resource/issuetype.rb +10 -0
- data/lib/jira/resource/priority.rb +10 -0
- data/lib/jira/resource/project.rb +24 -3
- data/lib/jira/resource/status.rb +10 -0
- data/lib/jira/resource/user.rb +14 -0
- data/lib/jira/resource/version.rb +10 -0
- data/lib/jira/resource/worklog.rb +16 -0
- data/lib/jira/version.rb +2 -2
- data/spec/integration/attachment_spec.rb +26 -0
- data/spec/integration/comment_spec.rb +55 -0
- data/spec/integration/component_spec.rb +25 -52
- data/spec/integration/issue_spec.rb +50 -47
- data/spec/integration/issuetype_spec.rb +27 -0
- data/spec/integration/priority_spec.rb +27 -0
- data/spec/integration/project_spec.rb +32 -24
- data/spec/integration/status_spec.rb +27 -0
- data/spec/integration/user_spec.rb +25 -0
- data/spec/integration/version_spec.rb +43 -0
- data/spec/integration/worklog_spec.rb +55 -0
- data/spec/jira/base_factory_spec.rb +46 -0
- data/spec/jira/base_spec.rb +555 -0
- data/spec/jira/client_spec.rb +12 -12
- data/spec/jira/has_many_proxy_spec.rb +45 -0
- data/spec/jira/{resource/http_error_spec.rb → http_error_spec.rb} +1 -1
- data/spec/jira/resource/attachment_spec.rb +20 -0
- data/spec/jira/resource/issue_spec.rb +83 -0
- data/spec/jira/resource/project_factory_spec.rb +3 -3
- data/spec/jira/resource/project_spec.rb +28 -0
- data/spec/jira/resource/worklog_spec.rb +24 -0
- data/spec/mock_responses/attachment/10000.json +20 -0
- data/spec/mock_responses/component/10000.invalid.put.json +5 -0
- data/spec/mock_responses/issue.json +1108 -0
- data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
- data/spec/mock_responses/issue/10002.json +13 -1
- data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
- data/spec/mock_responses/issue/10002/comment.json +65 -0
- data/spec/mock_responses/issue/10002/comment.post.json +29 -0
- data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
- data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
- data/spec/mock_responses/issue/10002/worklog.json +98 -0
- data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
- data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
- data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
- data/spec/mock_responses/issuetype.json +42 -0
- data/spec/mock_responses/issuetype/5.json +8 -0
- data/spec/mock_responses/priority.json +42 -0
- data/spec/mock_responses/priority/1.json +8 -0
- data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
- data/spec/mock_responses/project/SAMPLEPROJECT.json +15 -1
- data/spec/mock_responses/status.json +37 -0
- data/spec/mock_responses/status/1.json +7 -0
- data/spec/mock_responses/user?username=admin.json +17 -0
- data/spec/mock_responses/version.post.json +7 -0
- data/spec/mock_responses/version/10000.invalid.put.json +5 -0
- data/spec/mock_responses/version/10000.json +11 -0
- data/spec/mock_responses/version/10000.put.json +7 -0
- data/spec/spec_helper.rb +7 -12
- data/spec/support/matchers/have_attributes.rb +11 -0
- data/spec/support/matchers/have_many.rb +9 -0
- data/spec/support/matchers/have_one.rb +5 -0
- data/spec/support/shared_examples/integration.rb +174 -0
- metadata +139 -24
- data/README.markdown +0 -81
- data/lib/jira/resource/base.rb +0 -148
- data/lib/jira/resource/base_factory.rb +0 -44
- data/lib/jira/resource/http_error.rb +0 -17
- data/spec/jira/resource/base_factory_spec.rb +0 -36
- data/spec/jira/resource/base_spec.rb +0 -292
@@ -0,0 +1,49 @@
|
|
1
|
+
module JIRA
|
2
|
+
|
3
|
+
# This is the base class for all the JIRA resource factory instances.
|
4
|
+
class BaseFactory
|
5
|
+
|
6
|
+
attr_reader :client
|
7
|
+
|
8
|
+
def initialize(client)
|
9
|
+
@client = client
|
10
|
+
end
|
11
|
+
|
12
|
+
# Return the name of the class which this factory generates, i.e.
|
13
|
+
# JIRA::Resource::FooFactory creates JIRA::Resource::Foo instances.
|
14
|
+
def target_class
|
15
|
+
# Need to do a little bit of work here as Module.const_get doesn't work
|
16
|
+
# with nested class names, i.e. JIRA::Resource::Foo.
|
17
|
+
#
|
18
|
+
# So create a method chain from the class componenets. This code will
|
19
|
+
# unroll to:
|
20
|
+
# Module.const_get('JIRA').const_get('Resource').const_get('Foo')
|
21
|
+
#
|
22
|
+
target_class_name = self.class.name.sub(/Factory$/, '')
|
23
|
+
class_components = target_class_name.split('::')
|
24
|
+
|
25
|
+
class_components.inject(Module) do |mod, const_name|
|
26
|
+
mod.const_get(const_name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.delegate_to_target_class(*method_names)
|
31
|
+
method_names.each do |method_name|
|
32
|
+
define_method method_name do |*args|
|
33
|
+
target_class.send(method_name, @client, *args)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# The priciple purpose of this class is to delegate methods to the corresponding
|
39
|
+
# non-factory class and automatically prepend the client argument to the argument
|
40
|
+
# list.
|
41
|
+
delegate_to_target_class :all, :find, :collection_path, :singular_path
|
42
|
+
|
43
|
+
# This method needs special handling as it has a default argument value
|
44
|
+
def build(attrs={})
|
45
|
+
target_class.build(@client, attrs)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
data/lib/jira/client.rb
CHANGED
@@ -2,19 +2,44 @@ require 'oauth'
|
|
2
2
|
require 'json'
|
3
3
|
require 'forwardable'
|
4
4
|
|
5
|
-
module
|
5
|
+
module JIRA
|
6
|
+
|
7
|
+
# This class is the main access point for all JIRA::Resource instances.
|
8
|
+
#
|
9
|
+
# The client must be initialized with a consumer_key and consumer secret,
|
10
|
+
# and an optional hash of extra configuration options. The available options
|
11
|
+
# are:
|
12
|
+
#
|
13
|
+
# :site => 'http://localhost:2990',
|
14
|
+
# :signature_method => 'RSA-SHA1',
|
15
|
+
# :request_token_path => "/jira/plugins/servlet/oauth/request-token",
|
16
|
+
# :authorize_path => "/jira/plugins/servlet/oauth/authorize",
|
17
|
+
# :access_token_path => "/jira/plugins/servlet/oauth/access-token",
|
18
|
+
# :private_key_file => "rsakey.pem",
|
19
|
+
# :rest_base_path => "/jira/rest/api/2"
|
20
|
+
#
|
21
|
+
#
|
22
|
+
# See the JIRA::Base class methods for all of the available methods on these accessor
|
23
|
+
# objects.
|
24
|
+
#
|
6
25
|
class Client
|
7
26
|
|
8
27
|
extend Forwardable
|
9
28
|
|
29
|
+
# This exception is thrown when the client is used before the OAuth access token
|
30
|
+
# has been initialized.
|
10
31
|
class UninitializedAccessTokenError < StandardError
|
11
32
|
def message
|
12
33
|
"init_access_token must be called before using the client"
|
13
34
|
end
|
14
35
|
end
|
15
36
|
|
37
|
+
# The OAuth::Consumer instance used by this client
|
16
38
|
attr_accessor :consumer
|
39
|
+
|
40
|
+
# The configuration options for this client instance
|
17
41
|
attr_reader :options
|
42
|
+
|
18
43
|
delegate [:key, :secret, :get_request_token] => :consumer
|
19
44
|
|
20
45
|
DEFAULT_OPTIONS = {
|
@@ -35,34 +60,74 @@ module Jira
|
|
35
60
|
@consumer = OAuth::Consumer.new(consumer_key,consumer_secret,options)
|
36
61
|
end
|
37
62
|
|
38
|
-
def Project
|
39
|
-
|
63
|
+
def Project # :nodoc:
|
64
|
+
JIRA::Resource::ProjectFactory.new(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
def Issue # :nodoc:
|
68
|
+
JIRA::Resource::IssueFactory.new(self)
|
69
|
+
end
|
70
|
+
|
71
|
+
def Component # :nodoc:
|
72
|
+
JIRA::Resource::ComponentFactory.new(self)
|
73
|
+
end
|
74
|
+
|
75
|
+
def User # :nodoc:
|
76
|
+
JIRA::Resource::UserFactory.new(self)
|
77
|
+
end
|
78
|
+
|
79
|
+
def Issuetype # :nodoc:
|
80
|
+
JIRA::Resource::IssuetypeFactory.new(self)
|
81
|
+
end
|
82
|
+
|
83
|
+
def Priority # :nodoc:
|
84
|
+
JIRA::Resource::PriorityFactory.new(self)
|
85
|
+
end
|
86
|
+
|
87
|
+
def Status # :nodoc:
|
88
|
+
JIRA::Resource::StatusFactory.new(self)
|
89
|
+
end
|
90
|
+
|
91
|
+
def Comment # :nodoc:
|
92
|
+
JIRA::Resource::CommentFactory.new(self)
|
93
|
+
end
|
94
|
+
|
95
|
+
def Attachment # :nodoc:
|
96
|
+
JIRA::Resource::AttachmentFactory.new(self)
|
40
97
|
end
|
41
98
|
|
42
|
-
def
|
43
|
-
|
99
|
+
def Worklog # :nodoc:
|
100
|
+
JIRA::Resource::WorklogFactory.new(self)
|
44
101
|
end
|
45
102
|
|
46
|
-
def
|
47
|
-
|
103
|
+
def Version # :nodoc:
|
104
|
+
JIRA::Resource::VersionFactory.new(self)
|
48
105
|
end
|
49
106
|
|
107
|
+
# Returns the current request token if it is set, else it creates
|
108
|
+
# and sets a new token.
|
50
109
|
def request_token
|
51
110
|
@request_token ||= get_request_token
|
52
111
|
end
|
53
112
|
|
113
|
+
# Sets the request token from a given token and secret.
|
54
114
|
def set_request_token(token, secret)
|
55
115
|
@request_token = OAuth::RequestToken.new(@consumer, token, secret)
|
56
116
|
end
|
57
117
|
|
118
|
+
# Initialises and returns a new access token from the params hash
|
119
|
+
# returned by the OAuth transaction.
|
58
120
|
def init_access_token(params)
|
59
121
|
@access_token = request_token.get_access_token(params)
|
60
122
|
end
|
61
123
|
|
124
|
+
# Sets the access token from a preexisting token and secret.
|
62
125
|
def set_access_token(token, secret)
|
63
126
|
@access_token = OAuth::AccessToken.new(@consumer, token, secret)
|
64
127
|
end
|
65
128
|
|
129
|
+
# Returns the current access token. Raises an
|
130
|
+
# JIRA::Client::UninitializedAccessTokenError exception if it is not set.
|
66
131
|
def access_token
|
67
132
|
raise UninitializedAccessTokenError.new unless @access_token
|
68
133
|
@access_token
|
@@ -89,9 +154,15 @@ module Jira
|
|
89
154
|
request(:put, path, body, merge_default_headers(headers))
|
90
155
|
end
|
91
156
|
|
157
|
+
# Sends the specified HTTP request to the REST API through the
|
158
|
+
# OAuth token.
|
159
|
+
#
|
160
|
+
# Returns the response if the request was successful (HTTP::2xx) and
|
161
|
+
# raises a JIRA::HTTPError if it was not successful, with the response
|
162
|
+
# attached.
|
92
163
|
def request(http_method, path, *arguments)
|
93
164
|
response = access_token.request(http_method, path, *arguments)
|
94
|
-
raise
|
165
|
+
raise HTTPError.new(response) unless response.kind_of?(Net::HTTPSuccess)
|
95
166
|
response
|
96
167
|
end
|
97
168
|
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#
|
2
|
+
# Whenever a collection from a has_many relationship is accessed, an instance
|
3
|
+
# of this class is returned. This instance wraps the Array of instances in
|
4
|
+
# the collection with an extra build method, which allows new instances to be
|
5
|
+
# built on the collection with the correct properties.
|
6
|
+
#
|
7
|
+
# In practice, instances of this class behave exactly like an Array.
|
8
|
+
#
|
9
|
+
class JIRA::HasManyProxy
|
10
|
+
|
11
|
+
attr_reader :target_class, :parent
|
12
|
+
attr_accessor :collection
|
13
|
+
|
14
|
+
def initialize(parent, target_class, collection = [])
|
15
|
+
@parent = parent
|
16
|
+
@target_class = target_class
|
17
|
+
@collection = collection
|
18
|
+
end
|
19
|
+
|
20
|
+
# Builds an instance of this class with the correct parent.
|
21
|
+
# For example, issue.comments.build(attrs) will initialize a
|
22
|
+
# comment as follows:
|
23
|
+
#
|
24
|
+
# JIRA::Resource::Comment.new(issue.client,
|
25
|
+
# :attrs => attrs,
|
26
|
+
# :issue => issue)
|
27
|
+
def build(attrs = {})
|
28
|
+
resource = target_class.new(parent.client, :attrs => attrs, parent.to_sym => parent)
|
29
|
+
collection << resource
|
30
|
+
resource
|
31
|
+
end
|
32
|
+
|
33
|
+
# Forces an HTTP request to fetch all instances of the target class that
|
34
|
+
# are associated with the parent
|
35
|
+
def all
|
36
|
+
target_class.all(parent.client, parent.to_sym => parent)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Delegate any missing methods to the collection that this proxy wraps
|
40
|
+
def method_missing(method_name, *args, &block)
|
41
|
+
collection.send(method_name, *args, &block)
|
42
|
+
end
|
43
|
+
end
|
@@ -1,15 +1,10 @@
|
|
1
|
-
module
|
1
|
+
module JIRA
|
2
2
|
module Resource
|
3
3
|
|
4
|
-
class ComponentFactory < BaseFactory
|
5
|
-
|
6
|
-
class Component < Base
|
7
|
-
|
8
|
-
def self.key_attribute
|
9
|
-
:id
|
10
|
-
end
|
11
|
-
|
4
|
+
class ComponentFactory < JIRA::BaseFactory # :nodoc:
|
12
5
|
end
|
13
6
|
|
7
|
+
class Component < JIRA::Base ; end
|
8
|
+
|
14
9
|
end
|
15
10
|
end
|
data/lib/jira/resource/issue.rb
CHANGED
@@ -1,12 +1,56 @@
|
|
1
|
-
module
|
1
|
+
module JIRA
|
2
2
|
module Resource
|
3
3
|
|
4
|
-
class IssueFactory < BaseFactory
|
4
|
+
class IssueFactory < JIRA::BaseFactory # :nodoc:
|
5
|
+
end
|
6
|
+
|
7
|
+
class Issue < JIRA::Base
|
8
|
+
|
9
|
+
has_one :reporter, :class => JIRA::Resource::User,
|
10
|
+
:nested_under => 'fields'
|
11
|
+
has_one :assignee, :class => JIRA::Resource::User,
|
12
|
+
:nested_under => 'fields'
|
13
|
+
has_one :project, :nested_under => 'fields'
|
14
|
+
|
15
|
+
has_one :issuetype, :nested_under => 'fields'
|
16
|
+
|
17
|
+
has_one :priority, :nested_under => 'fields'
|
18
|
+
|
19
|
+
has_one :status, :nested_under => 'fields'
|
20
|
+
|
21
|
+
has_many :components, :nested_under => 'fields'
|
5
22
|
|
6
|
-
|
23
|
+
has_many :comments, :nested_under => ['fields','comment']
|
24
|
+
|
25
|
+
has_many :attachments, :nested_under => 'fields',
|
26
|
+
:attribute_key => 'attachment'
|
27
|
+
|
28
|
+
has_many :versions, :nested_under => 'fields'
|
29
|
+
|
30
|
+
has_many :worklogs, :nested_under => ['fields','worklog']
|
31
|
+
|
32
|
+
def self.all(client)
|
33
|
+
response = client.get(client.options[:rest_base_path] + "/search")
|
34
|
+
json = parse_json(response.body)
|
35
|
+
json['issues'].map do |issue|
|
36
|
+
client.Issue.build(issue)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def respond_to?(method_name)
|
41
|
+
if attrs.keys.include?('fields') && attrs['fields'].keys.include?(method_name.to_s)
|
42
|
+
true
|
43
|
+
else
|
44
|
+
super(method_name)
|
45
|
+
end
|
46
|
+
end
|
7
47
|
|
8
|
-
def
|
9
|
-
|
48
|
+
def method_missing(method_name, *args, &block)
|
49
|
+
if attrs.keys.include?('fields') && attrs['fields'].keys.include?(method_name.to_s)
|
50
|
+
attrs['fields'][method_name.to_s]
|
51
|
+
else
|
52
|
+
super(method_name)
|
53
|
+
end
|
10
54
|
end
|
11
55
|
|
12
56
|
end
|
@@ -1,9 +1,30 @@
|
|
1
|
-
module
|
1
|
+
module JIRA
|
2
2
|
module Resource
|
3
3
|
|
4
|
-
class ProjectFactory < BaseFactory
|
4
|
+
class ProjectFactory < JIRA::BaseFactory # :nodoc:
|
5
|
+
end
|
5
6
|
|
6
|
-
class Project < Base
|
7
|
+
class Project < JIRA::Base
|
8
|
+
|
9
|
+
has_one :lead, :class => JIRA::Resource::User
|
10
|
+
has_many :components
|
11
|
+
has_many :issuetypes, :attribute_key => 'issueTypes'
|
12
|
+
has_many :versions
|
13
|
+
|
14
|
+
def self.key_attribute
|
15
|
+
:key
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns all the issues for this project
|
19
|
+
def issues
|
20
|
+
response = client.get(client.options[:rest_base_path] + "/search?jql=project%3D'#{key}'")
|
21
|
+
json = self.class.parse_json(response.body)
|
22
|
+
json['issues'].map do |issue|
|
23
|
+
client.Issue.build(issue)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
7
28
|
|
8
29
|
end
|
9
30
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module JIRA
|
2
|
+
module Resource
|
3
|
+
|
4
|
+
class UserFactory < JIRA::BaseFactory # :nodoc:
|
5
|
+
end
|
6
|
+
|
7
|
+
class User < JIRA::Base
|
8
|
+
def self.singular_path(client, key, prefix = '/')
|
9
|
+
collection_path(client, prefix) + '?username=' + key
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|