Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/build.yml

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium

ruby-version: ["3.4"]

The Send coverage step references reports/3.4-forest_admin_datasource_pylon/coverage.json, but coverage artifacts are uploaded only for ruby-version == '4.0', so the artifact is named 4.0-forest_admin_datasource_pylon. The 3.4 path does not exist, so the Pylon coverage file is missing and the qltysh/qlty-action/coverage step receives a non-existent input. The coverage job matrix uses 3.4, which mismatches the 4.0 upload condition. Either use 4.0 in the files path for Pylon or align the upload and coverage job matrices to the same version.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @.github/workflows/build.yml around line 133:

The `Send coverage` step references `reports/3.4-forest_admin_datasource_pylon/coverage.json`, but coverage artifacts are uploaded only for `ruby-version == '4.0'`, so the artifact is named `4.0-forest_admin_datasource_pylon`. The `3.4` path does not exist, so the Pylon coverage file is missing and the `qltysh/qlty-action/coverage` step receives a non-existent input. The coverage job matrix uses `3.4`, which mismatches the `4.0` upload condition. Either use `4.0` in the `files` path for Pylon or align the upload and coverage job matrices to the same version.

Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
- forest_admin_datasource_zendesk
- forest_admin_datasource_snowflake
- forest_admin_datasource_mambu_payments
- forest_admin_datasource_pylon

steps:
- name: Checkout
Expand Down Expand Up @@ -76,6 +77,7 @@ jobs:
- forest_admin_datasource_zendesk
- forest_admin_datasource_snowflake
- forest_admin_datasource_mambu_payments
- forest_admin_datasource_pylon
services:
mongodb:
image: mongo:latest
Expand Down Expand Up @@ -143,7 +145,7 @@ jobs:
with:
verbose: true
oidc: true
files: ${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_agent/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_active_record/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_customizer/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_toolkit/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_mongoid/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_rpc_agent/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_rpc/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_zendesk/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_snowflake/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_mambu_payments/coverage.json
files: ${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_agent/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_active_record/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_customizer/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_toolkit/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_mongoid/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_rpc_agent/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_rpc/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_zendesk/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_snowflake/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_mambu_payments/coverage.json,${{ github.workspace }}/reports/${{ matrix.ruby-version }}-forest_admin_datasource_pylon/coverage.json

deploy:
name: Release package
Expand Down
5 changes: 5 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Gemspec/RequireMFA:
- 'packages/forest_admin_datasource_zendesk/forest_admin_datasource_zendesk.gemspec'
- 'packages/forest_admin_datasource_snowflake/forest_admin_datasource_snowflake.gemspec'
- 'packages/forest_admin_datasource_mambu_payments/forest_admin_datasource_mambu_payments.gemspec'
- 'packages/forest_admin_datasource_pylon/forest_admin_datasource_pylon.gemspec'

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
Expand Down Expand Up @@ -131,6 +132,7 @@ Style/MutableConstant:
- 'packages/forest_admin_datasource_zendesk/lib/forest_admin_datasource_zendesk/version.rb'
- 'packages/forest_admin_datasource_snowflake/lib/forest_admin_datasource_snowflake/version.rb'
- 'packages/forest_admin_datasource_mambu_payments/lib/forest_admin_datasource_mambu_payments/version.rb'
- 'packages/forest_admin_datasource_pylon/lib/forest_admin_datasource_pylon/version.rb'

# Offense count: 38
# This cop supports safe autocorrection (--autocorrect).
Expand Down Expand Up @@ -214,6 +216,7 @@ Style/StringLiterals:
- 'packages/forest_admin_datasource_zendesk/lib/forest_admin_datasource_zendesk/version.rb'
- 'packages/forest_admin_datasource_snowflake/lib/forest_admin_datasource_snowflake/version.rb'
- 'packages/forest_admin_datasource_mambu_payments/lib/forest_admin_datasource_mambu_payments/version.rb'
- 'packages/forest_admin_datasource_pylon/lib/forest_admin_datasource_pylon/version.rb'

# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
Expand Down Expand Up @@ -256,6 +259,7 @@ Metrics/ParameterLists:
Exclude:
- 'packages/forest_admin_datasource_zendesk/lib/forest_admin_datasource_zendesk/collections/base_collection.rb'
- 'packages/forest_admin_datasource_snowflake/lib/forest_admin_datasource_snowflake/datasource.rb'
- 'packages/forest_admin_datasource_pylon/lib/forest_admin_datasource_pylon/configuration.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/routes/query_handler.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/services/smart_action_checker.rb'
- 'packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/list_related_spec.rb'
Expand Down Expand Up @@ -287,6 +291,7 @@ Metrics/ModuleLength:
- 'packages/forest_admin_datasource_customizer/spec/**/*'
- 'packages/forest_admin_datasource_zendesk/spec/**/*'
- 'packages/forest_admin_datasource_mambu_payments/spec/**/*'
- 'packages/forest_admin_datasource_pylon/spec/**/*'
- 'packages/forest_admin_rails/spec/**/*'
- 'packages/forest_admin_rpc_agent/spec/**/*'
- 'packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/helpers.rb'
Expand Down
8 changes: 8 additions & 0 deletions packages/forest_admin_datasource_pylon/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.gem
.bundle/
Gemfile.lock
Gemfile-test.lock
coverage/
pkg/
tmp/
.rspec_status
3 changes: 3 additions & 0 deletions packages/forest_admin_datasource_pylon/.rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--format documentation
--color
--require spec_helper
16 changes: 16 additions & 0 deletions packages/forest_admin_datasource_pylon/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
source 'https://rubygems.org'

gemspec

gem 'forest_admin_datasource_customizer'
gem 'forest_admin_datasource_toolkit'
gem 'rake', '~> 13.0'
gem 'rubocop', '1.86.1'
gem 'rubocop-performance', '1.26.1'
gem 'rubocop-rspec', '3.9.0'

group :development, :test do
gem 'rspec', '~> 3.0'
gem 'simplecov', '~> 0.22', require: false
gem 'webmock', '~> 3.0'
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 17 lines of identical code in 3 locations (mass = 76) [qlty:identical-code]

19 changes: 19 additions & 0 deletions packages/forest_admin_datasource_pylon/Gemfile-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in forest_admin_datasource_pylon.gemspec
gemspec

gem 'rake', '~> 13.0'
gem 'rubocop', '1.86.1'
gem 'rubocop-performance', '1.26.1'
gem 'rubocop-rspec', '3.9.0'

group :development, :test do
gem 'forest_admin_datasource_customizer', path: '../forest_admin_datasource_customizer'
gem 'forest_admin_datasource_toolkit', path: '../forest_admin_datasource_toolkit'
gem 'rspec', '~> 3.0'
gem 'simplecov', '~> 0.22', require: false
gem 'simplecov-html', '~> 0.12.3'
gem 'simplecov_json_formatter', '~> 0.1.4'
gem 'webmock', '~> 3.0'
end
6 changes: 6 additions & 0 deletions packages/forest_admin_datasource_pylon/Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:spec)

task default: :spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)

require_relative 'lib/forest_admin_datasource_pylon/version'

Gem::Specification.new do |spec|
spec.name = 'forest_admin_datasource_pylon'
spec.version = ForestAdminDatasourcePylon::VERSION
spec.authors = ['Forest Admin']
spec.email = ['contact@forestadmin.com']
spec.homepage = 'https://www.forestadmin.com'
spec.summary = 'Pylon datasource for Forest Admin Ruby agent.'
spec.description = 'Surface Pylon issues, accounts, contacts, users and teams as Forest Admin collections.'
spec.license = 'GPL-3.0'
spec.required_ruby_version = '>= 3.0.0'

spec.metadata['homepage_uri'] = spec.homepage
spec.metadata['source_code_uri'] = 'https://github.com/ForestAdmin/agent-ruby'
spec.metadata['changelog_uri'] = 'https://github.com/ForestAdmin/agent-ruby/blob/main/CHANGELOG.md'
spec.metadata['rubygems_mfa_required'] = 'false'

spec.files = Dir.chdir(__dir__) do
`git ls-files -z`.split("\x0").reject do |f|
(File.expand_path(f) == __FILE__) ||
f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
end
end
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']

spec.add_dependency 'activesupport', '>= 6.1'
spec.add_dependency 'faraday', '~> 2.0'
Comment on lines +32 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High forest_admin_datasource_pylon/forest_admin_datasource_pylon.gemspec:32

The gemspec omits forest_admin_datasource_toolkit from its runtime dependencies, so installing forest_admin_datasource_pylon does not pull in the toolkit. When users require 'forest_admin_datasource_pylon', the gem fails with LoadError because the toolkit gem is missing. Add forest_admin_datasource_toolkit as a runtime dependency via spec.add_dependency.

  spec.add_dependency 'forest_admin_datasource_toolkit'
+  spec.add_dependency 'activesupport', '>= 6.1'
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/forest_admin_datasource_pylon/forest_admin_datasource_pylon.gemspec around lines 32-33:

The gemspec omits `forest_admin_datasource_toolkit` from its runtime dependencies, so installing `forest_admin_datasource_pylon` does not pull in the toolkit. When users `require 'forest_admin_datasource_pylon'`, the gem fails with `LoadError` because the toolkit gem is missing. Add `forest_admin_datasource_toolkit` as a runtime dependency via `spec.add_dependency`.

spec.add_dependency 'faraday-retry', '~> 2.0'
spec.add_dependency 'zeitwerk', '~> 2.3'
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require_relative 'forest_admin_datasource_pylon/version'
require 'logger'
require 'zeitwerk'
require 'faraday'
require 'faraday/retry'
require 'forest_admin_datasource_toolkit'

loader = Zeitwerk::Loader.for_gem
loader.setup

module ForestAdminDatasourcePylon
class Error < StandardError; end
class ConfigurationError < Error; end
class UnsupportedOperatorError < Error; end

# Raised when a Pylon API call fails. Carries the HTTP status and the
# (parsed) response body so callers — smart actions in particular — can
# surface Pylon's own validation message instead of a generic string.
class APIError < Error
attr_reader :status, :body

def initialize(message, status: nil, body: nil)
super(message)
@status = status
@body = body
end
end

class << self
attr_writer :logger

def logger
@logger ||= default_logger
end

private

def default_logger
return Rails.logger if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger

Logger.new($stderr).tap { |l| l.progname = 'forest_admin_datasource_pylon' }
end
end
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 45 lines of similar code in 2 locations (mass = 143) [qlty:similar-code]

Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
module ForestAdminDatasourcePylon
class Client
RETRY_STATUSES = [429, 502, 503, 504].freeze

# Faraday only retries these by default; a 429 is safe to retry on any verb
# because Pylon rejected the request before processing it, whereas a 502 on a
# POST /issues may well have created the issue.
IDEMPOTENT_METHODS = %i[delete get head options put].freeze
RETRY_IF = ->(env, _exception) { env[:status] == 429 }

def initialize(configuration)
@configuration = configuration
end

# Health check: Pylon returns the details of the organization owning the
# token, which is enough to prove the credentials are usable.
def me
must_succeed('me') { extract_data(connection.get('me').body) }
end

private

# Pylon wraps payloads in { "data": ..., "pagination": ..., "request_id": ... }.
def extract_data(body)
return nil if body.nil? || body == ''
return body['data'] if body.is_a?(Hash) && body.key?('data')

body
end

def must_succeed(operation)
yield
rescue Faraday::Error => e
raise api_error(operation, e)
rescue StandardError => e
raise APIError, "Pylon API call failed: #{operation}: #{e.class}: #{e.message}"
end

# Builds an APIError preserving the HTTP status and Pylon's own error body so
# smart actions can show the operator the real reason instead of "failed".
def api_error(operation, error)
response = error.respond_to?(:response) ? error.response : nil
status = response.is_a?(Hash) ? response[:status] : nil
body = parse_body(response.is_a?(Hash) ? response[:body] : nil)
detail = error_detail(status, body) || "#{error.class}: #{error.message}"
APIError.new("Pylon API call failed: #{operation}: #{detail}", status: status, body: body)
end

def error_detail(status, body)
return nil unless status

["HTTP #{status}", error_message(body)].compact.join(' ').strip
end

def error_message(parsed)
return parsed.to_s[0, 500] unless parsed.is_a?(Hash)

nested = parsed['error']
message = parsed['message'] || (nested.is_a?(Hash) ? nested['message'] : nested) ||
join_errors(parsed['errors'])
message = parsed.to_json if message.to_s.empty?
message = "#{message} (request_id: #{parsed["request_id"]})" if parsed['request_id']
message.to_s[0, 500]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 5): error_message [qlty:function-complexity]

end

def join_errors(errors)
Array(errors).filter_map { |e| e.is_a?(Hash) ? (e['message'] || e['detail']) : e }.join('; ')
end

def parse_body(body)
return body unless body.is_a?(String) && !body.empty?

JSON.parse(body)
rescue JSON::ParserError
body
end

# Middleware order is deliberate: `raise_error` sits outside the JSON parser
# so it raises with an already-parsed body, and `retry` sits innermost so it
# inspects raw statuses — behind `raise_error` it would never see a 429.
def connection
@connection ||= Faraday.new(url: @configuration.url) do |f|
f.request :json
f.response :raise_error
f.response :json
f.request :retry, max: @configuration.max_retries, interval: @configuration.retry_interval,
backoff_factor: 2, max_interval: 5, retry_statuses: RETRY_STATUSES,
methods: IDEMPOTENT_METHODS, retry_if: RETRY_IF
f.headers['Authorization'] = "Bearer #{@configuration.api_key}"
f.headers['Accept'] = 'application/json'
f.headers['User-Agent'] = "forest_admin_datasource_pylon/#{VERSION}"
f.options.open_timeout = @configuration.open_timeout
f.options.timeout = @configuration.timeout
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module ForestAdminDatasourcePylon
class Configuration
DEFAULT_BASE_URL = 'https://api.usepylon.com'.freeze

attr_reader :api_key, :base_url, :open_timeout, :timeout, :max_retries, :retry_interval

def initialize(api_key:, base_url: nil, open_timeout: 5, timeout: 30, max_retries: 3, retry_interval: 0.5)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many parameters (count = 6): initialize [qlty:function-parameters]

@api_key = api_key
@base_url = base_url || DEFAULT_BASE_URL
@open_timeout = open_timeout
@timeout = timeout
@max_retries = max_retries
@retry_interval = retry_interval
validate!
end

# Pylon exposes unversioned paths (`/issues`, `/me`) directly under the host.
def url
@base_url.chomp('/')
end

private

def validate!
missing = []
missing << 'api_key' if blank?(@api_key)
return if missing.empty?

raise ConfigurationError,
"ForestAdminDatasourcePylon missing required config: #{missing.join(", ")}"
end

def blank?(value)
value.nil? || value.to_s.strip.empty?
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module ForestAdminDatasourcePylon
VERSION = "1.36.2"
end
Loading
Loading