activeproject 0.3.0 → 0.5.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 +4 -4
- data/README.md +201 -55
- data/lib/active_project/adapters/base.rb +154 -14
- data/lib/active_project/adapters/basecamp/comments.rb +34 -0
- data/lib/active_project/adapters/basecamp/connection.rb +6 -24
- data/lib/active_project/adapters/basecamp/issues.rb +6 -5
- data/lib/active_project/adapters/basecamp/webhooks.rb +7 -8
- data/lib/active_project/adapters/fizzy/columns.rb +116 -0
- data/lib/active_project/adapters/fizzy/comments.rb +129 -0
- data/lib/active_project/adapters/fizzy/connection.rb +41 -0
- data/lib/active_project/adapters/fizzy/issues.rb +221 -0
- data/lib/active_project/adapters/fizzy/projects.rb +105 -0
- data/lib/active_project/adapters/fizzy_adapter.rb +151 -0
- data/lib/active_project/adapters/github_project/comments.rb +91 -0
- data/lib/active_project/adapters/github_project/connection.rb +58 -0
- data/lib/active_project/adapters/github_project/helpers.rb +100 -0
- data/lib/active_project/adapters/github_project/issues.rb +287 -0
- data/lib/active_project/adapters/github_project/projects.rb +139 -0
- data/lib/active_project/adapters/github_project/webhooks.rb +168 -0
- data/lib/active_project/adapters/github_project.rb +8 -0
- data/lib/active_project/adapters/github_project_adapter.rb +65 -0
- data/lib/active_project/adapters/github_repo/connection.rb +62 -0
- data/lib/active_project/adapters/github_repo/issues.rb +242 -0
- data/lib/active_project/adapters/github_repo/projects.rb +116 -0
- data/lib/active_project/adapters/github_repo/webhooks.rb +354 -0
- data/lib/active_project/adapters/github_repo_adapter.rb +134 -0
- data/lib/active_project/adapters/jira/attribute_normalizer.rb +16 -0
- data/lib/active_project/adapters/jira/comments.rb +41 -0
- data/lib/active_project/adapters/jira/connection.rb +15 -15
- data/lib/active_project/adapters/jira/issues.rb +21 -7
- data/lib/active_project/adapters/jira/projects.rb +3 -1
- data/lib/active_project/adapters/jira/transitions.rb +2 -1
- data/lib/active_project/adapters/jira/webhooks.rb +5 -7
- data/lib/active_project/adapters/jira_adapter.rb +23 -3
- data/lib/active_project/adapters/trello/comments.rb +34 -0
- data/lib/active_project/adapters/trello/connection.rb +12 -9
- data/lib/active_project/adapters/trello/issues.rb +7 -5
- data/lib/active_project/adapters/trello/webhooks.rb +5 -7
- data/lib/active_project/adapters/trello_adapter.rb +5 -3
- data/lib/active_project/association_proxy.rb +3 -2
- data/lib/active_project/configuration.rb +6 -3
- data/lib/active_project/configurations/base_adapter_configuration.rb +102 -0
- data/lib/active_project/configurations/basecamp_configuration.rb +42 -0
- data/lib/active_project/configurations/fizzy_configuration.rb +47 -0
- data/lib/active_project/configurations/github_configuration.rb +57 -0
- data/lib/active_project/configurations/jira_configuration.rb +54 -0
- data/lib/active_project/configurations/trello_configuration.rb +24 -2
- data/lib/active_project/connections/base.rb +35 -0
- data/lib/active_project/connections/graph_ql.rb +83 -0
- data/lib/active_project/connections/http_client.rb +79 -0
- data/lib/active_project/connections/pagination.rb +44 -0
- data/lib/active_project/connections/rest.rb +33 -0
- data/lib/active_project/error_mapper.rb +38 -0
- data/lib/active_project/errors.rb +13 -0
- data/lib/active_project/railtie.rb +1 -3
- data/lib/active_project/resources/base_resource.rb +13 -14
- data/lib/active_project/resources/comment.rb +46 -2
- data/lib/active_project/resources/issue.rb +106 -18
- data/lib/active_project/resources/persistable_resource.rb +47 -0
- data/lib/active_project/resources/project.rb +1 -1
- data/lib/active_project/status_mapper.rb +145 -0
- data/lib/active_project/version.rb +1 -1
- data/lib/active_project/webhook_event.rb +34 -12
- data/lib/activeproject.rb +9 -6
- metadata +74 -16
- data/lib/active_project/adapters/http_client.rb +0 -71
- data/lib/active_project/adapters/pagination.rb +0 -68
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "faraday"
|
|
4
|
-
require "faraday/retry"
|
|
5
|
-
require "json"
|
|
6
|
-
|
|
7
|
-
module ActiveProject
|
|
8
|
-
module Adapters
|
|
9
|
-
module HttpClient
|
|
10
|
-
include Pagination
|
|
11
|
-
DEFAULT_HEADERS = {
|
|
12
|
-
"Content-Type" => "application/json",
|
|
13
|
-
"Accept" => "application/json",
|
|
14
|
-
"User-Agent" => -> { ActiveProject.user_agent }
|
|
15
|
-
}.freeze
|
|
16
|
-
RETRY_OPTS = { max: 5, interval: 0.5, backoff_factor: 2 }.freeze
|
|
17
|
-
|
|
18
|
-
def build_connection(base_url:, auth_middleware:, extra_headers: {})
|
|
19
|
-
@connection = Faraday.new(url: base_url) do |conn|
|
|
20
|
-
auth_middleware.call(conn) # <-- adapter-specific
|
|
21
|
-
conn.request :retry, **RETRY_OPTS
|
|
22
|
-
conn.response :raise_error
|
|
23
|
-
default_adapter = ENV.fetch("AP_DEFAULT_ADAPTER", "net_http").to_sym
|
|
24
|
-
conn.adapter default_adapter
|
|
25
|
-
conn.headers.merge!(DEFAULT_HEADERS.transform_values { |v| v.respond_to?(:call) ? v.call : v })
|
|
26
|
-
conn.headers.merge!(extra_headers)
|
|
27
|
-
yield conn if block_given? # optional extra tweaks
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def request(method, path, body: nil, query: nil, headers: {})
|
|
32
|
-
raise "HTTP connection not initialised" unless @connection
|
|
33
|
-
|
|
34
|
-
json_body = body.is_a?(String) ? body : (body ? JSON.generate(body) : nil)
|
|
35
|
-
response = @connection.run_request(method, path, json_body, headers) do |req|
|
|
36
|
-
req.params.update(query) if query&.any?
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
return nil if response.status == 204 || response.body.to_s.empty?
|
|
40
|
-
JSON.parse(response.body)
|
|
41
|
-
rescue Faraday::Error => e
|
|
42
|
-
raise translate_faraday_error(e)
|
|
43
|
-
rescue JSON::ParserError => e
|
|
44
|
-
raise ActiveProject::ApiError.new("Non-JSON response from #{path}",
|
|
45
|
-
original_error: e,
|
|
46
|
-
status_code: response&.status,
|
|
47
|
-
response_body: response&.body)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
private
|
|
51
|
-
|
|
52
|
-
def translate_faraday_error(err)
|
|
53
|
-
status = err.response_status
|
|
54
|
-
body = err.response_body.to_s
|
|
55
|
-
msg = begin JSON.parse(body)["message"] rescue body end
|
|
56
|
-
|
|
57
|
-
case status
|
|
58
|
-
when 401, 403 then ActiveProject::AuthenticationError.new(msg)
|
|
59
|
-
when 404 then ActiveProject::NotFoundError.new(msg)
|
|
60
|
-
when 429 then ActiveProject::RateLimitError.new(msg)
|
|
61
|
-
when 400, 422 then ActiveProject::ValidationError.new(msg, status_code: status, response_body: body)
|
|
62
|
-
else
|
|
63
|
-
ActiveProject::ApiError.new("HTTP #{status || 'N/A'}: #{msg}",
|
|
64
|
-
original_error: err,
|
|
65
|
-
status_code: status,
|
|
66
|
-
response_body: body)
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ActiveProject
|
|
4
|
-
module Adapters
|
|
5
|
-
module Pagination
|
|
6
|
-
# RFC 5988 Link header parsing. Returns { "next" => url, "prev" => url, … }
|
|
7
|
-
def parse_link_header(link_header)
|
|
8
|
-
return {} unless link_header
|
|
9
|
-
|
|
10
|
-
link_header.split(",").each_with_object({}) do |part, acc|
|
|
11
|
-
url, rel = part.split(";")
|
|
12
|
-
next unless rel && url
|
|
13
|
-
|
|
14
|
-
url = url[/\<([^>]+)\>/, 1] # strip angle brackets
|
|
15
|
-
rel = rel[/rel=\"?([^\";]+)\"?/, 1]
|
|
16
|
-
acc[rel] = url if url && rel
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Synchronous enumerator – works with any Faraday adapter.
|
|
21
|
-
#
|
|
22
|
-
# @param path [String] first request path or full URL
|
|
23
|
-
# @param method [Symbol] :get or :post
|
|
24
|
-
# @param body [Hash,String,nil]
|
|
25
|
-
# @param query [Hash]
|
|
26
|
-
def each_page(path, method: :get, body: nil, query: {})
|
|
27
|
-
next_path = path
|
|
28
|
-
loop do
|
|
29
|
-
data, link_header = perform_page_request(method, next_path, body, query)
|
|
30
|
-
yield data
|
|
31
|
-
next_path = parse_link_header(link_header)["next"]
|
|
32
|
-
break unless next_path
|
|
33
|
-
# After the first hop the URL is absolute; zero-out body/query for GETs
|
|
34
|
-
body = nil if method == :get
|
|
35
|
-
query = {}
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Fibre-friendly variant; launches a child task for every hop _after_ the
|
|
40
|
-
# first one, so JSON parsing in the current page overlaps the download of
|
|
41
|
-
# the next page (requires AP_DEFAULT_ADAPTER=async_http + Async scheduler).
|
|
42
|
-
def each_page_async(path, method: :get, body: nil, query: {}, &block)
|
|
43
|
-
require "async" # soft-require so callers don’t need Async unless used
|
|
44
|
-
Async do |task|
|
|
45
|
-
current_path = path
|
|
46
|
-
while current_path
|
|
47
|
-
data, link = perform_page_request(method, current_path, body, query)
|
|
48
|
-
next_url = parse_link_header(link)["next"]
|
|
49
|
-
body = nil if method == :get # as above
|
|
50
|
-
query = {}
|
|
51
|
-
# Prefetch next page while the caller consumes this one
|
|
52
|
-
fut = next_url ? task.async { perform_page_request(method, next_url, body, query) } : nil
|
|
53
|
-
yield data
|
|
54
|
-
current_path, data, link = next_url, *fut&.wait
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
private
|
|
60
|
-
|
|
61
|
-
# One tiny helper so the logic is not duplicated.
|
|
62
|
-
def perform_page_request(method, path, body, query)
|
|
63
|
-
json = request(method, path, body: body, query: query)
|
|
64
|
-
[ json, @last_response.headers["Link"] ]
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|