forked from danmayer/coverband
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
411 additions
and
718 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
# frozen_string_literal: true | ||
|
||
require "set" | ||
require "singleton" | ||
|
||
module Coverband | ||
module Collectors | ||
### | ||
# This abstract class makes it easy to track any used/unused with timestamp set of usage | ||
### | ||
class AbstractTracker | ||
REPORT_ROUTE = "/" | ||
TITLE = "abstract" | ||
|
||
attr_accessor :target | ||
attr_reader :logger, :store, :ignore_patterns | ||
|
||
def initialize(options = {}) | ||
raise NotImplementedError, "#{self.class.name} requires a newer version of Rails" unless self.class.supported_version? | ||
raise "Coverband: #{self.class.name} initialized before configuration!" if !Coverband.configured? && ENV["COVERBAND_TEST"] == "test" | ||
|
||
@ignore_patterns = Coverband.configuration.ignore | ||
@store = options.fetch(:store) { Coverband.configuration.store } | ||
@logger = options.fetch(:logger) { Coverband.configuration.logger } | ||
@target = options.fetch(:target) do | ||
concrete_target | ||
end | ||
|
||
@one_time_timestamp = false | ||
|
||
@logged_keys = Set.new | ||
@keys_to_record = Set.new | ||
end | ||
|
||
def logged_keys | ||
@logged_keys.to_a | ||
end | ||
|
||
def keys_to_record | ||
@keys_to_record.to_a | ||
end | ||
|
||
### | ||
# This method is called on every translation usage | ||
### | ||
def track_key(key) | ||
if key | ||
if newly_seen_key?(key) | ||
@logged_keys << key | ||
@keys_to_record << key if track_key?(key) | ||
end | ||
end | ||
end | ||
|
||
def used_keys | ||
redis_store.hgetall(tracker_key) | ||
end | ||
|
||
def all_keys | ||
target.uniq | ||
end | ||
|
||
def unused_keys(used_keys = nil) | ||
recently_used_keys = (used_keys || self.used_keys).keys | ||
all_keys.reject { |k| recently_used_keys.include?(k.to_s) } | ||
end | ||
|
||
def as_json | ||
used_keys = self.used_keys | ||
{ | ||
unused_keys: unused_keys(used_keys), | ||
used_keys: used_keys | ||
}.to_json | ||
end | ||
|
||
def tracking_since | ||
if (tracking_time = redis_store.get(tracker_time_key)) | ||
Time.at(tracking_time.to_i).iso8601 | ||
else | ||
"N/A" | ||
end | ||
end | ||
|
||
def reset_recordings | ||
redis_store.del(tracker_key) | ||
redis_store.del(tracker_time_key) | ||
end | ||
|
||
def clear_key!(key) | ||
return unless key | ||
puts "#{tracker_key} key #{key}" | ||
redis_store.hdel(tracker_key, key) | ||
@logged_keys.delete(key) | ||
end | ||
|
||
def save_report | ||
redis_store.set(tracker_time_key, Time.now.to_i) unless @one_time_timestamp || tracker_time_key_exists? | ||
@one_time_timestamp = true | ||
reported_time = Time.now.to_i | ||
@keys_to_record.to_a.each do |key| | ||
redis_store.hset(tracker_key, key.to_s, reported_time) | ||
end | ||
@keys_to_record.clear | ||
rescue => e | ||
# we don't want to raise errors if Coverband can't reach redis. | ||
# This is a nice to have not a bring the system down | ||
logger&.error "Coverband: #{self.class.name} failed to store, error #{e.class.name} info #{e.message}" | ||
end | ||
|
||
# This is the basic rails version supported, if there is something more unique over ride in subclass | ||
def self.supported_version? | ||
defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split(".").first.to_i >= 5 | ||
end | ||
|
||
def route | ||
self.class::REPORT_ROUTE | ||
end | ||
|
||
def title | ||
self.class::TITLE | ||
end | ||
|
||
protected | ||
|
||
def newly_seen_key?(key) | ||
!@logged_keys.include?(key) | ||
end | ||
|
||
def track_key?(key, options = {}) | ||
@ignore_patterns.none? { |pattern| key.to_s.include?(pattern) } | ||
end | ||
|
||
private | ||
|
||
def concrete_target | ||
raise "subclass must implement" | ||
end | ||
|
||
def redis_store | ||
store.raw_store | ||
end | ||
|
||
def tracker_time_key_exists? | ||
if defined?(redis_store.exists?) | ||
redis_store.exists?(tracker_time_key) | ||
else | ||
redis_store.exists(tracker_time_key) | ||
end | ||
end | ||
|
||
def tracker_key | ||
"#{class_key}_tracker" | ||
end | ||
|
||
def tracker_time_key | ||
"#{class_key}_tracker_time" | ||
end | ||
|
||
def class_key | ||
@class_key ||= self.class.name.split("::").last | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.