519 lines
14 KiB
Ruby
519 lines
14 KiB
Ruby
|
require 'carto/tracking/services/pubsub_tracker'
|
||
|
|
||
|
require_dependency 'carto/tracking/formats/internal'
|
||
|
require_dependency 'carto/tracking/services/pubsub'
|
||
|
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`
|
||
|
|
||
|
DEFAULT_EVENT_VERSION = 1
|
||
|
|
||
|
module Carto
|
||
|
module Tracking
|
||
|
module Events
|
||
|
class Event
|
||
|
include ::LoggerHelper
|
||
|
|
||
|
def initialize(reporter_id, properties)
|
||
|
properties.merge!({event_version: event_version})
|
||
|
|
||
|
@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 event_version
|
||
|
DEFAULT_EVENT_VERSION
|
||
|
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
|
||
|
@optional_properties += [:event_version]
|
||
|
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 StandardError => e
|
||
|
log_error(
|
||
|
message: 'Carto::Tracking: Error reporting event',
|
||
|
exception: e,
|
||
|
event: @format.to_hash.merge(name: name)
|
||
|
)
|
||
|
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::Services::PubSub
|
||
|
|
||
|
include Carto::Tracking::Validators::Visualization::Readable
|
||
|
include Carto::Tracking::Validators::User
|
||
|
|
||
|
required_properties :user_id, :visualization_id
|
||
|
|
||
|
def pubsub_name
|
||
|
'map_exported'
|
||
|
end
|
||
|
|
||
|
def event_version
|
||
|
2
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class MapEvent < Event
|
||
|
include Carto::Tracking::Services::Segment
|
||
|
include Carto::Tracking::Services::PubSub
|
||
|
|
||
|
include Carto::Tracking::Validators::Visualization::Writable
|
||
|
include Carto::Tracking::Validators::User
|
||
|
|
||
|
required_properties :user_id, :visualization_id
|
||
|
end
|
||
|
|
||
|
class CreatedMap < MapEvent
|
||
|
include Carto::Tracking::Services::Hubspot
|
||
|
|
||
|
required_properties :origin
|
||
|
optional_properties :connection
|
||
|
|
||
|
def pubsub_name
|
||
|
'map_created'
|
||
|
end
|
||
|
|
||
|
def event_version
|
||
|
3
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class DeletedMap < MapEvent
|
||
|
def pubsub_name
|
||
|
'map_deleted'
|
||
|
end
|
||
|
|
||
|
def event_version
|
||
|
2
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class ModifiedMap < MapEvent
|
||
|
def pubsub_name
|
||
|
'map_updated'
|
||
|
end
|
||
|
|
||
|
def event_version
|
||
|
2
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class PublishedMap < Event
|
||
|
include Carto::Tracking::Services::Hubspot
|
||
|
include Carto::Tracking::Services::Segment
|
||
|
include Carto::Tracking::Services::PubSub
|
||
|
|
||
|
include Carto::Tracking::Validators::Visualization::Writable
|
||
|
include Carto::Tracking::Validators::User
|
||
|
|
||
|
required_properties :user_id, :visualization_id
|
||
|
|
||
|
def pubsub_name
|
||
|
'map_published'
|
||
|
end
|
||
|
|
||
|
def event_version
|
||
|
2
|
||
|
end
|
||
|
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
|
||
|
include Carto::Tracking::Services::PubSub
|
||
|
|
||
|
def pubsub_name
|
||
|
'import_failed'
|
||
|
end
|
||
|
|
||
|
def event_version
|
||
|
3
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class FailedSync < Event
|
||
|
include Carto::Tracking::Services::PubSub
|
||
|
|
||
|
required_properties :user_id, :connection
|
||
|
|
||
|
def pubsub_name
|
||
|
'sync_failed'
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class ExceededQuota < Event
|
||
|
include Carto::Tracking::Services::Segment
|
||
|
include Carto::Tracking::Services::PubSub
|
||
|
|
||
|
include Carto::Tracking::Validators::User
|
||
|
|
||
|
required_properties :user_id
|
||
|
optional_properties :quota_overage
|
||
|
|
||
|
def pubsub_name
|
||
|
'quota_exceeded'
|
||
|
end
|
||
|
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::Services::PubSub
|
||
|
|
||
|
include Carto::Tracking::Validators::Visualization::Writable
|
||
|
include Carto::Tracking::Validators::User
|
||
|
|
||
|
required_properties :user_id, :visualization_id
|
||
|
end
|
||
|
|
||
|
class CreatedDataset < DatasetEvent
|
||
|
include Carto::Tracking::Services::Hubspot
|
||
|
|
||
|
required_properties :origin
|
||
|
optional_properties :connection
|
||
|
|
||
|
def pubsub_name
|
||
|
'dataset_created'
|
||
|
end
|
||
|
|
||
|
def event_version
|
||
|
3
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class DeletedDataset < DatasetEvent
|
||
|
def pubsub_name
|
||
|
'dataset_deleted'
|
||
|
end
|
||
|
|
||
|
def event_version
|
||
|
2
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class AnalysisEvent < Event
|
||
|
include Carto::Tracking::Services::Hubspot
|
||
|
include Carto::Tracking::Services::Segment
|
||
|
include Carto::Tracking::Services::PubSub
|
||
|
|
||
|
include Carto::Tracking::Validators::Visualization::Writable
|
||
|
include Carto::Tracking::Validators::User
|
||
|
|
||
|
required_properties :user_id, :visualization_id, :analysis
|
||
|
end
|
||
|
|
||
|
class CreatedAnalysis < AnalysisEvent
|
||
|
def pubsub_name
|
||
|
'analysis_created'
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class ModifiedAnalysis < AnalysisEvent
|
||
|
def pubsub_name
|
||
|
'analysis_updated'
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class DeletedAnalysis < AnalysisEvent
|
||
|
def pubsub_name
|
||
|
'analysis_deleted'
|
||
|
end
|
||
|
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::Services::PubSub
|
||
|
|
||
|
include Carto::Tracking::Validators::User
|
||
|
|
||
|
required_properties :user_id, :app_id, :app_name
|
||
|
end
|
||
|
|
||
|
class CreatedOauthApp < OauthAppEvent
|
||
|
def pubsub_name
|
||
|
'oauth_app_created'
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class DeletedOauthApp < OauthAppEvent
|
||
|
def pubsub_name
|
||
|
'oauth_app_deleted'
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class CreatedOauthAppUser < OauthAppEvent
|
||
|
def pubsub_name
|
||
|
'oauth_app_user_created'
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class DeletedOauthAppUser < OauthAppEvent
|
||
|
def pubsub_name
|
||
|
'oauth_app_user_deleted'
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class UpdatedFeatureFlag < Event
|
||
|
include Carto::Tracking::Services::PubSub
|
||
|
|
||
|
required_properties :user_id, :feature_flag
|
||
|
|
||
|
def pubsub_name
|
||
|
'feature_flag_updated'
|
||
|
end
|
||
|
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
|