Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sync checkin configuration from config file #508

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
57 changes: 57 additions & 0 deletions lib/honeybadger/backend/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

require 'honeybadger/logging'

require 'honeybadger/check_in'

module Honeybadger
module Backend
class Response
Expand Down Expand Up @@ -109,6 +111,61 @@ def track_deployment(payload)
notify(:deploys, payload)
end

# Get check_in by id
# @example
# backend.get_check_in('1234', 'ajdja")
#
# @param [String] project_id The unique project id
# @param [String] id The unique check_in id
# @raise NotImplementedError
def get_check_in(project_id, id)
raise NotImplementedError, 'must define #get_checkin on subclass'
end

# Get check_ins by project
# @example
# backend.get_check_ins('1234')
#
# @param [String] project_id The unique project id
# @raise NotImplementedError
def get_check_ins(project_id)
raise NotImplementedError, 'must define #get_checkins on subclass'
end

# Create check_in on project
# @example
# backend.create_check_in('1234', check_in)
#
# @param [String] project_id The unique project id
# @param [CheckIn] data A CheckIn object encapsulating the config
# @raise NotImplementedError
def create_check_in(project_id, data)
raise NotImplementedError, 'must define #create_check_in on subclass'
end

# Update check_in on project
# @example
# backend.update_check_in('1234', 'eajaj', check_in)
#
# @param [String] project_id The unique project id
# @param [String] id The unique check_in id
# @param [CheckIn] data A CheckIn object encapsulating the config
# @raise NotImplementedError
def update_check_in(project_id, id, data)
raise NotImplementedError, 'must define #update_check_in on subclass'
end

# Delete check_in
# @example
# backend.delete_check_in('1234', 'eajaj')
#
# @param [String] project_id The unique project id
# @param [String] id The unique check_in id
# @raise NotImplementedError
def delete_check_in(project_id, id)
raise NotImplementedError, 'must define #delete_check_in on subclass'
end

private

attr_reader :config
Expand Down
94 changes: 93 additions & 1 deletion lib/honeybadger/backend/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

require 'honeybadger/backend/base'
require 'honeybadger/util/http'
require 'honeybadger/util/app_http'

module Honeybadger
module Backend
Expand All @@ -16,11 +17,11 @@ class Server < Base

CHECK_IN_ENDPOINT = '/v1/check_in'.freeze


HTTP_ERRORS = Util::HTTP::ERRORS

def initialize(config)
@http = Util::HTTP.new(config)
@app_http = Util::AppHTTP.new(config)
super
end

Expand Down Expand Up @@ -48,6 +49,97 @@ def check_in(id)
Response.new(:error, nil, "HTTP Error: #{e.class}")
end


#
##### CheckIn Crud methods
#

# Get check_in by id
# @example
# backend.get_check_in('1234', 'ajdja")
#
# @param [String] project_id The unique project id
# @param [String] id The unique check_in id
# @returns [CheckIn] or nil if check_in is not found
# @raises CheckInSyncError on error

def get_check_in(project_id, id)
response = Response.new(@app_http.get("/v2/projects/#{project_id}/check_ins/#{id}"))
if response.success?
return CheckIn.from_remote(project_id, JSON.parse(response.body))
else
if response.code == 404
return nil
end
end
raise CheckInSyncError.new "Fetching CheckIn failed (Code: #{response.code}) #{response.body}"
end

# Get check_ins by project
# @example
# backend.get_check_ins('1234')
#
# @param [String] project_id The unique project id
# @returns [Array<CheckIn>] All checkins for this project
# @raises CheckInSyncError on error
def get_check_ins(project_id)
response = Response.new(@app_http.get("/v2/projects/#{project_id}/check_ins"))
if response.success?
all_check_ins = JSON.parse(response.body)["results"]
return all_check_ins.map{|cfg| CheckIn.from_remote(project_id, cfg) }
end
raise CheckInSyncError.new "Fetching CheckIns failed (Code: #{response.code}) #{response.body}"
end

# Create check_in on project
# @example
# backend.create_check_in('1234', check_in)
#
# @param [String] project_id The unique project id
# @param [CheckIn] check_in_config A CheckIn object encapsulating the config
# @returns [CheckIn] A CheckIn object additionally containing the id
# @raises CheckInSyncError on error
def create_check_in(project_id, check_in_config)
response = Response.new(@app_http.post("/v2/projects/#{project_id}/check_ins", check_in_config))
if response.success?
return CheckIn.from_remote(project_id, JSON.parse(response.body))
end
raise CheckInSyncError.new "Creating CheckIn failed (Code: #{response.code}) #{response.body}"
end

# Update check_in on project
# @example
# backend.update_check_in('1234', 'eajaj', check_in)
#
# @param [String] project_id The unique project id
# @param [String] id The unique check_in id
# @param [CheckIn] check_in_config A CheckIn object encapsulating the config
# @returns [CheckIn] updated CheckIn object
# @raises CheckInSyncError on error
def update_check_in(project_id, id, check_in_config)
response = Response.new(@app_http.put("/v2/projects/#{project_id}/check_ins/#{id}", check_in_config))
if response.success?
return check_in_config
end
raise CheckInSyncError.new "Updating CheckIn failed (Code: #{response.code}) #{response.body}"
end

# Delete check_in
# @example
# backend.delete_check_in('1234', 'eajaj')
#
# @param [String] project_id The unique project id
# @param [String] id The unique check_in id
# @returns [Boolean] true if deletion was successful
# @raises CheckInSyncError on error
def delete_check_in(project_id, id)
response = Response.new(@app_http.delete("/v2/projects/#{project_id}/check_ins/#{id}"))
if response.success?
return true
end
raise CheckInSyncError.new "Deleting CheckIn failed (Code: #{response.code}) #{response.body}"
end

private

def payload_headers(payload)
Expand Down
101 changes: 101 additions & 0 deletions lib/honeybadger/backend/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,107 @@ def check_in(id)
check_ins << id
super
end

def self.check_in_configs
@check_in_configs ||= {}
end

def check_in_configs
self.class.check_in_configs
end

# Set check_in by id, only for use in tests
# @example
# backend.set_check_in('1234', 'ajdja', check_in)
#
# @param [String] project_id The unique project id
# @param [String] id The unique check_in id
# @param [CheckIn] data CheckIn object with config
def set_check_in(project_id, id, data)
self.check_in_configs[project_id] = self.check_in_configs[project_id] || {}
self.check_in_configs[project_id][id] = data
end

# Get check_in by id
# @example
# backend.get_check_in('1234', 'ajdja")
#
# @param [String] project_id The unique project id
# @param [String] id The unique check_in id
# @returns [CheckIn] or nil if check_in is not found
def get_check_in(project_id, id)
self.check_in_configs ||= {}
self.check_in_configs[project_id]&.[](id)
end

# Get checkins by project
# @example
# backend.get_check_ins('1234')
#
# @param [String] project_id The unique project id
# @returns [Array<CheckIn>] All checkins for this project
def get_check_ins(project_id)
self.check_in_configs ||= {}
self.check_in_configs[project_id] = self.check_in_configs[project_id] || {}
return [] if self.check_in_configs[project_id].empty?
self.check_in_configs[project_id].values
end

# Create check_in on project
# @example
# backend.create_check_in('1234', check_in)
#
# @param [String] project_id The unique project id
# @param [CheckIn] data A CheckIn object encapsulating the config
# @returns [CheckIn] A check_in object containing the id
def create_check_in(project_id, data)
self.check_in_configs ||= {}
self.check_in_configs[project_id] = self.check_in_configs[project_id] || {}
id = self.check_in_configs[project_id].length + 1
loop do
break unless self.check_in_configs[project_id].has_key?(id)
id += 1
end
id = id.to_s
data.id = id
self.check_in_configs[project_id][id] = data
data
end

# Update check_in on project
# @example
# backend.update_check_in('1234', 'eajaj', check_in)
#
# @param [String] project_id The unique project id
# @param [String] id The unique check_in id
# @param [CheckIn] data A CheckIn object encapsulating the config
# @returns [CheckIn] updated CheckIn object
def update_check_in(project_id, id, data)
self.check_in_configs ||= {}
if self.check_in_configs.dig(project_id, id)
self.check_in_configs.dig(project_id, id).update_from(data)
return self.check_in_configs.dig(project_id, id)
else
raise "Update failed"
end
end

# Delete check_in
# @example
# backend.delete_check_in('1234', 'eajaj')
#
# @param [String] project_id The unique project id
# @param [String] id The unique check_in id
# @returns [Boolean] true if deletion was successful
# @raises CheckInSyncError on error
def delete_check_in(project_id, id)
self.check_in_configs ||= {}
if self.check_in_configs.dig(project_id, id)
self.check_in_configs[project_id].delete(id)
else
raise "Delete failed"
end
end
end
end
end
84 changes: 84 additions & 0 deletions lib/honeybadger/check_in.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
module Honeybadger
class InvalidCheckinConfig < StandardError; end
class CheckInSyncError < StandardError; end

class CheckIn
halfbyte marked this conversation as resolved.
Show resolved Hide resolved
ATTRIBUTES = %w[name slug schedule_type report_period grace_period cron_schedule cron_timezone].freeze
attr_reader :project_id, :data
attr_accessor :deleted, :id

def self.from_config(attrs)
attrs = normalize_keys(attrs)
self.new(attrs["project_id"], id: attrs["id"], attributes: attrs)
end

def self.from_remote(project_id, attrs)
attrs = normalize_keys(attrs)
self.new(project_id, id: attrs["id"], attributes: attrs)
end

def initialize(project_id, id: nil, attributes: nil)
@project_id = project_id
@id = id
@data = attributes.slice(*ATTRIBUTES) if attributes.is_a?(Hash)
@data["grace_period"] ||= ""
end

def ==(other)
(data.reject {|k,v| v.nil?}) == (other.data.reject {|k,v| v.nil?})
end


def to_json
subzero10 marked this conversation as resolved.
Show resolved Hide resolved
output = {
name: name,
slug: slug || '',
schedule_type: schedule_type,
grace_period: grace_period || ''
}
if schedule_type == 'simple'
output[:report_period] = report_period
else
output[:cron_schedule] = cron_schedule
output[:cron_timezone] = cron_timezone || ""
end

output.to_json
end

ATTRIBUTES.each do |meth|
define_method meth do
data[meth]
end
end

def validate!
raise InvalidCheckinConfig.new('project_id is required for each check_in') if blank?(project_id)
raise InvalidCheckinConfig.new('name is required for each check_in') if blank?(name)
raise InvalidCheckinConfig.new("#{name} schedule_type must be either 'simple' or 'cron'") unless ['simple', 'cron'].include? schedule_type
if schedule_type == 'simple'
raise InvalidCheckinConfig.new("#{name} report_period is required for simple checkins") if blank?(report_period)
else
raise InvalidCheckinConfig.new("#{name} cron_schedule is required for cron checkins") if blank?(cron_schedule)
end
end

def update_from(check_in)
@data = check_in.data
end

def deleted?
@deleted
end

private

def blank?(str)
str.nil? || str.to_s.strip == ""
end

def self.normalize_keys(hash)
hash.transform_keys(&:to_s)
end
end
end
Loading