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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +28 -82
  3. data/Rakefile +4 -2
  4. data/lib/active_project/adapters/base.rb +3 -14
  5. data/lib/active_project/adapters/basecamp/comments.rb +27 -0
  6. data/lib/active_project/adapters/basecamp/connection.rb +49 -0
  7. data/lib/active_project/adapters/basecamp/issues.rb +139 -0
  8. data/lib/active_project/adapters/basecamp/lists.rb +54 -0
  9. data/lib/active_project/adapters/basecamp/projects.rb +110 -0
  10. data/lib/active_project/adapters/basecamp/webhooks.rb +73 -0
  11. data/lib/active_project/adapters/basecamp_adapter.rb +46 -449
  12. data/lib/active_project/adapters/jira/comments.rb +28 -0
  13. data/lib/active_project/adapters/jira/connection.rb +47 -0
  14. data/lib/active_project/adapters/jira/issues.rb +132 -0
  15. data/lib/active_project/adapters/jira/projects.rb +100 -0
  16. data/lib/active_project/adapters/jira/transitions.rb +68 -0
  17. data/lib/active_project/adapters/jira/webhooks.rb +89 -0
  18. data/lib/active_project/adapters/jira_adapter.rb +59 -486
  19. data/lib/active_project/adapters/trello/comments.rb +21 -0
  20. data/lib/active_project/adapters/trello/connection.rb +37 -0
  21. data/lib/active_project/adapters/trello/issues.rb +117 -0
  22. data/lib/active_project/adapters/trello/lists.rb +27 -0
  23. data/lib/active_project/adapters/trello/projects.rb +82 -0
  24. data/lib/active_project/adapters/trello/webhooks.rb +91 -0
  25. data/lib/active_project/adapters/trello_adapter.rb +54 -377
  26. data/lib/active_project/association_proxy.rb +10 -3
  27. data/lib/active_project/configuration.rb +23 -17
  28. data/lib/active_project/configurations/trello_configuration.rb +1 -3
  29. data/lib/active_project/resource_factory.rb +20 -10
  30. data/lib/active_project/resources/comment.rb +0 -5
  31. data/lib/active_project/resources/issue.rb +0 -5
  32. data/lib/active_project/resources/project.rb +0 -3
  33. data/lib/active_project/resources/user.rb +0 -1
  34. data/lib/active_project/version.rb +3 -1
  35. data/lib/activeproject.rb +67 -15
  36. 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