activeproject 0.0.0 → 0.1.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 +4 -4
- data/README.md +28 -82
- data/Rakefile +4 -2
- data/lib/active_project/adapters/base.rb +3 -14
- data/lib/active_project/adapters/basecamp/comments.rb +27 -0
- data/lib/active_project/adapters/basecamp/connection.rb +49 -0
- data/lib/active_project/adapters/basecamp/issues.rb +139 -0
- data/lib/active_project/adapters/basecamp/lists.rb +54 -0
- data/lib/active_project/adapters/basecamp/projects.rb +110 -0
- data/lib/active_project/adapters/basecamp/webhooks.rb +73 -0
- data/lib/active_project/adapters/basecamp_adapter.rb +46 -449
- data/lib/active_project/adapters/jira/comments.rb +28 -0
- data/lib/active_project/adapters/jira/connection.rb +47 -0
- data/lib/active_project/adapters/jira/issues.rb +132 -0
- data/lib/active_project/adapters/jira/projects.rb +100 -0
- data/lib/active_project/adapters/jira/transitions.rb +68 -0
- data/lib/active_project/adapters/jira/webhooks.rb +89 -0
- data/lib/active_project/adapters/jira_adapter.rb +59 -486
- data/lib/active_project/adapters/trello/comments.rb +21 -0
- data/lib/active_project/adapters/trello/connection.rb +37 -0
- data/lib/active_project/adapters/trello/issues.rb +117 -0
- data/lib/active_project/adapters/trello/lists.rb +27 -0
- data/lib/active_project/adapters/trello/projects.rb +82 -0
- data/lib/active_project/adapters/trello/webhooks.rb +91 -0
- data/lib/active_project/adapters/trello_adapter.rb +54 -377
- data/lib/active_project/association_proxy.rb +10 -3
- data/lib/active_project/configuration.rb +23 -17
- data/lib/active_project/configurations/trello_configuration.rb +1 -3
- data/lib/active_project/resource_factory.rb +20 -10
- data/lib/active_project/resources/comment.rb +0 -5
- data/lib/active_project/resources/issue.rb +0 -5
- data/lib/active_project/resources/project.rb +0 -3
- data/lib/active_project/resources/user.rb +0 -1
- data/lib/active_project/version.rb +3 -1
- data/lib/activeproject.rb +67 -15
- metadata +26 -8
@@ -13,9 +13,6 @@ module ActiveProject
|
|
13
13
|
# Implements the interface defined in ActiveProject::Adapters::Base.
|
14
14
|
# API Docs: https://developer.atlassian.com/cloud/trello/rest/
|
15
15
|
class TrelloAdapter < Base
|
16
|
-
BASE_URL = "https://api.trello.com/1/"
|
17
|
-
USER_AGENT = "ActiveProject Gem (github.com/seuros/activeproject)"
|
18
|
-
|
19
16
|
# Computes the expected Trello webhook signature.
|
20
17
|
# @param callback_url [String] The exact URL registered for the webhook.
|
21
18
|
# @param response_body [String] The raw response body received from Trello.
|
@@ -29,22 +26,12 @@ module ActiveProject
|
|
29
26
|
|
30
27
|
attr_reader :config
|
31
28
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
@config = config
|
40
|
-
|
41
|
-
unless @config.api_key && !@config.api_key.empty? && @config.api_token && !@config.api_token.empty?
|
42
|
-
raise ArgumentError, "TrelloAdapter configuration requires :api_key and :api_token"
|
43
|
-
end
|
44
|
-
|
45
|
-
@connection = initialize_connection
|
46
|
-
end
|
47
|
-
|
29
|
+
include Trello::Connection
|
30
|
+
include Trello::Projects
|
31
|
+
include Trello::Issues
|
32
|
+
include Trello::Comments
|
33
|
+
include Trello::Lists
|
34
|
+
include Trello::Webhooks
|
48
35
|
|
49
36
|
# --- Resource Factories ---
|
50
37
|
|
@@ -60,318 +47,6 @@ module ActiveProject
|
|
60
47
|
ResourceFactory.new(adapter: self, resource_class: Resources::Issue)
|
61
48
|
end
|
62
49
|
|
63
|
-
# --- Implementation of Base methods ---
|
64
|
-
|
65
|
-
# Lists Trello boards accessible by the configured token.
|
66
|
-
# @return [Array<ActiveProject::Resources::Project>] An array of project resources.
|
67
|
-
def list_projects
|
68
|
-
path = "members/me/boards"
|
69
|
-
query = { fields: "id,name,desc" }
|
70
|
-
boards_data = make_request(:get, path, nil, query)
|
71
|
-
|
72
|
-
return [] unless boards_data.is_a?(Array)
|
73
|
-
|
74
|
-
boards_data.map do |board_data|
|
75
|
-
Resources::Project.new(self, # Pass adapter instance
|
76
|
-
id: board_data["id"],
|
77
|
-
key: nil,
|
78
|
-
name: board_data["name"],
|
79
|
-
adapter_source: :trello,
|
80
|
-
raw_data: board_data
|
81
|
-
)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# Finds a specific Trello Board by its ID.
|
86
|
-
# @param board_id [String] The ID of the Trello Board.
|
87
|
-
# @return [ActiveProject::Resources::Project] The project resource.
|
88
|
-
def find_project(board_id)
|
89
|
-
path = "boards/#{board_id}"
|
90
|
-
query = { fields: "id,name,desc" }
|
91
|
-
board_data = make_request(:get, path, nil, query)
|
92
|
-
|
93
|
-
Resources::Project.new(self, # Pass adapter instance
|
94
|
-
id: board_data["id"],
|
95
|
-
key: nil,
|
96
|
-
name: board_data["name"],
|
97
|
-
adapter_source: :trello,
|
98
|
-
raw_data: board_data
|
99
|
-
)
|
100
|
-
end
|
101
|
-
|
102
|
-
# Creates a new board in Trello.
|
103
|
-
# @param attributes [Hash] Board attributes. Required: :name. Optional: :description, :default_lists.
|
104
|
-
# @return [ActiveProject::Resources::Project] The created project resource.
|
105
|
-
def create_project(attributes)
|
106
|
-
unless attributes[:name] && !attributes[:name].empty?
|
107
|
-
raise ArgumentError, "Missing required attribute for Trello board creation: :name"
|
108
|
-
end
|
109
|
-
|
110
|
-
path = "boards/"
|
111
|
-
query_params = {
|
112
|
-
name: attributes[:name],
|
113
|
-
desc: attributes[:description],
|
114
|
-
defaultLists: attributes.fetch(:default_lists, true) # Default to creating lists
|
115
|
-
# Add other board options here if needed (e.g., idOrganization)
|
116
|
-
}.compact
|
117
|
-
|
118
|
-
board_data = make_request(:post, path, nil, query_params)
|
119
|
-
|
120
|
-
Resources::Project.new(self, # Pass adapter instance
|
121
|
-
id: board_data["id"],
|
122
|
-
key: nil,
|
123
|
-
name: board_data["name"],
|
124
|
-
adapter_source: :trello,
|
125
|
-
raw_data: board_data
|
126
|
-
)
|
127
|
-
end
|
128
|
-
|
129
|
-
# Creates a new list on a Trello board.
|
130
|
-
# @param board_id [String] The ID of the board.
|
131
|
-
# @param attributes [Hash] List attributes. Required: :name. Optional: :pos.
|
132
|
-
# @return [Hash] The raw data hash of the created list.
|
133
|
-
def create_list(board_id, attributes)
|
134
|
-
unless attributes[:name] && !attributes[:name].empty?
|
135
|
-
raise ArgumentError, "Missing required attribute for Trello list creation: :name"
|
136
|
-
end
|
137
|
-
|
138
|
-
path = "boards/#{board_id}/lists"
|
139
|
-
query_params = {
|
140
|
-
name: attributes[:name],
|
141
|
-
pos: attributes[:pos]
|
142
|
-
}.compact
|
143
|
-
|
144
|
-
make_request(:post, path, nil, query_params)
|
145
|
-
end
|
146
|
-
|
147
|
-
# Deletes a board in Trello.
|
148
|
-
# WARNING: This is a permanent deletion.
|
149
|
-
# @param board_id [String] The ID of the board to delete.
|
150
|
-
# @return [Boolean] true if deletion was successful (API returns 200).
|
151
|
-
# @raise [NotFoundError] if the board is not found.
|
152
|
-
# @raise [AuthenticationError] if credentials lack permission.
|
153
|
-
# @raise [ApiError] for other errors.
|
154
|
-
def delete_project(board_id)
|
155
|
-
path = "/boards/#{board_id}"
|
156
|
-
make_request(:delete, path) # DELETE returns 200 OK on success
|
157
|
-
true # Return true if make_request doesn't raise an error
|
158
|
-
end
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
# Lists Trello cards on a specific board.
|
164
|
-
# @param board_id [String] The ID of the Trello board.
|
165
|
-
# @param options [Hash] Optional filtering options.
|
166
|
-
# @return [Array<ActiveProject::Resources::Issue>] An array of issue resources.
|
167
|
-
def list_issues(board_id, options = {})
|
168
|
-
path = "boards/#{board_id}/cards"
|
169
|
-
# Fetch idMembers and list name for potential name mapping fallback
|
170
|
-
query = { fields: "id,name,desc,closed,idList,idBoard,due,dueComplete,idMembers", list: true }
|
171
|
-
query[:filter] = options[:filter] if options[:filter]
|
172
|
-
|
173
|
-
cards_data = make_request(:get, path, nil, query)
|
174
|
-
return [] unless cards_data.is_a?(Array)
|
175
|
-
|
176
|
-
cards_data.map { |card_data| map_card_data(card_data, board_id) }
|
177
|
-
end
|
178
|
-
|
179
|
-
# Finds a specific Card by its ID.
|
180
|
-
# @param card_id [String] The ID of the Trello Card.
|
181
|
-
# @param context [Hash] Optional context (ignored).
|
182
|
-
# @return [ActiveProject::Resources::Issue] The issue resource.
|
183
|
-
def find_issue(card_id, context = {})
|
184
|
-
path = "cards/#{card_id}"
|
185
|
-
# Fetch idMembers and list name for potential name mapping fallback
|
186
|
-
query = { fields: "id,name,desc,closed,idList,idBoard,due,dueComplete,idMembers", list: true }
|
187
|
-
card_data = make_request(:get, path, nil, query)
|
188
|
-
map_card_data(card_data, card_data["idBoard"])
|
189
|
-
end
|
190
|
-
|
191
|
-
# Creates a new Card in Trello.
|
192
|
-
# @param _board_id [String] Ignored (context).
|
193
|
-
# @param attributes [Hash] Card attributes. Required: :list_id, :title. Optional: :description, :assignee_ids, :due_on.
|
194
|
-
# @return [ActiveProject::Resources::Issue] The created issue resource.
|
195
|
-
def create_issue(_board_id, attributes)
|
196
|
-
list_id = attributes[:list_id]
|
197
|
-
title = attributes[:title]
|
198
|
-
|
199
|
-
unless list_id && title && !title.empty?
|
200
|
-
raise ArgumentError, "Missing required attributes for Trello card creation: :list_id, :title"
|
201
|
-
end
|
202
|
-
|
203
|
-
path = "cards"
|
204
|
-
query_params = {
|
205
|
-
idList: list_id,
|
206
|
-
name: title,
|
207
|
-
desc: attributes[:description],
|
208
|
-
# Use assignee_ids (expects an array of Trello member IDs)
|
209
|
-
idMembers: attributes[:assignee_ids]&.join(","),
|
210
|
-
due: attributes[:due_on]&.iso8601
|
211
|
-
}.compact
|
212
|
-
|
213
|
-
card_data = make_request(:post, path, nil, query_params)
|
214
|
-
map_card_data(card_data, card_data["idBoard"])
|
215
|
-
end
|
216
|
-
|
217
|
-
# Updates an existing Card in Trello.
|
218
|
-
# @param card_id [String] The ID of the Trello Card.
|
219
|
-
# @param attributes [Hash] Attributes to update (e.g., :title, :description, :list_id, :closed, :due_on, :assignee_ids, :status).
|
220
|
-
# @param context [Hash] Optional context (ignored).
|
221
|
-
# @return [ActiveProject::Resources::Issue] The updated issue resource.
|
222
|
-
def update_issue(card_id, attributes, context = {})
|
223
|
-
# Make a mutable copy of attributes
|
224
|
-
update_attributes = attributes.dup
|
225
|
-
|
226
|
-
# Handle :status mapping to :list_id
|
227
|
-
if update_attributes.key?(:status)
|
228
|
-
target_status = update_attributes.delete(:status) # Remove status key
|
229
|
-
|
230
|
-
# Fetch board_id efficiently if not already known
|
231
|
-
# We need the board_id to look up the correct status mapping
|
232
|
-
board_id = update_attributes[:board_id] || begin
|
233
|
-
find_issue(card_id).project_id # Fetch the issue to get its board_id
|
234
|
-
rescue NotFoundError
|
235
|
-
# Re-raise NotFoundError if the card itself doesn't exist
|
236
|
-
raise NotFoundError, "Trello card with ID '#{card_id}' not found."
|
237
|
-
end
|
238
|
-
|
239
|
-
unless board_id
|
240
|
-
# This should theoretically not happen if find_issue succeeded or board_id was passed
|
241
|
-
raise ApiError, "Could not determine board ID for card '#{card_id}' to perform status mapping."
|
242
|
-
end
|
243
|
-
|
244
|
-
# Use stored config for status mappings
|
245
|
-
board_mappings = @config.status_mappings[board_id]
|
246
|
-
unless board_mappings
|
247
|
-
raise ConfigurationError, "Trello status mapping not configured for board ID '#{board_id}'. Cannot map status ':#{target_status}'."
|
248
|
-
end
|
249
|
-
|
250
|
-
# Find the target list ID by looking up the status symbol in the board's mappings.
|
251
|
-
# We iterate through the mappings hash { list_id => status_symbol }
|
252
|
-
target_list_id = board_mappings.key(target_status)
|
253
|
-
|
254
|
-
unless target_list_id
|
255
|
-
raise ConfigurationError, "Target status ':#{target_status}' not found in configured Trello status mappings for board ID '#{board_id}'."
|
256
|
-
end
|
257
|
-
|
258
|
-
# Add the resolved list_id to the attributes to be updated
|
259
|
-
update_attributes[:list_id] = target_list_id
|
260
|
-
end
|
261
|
-
|
262
|
-
|
263
|
-
path = "cards/#{card_id}"
|
264
|
-
|
265
|
-
# Build query parameters from the potentially modified update_attributes
|
266
|
-
query_params = {}
|
267
|
-
query_params[:name] = update_attributes[:title] if update_attributes.key?(:title)
|
268
|
-
query_params[:desc] = update_attributes[:description] if update_attributes.key?(:description)
|
269
|
-
query_params[:closed] = update_attributes[:closed] if update_attributes.key?(:closed)
|
270
|
-
query_params[:idList] = update_attributes[:list_id] if update_attributes.key?(:list_id) # Use the mapped list_id if status was provided
|
271
|
-
query_params[:due] = update_attributes[:due_on]&.iso8601 if update_attributes.key?(:due_on)
|
272
|
-
query_params[:dueComplete] = update_attributes[:dueComplete] if update_attributes.key?(:dueComplete)
|
273
|
-
# Use assignee_ids (expects an array of Trello member IDs)
|
274
|
-
query_params[:idMembers] = update_attributes[:assignee_ids]&.join(",") if update_attributes.key?(:assignee_ids)
|
275
|
-
|
276
|
-
# If after processing :status, there are no actual changes, just return the current issue state
|
277
|
-
return find_issue(card_id, context) if query_params.empty?
|
278
|
-
|
279
|
-
# Make the PUT request to update the card
|
280
|
-
card_data = make_request(:put, path, nil, query_params.compact)
|
281
|
-
|
282
|
-
# Return the updated issue resource, mapped with potentially new status
|
283
|
-
map_card_data(card_data, card_data["idBoard"])
|
284
|
-
end
|
285
|
-
|
286
|
-
# Adds a comment to a Card in Trello.
|
287
|
-
# @param card_id [String] The ID of the Trello Card.
|
288
|
-
# @param comment_body [String] The comment text (Markdown).
|
289
|
-
# @param context [Hash] Optional context (ignored).
|
290
|
-
# @return [ActiveProject::Resources::Comment] The created comment resource.
|
291
|
-
def add_comment(card_id, comment_body, context = {})
|
292
|
-
path = "cards/#{card_id}/actions/comments"
|
293
|
-
query_params = { text: comment_body }
|
294
|
-
comment_data = make_request(:post, path, nil, query_params)
|
295
|
-
map_comment_action_data(comment_data, card_id)
|
296
|
-
end
|
297
|
-
|
298
|
-
# Parses an incoming Trello webhook payload.
|
299
|
-
# @param request_body [String] The raw JSON request body.
|
300
|
-
# @param headers [Hash] Request headers (unused).
|
301
|
-
# @return [ActiveProject::WebhookEvent, nil] Parsed event or nil if unhandled.
|
302
|
-
def parse_webhook(request_body, headers = {})
|
303
|
-
payload = JSON.parse(request_body) rescue nil
|
304
|
-
return nil unless payload.is_a?(Hash) && payload["action"].is_a?(Hash)
|
305
|
-
|
306
|
-
action = payload["action"]
|
307
|
-
action_type = action["type"]
|
308
|
-
actor_data = action.dig("memberCreator")
|
309
|
-
timestamp = Time.parse(action["date"]) rescue nil
|
310
|
-
board_id = action.dig("data", "board", "id")
|
311
|
-
card_data = action.dig("data", "card")
|
312
|
-
comment_text = action.dig("data", "text")
|
313
|
-
old_data = action.dig("data", "old") # For updateCard events
|
314
|
-
|
315
|
-
event_type = nil
|
316
|
-
object_kind = nil
|
317
|
-
event_object_id = nil
|
318
|
-
object_key = nil
|
319
|
-
changes = nil
|
320
|
-
object_data = nil
|
321
|
-
|
322
|
-
case action_type
|
323
|
-
when "createCard"
|
324
|
-
event_type = :issue_created
|
325
|
-
object_kind = :issue
|
326
|
-
event_object_id = card_data["id"]
|
327
|
-
object_key = card_data["idShort"]
|
328
|
-
when "updateCard"
|
329
|
-
event_type = :issue_updated
|
330
|
-
object_kind = :issue
|
331
|
-
event_object_id = card_data["id"]
|
332
|
-
object_key = card_data["idShort"]
|
333
|
-
# Parse changes for updateCard
|
334
|
-
if old_data.is_a?(Hash)
|
335
|
-
changes = {}
|
336
|
-
old_data.each do |field, old_value|
|
337
|
-
# Find the corresponding new value in the card data if possible
|
338
|
-
new_value = card_data[field]
|
339
|
-
changes[field.to_sym] = [ old_value, new_value ]
|
340
|
-
end
|
341
|
-
end
|
342
|
-
when "commentCard"
|
343
|
-
event_type = :comment_added
|
344
|
-
object_kind = :comment
|
345
|
-
event_object_id = action["id"] # Action ID is comment ID
|
346
|
-
object_key = nil
|
347
|
-
when "addMemberToCard", "removeMemberFromCard"
|
348
|
-
event_type = :issue_updated
|
349
|
-
object_kind = :issue
|
350
|
-
event_object_id = card_data["id"]
|
351
|
-
object_key = card_data["idShort"]
|
352
|
-
changes = { assignees: true } # Indicate assignees changed, specific diff not easily available
|
353
|
-
else
|
354
|
-
return nil # Unhandled action type
|
355
|
-
end
|
356
|
-
|
357
|
-
WebhookEvent.new(
|
358
|
-
event_type: event_type,
|
359
|
-
object_kind: object_kind,
|
360
|
-
event_object_id: event_object_id,
|
361
|
-
object_key: object_key,
|
362
|
-
project_id: board_id,
|
363
|
-
actor: map_user_data(actor_data), # Use helper
|
364
|
-
timestamp: timestamp,
|
365
|
-
adapter_source: :trello,
|
366
|
-
changes: changes,
|
367
|
-
object_data: object_data, # Keep nil for now
|
368
|
-
raw_data: payload
|
369
|
-
)
|
370
|
-
rescue JSON::ParserError
|
371
|
-
nil # Ignore unparseable payloads
|
372
|
-
end
|
373
|
-
|
374
|
-
|
375
50
|
# Retrieves details for the currently authenticated user.
|
376
51
|
# @return [ActiveProject::Resources::User] The user object.
|
377
52
|
# @raise [ActiveProject::AuthenticationError] if authentication fails.
|
@@ -391,18 +66,9 @@ module ActiveProject
|
|
391
66
|
false
|
392
67
|
end
|
393
68
|
|
394
|
-
|
395
69
|
private
|
396
70
|
|
397
71
|
# Initializes the Faraday connection object.
|
398
|
-
def initialize_connection
|
399
|
-
Faraday.new(url: BASE_URL) do |conn|
|
400
|
-
conn.request :retry
|
401
|
-
conn.headers["Accept"] = "application/json"
|
402
|
-
conn.response :raise_error
|
403
|
-
conn.headers["User-Agent"] = ActiveProject.user_agent
|
404
|
-
end
|
405
|
-
end
|
406
72
|
|
407
73
|
# Helper method for making requests.
|
408
74
|
def make_request(method, path, body = nil, query_params = {})
|
@@ -418,6 +84,7 @@ module ActiveProject
|
|
418
84
|
end
|
419
85
|
|
420
86
|
return nil if response.status == 204 || response.body.empty?
|
87
|
+
|
421
88
|
JSON.parse(response.body)
|
422
89
|
rescue Faraday::Error => e
|
423
90
|
handle_faraday_error(e)
|
@@ -441,11 +108,14 @@ module ActiveProject
|
|
441
108
|
when 400, 422
|
442
109
|
if status == 400 && message&.include?("invalid id")
|
443
110
|
raise NotFoundError, "Trello resource not found (Status: 400, Message: #{message})"
|
444
|
-
else
|
445
|
-
raise ValidationError.new("Trello validation failed (Status: #{status}): #{message}", status_code: status, response_body: body)
|
446
111
|
end
|
112
|
+
|
113
|
+
raise ValidationError.new("Trello validation failed (Status: #{status}): #{message}", status_code: status,
|
114
|
+
response_body: body)
|
115
|
+
|
447
116
|
else
|
448
|
-
raise ApiError.new("Trello API error (Status: #{status || 'N/A'}): #{message}", original_error: error,
|
117
|
+
raise ApiError.new("Trello API error (Status: #{status || 'N/A'}): #{message}", original_error: error,
|
118
|
+
status_code: status, response_body: body)
|
449
119
|
end
|
450
120
|
end
|
451
121
|
|
@@ -462,48 +132,54 @@ module ActiveProject
|
|
462
132
|
status = board_mappings[list_id]
|
463
133
|
# Fallback: Try mapping by List Name if ID mapping failed and list data is present
|
464
134
|
elsif board_mappings && card_data["list"] && board_mappings.key?(card_data["list"]["name"])
|
465
|
-
|
135
|
+
status = board_mappings[card_data["list"]["name"]]
|
466
136
|
end
|
467
137
|
|
468
138
|
# Override status if the card is archived (closed)
|
469
139
|
status = :closed if card_data["closed"]
|
470
140
|
|
471
|
-
created_timestamp =
|
472
|
-
|
141
|
+
created_timestamp = begin
|
142
|
+
Time.at(card_data["id"][0..7].to_i(16))
|
143
|
+
rescue StandardError
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
due_on = begin
|
147
|
+
card_data["due"] ? Date.parse(card_data["due"]) : nil
|
148
|
+
rescue StandardError
|
149
|
+
nil
|
150
|
+
end
|
473
151
|
|
474
152
|
Resources::Issue.new(self, # Pass adapter instance
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
)
|
153
|
+
id: card_data["id"],
|
154
|
+
key: nil,
|
155
|
+
title: card_data["name"],
|
156
|
+
description: card_data["desc"],
|
157
|
+
status: status, # Use the determined status
|
158
|
+
assignees: map_member_ids_to_users(card_data["idMembers"]), # Use new method
|
159
|
+
reporter: nil, # Trello cards don't have a distinct reporter
|
160
|
+
project_id: board_id,
|
161
|
+
created_at: created_timestamp,
|
162
|
+
updated_at: nil, # Trello API doesn't provide a standard updated_at for cards easily
|
163
|
+
due_on: due_on,
|
164
|
+
priority: nil, # Trello doesn't have priority
|
165
|
+
adapter_source: :trello,
|
166
|
+
raw_data: card_data)
|
490
167
|
end
|
491
168
|
|
492
169
|
# Maps raw Trello comment action data hash to a Comment resource.
|
493
170
|
def map_comment_action_data(action_data, card_id)
|
494
|
-
author_data = action_data
|
171
|
+
author_data = action_data["memberCreator"]
|
495
172
|
comment_text = action_data.dig("data", "text")
|
496
173
|
|
497
174
|
Resources::Comment.new(self, # Pass adapter instance
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
)
|
175
|
+
id: action_data["id"],
|
176
|
+
body: comment_text,
|
177
|
+
author: map_user_data(author_data), # Use user mapping
|
178
|
+
created_at: action_data["date"] ? Time.parse(action_data["date"]) : nil,
|
179
|
+
updated_at: nil,
|
180
|
+
issue_id: card_id,
|
181
|
+
adapter_source: :trello,
|
182
|
+
raw_data: action_data)
|
507
183
|
end
|
508
184
|
|
509
185
|
# Maps an array of Trello member IDs to an array of User resources.
|
@@ -512,6 +188,7 @@ module ActiveProject
|
|
512
188
|
# @return [Array<Resources::User>]
|
513
189
|
def map_member_ids_to_users(member_ids)
|
514
190
|
return [] unless member_ids.is_a?(Array)
|
191
|
+
|
515
192
|
member_ids.map do |id|
|
516
193
|
Resources::User.new(self, id: id, adapter_source: :trello, raw_data: { id: id })
|
517
194
|
end
|
@@ -522,13 +199,13 @@ module ActiveProject
|
|
522
199
|
# @return [Resources::User, nil]
|
523
200
|
def map_user_data(member_data)
|
524
201
|
return nil unless member_data && member_data["id"]
|
202
|
+
|
525
203
|
Resources::User.new(self, # Pass adapter instance
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
)
|
204
|
+
id: member_data["id"],
|
205
|
+
name: member_data["fullName"], # Trello uses fullName
|
206
|
+
email: nil, # Trello API often doesn't provide email directly here
|
207
|
+
adapter_source: :trello,
|
208
|
+
raw_data: member_data)
|
532
209
|
end
|
533
210
|
end
|
534
211
|
end
|
@@ -18,7 +18,7 @@ module ActiveProject
|
|
18
18
|
# Add other associations like :project for an issue?
|
19
19
|
else raise "Unknown association: #{association_name}"
|
20
20
|
end
|
21
|
-
end
|
21
|
+
end
|
22
22
|
|
23
23
|
# --- Proxy Methods ---
|
24
24
|
|
@@ -32,6 +32,7 @@ module ActiveProject
|
|
32
32
|
# Ensure owner.id is accessed correctly
|
33
33
|
owner_id = @owner.respond_to?(:id) ? @owner.id : nil
|
34
34
|
raise "Owner object #{@owner.inspect} does not have an ID for association call." unless owner_id
|
35
|
+
|
35
36
|
@adapter.send(list_method, owner_id, options)
|
36
37
|
end
|
37
38
|
|
@@ -79,6 +80,8 @@ module ActiveProject
|
|
79
80
|
@target_resource_class.new(@adapter, merged_attrs)
|
80
81
|
end
|
81
82
|
|
83
|
+
alias new build
|
84
|
+
|
82
85
|
# Creates and saves a new associated resource.
|
83
86
|
# Example: project.issues.create(title: 'New', list_id: '...')
|
84
87
|
# @param attributes [Hash] Attributes for the new resource.
|
@@ -86,12 +89,13 @@ module ActiveProject
|
|
86
89
|
# @raise [NotImplementedError] Currently raises because #save is not fully implemented on resource.
|
87
90
|
def create(attributes = {})
|
88
91
|
create_method = determine_create_method
|
89
|
-
|
92
|
+
determine_context # Get owner context
|
90
93
|
# Pass owner ID/context first, then attributes
|
91
94
|
owner_id = @owner.respond_to?(:id) ? @owner.id : nil
|
92
95
|
raise "Owner object #{@owner.inspect} does not have an ID for association call." unless owner_id
|
96
|
+
|
93
97
|
@adapter.send(create_method, owner_id, attributes)
|
94
|
-
#
|
98
|
+
# NOTE: This currently returns the result from the adapter directly.
|
95
99
|
# A full implementation would likely build and then save, or re-fetch.
|
96
100
|
end
|
97
101
|
|
@@ -113,6 +117,7 @@ module ActiveProject
|
|
113
117
|
unless @adapter.respond_to?(method_name)
|
114
118
|
raise NotImplementedError, "#{@adapter.class.name} does not implement ##{method_name}"
|
115
119
|
end
|
120
|
+
|
116
121
|
method_name
|
117
122
|
end
|
118
123
|
|
@@ -125,6 +130,7 @@ module ActiveProject
|
|
125
130
|
unless @adapter.respond_to?(method_name)
|
126
131
|
raise NotImplementedError, "#{@adapter.class.name} does not implement ##{method_name}"
|
127
132
|
end
|
133
|
+
|
128
134
|
method_name
|
129
135
|
end
|
130
136
|
|
@@ -136,6 +142,7 @@ module ActiveProject
|
|
136
142
|
unless @adapter.respond_to?(method_name)
|
137
143
|
raise NotImplementedError, "#{@adapter.class.name} does not implement ##{method_name}"
|
138
144
|
end
|
145
|
+
|
139
146
|
method_name
|
140
147
|
end
|
141
148
|
end
|
@@ -16,44 +16,50 @@ module ActiveProject
|
|
16
16
|
|
17
17
|
def initialize
|
18
18
|
@adapter_configs = {}
|
19
|
+
@user_agent = "ActiveProject Gem (github.com/seuros/active_project)"
|
19
20
|
end
|
20
|
-
@user_agent = "ActiveProject Gem (github.com/seuros/activeproject)"
|
21
21
|
|
22
22
|
# Adds or updates the configuration for a specific adapter.
|
23
23
|
# If a block is given and a specific configuration class exists for the adapter,
|
24
24
|
# an instance of that class is yielded to the block. Otherwise, a basic
|
25
25
|
# configuration object is created from the options hash.
|
26
26
|
#
|
27
|
-
# @param
|
27
|
+
# @param adapter_type [Symbol] The name of the adapter (e.g., :basecamp, :jira, :trello).
|
28
|
+
# @param instance_name [Symbol, Hash] The name of the adapter instance (default: :primary) or options hash.
|
28
29
|
# @param options [Hash] Configuration options for the adapter (e.g., site, api_key, token).
|
29
30
|
# @yield [BaseAdapterConfiguration] Yields an adapter-specific configuration object if a block is given.
|
30
|
-
def add_adapter(
|
31
|
-
unless
|
32
|
-
|
31
|
+
def add_adapter(adapter_type, instance_name = :primary, options = {}, &block)
|
32
|
+
raise ArgumentError, "Adapter type must be a Symbol (e.g., :basecamp)" unless adapter_type.is_a?(Symbol)
|
33
|
+
|
34
|
+
# Handle the case where instance_name is actually the options hash
|
35
|
+
if instance_name.is_a?(Hash) && options.empty?
|
36
|
+
options = instance_name
|
37
|
+
instance_name = :primary
|
33
38
|
end
|
34
39
|
|
35
|
-
|
40
|
+
key = "#{adapter_type}_#{instance_name}".to_sym
|
41
|
+
|
42
|
+
config_class = ADAPTER_CONFIG_CLASSES[adapter_type]
|
36
43
|
|
37
|
-
# Use specific config class if block is given and class exists
|
38
44
|
if block && config_class
|
39
45
|
adapter_config_obj = config_class.new(options)
|
40
|
-
yield adapter_config_obj
|
41
|
-
@adapter_configs[
|
42
|
-
# Use specific config class if no block but class exists (handles options like status_mappings passed directly)
|
46
|
+
yield adapter_config_obj
|
47
|
+
@adapter_configs[key] = adapter_config_obj.freeze
|
43
48
|
elsif config_class
|
44
|
-
|
45
|
-
|
46
|
-
# Fallback to base config class if no specific class or no block
|
49
|
+
adapter_config_obj = config_class.new(options)
|
50
|
+
@adapter_configs[key] = adapter_config_obj.freeze
|
47
51
|
else
|
48
|
-
@adapter_configs[
|
52
|
+
@adapter_configs[key] = Configurations::BaseAdapterConfiguration.new(options).freeze
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
52
56
|
# Retrieves the configuration object for a specific adapter.
|
53
|
-
# @param
|
57
|
+
# @param adapter_type [Symbol] The name of the adapter (e.g., :jira, :trello).
|
58
|
+
# @param instance_name [Symbol] The name of the adapter instance (default: :primary).
|
54
59
|
# @return [BaseAdapterConfiguration, nil] The configuration object or nil if not found.
|
55
|
-
def adapter_config(
|
56
|
-
|
60
|
+
def adapter_config(adapter_type, instance_name = :primary)
|
61
|
+
key = "#{adapter_type}_#{instance_name}".to_sym
|
62
|
+
@adapter_configs[key]
|
57
63
|
end
|
58
64
|
end
|
59
65
|
end
|