trueandco_analytics 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +152 -0
- data/Rakefile +36 -0
- data/app/assets/javascripts/trueandco_analytics.js +170 -0
- data/app/commands/trueandco_analytics/arr_jsons_to_arr_hash.rb +20 -0
- data/app/commands/trueandco_analytics/metric_c/add_list.rb +65 -0
- data/app/commands/trueandco_analytics/report_c/generate.rb +31 -0
- data/app/commands/trueandco_analytics/session_c/create.rb +65 -0
- data/app/commands/trueandco_analytics/session_c/put_data.rb +46 -0
- data/app/commands/trueandco_analytics/user_c/create_or_update_user_if_exist.rb +41 -0
- data/app/controllers/trueandco_analytics/application_controller.rb +5 -0
- data/app/controllers/trueandco_analytics/reciver_controller.rb +19 -0
- data/app/models/trueandco_analytics/application_record.rb +5 -0
- data/app/models/trueandco_analytics/metric_user.rb +8 -0
- data/app/models/trueandco_analytics/metric_user_session.rb +8 -0
- data/app/models/trueandco_analytics/metric_user_visit.rb +8 -0
- data/app/views/trueandco_analytics/receiver/pull_user_statistic.erb +0 -0
- data/app/workers/trueandco_analytics/user_session_metric_worker.rb +21 -0
- data/config/routes.rb +3 -0
- data/config/secrets.yml +2 -0
- data/db/migrate/20170802133950_create_metric_users.rb +19 -0
- data/db/migrate/20170802134044_create_metric_user_visits.rb +20 -0
- data/db/migrate/20170802134059_create_metric_user_sessions.rb +27 -0
- data/exe/trueandco_analytics +28 -0
- data/lib/extension_string.rb +20 -0
- data/lib/generators/trueandco_analytics/install_generator.rb +14 -0
- data/lib/generators/trueandco_analytics/templates/trueandco_analytics.rb +37 -0
- data/lib/trueandco_analytics/cli/common.rb +49 -0
- data/lib/trueandco_analytics/cli/report.rb +51 -0
- data/lib/trueandco_analytics/config/params.rb +81 -0
- data/lib/trueandco_analytics/engine.rb +31 -0
- data/lib/trueandco_analytics/helpers/trueandco_analytics_helper.rb +35 -0
- data/lib/trueandco_analytics/locales/en.yml +16 -0
- data/lib/trueandco_analytics/modules/info.rb +21 -0
- data/lib/trueandco_analytics/reports/application_report.rb +61 -0
- data/lib/trueandco_analytics/reports/details_report.rb +24 -0
- data/lib/trueandco_analytics/reports/page_buy_in_date_range_report.rb +29 -0
- data/lib/trueandco_analytics/services/logger.rb +24 -0
- data/lib/trueandco_analytics/services/redis_connect.rb +12 -0
- data/lib/trueandco_analytics/services/reports.rb +30 -0
- data/lib/trueandco_analytics/services/workers.rb +14 -0
- data/lib/trueandco_analytics/version.rb +3 -0
- data/lib/trueandco_analytics.rb +37 -0
- metadata +242 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module TrueandcoAnalytics
|
2
|
+
module Cli
|
3
|
+
class Common
|
4
|
+
include TrueandcoAnalytics::Modules::Info
|
5
|
+
|
6
|
+
def initialize(args)
|
7
|
+
@args = args
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute
|
11
|
+
if args.empty?
|
12
|
+
info(trans('help.need_help'))
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
unless args[0] =~ /^-/
|
16
|
+
TrueandcoAnalytics::Cli::Report.new(args).execute
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
OptionParser.new do |opts|
|
20
|
+
opts.banner = trans('help.need_help')
|
21
|
+
|
22
|
+
opts.on_tail('-h', '--help', 'Help') do
|
23
|
+
info(opts)
|
24
|
+
info(trans('help.description').gsub('\n', "\n"))
|
25
|
+
end
|
26
|
+
opts.on_tail('-v', '--version', 'Version of gem') do
|
27
|
+
info(trans('help.gem_version'))
|
28
|
+
info(TrueandcoAnalytics::VERSION)
|
29
|
+
end
|
30
|
+
opts.on_tail('-r', '--reports', 'List names of reports') do
|
31
|
+
info(TrueandcoAnalytics::Reports.available_reports)
|
32
|
+
end
|
33
|
+
opts.on_tail('-f', '--formats', 'List available formats of reports') do
|
34
|
+
info(TrueandcoAnalytics::Reports.available_formats)
|
35
|
+
end
|
36
|
+
opts.parse!
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :args
|
43
|
+
|
44
|
+
def trans(value)
|
45
|
+
I18n.t "trueandco_analytics.#{value}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module TrueandcoAnalytics
|
2
|
+
module Cli
|
3
|
+
class Report
|
4
|
+
include TrueandcoAnalytics::Modules::Info
|
5
|
+
|
6
|
+
def initialize(args)
|
7
|
+
@args = args
|
8
|
+
@report_args = (Struct.new(:name, :format, :datetime_start, :datetime_end, :path)).new
|
9
|
+
@report_args.name = args[0]
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute
|
13
|
+
need_generate_report = true
|
14
|
+
OptionParser.new do |opts|
|
15
|
+
opts.banner = trans('help.need_help')
|
16
|
+
# Info
|
17
|
+
opts.on_tail("-h", "--help", trans('help.about_help')) do
|
18
|
+
need_generate_report = false
|
19
|
+
info(opts)
|
20
|
+
info(trans('help.description').gsub('\n', "\n"))
|
21
|
+
end
|
22
|
+
# Args
|
23
|
+
opts.on("-fValue", "--format=Value", 'format') do |value|
|
24
|
+
@report_args.format = value
|
25
|
+
end
|
26
|
+
opts.on("-sValue", "--datetime_start=Value", 'datetime_strat') do |value|
|
27
|
+
@report_args.datetime_strat = value
|
28
|
+
end
|
29
|
+
opts.on("-eValue", "--datetime_end=Value", 'datetime_end') do |value|
|
30
|
+
@report_args.datetime_end = value
|
31
|
+
end
|
32
|
+
opts.on("-pValue", "--path=Value", 'path') do |value|
|
33
|
+
@report_args.path = value
|
34
|
+
end
|
35
|
+
opts.parse!
|
36
|
+
end
|
37
|
+
if need_generate_report
|
38
|
+
TrueandcoAnalytics::ReportC::Generate.new(report_args.to_h).execute
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :report_args, :args
|
45
|
+
|
46
|
+
def trans(value)
|
47
|
+
I18n.t "trueandco_analytics.#{value}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module TrueandcoAnalytics
|
2
|
+
module Config
|
3
|
+
module Params
|
4
|
+
|
5
|
+
@time_survey = 15
|
6
|
+
@time_survey_check = false
|
7
|
+
if Object.const_defined?('Rails')
|
8
|
+
@time_dead_session = Rails.env.development? ? 30 : 600
|
9
|
+
end
|
10
|
+
@user_method_check = false
|
11
|
+
@buy_selector_class = 'buy'
|
12
|
+
@buy_selector_class_check = false
|
13
|
+
@connect_redis = { redis_db: 1, }
|
14
|
+
@connect_redis_check = false
|
15
|
+
@sidekiq_configure_client_url = 'redis://localhost:6379/1'
|
16
|
+
@sidekiq_configure_client_check = false
|
17
|
+
@sidekiq_configure_server_url = 'redis://localhost:6379/1'
|
18
|
+
@sidekiq_configure_server_check = false
|
19
|
+
@sidekiq_configure_namespace = 'metric'
|
20
|
+
@sidekiq_configure_namespace_check = false
|
21
|
+
@log = nil
|
22
|
+
@log_check = false
|
23
|
+
@connect_db = nil
|
24
|
+
@connect_db_check = false
|
25
|
+
|
26
|
+
class << self
|
27
|
+
|
28
|
+
def set_params
|
29
|
+
yield self if block_given?
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.attr_writer_only_once(*args)
|
33
|
+
args.each do |arg|
|
34
|
+
self.send(:define_method, "#{arg}=".intern) do |value|
|
35
|
+
return if value.nil?
|
36
|
+
return if instance_variable_get("@#{arg}_check")
|
37
|
+
return unless value.present?
|
38
|
+
instance_variable_set("@#{arg}", value)
|
39
|
+
instance_variable_set("@#{arg}_check", true)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :time_survey, :time_dead_session, :buy_selector_class,
|
45
|
+
:sidekiq_configure_client_url, :sidekiq_configure_server_url, :sidekiq_configure_namespace,
|
46
|
+
:connect_db, :connect_redis
|
47
|
+
|
48
|
+
attr_writer_only_once :time_survey, :user_method,
|
49
|
+
:sidekiq_configure_client_url, :sidekiq_configure_server_url, :sidekiq_configure_namespace,
|
50
|
+
:connect_db,:connect_redis
|
51
|
+
|
52
|
+
def buy_selector_class=(value)
|
53
|
+
return if value.nil?
|
54
|
+
@buy_selector_class = value.delete('.') unless @buy_selector_class_check
|
55
|
+
@buy_selector_class_check = true
|
56
|
+
end
|
57
|
+
|
58
|
+
def user_method
|
59
|
+
return OpenStruct.new(id: nil, name: '', email: '') unless @user_method_check
|
60
|
+
::ApplicationController.new.public_send(@user_method)
|
61
|
+
end
|
62
|
+
|
63
|
+
def log
|
64
|
+
return unless @log_check
|
65
|
+
@log
|
66
|
+
end
|
67
|
+
|
68
|
+
def log=(value)
|
69
|
+
return if value.nil?
|
70
|
+
@log = value.chomp('.log') unless @log_check
|
71
|
+
@log_check = true
|
72
|
+
end
|
73
|
+
|
74
|
+
def log_obj
|
75
|
+
return unless @log_check
|
76
|
+
TrueandcoAnalytics::Logger.instance
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "trueandco_analytics/helpers/trueandco_analytics_helper"
|
2
|
+
|
3
|
+
module TrueandcoAnalytics
|
4
|
+
|
5
|
+
class Engine < ::Rails::Engine
|
6
|
+
isolate_namespace TrueandcoAnalytics
|
7
|
+
|
8
|
+
initializer :append_migrations do |app|
|
9
|
+
unless app.root.to_s.match root.to_s
|
10
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
11
|
+
app.config.paths["db/migrate"] << expanded_path
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
initializer :action_controller do
|
17
|
+
::TrueandcoAnalytics::Workers.init
|
18
|
+
ActiveSupport.on_load(:action_controller) do
|
19
|
+
self.try(:helper, ::TrueandcoAnalytics::TrueandcoAnalyticsHelper) ||
|
20
|
+
self.try(:include, ::TrueandcoAnalytics::TrueandcoAnalyticsHelper)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
config.generators do |g|
|
25
|
+
g.test_framework :rspec, :fixture => false
|
26
|
+
g.fixture_replacement :factory_girl, :dir => 'spec/factories'
|
27
|
+
g.assets false
|
28
|
+
g.helper false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module TrueandcoAnalytics
|
2
|
+
module TrueandcoAnalyticsHelper
|
3
|
+
|
4
|
+
def track_user_behavior
|
5
|
+
pull_user_statistic_path = Engine.routes.url_helpers.receiver_pull_user_statistic_path
|
6
|
+
return unless @user_behavior.nil?
|
7
|
+
@user_behavior = 1
|
8
|
+
js_string = <<-USER_BEHAVIOR_TRACK
|
9
|
+
var statistics_url = "#{root_url.chomp('/')}#{pull_user_statistic_path}",
|
10
|
+
app_token = "#{form_authenticity_token}";
|
11
|
+
|
12
|
+
userLog.init(statistics_url, app_token, {
|
13
|
+
|
14
|
+
clickCount: true,
|
15
|
+
clickDetails: true,
|
16
|
+
actionItem: {
|
17
|
+
processOnAction: true,
|
18
|
+
selector: '.buy',
|
19
|
+
event: 'click'
|
20
|
+
},
|
21
|
+
processTime: #{Config::Params.time_survey},
|
22
|
+
processData: function(results, statistics_url, app_token) {
|
23
|
+
var xhr = new XMLHttpRequest();
|
24
|
+
xhr.open('POST', statistics_url, true);
|
25
|
+
xhr.setRequestHeader('X-CSRF-Token', app_token);
|
26
|
+
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
27
|
+
var data = "user_metric=" + JSON.stringify(results);
|
28
|
+
xhr.send(data);
|
29
|
+
},
|
30
|
+
});
|
31
|
+
USER_BEHAVIOR_TRACK
|
32
|
+
javascript_tag js_string, defer: 'defer'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
en:
|
2
|
+
trueandco_analytics:
|
3
|
+
help:
|
4
|
+
need_help: '(-h or --help shows available options for use)'
|
5
|
+
description: 'For generate reports use:\nbundle exec trueandco_analytics report_name --datetime_start=datetime --datetime_end=datetime --format=format --path=path_destination'
|
6
|
+
gem_version: 'Gem version is:'
|
7
|
+
|
8
|
+
error:
|
9
|
+
argument:
|
10
|
+
unknown_format: Unknown format of reports
|
11
|
+
unknown_report: Report with the specified name was not found
|
12
|
+
log: 'An error occurred in the analytics'
|
13
|
+
|
14
|
+
result:
|
15
|
+
done: 'The report is created and located: %{path}'
|
16
|
+
data: 'Its Done'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module TrueandcoAnalytics::Modules
|
2
|
+
|
3
|
+
module Info
|
4
|
+
|
5
|
+
def error_message(message)
|
6
|
+
$stderr.puts message.colorize(:red)
|
7
|
+
logger = Config::Params.log_obj
|
8
|
+
return if logger.nil?
|
9
|
+
logger.fatal { I18n.t('trueandco_analytics.error.log') + "\n" + message }
|
10
|
+
end
|
11
|
+
|
12
|
+
def info(message)
|
13
|
+
puts message.to_s.colorize(:blue)
|
14
|
+
end
|
15
|
+
|
16
|
+
def result(message)
|
17
|
+
puts I18n.t('trueandco_analytics.result.data').colorize(:green)
|
18
|
+
puts message.colorize(:green)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module TrueandcoAnalytics
|
2
|
+
class ApplicationReport
|
3
|
+
|
4
|
+
AVAILABLE_FORMATS = %w(csv arr).freeze
|
5
|
+
DEFAULT_FORMAT = AVAILABLE_FORMATS[0]
|
6
|
+
|
7
|
+
def initialize(format = nil, date_start = nil, date_end = nil)
|
8
|
+
format ||= DEFAULT_FORMAT
|
9
|
+
@format = format
|
10
|
+
unless format_available?
|
11
|
+
raise ArgumentError, I18n.t("trueandco_analytics.error.argument.unknown_format")
|
12
|
+
end
|
13
|
+
date_start = date_start.kind_of?(DateTime) ? date_start : DateTime.new(1970,1,1,0,0,0)
|
14
|
+
date_end = date_end.kind_of?(DateTime) ? date_end : DateTime.now
|
15
|
+
date_end += 1.days
|
16
|
+
arr = source_data(date_start, date_end)
|
17
|
+
|
18
|
+
@data = send("to_#{format}", arr)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.formats
|
22
|
+
AVAILABLE_FORMATS
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.reports
|
26
|
+
self.subclasses.map{ |name| name.to_s.sub(/TrueandcoAnalytics::/,'').underscore }
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.subclasses
|
30
|
+
self.descendants
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.descendants
|
34
|
+
ObjectSpace.each_object(Class).select { |klass| klass < self }
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :format, :data, :report_name
|
40
|
+
|
41
|
+
def format_available?
|
42
|
+
AVAILABLE_FORMATS.include?(format)
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_csv(arr)
|
46
|
+
return unless arr.kind_of?(Array)
|
47
|
+
return if arr.empty?
|
48
|
+
::CSV.generate do |csv|
|
49
|
+
csv << arr.first.keys
|
50
|
+
arr.each do |hash|
|
51
|
+
csv << hash.values
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_arr(arr)
|
57
|
+
return unless arr.kind_of?(Array)
|
58
|
+
arr
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module TrueandcoAnalytics
|
2
|
+
class DetailsReport < ApplicationReport
|
3
|
+
|
4
|
+
attr_reader :data
|
5
|
+
|
6
|
+
def report_name
|
7
|
+
"details-report-#{DateTime.now.strftime("%Y.%m.%d %H:%M")}.#{@format}"
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def source_data(date_start, date_end)
|
13
|
+
sql = <<-SQL_TEXT
|
14
|
+
SELECT DATE(metric_user_sessions.session_start) as 'date', metric_users.*, metric_user_sessions.*, metric_user_visits.*
|
15
|
+
FROM metric_user_visits
|
16
|
+
LEFT JOIN metric_users ON metric_user_visits.metric_user_id = metric_users.id
|
17
|
+
LEFT JOIN metric_user_sessions ON metric_user_visits.metric_user_session_id = metric_user_sessions.id
|
18
|
+
WHERE DATE(metric_user_sessions.session_start) BETWEEN CAST('#{date_start}' AS DATE) AND CAST('#{date_end}' AS DATE)
|
19
|
+
ORDER BY DATE(metric_user_sessions.session_start) DESC, metric_users.id, metric_user_sessions.session_start ASC
|
20
|
+
SQL_TEXT
|
21
|
+
ActiveRecord::Base.connection.exec_query(sql).to_a
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module TrueandcoAnalytics
|
2
|
+
class PageBuyInDateRangeReport < ApplicationReport
|
3
|
+
|
4
|
+
attr_reader :data
|
5
|
+
|
6
|
+
def report_name
|
7
|
+
"page-buy-report-#{DateTime.now.strftime("%Y.%m.%d %H:%M")}.#{@format}"
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def source_data(date_start, date_end)
|
13
|
+
sql = <<-SQL_TEXT
|
14
|
+
SELECT metric_user_visits.page_path, DATE(metric_user_sessions.session_start) as session_start, COUNT(metric_user_visits.metric_user_id) as buy_visit, all_visit.all_visit, group_concat(metric_user_visits.metric_user_id) as user_ids
|
15
|
+
FROM metric_user_visits
|
16
|
+
LEFT JOIN metric_user_sessions ON metric_user_visits.metric_user_session_id = metric_user_sessions.id
|
17
|
+
LEFT JOIN (SELECT metric_user_visits.page_path, DATE(metric_user_sessions.session_start) as session_start, COUNT(metric_user_visits.metric_user_id) as all_visit
|
18
|
+
FROM metric_user_visits
|
19
|
+
LEFT JOIN metric_user_sessions ON metric_user_visits.metric_user_session_id = metric_user_sessions.id
|
20
|
+
GROUP BY page_path, DATE(metric_user_sessions.session_start)
|
21
|
+
) AS all_visit ON metric_user_visits.page_path = all_visit.page_path AND DATE(metric_user_sessions.session_start) = all_visit.session_start
|
22
|
+
WHERE metric_user_visits.is_buy = true AND
|
23
|
+
DATE(metric_user_sessions.session_start) BETWEEN CAST('#{date_start}' AS DATE) AND CAST('#{date_end}' AS DATE)
|
24
|
+
GROUP BY page_path, DATE(metric_user_sessions.session_start)
|
25
|
+
SQL_TEXT
|
26
|
+
ActiveRecord::Base.connection.exec_query(sql).to_a
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module TrueandcoAnalytics
|
2
|
+
|
3
|
+
class Logger < Logger
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
super(Rails.root.join("log/#{Config::Params.log}.log"))
|
8
|
+
self.formatter = formatter
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def formatter
|
13
|
+
Proc.new { |severity, time, progname, msg|
|
14
|
+
formatted_severity = sprintf("%-5s", severity.to_s)
|
15
|
+
formatted_time = time.strftime("%Y-%m-%d %H:%M:%S")
|
16
|
+
"[#{formatted_severity} #{formatted_time} #{$$}] #{msg.to_s.strip}\n"
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
delegate :error, :debug, :fatal, :info, :warn, :add, :log, to: :instance
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|