activeproject 0.1.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46d0e95fea9e696d7f35f2f8ad2cc32a82f9c068aaf834a9daa2650824650620
4
- data.tar.gz: 709c98e0c3bab903db0447c7d53559c55e5c7c98cffc511a771c3b9f08cfa2fc
3
+ metadata.gz: 9960da80d5e32d0cdfa4fce3ffe824ebf3c36a8710d0a7782dbac3ec7001b863
4
+ data.tar.gz: 1ca92b2e165afbe314b70d970cb42817e5987ae3bfaa5a7d0ba58987999afe45
5
5
  SHA512:
6
- metadata.gz: 7fb8db4e4f952b10364e91727ebec93e6f0e65f6ebf7d5bd331dab77013bd3fc34d100f522a76d5f8d7014d947081a16fdee34535a6327cbabfe6bb74cbfc00b
7
- data.tar.gz: 15231c9e5b94a103f21b643f219ffebf4747223f2662a410389ecbf142cc1f618c96384dd5314ba56d00afa6bdf4d0f81cfc0ba33bd580f2fa4999dae8a954eb
6
+ metadata.gz: 46a0f331e8f7e956d643e6070d21dc20abe3825308c0761067df3773b3a20fcea7b94aa9c07f333c0fc794443945419436486b3daec49e4a19b5e473b05b4c6c
7
+ data.tar.gz: d78685242c2ea12a5a522a74e64b8f438d613dd2bb2977c14aca0ed7ba918ef953719f17b23b8cf6400723ea23886f7a63df898a9b804214eab4db635c8fb794
@@ -75,6 +75,12 @@ module ActiveProject
75
75
  raise NotImplementedError, "#{self.class.name} must implement #update_issue"
76
76
  end
77
77
 
78
+ # Base implementation of delete_issue that raises NotImplementedError
79
+ # This will be included in the base adapter class and overridden by specific adapters
80
+ def delete_issue(id, context = {})
81
+ raise NotImplementedError, "The #{self.class.name} adapter does not implement delete_issue"
82
+ end
83
+
78
84
  # Adds a comment to an issue.
79
85
  # @param issue_id [String, Integer] The ID or key of the issue.
80
86
  # @param comment_body [String] The text of the comment.
@@ -6,11 +6,12 @@ module ActiveProject
6
6
  module Issues
7
7
  # Lists To-dos within a specific project.
8
8
  # @param project_id [String, Integer] The ID of the Basecamp project.
9
- # @param options [Hash] Optional options. Accepts :todolist_id.
9
+ # @param options [Hash] Optional options. Accepts :todolist_id and :page_size.
10
10
  # @return [Array<ActiveProject::Resources::Issue>] An array of issue resources.
11
11
  def list_issues(project_id, options = {})
12
12
  all_todos = []
13
13
  todolist_id = options[:todolist_id]
14
+ page_size = options[:page_size] || 50
14
15
 
15
16
  unless todolist_id
16
17
  todolist_id = find_first_todolist_id(project_id)
@@ -18,14 +19,16 @@ module ActiveProject
18
19
  end
19
20
 
20
21
  path = "buckets/#{project_id}/todolists/#{todolist_id}/todos.json"
22
+ query = {}
23
+ query[:per_page] = page_size if page_size
21
24
 
22
25
  loop do
23
- response = @connection.get(path)
26
+ response = @connection.get(path, query)
24
27
  todos_data = begin
25
- JSON.parse(response.body)
26
- rescue StandardError
27
- []
28
- end
28
+ JSON.parse(response.body)
29
+ rescue StandardError
30
+ []
31
+ end
29
32
  break if todos_data.empty?
30
33
 
31
34
  todos_data.each do |todo_data|
@@ -37,6 +40,7 @@ module ActiveProject
37
40
  break unless next_url
38
41
 
39
42
  path = next_url.sub(@base_url, "").sub(%r{^/}, "")
43
+ query = {} # Clear query as pagination is in the URL now
40
44
  end
41
45
 
42
46
  all_todos
@@ -133,6 +137,21 @@ module ActiveProject
133
137
 
134
138
  find_issue(todo_id, context)
135
139
  end
140
+
141
+ # Deletes a To-do in Basecamp.
142
+ # @param todo_id [String, Integer] The ID of the Basecamp To-do to delete.
143
+ # @param context [Hash] Required context: { project_id: '...' }.
144
+ # @return [Boolean] True if successfully deleted.
145
+ def delete_issue(todo_id, context = {})
146
+ project_id = context[:project_id]
147
+ unless project_id
148
+ raise ArgumentError, "Missing required context: :project_id must be provided for BasecampAdapter#delete_issue"
149
+ end
150
+
151
+ path = "buckets/#{project_id}/todos/#{todo_id}.json"
152
+ make_request(:delete, path)
153
+ true
154
+ end
136
155
  end
137
156
  end
138
157
  end
@@ -4,14 +4,17 @@ module ActiveProject
4
4
  module Adapters
5
5
  module Jira
6
6
  module Issues
7
+ DEFAULT_FIELDS = %w[summary description status assignee reporter created updated project issuetype duedate priority].freeze
8
+
7
9
  # Lists issues within a specific project, optionally filtered by JQL.
8
10
  # @param project_id_or_key [String, Integer] The ID or key of the project.
9
- # @param options [Hash] Optional filtering/pagination options.
11
+ # @param options [Hash] Optional filtering/pagination options. Accepts :jql, :fields, :start_at, :max_results.
10
12
  # @return [Array<ActiveProject::Resources::Issue>]
11
13
  def list_issues(project_id_or_key, options = {})
12
14
  start_at = options.fetch(:start_at, 0)
13
15
  max_results = options.fetch(:max_results, 50)
14
16
  jql = options.fetch(:jql, "project = '#{project_id_or_key}' ORDER BY created DESC")
17
+ fields = options[:fields] || DEFAULT_FIELDS
15
18
 
16
19
  all_issues = []
17
20
  path = "/rest/api/3/search"
@@ -20,8 +23,7 @@ module ActiveProject
20
23
  jql: jql,
21
24
  startAt: start_at,
22
25
  maxResults: max_results,
23
- fields: %w[summary description status assignee reporter created updated project
24
- issuetype duedate priority]
26
+ fields: fields
25
27
  }.to_json
26
28
 
27
29
  response_data = make_request(:post, path, payload)
@@ -36,11 +38,12 @@ module ActiveProject
36
38
 
37
39
  # Finds a specific issue by its ID or key using the V3 endpoint.
38
40
  # @param id_or_key [String, Integer] The ID or key of the issue.
39
- # @param context [Hash] Optional context (ignored).
41
+ # @param context [Hash] Optional context. Accepts :fields for field selection.
40
42
  # @return [ActiveProject::Resources::Issue]
41
- def find_issue(id_or_key, _context = {})
42
- fields = "summary,description,status,assignee,reporter,created,updated,project,issuetype,duedate,priority"
43
- path = "/rest/api/3/issue/#{id_or_key}?fields=#{fields}"
43
+ def find_issue(id_or_key, context = {})
44
+ fields = context[:fields] || DEFAULT_FIELDS
45
+ fields_param = fields.is_a?(Array) ? fields.join(",") : fields
46
+ path = "/rest/api/3/issue/#{id_or_key}?fields=#{fields_param}"
44
47
 
45
48
  issue_data = make_request(:get, path)
46
49
  map_issue_data(issue_data)
@@ -54,8 +57,8 @@ module ActiveProject
54
57
  path = "/rest/api/3/issue"
55
58
 
56
59
  unless attributes[:project].is_a?(Hash) && (attributes[:project][:id] || attributes[:project][:key]) &&
57
- attributes[:summary] && !attributes[:summary].empty? &&
58
- attributes[:issue_type] && (attributes[:issue_type][:id] || attributes[:issue_type][:name])
60
+ attributes[:summary] && !attributes[:summary].empty? &&
61
+ attributes[:issue_type] && (attributes[:issue_type][:id] || attributes[:issue_type][:name])
59
62
  raise ArgumentError,
60
63
  "Missing required attributes for issue creation: :project (must be a Hash with id/key), :summary, :issue_type (with id/name)"
61
64
  end
@@ -83,6 +86,8 @@ module ActiveProject
83
86
 
84
87
  fields_payload[:priority] = attributes[:priority] if attributes.key?(:priority)
85
88
 
89
+ fields_payload[:parent] = attributes[:parent] if attributes.key?(:parent)
90
+
86
91
  payload = { fields: fields_payload }.to_json
87
92
  response_data = make_request(:post, path, payload)
88
93
 
@@ -92,9 +97,9 @@ module ActiveProject
92
97
  # Updates an existing issue in Jira using the V3 endpoint.
93
98
  # @param id_or_key [String, Integer] The ID or key of the issue to update.
94
99
  # @param attributes [Hash] Issue attributes to update (e.g., :summary, :description, :assignee_id, :due_on, :priority).
95
- # @param context [Hash] Optional context (ignored).
100
+ # @param context [Hash] Optional context. Accepts :fields for field selection on return.
96
101
  # @return [ActiveProject::Resources::Issue]
97
- def update_issue(id_or_key, attributes, _context = {})
102
+ def update_issue(id_or_key, attributes, context = {})
98
103
  path = "/rest/api/3/issue/#{id_or_key}"
99
104
 
100
105
  update_fields = {}
@@ -119,12 +124,25 @@ module ActiveProject
119
124
 
120
125
  update_fields[:priority] = attributes[:priority] if attributes.key?(:priority)
121
126
 
122
- return find_issue(id_or_key) if update_fields.empty?
127
+ return find_issue(id_or_key, context) if update_fields.empty?
123
128
 
124
129
  payload = { fields: update_fields }.to_json
125
130
  make_request(:put, path, payload)
126
131
 
127
- find_issue(id_or_key)
132
+ find_issue(id_or_key, context)
133
+ end
134
+
135
+ # Deletes an issue from Jira.
136
+ # @param id_or_key [String, Integer] The ID or key of the issue to delete.
137
+ # @param context [Hash] Optional context. Accepts :delete_subtasks to indicate whether subtasks should be deleted.
138
+ # @return [Boolean] True if successfully deleted.
139
+ def delete_issue(id_or_key, context = {})
140
+ delete_subtasks = context[:delete_subtasks] || false
141
+ path = "/rest/api/3/issue/#{id_or_key}"
142
+ query = { deleteSubtasks: delete_subtasks }
143
+
144
+ make_request(:delete, path, nil, query)
145
+ true
128
146
  end
129
147
  end
130
148
  end
@@ -57,8 +57,10 @@ module ActiveProject
57
57
  # Initializes the Faraday connection object.
58
58
 
59
59
  # Makes an HTTP request. Returns parsed JSON or raises appropriate error.
60
- def make_request(method, path, body = nil)
61
- response = @connection.run_request(method, path, body, nil)
60
+ def make_request(method, path, body = nil, query = nil)
61
+ response = @connection.run_request(method, path, body, nil) do |req|
62
+ req.params = query if query # Add query params to the request
63
+ end
62
64
 
63
65
  # Check for AUTHENTICATED_FAILED header even on 200 OK
64
66
  if response.status == 200 && response.headers["x-seraph-loginreason"]&.include?("AUTHENTICATED_FAILED")
@@ -78,7 +80,7 @@ module ActiveProject
78
80
  status = e.response&.status
79
81
  body = e.response&.body
80
82
  raise ApiError.new("Jira API connection error (Status: #{status || 'N/A'}): #{e.message}", original_error: e,
81
- status_code: status, response_body: body)
83
+ status_code: status, response_body: body)
82
84
  end
83
85
 
84
86
  # Handles Faraday errors based on the response object (for non-2xx responses).
@@ -4,13 +4,17 @@ module ActiveProject
4
4
  module Adapters
5
5
  module Trello
6
6
  module Issues
7
+ DEFAULT_FIELDS = %w[id name desc closed idList idBoard due dueComplete idMembers].freeze
8
+
7
9
  # Lists Trello cards on a specific board.
8
10
  # @param board_id [String] The ID of the Trello board.
9
- # @param options [Hash] Optional filtering options.
11
+ # @param options [Hash] Optional filtering options. Accepts :filter and :fields.
10
12
  # @return [Array<ActiveProject::Resources::Issue>]
11
13
  def list_issues(board_id, options = {})
12
14
  path = "boards/#{board_id}/cards"
13
- query = { fields: "id,name,desc,closed,idList,idBoard,due,dueComplete,idMembers", list: true }
15
+
16
+ fields = options[:fields] ? Array(options[:fields]).join(",") : DEFAULT_FIELDS.join(",")
17
+ query = { fields: fields, list: true }
14
18
  query[:filter] = options[:filter] if options[:filter]
15
19
 
16
20
  cards_data = make_request(:get, path, nil, query)
@@ -21,11 +25,14 @@ module ActiveProject
21
25
 
22
26
  # Finds a specific Card by its ID.
23
27
  # @param card_id [String] The ID of the Trello Card.
24
- # @param context [Hash] Optional context (ignored).
28
+ # @param context [Hash] Optional context. Accepts :fields for specific field selection.
25
29
  # @return [ActiveProject::Resources::Issue]
26
- def find_issue(card_id, _context = {})
30
+ def find_issue(card_id, context = {})
27
31
  path = "cards/#{card_id}"
28
- query = { fields: "id,name,desc,closed,idList,idBoard,due,dueComplete,idMembers", list: true }
32
+
33
+ fields = context[:fields] ? Array(context[:fields]).join(",") : DEFAULT_FIELDS.join(",")
34
+ query = { fields: fields, list: true }
35
+
29
36
  card_data = make_request(:get, path, nil, query)
30
37
  map_card_data(card_data, card_data["idBoard"])
31
38
  end
@@ -58,7 +65,7 @@ module ActiveProject
58
65
  # Updates an existing Card in Trello.
59
66
  # @param card_id [String] The ID of the Trello Card.
60
67
  # @param attributes [Hash] Attributes to update (e.g., :title, :description, :list_id, :closed, :due_on, :assignee_ids, :status).
61
- # @param context [Hash] Optional context (ignored).
68
+ # @param context [Hash] Optional context. Accepts :fields for return data field selection.
62
69
  # @return [ActiveProject::Resources::Issue]
63
70
  def update_issue(card_id, attributes, context = {})
64
71
  update_attributes = attributes.dup
@@ -67,10 +74,10 @@ module ActiveProject
67
74
  target_status = update_attributes.delete(:status)
68
75
 
69
76
  board_id = update_attributes[:board_id] || begin
70
- find_issue(card_id).project_id
71
- rescue NotFoundError
72
- raise NotFoundError, "Trello card with ID '#{card_id}' not found."
73
- end
77
+ find_issue(card_id).project_id
78
+ rescue NotFoundError
79
+ raise NotFoundError, "Trello card with ID '#{card_id}' not found."
80
+ end
74
81
 
75
82
  unless board_id
76
83
  raise ApiError, "Could not determine board ID for card '#{card_id}' to perform status mapping."
@@ -111,6 +118,15 @@ module ActiveProject
111
118
  card_data = make_request(:put, path, nil, query_params.compact)
112
119
  map_card_data(card_data, card_data["idBoard"])
113
120
  end
121
+
122
+ # Deletes a Trello card.
123
+ # @param card_id [String] The ID of the Trello Card to delete.
124
+ # @return [Boolean] True if successfully deleted.
125
+ def delete_issue(card_id, **)
126
+ path = "cards/#{card_id}"
127
+ make_request(:delete, path)
128
+ true
129
+ end
114
130
  end
115
131
  end
116
132
  end
@@ -96,7 +96,11 @@ module ActiveProject
96
96
  def handle_faraday_error(error)
97
97
  status = error.response_status
98
98
  body = error.response_body
99
- message = body || "Unknown Trello Error"
99
+ body = JSON.parse(body) if body.is_a?(String) && !body.empty? rescue body
100
+ if body.is_a?(Hash)
101
+ message = body["message"]
102
+ end
103
+ message ||= body || "Unknown Trello Error"
100
104
 
101
105
  case status
102
106
  when 401, 403
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveProject
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,10 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeproject
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
11
  date: 2025-04-10 00:00:00.000000000 Z
@@ -125,6 +126,7 @@ licenses:
125
126
  metadata:
126
127
  homepage_uri: https://github.com/seuros/active_project
127
128
  source_code_uri: https://github.com/seuros/active_project
129
+ post_install_message:
128
130
  rdoc_options: []
129
131
  require_paths:
130
132
  - lib
@@ -139,7 +141,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
141
  - !ruby/object:Gem::Version
140
142
  version: '0'
141
143
  requirements: []
142
- rubygems_version: 3.6.5
144
+ rubygems_version: 3.5.22
145
+ signing_key:
143
146
  specification_version: 4
144
147
  summary: A standardized Ruby interface for multiple project management APIs (Jira,
145
148
  Basecamp, Trello, etc.).