rujira 0.5.1 → 0.7.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab245fcbc9d445b1ffa59c05d66e2b330375f9883d7e8949a4205af02f495824
4
- data.tar.gz: 3f7d40b25d30fe5b6f966de78d283604f1d66c23cd9d34c3f433c5f722a7c103
3
+ metadata.gz: 0f34a8819ae3c7e4746ba9230b4a90fc4e12bbd07511b637bc9f525e154658cb
4
+ data.tar.gz: 0ae20244cf2b25fff933d644499d749d62e41a69fefe71f98c884d716935e6ce
5
5
  SHA512:
6
- metadata.gz: c90e9d4ac246ec1c39a42dc618450bd6d8a3bda5e8095edb229919b2a5fc89d001c72a986d4c1465d9155ff64785f0439591e37fce9f79041611f231719e6a8f
7
- data.tar.gz: ec925b95ca7d73c228cde2b543ef77eee66b049614d3665f66cdc4f7af6913b1ac041581266094daaa645c03df86c4e188a5cd3b58818b01055b4abba9ac5238
6
+ metadata.gz: 0a7f45cf6ca2ec3b6e812d1acc0c4286453da1b0f5695f19c4bb73e27e5b12f2ed6b3c13a0b8f3c887035c93fb4d84c4bdcb209e1374bcfd42d259d843f966d4
7
+ data.tar.gz: '07279fa66131a36441a959028e7d41ee6cffade569f4fe251db24dff47ba412452810f1930c321a7c999aa9dbf1f77414f31f3f345c66f4425721f5d8e64a608'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## [0.7.0] - 2025-10-01
2
+
3
+ ### 🚀 Features
4
+
5
+ - Added project as obj
6
+
7
+ ### 🚜 Refactor
8
+
9
+ - Tuning for tests
10
+
11
+ ### 📚 Documentation
12
+
13
+ - Updated commentaries for resources
14
+ ## [0.6.0] - 2025-09-17
15
+
16
+ ### 🚀 Features
17
+
18
+ - Added addapter method
19
+ - Commitable style
20
+ - Response object added
21
+
22
+ ### 🚜 Refactor
23
+
24
+ - Changes for a client
1
25
  ## [0.5.1] - 2025-09-15
2
26
 
3
27
  ### 🚜 Refactor
@@ -62,23 +86,3 @@
62
86
  - Cleanup data method
63
87
  - Abort if id is nil #2
64
88
  - Abort if id is nil
65
-
66
- ### 📚 Documentation
67
-
68
- - Use ENV for URL getting
69
- - Added some docs
70
- ## [0.3.0] - 2025-09-13
71
-
72
- ### 🚀 Features
73
-
74
- - Added client supports
75
-
76
- ### 🚜 Refactor
77
-
78
- - Updates tasks
79
- - Use params
80
- - Added examples
81
-
82
- ### Reafactor
83
-
84
- - How to search sprints
data/README.md CHANGED
@@ -1,7 +1,11 @@
1
1
  # RUJIRA
2
2
 
3
+ [![Ruby](https://github.com/itmagelab/rujira/actions/workflows/ruby.yml/badge.svg)](https://github.com/itmagelab/rujira/actions/workflows/ruby.yml)
4
+
3
5
  **RUJIRA** is a Ruby gem for easy interaction with the Jira API. It provides a simple and flexible interface to work with Jira resources and includes Rake tasks for convenient command-line operations.
4
6
 
7
+ This project was created as an alternative to <https://github.com/sumoheavy/jira-ruby>, offering a more user-friendly and intuitive interface. It lets you work with requests as objects or hash arrays, provides flexibility in choosing a connection adapter, and makes the codebase easier to maintain.
8
+
5
9
  ---
6
10
 
7
11
  ## Features
@@ -16,7 +20,7 @@
16
20
  Add to your `Gemfile`:
17
21
 
18
22
  ```ruby
19
- gem 'rujira', '~> 0.1.0'
23
+ gem 'rujira', '~> 0.7.0'
20
24
  ```
21
25
 
22
26
  Or install directly:
@@ -33,13 +37,31 @@ gem install rujira
33
37
 
34
38
  ```ruby
35
39
  ❯ cat .env
36
- RUJIRA_DEBUG=false
37
40
  RUJIRA_TOKEN='<TOKEN>'
38
- RUJIRA_URL='http://localhost:8080'
41
+ RUJIRA_DEBUG=true
42
+ LOG_LEVEL=error
39
43
  ```
40
44
 
41
45
  ### Example of usage
42
46
 
47
+ By default, we can use an object-oriented approach, but this method does not cover all API capabilities.
48
+
49
+ ```ruby
50
+ url = ENV.fetch('RUJIRA_URL', 'http://localhost:8080')
51
+ client = Rujira::Client.new(url, dispatchable: false)
52
+
53
+ project = random_name
54
+ me = client.Myself.get
55
+ project = me.create_software_project key: project.to_s,
56
+ name: project.to_s
57
+ task = project.add_task summary: 'BOT: added a new task.',
58
+ description: 'This task was generated by the bot when creating changes in the repository.'
59
+ task.add_comment 'Bot added a comment as obj #1'
60
+ task.attach_file '/tmp/upload.file'
61
+ ```
62
+
63
+ Alternatively, we can work with the API directly, as with a regular request dispatcher; in this form, most operations are available.
64
+
43
65
  ```ruby
44
66
  require 'date'
45
67
 
data/Rakefile CHANGED
@@ -23,3 +23,11 @@ end
23
23
  task :version do
24
24
  puts Rujira::VERSION
25
25
  end
26
+
27
+ desc 'Cleanup test installation'
28
+ task :cleanup do
29
+ url = ENV.fetch('RUJIRA_URL', 'http://localhost:8080')
30
+ client = Rujira::Client.new(url, dispatchable: false)
31
+ projects = client.Project.list
32
+ projects.map(&:delete)
33
+ end
data/compose.yaml CHANGED
@@ -13,7 +13,7 @@ services:
13
13
 
14
14
  db:
15
15
  image: postgres
16
- mem_limit: 128mb
16
+ mem_limit: 128mb
17
17
  restart: always
18
18
  shm_size: 128mb
19
19
  environment:
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dotenv'
4
+ require_relative '../lib/rujira'
5
+
6
+ Dotenv.load
7
+
8
+ client = Rujira::Client.new('http://localhost:8080', dispatchable: false)
9
+
10
+ project_name = 'TEST124'
11
+ project_key = project_name
12
+
13
+ issue = client.Issue.create do
14
+ payload fields: {
15
+ project: { key: project_key },
16
+ summary: 'BOT: added a new feature.',
17
+ description: 'This task was generated by the bot when creating changes in the repository.',
18
+ issuetype: { name: 'Task' }
19
+ }
20
+ params updateHistory: true
21
+ end
22
+
23
+ puts "Issue create #{issue.key}"
24
+
25
+ issue.add_comment('Bot added a comment as obj #1')
26
+ comment = issue.add_comment('Bot added a comment as obj #2')
27
+ comment.delete
28
+
29
+ issue.add_comment('Bot added a comment as obj #3')
30
+
31
+ issue.delete
@@ -7,21 +7,15 @@ Dotenv.load
7
7
 
8
8
  client = Rujira::Client.new('http://localhost:8080', dispatchable: true)
9
9
 
10
- project_name = 'EXAMPLE2'
11
- myself = client.Myself.get
12
-
10
+ project_name = 'TEST124'
11
+ project_key = project_name
13
12
  name = client.Myself.get['name']
14
13
 
15
- begin
16
- project = client.Project.create do
17
- payload key: project_name.to_s,
18
- name: project_name.to_s,
19
- projectTypeKey: 'software',
20
- lead: name
21
- end
22
- rescue StandardError
23
- projects = client.Project.list
24
- project = projects.find { |p| p['name'] == project_name }
14
+ project = client.Project.create do
15
+ payload key: project_key,
16
+ name: project_name.to_s,
17
+ projectTypeKey: 'software',
18
+ lead: name
25
19
  end
26
20
 
27
21
  client.Issue.create do
@@ -43,7 +37,7 @@ sprint = client.Sprint.create do
43
37
  goal: 'Finish core features for release 1.0',
44
38
  startDate: now,
45
39
  endDate: before,
46
- autoStartStop: true
40
+ autoStartStop: false
47
41
  end
48
42
 
49
43
  client.Sprint.update sprint['id'] do
@@ -8,7 +8,7 @@ module Rujira
8
8
  # API reference:
9
9
  # https://docs.atlassian.com/jira-software/REST/9.17.0/#agile/1.0/board
10
10
  #
11
- class Board < Common
11
+ class Board < Common # rubocop:disable Metrics/ClassLength
12
12
  # Initializes a new Board API client.
13
13
  #
14
14
  # @param [Object] client The HTTP client instance used to perform requests.
@@ -63,6 +63,145 @@ module Rujira
63
63
  call
64
64
  end
65
65
 
66
+ def create(&block)
67
+ builder do
68
+ method :post
69
+ path 'board'
70
+ instance_eval(&block) if block_given?
71
+ end
72
+ call
73
+ end
74
+
75
+ def delete(id, &block)
76
+ abort 'Board ID is required' if id.to_s.strip.empty?
77
+ builder do
78
+ method :delete
79
+ path "board/#{id}"
80
+ instance_eval(&block) if block_given?
81
+ end
82
+ call
83
+ end
84
+
85
+ def backlog(id, &block)
86
+ abort 'Board ID is required' if id.to_s.strip.empty?
87
+ builder do
88
+ path "board/#{id}/backlog"
89
+ instance_eval(&block) if block_given?
90
+ end
91
+ call
92
+ end
93
+
94
+ def configuration(id, &block)
95
+ abort 'Board ID is required' if id.to_s.strip.empty?
96
+ builder do
97
+ path "board/#{id}/configuration"
98
+ instance_eval(&block) if block_given?
99
+ end
100
+ call
101
+ end
102
+
103
+ def issue(id, &block)
104
+ abort 'Board ID is required' if id.to_s.strip.empty?
105
+ builder do
106
+ path "board/#{id}/issue"
107
+ instance_eval(&block) if block_given?
108
+ end
109
+ call
110
+ end
111
+
112
+ def epic(id, &block)
113
+ abort 'Board ID is required' if id.to_s.strip.empty?
114
+ builder do
115
+ path "board/#{id}/epic"
116
+ instance_eval(&block) if block_given?
117
+ end
118
+ call
119
+ end
120
+
121
+ def epic_issues(id, epic_id, &block)
122
+ abort 'Board ID is required' if id.to_s.strip.empty?
123
+ builder do
124
+ path "board/#{id}/epic/#{epic_id}/issue"
125
+ instance_eval(&block) if block_given?
126
+ end
127
+ call
128
+ end
129
+
130
+ def epic_none_issues(id, &block)
131
+ abort 'Board ID is required' if id.to_s.strip.empty?
132
+ builder do
133
+ path "board/#{id}/epic/none/issue"
134
+ instance_eval(&block) if block_given?
135
+ end
136
+ call
137
+ end
138
+
139
+ def project(id, &block)
140
+ abort 'Board ID is required' if id.to_s.strip.empty?
141
+ builder do
142
+ path "board/#{id}/project"
143
+ instance_eval(&block) if block_given?
144
+ end
145
+ call
146
+ end
147
+
148
+ def settings(id, &block)
149
+ abort 'Board ID is required' if id.to_s.strip.empty?
150
+ builder do
151
+ path "board/#{id}/settings/refined-velocity"
152
+ instance_eval(&block) if block_given?
153
+ end
154
+ call
155
+ end
156
+
157
+ def set_settings(id, &block)
158
+ abort 'Board ID is required' if id.to_s.strip.empty?
159
+ builder do
160
+ method :put
161
+ path "board/#{id}/settings/refined-velocity"
162
+ instance_eval(&block) if block_given?
163
+ end
164
+ call
165
+ end
166
+
167
+ def properties(id, &block)
168
+ abort 'Board ID is required' if id.to_s.strip.empty?
169
+ builder do
170
+ path "board/#{id}/properties"
171
+ instance_eval(&block) if block_given?
172
+ end
173
+ call
174
+ end
175
+
176
+ def set_properties(id, property_key, &block)
177
+ abort 'board id is required' if id.to_s.strip.empty?
178
+ builder do
179
+ method :put
180
+ path "board/#{id}/properties/#{property_key}"
181
+ instance_eval(&block) if block_given?
182
+ end
183
+ call
184
+ end
185
+
186
+ def get_properties(id, property_key, &block)
187
+ abort 'board id is required' if id.to_s.strip.empty?
188
+ builder do
189
+ path "board/#{id}/properties/#{property_key}"
190
+ instance_eval(&block) if block_given?
191
+ end
192
+ call
193
+ end
194
+
195
+ def delete_properties(id, property_key, &block)
196
+ abort 'board id is required' if id.to_s.strip.empty?
197
+ builder do
198
+ method :delete
199
+ path "board/#{id}/properties/#{property_key}"
200
+ instance_eval(&block) if block_given?
201
+ end
202
+ call
203
+ end
204
+
66
205
  # Retrieves all sprints for a specific board.
67
206
  #
68
207
  # Available query parameters:
@@ -19,6 +19,8 @@ module Rujira
19
19
  # end
20
20
  #
21
21
  def create(id_or_key, &block)
22
+ owned_by id_or_key
23
+
22
24
  abort 'Issue ID or KEY is required' if id_or_key.to_s.strip.empty?
23
25
  builder do
24
26
  path "issue/#{id_or_key}/comment"
@@ -27,6 +29,26 @@ module Rujira
27
29
  end
28
30
  call
29
31
  end
32
+
33
+ # Deletes a comment from an issue.
34
+ #
35
+ # @param [String] id_or_key The issue ID or key.
36
+ # @param [String] id The comment ID.
37
+ # @yield [builder] Optional block to configure additional request parameters.
38
+ # @return [Object] The API response after deleting the comment.
39
+ #
40
+ # @example Delete a comment
41
+ # client.Issue.delete_comment("TEST-123", "10001")
42
+ #
43
+ def delete(id_or_key, id, &block)
44
+ abort 'Issue ID or KEY is required' if id_or_key.to_s.strip.empty?
45
+ builder do
46
+ path "issue/#{id_or_key}/comment/#{id}"
47
+ method :delete
48
+ instance_eval(&block) if block_given?
49
+ end
50
+ call
51
+ end
30
52
  end
31
53
  end
32
54
  end
@@ -13,6 +13,7 @@ module Rujira
13
13
  def initialize(client)
14
14
  # Store the passed client object in an instance variable for later use
15
15
  @client = client
16
+ @metadata ||= {}
16
17
  @request = Request.new.builder do
17
18
  # Set the Bearer token for authorization
18
19
  bearer @token
@@ -40,13 +41,58 @@ module Rujira
40
41
  def call
41
42
  @client.logger.debug "Call the method: #{caller_locations(1, 1)[0].label}"
42
43
  return @client.dispatch(@request) if @client.dispatchable
44
+ return self if @client.lazy
43
45
 
44
- self
46
+ to_obj
45
47
  end
46
48
 
49
+ # Executes the configured request via the client's dispatch mechanism.
50
+ #
51
+ # @return [Object] The raw API response from the dispatched request.
47
52
  def commit
48
53
  @client.dispatch(@request)
49
54
  end
55
+ alias execute commit
56
+
57
+ # Converts the API response into structured Ruby objects.
58
+ # If the response is an array of hashes, maps each element through `process`.
59
+ # If it's a single hash, processes it directly. Otherwise, returns as-is.
60
+ #
61
+ # @return [Object] Processed response as one or more resource objects, or original response.
62
+ def to_obj
63
+ response = execute
64
+
65
+ return response.map { |el| process(el) } if response.is_a?(Array) && response.all?(Hash)
66
+ return response unless response.is_a?(Hash)
67
+
68
+ process response
69
+ end
70
+
71
+ # Processes a single response hash by merging metadata and instantiating
72
+ # the corresponding Resource object based on the current class name.
73
+ #
74
+ # @param [Hash] response The API response hash to process.
75
+ # @return [Object] An instance of the corresponding Resource class.
76
+ # @raise [NameError] If the corresponding Resource class does not exist.
77
+ def process(response)
78
+ response.merge!(@metadata)
79
+ resource_class_name = self.class.name.sub('Api', 'Resource')
80
+ begin
81
+ Object.const_get(resource_class_name).new(@client, **response)
82
+ rescue NameError
83
+ raise "Resource class '#{resource_class_name}' not found. " \
84
+ 'Please ensure the class exists or use dispatchable mode.'
85
+ end
86
+ end
87
+
88
+ # Sets ownership context for the resource by storing a parent ID or key
89
+ # in metadata, which may be used during object instantiation.
90
+ #
91
+ # @param [String, Integer] id_or_key The ID or key of the parent resource.
92
+ # @return [Hash] The updated metadata hash with the parent set.
93
+ def owned_by(id_or_key)
94
+ @metadata.merge!({ parent: id_or_key })
95
+ end
50
96
  end
51
97
  end
52
98
  end
@@ -42,27 +42,6 @@ module Rujira
42
42
  call
43
43
  end
44
44
 
45
- # Adds a new comment to an issue.
46
- #
47
- # @param [String] id_or_key The issue ID or key.
48
- # @yield [builder] Block to configure the payload for the new comment.
49
- # @return [Object] The API response containing the created comment.
50
- #
51
- # @example Add a comment
52
- # client.Issue.add_comment("TEST-123") do
53
- # payload body: "New comment text"
54
- # end
55
- #
56
- def add_comment(id_or_key, &block)
57
- abort 'Issue ID or KEY is required' if id_or_key.to_s.strip.empty?
58
- builder do
59
- path "issue/#{id_or_key}/comment"
60
- method :post
61
- instance_eval(&block) if block_given?
62
- end
63
- call
64
- end
65
-
66
45
  # Retrieves a specific comment by its ID.
67
46
  #
68
47
  # @param [String] id_or_key The issue ID or key.
@@ -173,7 +152,7 @@ module Rujira
173
152
  # payload body: "This is a comment"
174
153
  # end
175
154
  #
176
- def comment(id_or_key, &block)
155
+ def add_comment(id_or_key, &block)
177
156
  abort 'Issue ID or KEY is required' if id_or_key.to_s.strip.empty?
178
157
  @client.Comment.create id_or_key, &block
179
158
  end
data/lib/rujira/client.rb CHANGED
@@ -11,7 +11,9 @@ module Rujira
11
11
  # client.issue.get("TEST-123")
12
12
  #
13
13
  class Client
14
- attr_reader :logger, :dispatchable
14
+ attr_reader :dispatchable, :logger, :lazy
15
+
16
+ SUPPORTED_METHODS = %i[get delete head post put patch].freeze
15
17
 
16
18
  # Initializes a new Jira client.
17
19
  #
@@ -21,8 +23,9 @@ module Rujira
21
23
  # @example Initialize client
22
24
  # client = Rujira::Client.new("https://jira.example.com", debug: true)
23
25
  #
24
- def initialize(url, debug: false, dispatchable: true, log_level: 'error') # rubocop:disable Metrics/MethodLength
26
+ def initialize(url, debug: false, dispatchable: true, lazy: false, log_level: 'error') # rubocop:disable Metrics/MethodLength
25
27
  @dispatchable = dispatchable
28
+ @lazy = lazy
26
29
  @uri = URI(url)
27
30
  @debug = ENV.fetch('RUJIRA_DEBUG', debug.to_s) == 'true'
28
31
  @raise_error = false
@@ -40,6 +43,22 @@ module Rujira
40
43
  ENV.fetch('LOG_LEVEL', log_level).downcase,
41
44
  Logger::ERROR
42
45
  )
46
+
47
+ @adapter = adapter
48
+ end
49
+
50
+ def adapter # rubocop:disable Metrics/MethodLength
51
+ case ENV.fetch('RUJIRA_ADAPTER', nil)
52
+ when 'typhoeus'
53
+ @logger.debug 'Using Typhoeus adapter'
54
+ require 'faraday/typhoeus'
55
+ :typhoeus
56
+ when 'async'
57
+ require 'async/http/faraday'
58
+ :async_http
59
+ else
60
+ Faraday.default_adapter
61
+ end
43
62
  end
44
63
 
45
64
  # Dynamically instantiates the appropriate API resource class.
@@ -68,23 +87,27 @@ module Rujira
68
87
  Rujira::Api.const_defined?(method_name.to_s) || super
69
88
  end
70
89
 
90
+ def build_options(request)
91
+ {
92
+ url: @uri,
93
+ headers: request.headers,
94
+ params: request.params
95
+ }
96
+ end
97
+
71
98
  # Executes the configured request.
72
99
  #
73
100
  # @return [Object] The API response body if successful
74
101
  # @raise [RuntimeError] If the request fails or method is unsupported
75
102
  #
76
103
  def dispatch(request) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
77
- raise "method #{request.method} not supported" unless %i[get delete head post put patch].include?(request.method)
104
+ raise "method #{request.method} not supported" unless SUPPORTED_METHODS.include?(request.method)
78
105
 
79
106
  begin
80
107
  args = [request.path]
81
108
  args << request.payload if %i[post put patch].include?(request.method)
82
109
 
83
- options = {
84
- url: @uri,
85
- headers: request.headers,
86
- params: request.params
87
- }
110
+ options = build_options(request)
88
111
  response = connection(options, request.authorization).public_send(request.method, *args)
89
112
 
90
113
  if response.success?
@@ -94,7 +117,8 @@ module Rujira
94
117
  "and body #{response.body}")
95
118
  end
96
119
  rescue StandardError => e
97
- raise "Error: #{e.class} - #{e.message}"
120
+ @logger.error "Error: #{e.class} - #{e.message}"
121
+ raise
98
122
  end
99
123
  end
100
124
 
@@ -104,6 +128,7 @@ module Rujira
104
128
  #
105
129
  def connection(options, authorization)
106
130
  Faraday.new(options) do |builder|
131
+ builder.adapter @adapter
107
132
  builder.request :authorization, *authorization if authorization
108
133
  builder.request :multipart, flat_encode: true
109
134
  builder.request :json
@@ -106,6 +106,14 @@ module Rujira
106
106
  @authorization = :basic, username, password
107
107
  end
108
108
 
109
+ # Disables any authentication for subsequent requests.
110
+ #
111
+ # @return [void]
112
+ #
113
+ def disable_auth
114
+ @authorization = nil
115
+ end
116
+
109
117
  # Gets or sets the request path (appended to rest_base_path).
110
118
  #
111
119
  # @param [String, nil] path The relative path to set.
@@ -128,5 +136,14 @@ module Rujira
128
136
 
129
137
  @payload = payload
130
138
  end
139
+
140
+ # Extends the current payload by merging the given hash into it.
141
+ #
142
+ # @param [Hash, nil] payload Optional hash to merge into the current payload.
143
+ def extend_payload(payload = nil)
144
+ return @payload if payload.nil?
145
+
146
+ @payload.merge!(payload)
147
+ end
131
148
  end
132
149
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rujira
4
+ module Resource
5
+ # Represents a Jira comment resource.
6
+ # Provides access to comment attributes and actions such as deletion.
7
+ #
8
+ # Example usage:
9
+ # comment = Rujira::Resource::Comment.new(client, data)
10
+ # comment.delete # Deletes the comment via the API
11
+ #
12
+ class Comment < Common
13
+ # @return [String] The ID of the comment
14
+ attr_reader :id
15
+ # @return [String] The issue key or resource key associated with the comment
16
+ attr_reader :key
17
+ # @return [String] The URL to access this comment via Jira REST API
18
+ attr_reader :url
19
+
20
+ # Initializes a Comment resource
21
+ #
22
+ # @param [Object] client The API client instance used to make requests
23
+ # @param [Hash] hash The comment data from the Jira API
24
+ def initialize(client, **args)
25
+ super
26
+
27
+ @url = args['self']
28
+ @id = args['id']
29
+
30
+ @author = args['author']
31
+ @body = args['body']
32
+ @update_author = args['updateAuthor']
33
+ @created = args['created']
34
+ @updated = args['updated']
35
+
36
+ @parent = args[:parent] # The parent issue key or resource
37
+ end
38
+
39
+ # Deletes this comment from Jira.
40
+ #
41
+ # @return [Object] The API response from the delete operation
42
+ #
43
+ # @example Delete a comment
44
+ # comment.delete
45
+ def delete
46
+ @client.Comment.delete(@parent, @id)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rujira
4
+ module Resource
5
+ class Common # rubocop:disable Style/Documentation
6
+ def initialize(client, **_args)
7
+ @client = client
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rujira
4
+ module Resource
5
+ # Represents a Jira issue resource.
6
+ # Provides access to issue attributes and actions such as adding comments.
7
+ #
8
+ # Example usage:
9
+ # issue = Rujira::Resource::Issue.new(client, data)
10
+ # issue.add_comment("This is a comment")
11
+ #
12
+ class Issue < Common
13
+ # @return [String] The ID of the issue
14
+ attr_reader :id
15
+ # @return [String] The key of the issue (e.g., "TEST-123")
16
+ attr_reader :key
17
+ # @return [String] The URL to access this issue via Jira REST API
18
+ attr_reader :url
19
+
20
+ # Initializes an Issue resource
21
+ #
22
+ # @param [Object] client The API client instance used to make requests
23
+ # @param [Hash] hash The issue data from the Jira API
24
+ def initialize(client, **args)
25
+ super
26
+
27
+ @id = args['id']
28
+ @key = args['key']
29
+ @url = args['self']
30
+ end
31
+
32
+ # Adds a comment to this issue.
33
+ #
34
+ # @param [String] text The content of the comment
35
+ # @return [Object] The API response after adding the comment
36
+ #
37
+ # @example Add a comment to an issue
38
+ # issue.add_comment("This is a new comment")
39
+ def add_comment(text)
40
+ @client.logger.debug "Adding comment to issue #{@key}"
41
+ @client.Issue.add_comment(@id) do
42
+ payload body: text
43
+ end
44
+ end
45
+
46
+ def delete
47
+ @client.logger.debug "Deleting issue #{@key}"
48
+ @client.Issue.delete(@id)
49
+ end
50
+
51
+ def attach_file(path)
52
+ @client.Issue.attachments @key, path
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rujira
4
+ module Resource
5
+ # Represents the currently authenticated Jira user (Myself).
6
+ # Provides access to basic user attributes retrieved from the Jira API.
7
+ #
8
+ class Myself < Common
9
+ attr_reader :url, :email, :name, :id, :key
10
+
11
+ # Initializes a Myself resource with data from the Jira API response.
12
+ #
13
+ # @param [Object] client The HTTP client used for API communication.
14
+ # @param [Hash] args The user data hash returned by the Jira API.
15
+ # @option args [String] 'self' The URL of the user's Jira profile.
16
+ # @option args [String] 'key' The user's Jira key.
17
+ # @option args [String] 'name' The user's display name.
18
+ # @option args [String] 'emailAddress' The user's email address.
19
+ def initialize(client, **args)
20
+ super
21
+
22
+ @url = args['self']
23
+
24
+ @key = args['key']
25
+ @name = args['name']
26
+ @email = args['emailAddress']
27
+ end
28
+
29
+ def create_software_project(**args)
30
+ key = args[:key]
31
+ project_name = args[:name]
32
+ name = @name
33
+ @client.Project.create do
34
+ payload key: key,
35
+ name: project_name,
36
+ projectTypeKey: 'software',
37
+ lead: name
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rujira
4
+ module Resource
5
+ # Represents a Jira project.
6
+ # Provides access to project attributes and operations such as deletion.
7
+ #
8
+ class Project < Common
9
+ attr_reader :url, :id, :key
10
+
11
+ # Initializes a Project resource with data from the Jira API response.
12
+ #
13
+ # @param [Object] client The HTTP client used for API communication.
14
+ # @param [Hash] args The project data hash returned by the Jira API.
15
+ # @option args [String] 'self' The URL of the project in Jira.
16
+ # @option args [String] 'id' The internal ID of the project.
17
+ # @option args [String] 'key' The project key (e.g., PROJ).
18
+ def initialize(client, **args)
19
+ super
20
+
21
+ @url = args['self']
22
+ @id = args['id']
23
+ @key = args['key']
24
+ end
25
+
26
+ # Deletes the project from Jira using the client's Project API.
27
+ #
28
+ # @return [Object] The API response from the delete operation.
29
+ # @note This operation is irreversible and should be used with caution.
30
+ def delete
31
+ @client.Project.delete(@id)
32
+ end
33
+
34
+ def add_task(**args)
35
+ key = @key
36
+ @client.Issue.create do
37
+ payload fields: {
38
+ project: { key: key },
39
+ summary: args[:summary],
40
+ description: args[:description],
41
+ issuetype: { name: 'Task' }
42
+ }
43
+ params updateHistory: true
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -8,7 +8,7 @@ require 'json'
8
8
  module Rujira
9
9
  module Tasks
10
10
  # TODO
11
- class Jira
11
+ class Jira # rubocop:disable Metrics/ClassLength
12
12
  include Rake::DSL if defined?(Rake::DSL)
13
13
  def initialize
14
14
  generate
@@ -34,7 +34,7 @@ module Rujira
34
34
  end
35
35
 
36
36
  def generate # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
37
- namespace :jira do
37
+ namespace :jira do # rubocop:disable Metrics/BlockLength
38
38
  desc 'Test connection by getting username'
39
39
  task :whoami do
40
40
  result = client.Myself.get
@@ -53,6 +53,18 @@ module Rujira
53
53
  result = client.Project.list
54
54
  puts JSON.pretty_generate(result)
55
55
  end
56
+
57
+ desc 'Create project'
58
+ task :create do |t|
59
+ options = fetch_options(%w[KEY NAME TYPE LEAD], t.name)
60
+ result = client.Project.create do
61
+ payload key: options[:key],
62
+ name: options[:name],
63
+ projectTypeKey: options[:type],
64
+ lead: options[:lead]
65
+ end
66
+ puts JSON.pretty_generate(result)
67
+ end
56
68
  end
57
69
 
58
70
  namespace :dashboard do
@@ -107,15 +119,15 @@ module Rujira
107
119
  end
108
120
  end
109
121
 
110
- namespace :issue do
122
+ namespace :issue do # rubocop:disable Metrics/BlockLength
111
123
  desc 'Create a issue'
112
124
  task :create do |t|
113
- options = fetch_options(%w[PROJECT SUMMARY DESCRIPTION ISSUETYPE], t.name)
125
+ options = fetch_options(%w[PROJECT_KEY SUMMARY DESCRIPTION ISSUETYPE], t.name)
114
126
  abort 'ISSUETYPE must start with a capital letter' unless options[:issuetype].match?(/\A[A-Z]/)
115
127
 
116
128
  result = client.Issue.create do
117
129
  payload fields: {
118
- project: { key: options[:project] },
130
+ project: { key: options[:project_key] },
119
131
  summary: options[:summary],
120
132
  issuetype: { name: options[:issuetype] },
121
133
  description: options[:description]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rujira
4
- VERSION = '0.5.1'
4
+ VERSION = '0.7.1'
5
5
  end
data/lib/rujira.rb CHANGED
@@ -7,17 +7,21 @@ require 'faraday'
7
7
  require 'faraday/multipart'
8
8
  require 'json'
9
9
 
10
- require_relative 'rujira/error'
11
10
  require_relative 'rujira/version'
12
11
  require_relative 'rujira/request'
13
12
  require_relative 'rujira/client'
14
13
  require_relative 'rujira/api/common'
14
+ require_relative 'rujira/resource/common'
15
15
  require_relative 'rujira/api/search'
16
16
  require_relative 'rujira/api/issue'
17
+ require_relative 'rujira/resource/issue'
17
18
  require_relative 'rujira/api/project'
19
+ require_relative 'rujira/resource/project'
18
20
  require_relative 'rujira/api/comment'
21
+ require_relative 'rujira/resource/comment'
19
22
  require_relative 'rujira/api/attachments'
20
23
  require_relative 'rujira/api/myself'
24
+ require_relative 'rujira/resource/myself'
21
25
  require_relative 'rujira/api/server_info'
22
26
  require_relative 'rujira/api/dashboard'
23
27
  require_relative 'rujira/api/board'
@@ -43,16 +47,4 @@ module Rujira
43
47
  # raise Rujira::Error, "Something went wrong"
44
48
  #
45
49
  class Error < StandardError; end
46
-
47
- # Checks if an environment variable is truthy.
48
- #
49
- # @param [String] var The name of the environment variable
50
- # @return [Boolean] true if the value is 'true', '1', or 'yes' (case-insensitive), false otherwise
51
- #
52
- # @example Check if debug mode is enabled
53
- # Rujira.env_var?('RUJIRA_DEBUG')
54
- #
55
- def self.env_var?(var)
56
- %w[true 1 yes].include?(ENV[var]&.downcase)
57
- end
58
50
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rujira
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Semenov
@@ -53,6 +53,7 @@ files:
53
53
  - cliff.toml
54
54
  - compose.yaml
55
55
  - contrib/jira_link_generator.rb
56
+ - examples/create_issue_as_obj.rb
56
57
  - examples/create_issue_in_sprint.rb
57
58
  - lib/rujira.rb
58
59
  - lib/rujira/api/application_properties.rb
@@ -77,8 +78,12 @@ files:
77
78
  - lib/rujira/api/server_info.rb
78
79
  - lib/rujira/api/sprint.rb
79
80
  - lib/rujira/client.rb
80
- - lib/rujira/error.rb
81
81
  - lib/rujira/request.rb
82
+ - lib/rujira/resource/comment.rb
83
+ - lib/rujira/resource/common.rb
84
+ - lib/rujira/resource/issue.rb
85
+ - lib/rujira/resource/myself.rb
86
+ - lib/rujira/resource/project.rb
82
87
  - lib/rujira/tasks/generate.rake
83
88
  - lib/rujira/version.rb
84
89
  - sig/rujira.rbs
data/lib/rujira/error.rb DELETED
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rujira
4
- class Error < StandardError; end
5
-
6
- # TODO: add docs
7
- # Some description
8
- class PathArgumentError < Error
9
- def message
10
- "No argument to 'path' was given."
11
- end
12
- end
13
-
14
- # TODO: add docs
15
- # Some description
16
- class DataArgumentError < Error
17
- def message
18
- "No argument to 'data' was given."
19
- end
20
- end
21
- end