class Itly::Plugin::Iteratively::Client

HTTP client for the plugin requests

Attributes

api_key[R]
batch_size[R]
branch[R]
flush_interval_ms[R]
flush_queue_size[R]
logger[R]
max_retries[R]
omit_values[R]
retry_delay_max[R]
retry_delay_min[R]
url[R]
version[R]

Public Class Methods

new( url:, api_key:, logger:, flush_queue_size:, batch_size:, flush_interval_ms:, max_retries:, retry_delay_min:, retry_delay_max:, omit_values:, branch:, version: ) click to toggle source
# File lib/itly/plugin/iteratively/client.rb, line 17
def initialize(
  url:, api_key:, logger:, flush_queue_size:, batch_size:, flush_interval_ms:, max_retries:,
  retry_delay_min:, retry_delay_max:, omit_values:, branch:, version:
)
  @buffer = ::Concurrent::Array.new
  @runner = @scheduler = nil

  @api_key = api_key
  @url = url
  @logger = logger
  @flush_queue_size = flush_queue_size
  @batch_size = batch_size
  @flush_interval_ms = flush_interval_ms
  @max_retries = max_retries
  @retry_delay_min = retry_delay_min
  @retry_delay_max = retry_delay_max
  @omit_values = omit_values
  @branch = branch
  @version = version

  # Start the scheduler
  start_scheduler
end

Public Instance Methods

flush() click to toggle source
# File lib/itly/plugin/iteratively/client.rb, line 49
def flush
  # Case: the runner is on, cannot call flush again
  return unless runner_complete?

  # Exit if there is nothing to do
  return if @buffer.empty?

  # Extract the current content of the buffer for processing
  processing = @buffer.each_slice(@batch_size).to_a
  @buffer.clear

  # Run in the background
  @runner = Concurrent::Future.new do
    processing.each do |batch|
      # Initialization before the loop starts
      tries = 0

      loop do
        # Count the number of tries
        tries += 1

        # Case: successfully sent
        break if post_models batch

        # Case: could not sent and reached maximum number of allowed tries
        if tries >= @max_retries
          # Log
          logger&.error 'Iteratively::Client: flush() reached maximum number of tries. '\
            "#{batch.count} events won't be sent to the server"

          # Discard the list of event in the batch queue
          break

        # Case: could not sent and wait before retrying
        else
          sleep delay_before_next_try(tries)
        end
      end
    end
  end

  @runner.execute
end
shutdown(force: false) click to toggle source
# File lib/itly/plugin/iteratively/client.rb, line 93
def shutdown(force: false)
  @scheduler&.cancel

  if force
    @runner&.cancel
    return
  end

  @max_retries = 0
  flush
  @runner&.wait_or_cancel @retry_delay_min
end
track(type:, event:, properties:, validation:) click to toggle source
# File lib/itly/plugin/iteratively/client.rb, line 41
def track(type:, event:, properties:, validation:)
  @buffer << ::Itly::Plugin::Iteratively::TrackModel.new(
    omit_values: omit_values, type: type, event: event, properties: properties, validation: validation
  )

  flush if buffer_full?
end

Private Instance Methods

buffer_full?() click to toggle source
# File lib/itly/plugin/iteratively/client.rb, line 108
def buffer_full?
  @buffer.length >= @flush_queue_size
end
delay_before_next_try(nbr_tries) click to toggle source

Generates progressively increasing values to wait between client calls For max_retries: 25, retry_delay_min: 10.0, retry_delay_max: 3600.0, generated values are: 10, 18, 41, 79, 132, 201, 283, 380, 491, 615, 752, 901, 1061, 1233, 1415, 1606, 1805, 2012, 2226, 2446, 2671, 2900, 3131, 3365, 3600

# File lib/itly/plugin/iteratively/client.rb, line 145
def delay_before_next_try(nbr_tries)
  percent = (nbr_tries - 1).to_f / (@max_retries - 1)
  rad = percent * Math::PI / 2
  delta = (Math.cos(rad) - 1).abs

  retry_delay_min + delta * (@retry_delay_max - @retry_delay_min)
end
post_models(models) click to toggle source
# File lib/itly/plugin/iteratively/client.rb, line 112
def post_models(models)
  data = {
    branchName: @branch,
    trackingPlanVersion: @version,
    objects: models
  }.to_json
  headers = {
    'Content-Type' => 'application/json',
    'authorization' => "Bearer #{@api_key}"
  }
  resp = Faraday.post(@url, data, headers)

  # Case: HTTP response 2xx is a Success
  return true if (200...300).include? resp.status

  # Case: Error
  logger&.error "Iteratively::Client: post_models() unexpected response. Url: #{url} "\
    "Data: #{data} Response status: #{resp.status} Response headers: #{resp.headers} "\
    "Response body: #{resp.body}"
  false
rescue StandardError => e
  logger&.error "Iteratively::Client: post_models() exception #{e.class.name}: #{e.message}"
  false
end
runner_complete?() click to toggle source
# File lib/itly/plugin/iteratively/client.rb, line 137
def runner_complete?
  @runner.nil? || @runner.complete?
end
start_scheduler() click to toggle source
# File lib/itly/plugin/iteratively/client.rb, line 153
def start_scheduler
  @scheduler = Concurrent::ScheduledTask.new(@flush_interval_ms / 1000.0) do
    flush unless runner_complete?
    start_scheduler
  end
  @scheduler.execute
end