# frozen_string_literal: true require File.join(File.dirname(__FILE__), 'service') require 'securerandom' module Charrington # Inserts and modifies a Postgres/Redshift database class TransformRedshift include Service attr_accessor :event attr_reader :top_level_keys Error = Class.new(StandardError) EventNil = Class.new(Error) TableNameNil = Class.new(Error) ColumnBlacklist = Class.new(Error) KEY_FILTER_BLACKLIST = %w[host path jwt sequence].freeze KEY_RAISE_BLACKLIST = ['inserted_at'].freeze def initialize(event) raise EventNil, 'Event is nil' if event.nil? event = event.to_hash @event = drop_keys(event) @top_level_keys = @event.keys check_blacklist end def call handle_event_key(event) add_id_to_event(event) handle_key_transform(event, 'anonymous_id', 'anonymous_user') handle_key_transform(event, 'sent_at', 'published_at') handle_key_transform(event, 'original_timestamp', 'sent_at') handle_key_transform(event, 'received_at', 'sent_at') handle_key_transform(event, 'timestamp', 'sent_at') handle_meta_section(event) transform_session_stuff(event) event.delete_if { |k, _v| %w[session meta published_at anonymous_user].include?(k) } event end private def handle_key_transform(hash, key_that_should_be_there, key_to_take_value_from) if hash.key?(key_that_should_be_there) hash else hash[key_that_should_be_there] = hash[key_to_take_value_from] || '' end end def add_id_to_event(hash) hash['id'] = SecureRandom.hex(10) end def handle_event_key(hash) event_name = hash['event'] || '' hash['event_text'] = event_name hash['event'] = underscore_event_name(event_name) end def underscore_event_name(event_name) event_name .gsub(/::/, '/') .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') .gsub(/([a-z\d])([A-Z])/, '\1_\2') .downcase .gsub(/[^a-z0-9]+/, '_') .gsub(/\A_+/, '') .gsub(/_+\z/, '') end def transform_session_stuff(hash) session_stuff = hash['session'] || {} session_stuff.each do |k, v| next unless k.is_a?(String) hash["context_#{k}"] = v end end def handle_meta_section(hash) meta_section = hash['meta'] || {} meta_section.each do |k, v| next unless k.is_a?(String) next if [Array, Hash].map { |type| v.is_a?(type) }.any? hash[k] = v end end def check_blacklist arr = [] KEY_RAISE_BLACKLIST.each { |k| arr << k if event.keys.include?(k) } raise ColumnBlacklist, "Event contains these blacklisted keys: #{arr.join(',')}" unless arr.empty? end def drop_keys(event) event.delete_if { |k, _v| k.start_with?('@') || KEY_FILTER_BLACKLIST.include?(k) } end def flatten_hash(hash) hash.each_with_object({}) do |(k, v), acc| if v.is_a? Hash flatten_hash(v).map do |h_k, h_v| acc["#{k}_#{h_k}"] = h_v end else acc[k] = v end end end end end