require_dependency 'carto/tracking/formats/internal' require_dependency 'carto/tracking/services/segment' require_dependency 'carto/tracking/services/hubspot' require_dependency 'carto/tracking/validators/visualization' require_dependency 'carto/tracking/validators/layer' require_dependency 'carto/tracking/validators/user' require_dependency 'carto/tracking/validators/widget' # IMPORTANT: Events must be kept in sync with frontend! # See `/lib/assets/javascripts/builder/components/metrics/metrics-types.js` module Carto module Tracking module Events class Event def initialize(reporter_id, properties) @format = Carto::Tracking::Formats::Internal.new(properties) @reporter = Carto::User.find(reporter_id) end def name self.class.name.demodulize.underscore.humanize.capitalize end def self.required_properties(*required_properties) @required_properties ||= [] @required_properties += required_properties end def required_properties these_required_properties = self.class.instance_eval { @required_properties || [] } these_required_properties + self.class.superclass.required_properties end def self.optional_properties(*optional_properties) @optional_properties ||= [] @optional_properties += optional_properties end def optional_properties these_optional_properties = self.class.instance_eval { @optional_properties || [] } these_optional_properties + self.class.superclass.optional_properties end def report report! rescue => exception CartoDB::Logger.error(message: 'Carto::Tracking: Couldn\'t report event', exception: exception, name: name, properties: @format.to_hash) end def report! check_required_properties! check_no_extra_properties! authorize! report_to_methods = methods.select do |method_name| method_name.to_s.start_with?('report_to') end report_to_methods.each do |report_method| send(report_method) end end private def check_required_properties! missing_properties = required_properties - @format.to_hash.symbolize_keys.keys unless missing_properties.empty? message = "#{name} is missing the following properties: #{missing_properties.join(', ')}" raise Carto::UnprocesableEntityError.new(message) end end def check_no_extra_properties! extra_properties = @format.to_hash.symbolize_keys.keys - required_properties - optional_properties unless extra_properties.empty? message = "#{name} is adding the following extra properties: #{extra_properties.join(', ')}" raise Carto::UnprocesableEntityError.new(message) end end # Validators are modules that should be included in Event classes. These # modules contain methods that start with 'check_'. They raise an # exception if whatever condition they validate is not met. All methods # that match this criteria in validator modules included in an Event # class will be run automatically when .report or .report! is called for # that same class. def authorize! check_methods = methods.select do |method_name| method_name.to_s.start_with?('check_') end check_methods.each do |check_method| send(check_method) end end end class ExportedMap < Event include Carto::Tracking::Services::Hubspot include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Readable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id end class MapEvent < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id end class CreatedMap < MapEvent required_properties :origin end class DeletedMap < MapEvent; end class PublishedMap < Event include Carto::Tracking::Services::Hubspot include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id end class ConnectionEvent < Event include Carto::Tracking::Services::Hubspot include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::User required_properties :user_id, :connection end class CompletedConnection < ConnectionEvent; end class FailedConnection < ConnectionEvent; end class ExceededQuota < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::User required_properties :user_id optional_properties :quota_overage end class ScoredTrendingMap < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id, :mapviews end class VisitedPrivatePage < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::User required_properties :user_id, :page def report_to_user_model @format.fetch_record!(:user).view_dashboard if @format.to_hash['page'] == 'dashboard' end end class DatasetEvent < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id end class CreatedDataset < DatasetEvent required_properties :origin end class DeletedDataset < DatasetEvent; end class AnalysisEvent < Event include Carto::Tracking::Services::Hubspot include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id, :analysis end class CreatedAnalysis < AnalysisEvent; end class ModifiedAnalysis < AnalysisEvent; end class DeletedAnalysis < AnalysisEvent; end class AppliedSql < Event include Carto::Tracking::Services::Hubspot include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id, :sql optional_properties :node_id, :dataset_id end class AppliedCartocss < Event include Carto::Tracking::Services::Hubspot include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id, :layer_id, :cartocss end class ModifiedStyleForm < AppliedCartocss required_properties :style_properties end class CreatedWidget < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Widget::Existence include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id, :widget_id end class DownloadedLayer < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::Layer include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id, :layer_id, :format, :source, :visible, :table_name optional_properties :from_view end class StyledByValue < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id, :attribute, :attribute_type end class DraggedNode < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id end class CreatedLayer < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::Layer include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id, :layer_id, :empty end class ChangedDefaultGeometry < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id end class AggregatedGeometries < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id, :previous_agg_type, :agg_type end class UsedAdvancedMode < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::Visualization::Writable include Carto::Tracking::Validators::User required_properties :user_id, :visualization_id, :mode_type end class OauthAppEvent < Event include Carto::Tracking::Services::Segment include Carto::Tracking::Validators::User required_properties :user_id, :app_id, :app_name end class CreatedOauthApp < OauthAppEvent; end class DeletedOauthApp < OauthAppEvent; end class CreatedOauthAppUser < OauthAppEvent; end class DeletedOauthAppUser < OauthAppEvent; end # Models a generic event for segment. class SegmentEvent < Event include Carto::Tracking::Services::Segment attr_reader :name private_class_method :new # Just pass any hash at `properties` and it will be sent to Segment. def self.build(name, reporter_id, properties) new(name, reporter_id, properties) if EVENTS.include?(name) end private EVENTS = ['WebGL stats'].freeze def initialize(name, reporter_id, properties) @name = name @properties = properties @format = SegmentFormat.new(@properties) @reporter = Carto::User.where(id: reporter_id).first end end class SegmentFormat < Carto::Tracking::Formats::Internal def to_segment data = super data[:data_properties] = to_hash data end end end end end