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
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
module Adapters
|
5
|
+
module Trello
|
6
|
+
module Connection
|
7
|
+
BASE_URL = "https://api.trello.com/1/"
|
8
|
+
# @raise [ArgumentError] if required configuration options (:api_key, :api_token) are missing.
|
9
|
+
def initialize(config:)
|
10
|
+
unless config.is_a?(ActiveProject::Configurations::TrelloConfiguration)
|
11
|
+
raise ArgumentError, "TrelloAdapter requires a TrelloConfiguration object"
|
12
|
+
end
|
13
|
+
|
14
|
+
@config = config
|
15
|
+
|
16
|
+
unless @config.api_key && !@config.api_key.empty? && @config.api_token && !@config.api_token.empty?
|
17
|
+
raise ArgumentError, "TrelloAdapter configuration requires :api_key and :api_token"
|
18
|
+
end
|
19
|
+
|
20
|
+
@connection = initialize_connection
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Initializes the Faraday connection object.
|
26
|
+
def initialize_connection
|
27
|
+
Faraday.new(url: BASE_URL) do |conn|
|
28
|
+
conn.request :retry
|
29
|
+
conn.headers["Accept"] = "application/json"
|
30
|
+
conn.response :raise_error
|
31
|
+
conn.headers["User-Agent"] = ActiveProject.user_agent
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
module Adapters
|
5
|
+
module Trello
|
6
|
+
module Issues
|
7
|
+
# Lists Trello cards on a specific board.
|
8
|
+
# @param board_id [String] The ID of the Trello board.
|
9
|
+
# @param options [Hash] Optional filtering options.
|
10
|
+
# @return [Array<ActiveProject::Resources::Issue>]
|
11
|
+
def list_issues(board_id, options = {})
|
12
|
+
path = "boards/#{board_id}/cards"
|
13
|
+
query = { fields: "id,name,desc,closed,idList,idBoard,due,dueComplete,idMembers", list: true }
|
14
|
+
query[:filter] = options[:filter] if options[:filter]
|
15
|
+
|
16
|
+
cards_data = make_request(:get, path, nil, query)
|
17
|
+
return [] unless cards_data.is_a?(Array)
|
18
|
+
|
19
|
+
cards_data.map { |card_data| map_card_data(card_data, board_id) }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Finds a specific Card by its ID.
|
23
|
+
# @param card_id [String] The ID of the Trello Card.
|
24
|
+
# @param context [Hash] Optional context (ignored).
|
25
|
+
# @return [ActiveProject::Resources::Issue]
|
26
|
+
def find_issue(card_id, _context = {})
|
27
|
+
path = "cards/#{card_id}"
|
28
|
+
query = { fields: "id,name,desc,closed,idList,idBoard,due,dueComplete,idMembers", list: true }
|
29
|
+
card_data = make_request(:get, path, nil, query)
|
30
|
+
map_card_data(card_data, card_data["idBoard"])
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates a new Card in Trello.
|
34
|
+
# @param _board_id [String] Ignored (context).
|
35
|
+
# @param attributes [Hash] Card attributes. Required: :list_id, :title. Optional: :description, :assignee_ids, :due_on.
|
36
|
+
# @return [ActiveProject::Resources::Issue]
|
37
|
+
def create_issue(_board_id, attributes)
|
38
|
+
list_id = attributes[:list_id]
|
39
|
+
title = attributes[:title]
|
40
|
+
|
41
|
+
unless list_id && title && !title.empty?
|
42
|
+
raise ArgumentError, "Missing required attributes for Trello card creation: :list_id, :title"
|
43
|
+
end
|
44
|
+
|
45
|
+
path = "cards"
|
46
|
+
query_params = {
|
47
|
+
idList: list_id,
|
48
|
+
name: title,
|
49
|
+
desc: attributes[:description],
|
50
|
+
idMembers: attributes[:assignee_ids]&.join(","),
|
51
|
+
due: attributes[:due_on]&.iso8601
|
52
|
+
}.compact
|
53
|
+
|
54
|
+
card_data = make_request(:post, path, nil, query_params)
|
55
|
+
map_card_data(card_data, card_data["idBoard"])
|
56
|
+
end
|
57
|
+
|
58
|
+
# Updates an existing Card in Trello.
|
59
|
+
# @param card_id [String] The ID of the Trello Card.
|
60
|
+
# @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).
|
62
|
+
# @return [ActiveProject::Resources::Issue]
|
63
|
+
def update_issue(card_id, attributes, context = {})
|
64
|
+
update_attributes = attributes.dup
|
65
|
+
|
66
|
+
if update_attributes.key?(:status)
|
67
|
+
target_status = update_attributes.delete(:status)
|
68
|
+
|
69
|
+
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
|
74
|
+
|
75
|
+
unless board_id
|
76
|
+
raise ApiError, "Could not determine board ID for card '#{card_id}' to perform status mapping."
|
77
|
+
end
|
78
|
+
|
79
|
+
board_mappings = @config.status_mappings[board_id]
|
80
|
+
unless board_mappings
|
81
|
+
raise ConfigurationError,
|
82
|
+
"Trello status mapping not configured for board ID '#{board_id}'. Cannot map status ':#{target_status}'."
|
83
|
+
end
|
84
|
+
|
85
|
+
target_list_id = board_mappings.key(target_status)
|
86
|
+
|
87
|
+
unless target_list_id
|
88
|
+
raise ConfigurationError,
|
89
|
+
"Target status ':#{target_status}' not found in configured Trello status mappings for board ID '#{board_id}'."
|
90
|
+
end
|
91
|
+
|
92
|
+
update_attributes[:list_id] = target_list_id
|
93
|
+
end
|
94
|
+
|
95
|
+
path = "cards/#{card_id}"
|
96
|
+
|
97
|
+
query_params = {}
|
98
|
+
query_params[:name] = update_attributes[:title] if update_attributes.key?(:title)
|
99
|
+
query_params[:desc] = update_attributes[:description] if update_attributes.key?(:description)
|
100
|
+
query_params[:closed] = update_attributes[:closed] if update_attributes.key?(:closed)
|
101
|
+
query_params[:idList] = update_attributes[:list_id] if update_attributes.key?(:list_id)
|
102
|
+
query_params[:due] = update_attributes[:due_on]&.iso8601 if update_attributes.key?(:due_on)
|
103
|
+
query_params[:dueComplete] = update_attributes[:dueComplete] if update_attributes.key?(:dueComplete)
|
104
|
+
if update_attributes.key?(:assignee_ids)
|
105
|
+
query_params[:idMembers] =
|
106
|
+
update_attributes[:assignee_ids]&.join(",")
|
107
|
+
end
|
108
|
+
|
109
|
+
return find_issue(card_id, context) if query_params.empty?
|
110
|
+
|
111
|
+
card_data = make_request(:put, path, nil, query_params.compact)
|
112
|
+
map_card_data(card_data, card_data["idBoard"])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
module Adapters
|
5
|
+
module Trello
|
6
|
+
module Lists
|
7
|
+
# Creates a new list on a Trello board.
|
8
|
+
# @param board_id [String] The ID of the board.
|
9
|
+
# @param attributes [Hash] List attributes. Required: :name. Optional: :pos.
|
10
|
+
# @return [Hash] The raw data hash of the created list.
|
11
|
+
def create_list(board_id, attributes)
|
12
|
+
unless attributes[:name] && !attributes[:name].empty?
|
13
|
+
raise ArgumentError, "Missing required attribute for Trello list creation: :name"
|
14
|
+
end
|
15
|
+
|
16
|
+
path = "boards/#{board_id}/lists"
|
17
|
+
query_params = {
|
18
|
+
name: attributes[:name],
|
19
|
+
pos: attributes[:pos]
|
20
|
+
}.compact
|
21
|
+
|
22
|
+
make_request(:post, path, nil, query_params)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
module Adapters
|
5
|
+
module Trello
|
6
|
+
module Projects
|
7
|
+
# Lists Trello boards accessible by the configured token.
|
8
|
+
# @return [Array<ActiveProject::Resources::Project>] An array of project resources.
|
9
|
+
def list_projects
|
10
|
+
path = "members/me/boards"
|
11
|
+
query = { fields: "id,name,desc" }
|
12
|
+
boards_data = make_request(:get, path, nil, query)
|
13
|
+
|
14
|
+
return [] unless boards_data.is_a?(Array)
|
15
|
+
|
16
|
+
boards_data.map do |board_data|
|
17
|
+
Resources::Project.new(self,
|
18
|
+
id: board_data["id"],
|
19
|
+
key: nil,
|
20
|
+
name: board_data["name"],
|
21
|
+
adapter_source: :trello,
|
22
|
+
raw_data: board_data)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Finds a specific Trello Board by its ID.
|
27
|
+
# @param board_id [String] The ID of the Trello Board.
|
28
|
+
# @return [ActiveProject::Resources::Project]
|
29
|
+
def find_project(board_id)
|
30
|
+
path = "boards/#{board_id}"
|
31
|
+
query = { fields: "id,name,desc" }
|
32
|
+
board_data = make_request(:get, path, nil, query)
|
33
|
+
|
34
|
+
Resources::Project.new(self,
|
35
|
+
id: board_data["id"],
|
36
|
+
key: nil,
|
37
|
+
name: board_data["name"],
|
38
|
+
adapter_source: :trello,
|
39
|
+
raw_data: board_data)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates a new board in Trello.
|
43
|
+
# @param attributes [Hash] Board attributes. Required: :name. Optional: :description, :default_lists.
|
44
|
+
# @return [ActiveProject::Resources::Project]
|
45
|
+
def create_project(attributes)
|
46
|
+
unless attributes[:name] && !attributes[:name].empty?
|
47
|
+
raise ArgumentError, "Missing required attribute for Trello board creation: :name"
|
48
|
+
end
|
49
|
+
|
50
|
+
path = "boards/"
|
51
|
+
query_params = {
|
52
|
+
name: attributes[:name],
|
53
|
+
desc: attributes[:description],
|
54
|
+
defaultLists: attributes.fetch(:default_lists, true)
|
55
|
+
}.compact
|
56
|
+
|
57
|
+
board_data = make_request(:post, path, nil, query_params)
|
58
|
+
|
59
|
+
Resources::Project.new(self,
|
60
|
+
id: board_data["id"],
|
61
|
+
key: nil,
|
62
|
+
name: board_data["name"],
|
63
|
+
adapter_source: :trello,
|
64
|
+
raw_data: board_data)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Deletes a board in Trello.
|
68
|
+
# WARNING: This is a permanent deletion.
|
69
|
+
# @param board_id [String] The ID of the board to delete.
|
70
|
+
# @return [Boolean] true if deletion was successful (API returns 200).
|
71
|
+
# @raise [NotFoundError] if the board is not found.
|
72
|
+
# @raise [AuthenticationError] if credentials lack permission.
|
73
|
+
# @raise [ApiError] for other errors.
|
74
|
+
def delete_project(board_id)
|
75
|
+
path = "/boards/#{board_id}"
|
76
|
+
make_request(:delete, path)
|
77
|
+
true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
module Adapters
|
5
|
+
module Trello
|
6
|
+
module Webhooks
|
7
|
+
# Parses an incoming Trello webhook payload.
|
8
|
+
# @param request_body [String] The raw JSON request body.
|
9
|
+
# @param headers [Hash] Request headers (unused).
|
10
|
+
# @return [ActiveProject::WebhookEvent, nil] Parsed event or nil if unhandled.
|
11
|
+
def parse_webhook(request_body, _headers = {})
|
12
|
+
payload = begin
|
13
|
+
JSON.parse(request_body)
|
14
|
+
rescue StandardError
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
return nil unless payload.is_a?(Hash) && payload["action"].is_a?(Hash)
|
18
|
+
|
19
|
+
action = payload["action"]
|
20
|
+
action_type = action["type"]
|
21
|
+
actor_data = action["memberCreator"]
|
22
|
+
timestamp = begin
|
23
|
+
Time.parse(action["date"])
|
24
|
+
rescue StandardError
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
board_id = action.dig("data", "board", "id")
|
28
|
+
card_data = action.dig("data", "card")
|
29
|
+
action.dig("data", "text")
|
30
|
+
old_data = action.dig("data", "old")
|
31
|
+
|
32
|
+
event_type = nil
|
33
|
+
object_kind = nil
|
34
|
+
event_object_id = nil
|
35
|
+
object_key = nil
|
36
|
+
changes = nil
|
37
|
+
object_data = nil
|
38
|
+
|
39
|
+
case action_type
|
40
|
+
when "createCard"
|
41
|
+
event_type = :issue_created
|
42
|
+
object_kind = :issue
|
43
|
+
event_object_id = card_data["id"]
|
44
|
+
object_key = card_data["idShort"]
|
45
|
+
when "updateCard"
|
46
|
+
event_type = :issue_updated
|
47
|
+
object_kind = :issue
|
48
|
+
event_object_id = card_data["id"]
|
49
|
+
object_key = card_data["idShort"]
|
50
|
+
if old_data.is_a?(Hash)
|
51
|
+
changes = {}
|
52
|
+
old_data.each do |field, old_value|
|
53
|
+
new_value = card_data[field]
|
54
|
+
changes[field.to_sym] = [ old_value, new_value ]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
when "commentCard"
|
58
|
+
event_type = :comment_added
|
59
|
+
object_kind = :comment
|
60
|
+
event_object_id = action["id"]
|
61
|
+
object_key = nil
|
62
|
+
when "addMemberToCard", "removeMemberFromCard"
|
63
|
+
event_type = :issue_updated
|
64
|
+
object_kind = :issue
|
65
|
+
event_object_id = card_data["id"]
|
66
|
+
object_key = card_data["idShort"]
|
67
|
+
changes = { assignees: true }
|
68
|
+
else
|
69
|
+
return nil
|
70
|
+
end
|
71
|
+
|
72
|
+
WebhookEvent.new(
|
73
|
+
event_type: event_type,
|
74
|
+
object_kind: object_kind,
|
75
|
+
event_object_id: event_object_id,
|
76
|
+
object_key: object_key,
|
77
|
+
project_id: board_id,
|
78
|
+
actor: map_user_data(actor_data),
|
79
|
+
timestamp: timestamp,
|
80
|
+
adapter_source: :trello,
|
81
|
+
changes: changes,
|
82
|
+
object_data: object_data,
|
83
|
+
raw_data: payload
|
84
|
+
)
|
85
|
+
rescue JSON::ParserError
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|