lookout-ahoy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +99 -0
- data/Rakefile +24 -0
- data/app/assets/images/lookout/apple-touch-icon.png +0 -0
- data/app/assets/images/lookout/favicon-16x16.png +0 -0
- data/app/assets/images/lookout/favicon-32x32.png +0 -0
- data/app/assets/images/lookout/logo.png +0 -0
- data/app/assets/images/lookout/safari-pinned-tab.png +0 -0
- data/app/assets/images/lookout/safari-pinned-tab.svg +199 -0
- data/app/assets/javascript/lookout/application.js +2 -0
- data/app/assets/javascript/lookout/controllers/application.js +9 -0
- data/app/assets/javascript/lookout/controllers/application_controller.js +33 -0
- data/app/assets/javascript/lookout/controllers/combobox_controller.js +371 -0
- data/app/assets/javascript/lookout/controllers/details_modal_controller.js +18 -0
- data/app/assets/javascript/lookout/controllers/dropdown_label_controller.js +39 -0
- data/app/assets/javascript/lookout/controllers/filter/item_controller.js +12 -0
- data/app/assets/javascript/lookout/controllers/filter_form_controller.js +13 -0
- data/app/assets/javascript/lookout/controllers/filter_modal_controller.js +45 -0
- data/app/assets/javascript/lookout/controllers/frame_link_controller.js +20 -0
- data/app/assets/javascript/lookout/controllers/funnel_chart_controller.js +159 -0
- data/app/assets/javascript/lookout/controllers/index.js +4 -0
- data/app/assets/javascript/lookout/controllers/interval_controller.js +15 -0
- data/app/assets/javascript/lookout/controllers/line_chart_controller.js +251 -0
- data/app/assets/javascript/lookout/controllers/predicate_select_controller.js +10 -0
- data/app/assets/javascript/lookout/controllers/properties_controller.js +8 -0
- data/app/assets/javascript/lookout/controllers/property_filter_controller.js +45 -0
- data/app/assets/javascript/lookout/controllers/realtime_controller.js +30 -0
- data/app/assets/javascript/lookout/controllers/sparkline_controller.js +64 -0
- data/app/assets/javascript/lookout/controllers/tile_controller.js +33 -0
- data/app/assets/javascript/lookout/controllers/toggle_controller.js +17 -0
- data/app/assets/javascript/lookout/helpers/chart_utils.js +156 -0
- data/app/assets/javascript/lookout/helpers/countries.js +2261 -0
- data/app/assets/javascript/lookout/helpers/number_formatters.js +55 -0
- data/app/assets/manifest/lookout/manifest.js +2 -0
- data/app/components/lookout/combobox_component.html.erb +33 -0
- data/app/components/lookout/combobox_component.rb +13 -0
- data/app/components/lookout/comparison_link_component.html.erb +17 -0
- data/app/components/lookout/comparison_link_component.rb +44 -0
- data/app/components/lookout/dropdown_button_component.html.erb +16 -0
- data/app/components/lookout/dropdown_button_component.rb +14 -0
- data/app/components/lookout/dropdown_link_component.html.erb +17 -0
- data/app/components/lookout/dropdown_link_component.rb +19 -0
- data/app/components/lookout/filter/dropdown_component.html.erb +50 -0
- data/app/components/lookout/filter/dropdown_component.rb +51 -0
- data/app/components/lookout/filter/modal_component.html.erb +16 -0
- data/app/components/lookout/filter/modal_component.rb +13 -0
- data/app/components/lookout/filter/select_component.html.erb +25 -0
- data/app/components/lookout/filter/select_component.rb +64 -0
- data/app/components/lookout/filter/tag_component.html.erb +13 -0
- data/app/components/lookout/filter/tag_component.rb +14 -0
- data/app/components/lookout/filter/tag_container_component.html.erb +4 -0
- data/app/components/lookout/filter/tag_container_component.rb +6 -0
- data/app/components/lookout/previous_next_component.html.erb +8 -0
- data/app/components/lookout/previous_next_component.rb +11 -0
- data/app/components/lookout/stats/comparable_container_component.html.erb +25 -0
- data/app/components/lookout/stats/comparable_container_component.rb +103 -0
- data/app/components/lookout/stats/container_component.html.erb +23 -0
- data/app/components/lookout/stats/container_component.rb +28 -0
- data/app/components/lookout/sticky_nav_component.html.erb +32 -0
- data/app/components/lookout/sticky_nav_component.rb +24 -0
- data/app/components/lookout/table_component.html.erb +16 -0
- data/app/components/lookout/table_component.rb +48 -0
- data/app/components/lookout/tables/devices_table_component.rb +11 -0
- data/app/components/lookout/tables/dynamic_table.rb +13 -0
- data/app/components/lookout/tables/dynamic_table_component.rb +207 -0
- data/app/components/lookout/tables/goals_table_component.rb +17 -0
- data/app/components/lookout/tables/header_component.html.erb +6 -0
- data/app/components/lookout/tables/header_component.rb +18 -0
- data/app/components/lookout/tables/headers/header_component.html.erb +5 -0
- data/app/components/lookout/tables/headers/header_component.rb +16 -0
- data/app/components/lookout/tables/properties_table_component.rb +27 -0
- data/app/components/lookout/tables/row_component.html.erb +4 -0
- data/app/components/lookout/tables/rows/row_component.html.erb +6 -0
- data/app/components/lookout/tables/rows/row_component.rb +40 -0
- data/app/components/lookout/tile_component.html.erb +24 -0
- data/app/components/lookout/tile_component.rb +24 -0
- data/app/components/lookout/tooltip_component.html.erb +3 -0
- data/app/components/lookout/tooltip_component.rb +18 -0
- data/app/controllers/lookout/application_controller.rb +83 -0
- data/app/controllers/lookout/campaigns_controller.rb +19 -0
- data/app/controllers/lookout/devices_controller.rb +20 -0
- data/app/controllers/lookout/entry_pages_controller.rb +19 -0
- data/app/controllers/lookout/exit_pages_controller.rb +19 -0
- data/app/controllers/lookout/exports_controller.rb +14 -0
- data/app/controllers/lookout/filters/base_controller.rb +15 -0
- data/app/controllers/lookout/filters/goals_controller.rb +9 -0
- data/app/controllers/lookout/filters/locations_controller.rb +11 -0
- data/app/controllers/lookout/filters/operating_systems/names_controller.rb +13 -0
- data/app/controllers/lookout/filters/operating_systems/versions_controller.rb +13 -0
- data/app/controllers/lookout/filters/pages/actions_controller.rb +13 -0
- data/app/controllers/lookout/filters/pages/entry_pages_controller.rb +14 -0
- data/app/controllers/lookout/filters/pages/exit_pages_controller.rb +15 -0
- data/app/controllers/lookout/filters/properties/names_controller.rb +29 -0
- data/app/controllers/lookout/filters/properties/values_controller.rb +15 -0
- data/app/controllers/lookout/filters/screens_controller.rb +11 -0
- data/app/controllers/lookout/filters/sources_controller.rb +11 -0
- data/app/controllers/lookout/filters/utms_controller.rb +10 -0
- data/app/controllers/lookout/funnels_controller.rb +8 -0
- data/app/controllers/lookout/goals_controller.rb +7 -0
- data/app/controllers/lookout/locations/cities_controller.rb +22 -0
- data/app/controllers/lookout/locations/countries_controller.rb +22 -0
- data/app/controllers/lookout/locations/maps_controller.rb +24 -0
- data/app/controllers/lookout/locations/regions_controller.rb +22 -0
- data/app/controllers/lookout/properties_controller.rb +73 -0
- data/app/controllers/lookout/realtimes_controller.rb +7 -0
- data/app/controllers/lookout/roots_controller.rb +6 -0
- data/app/controllers/lookout/sources_controller.rb +21 -0
- data/app/controllers/lookout/stats/base_controller.rb +148 -0
- data/app/controllers/lookout/stats/bounce_rates_controller.rb +12 -0
- data/app/controllers/lookout/stats/total_pageviews_controller.rb +10 -0
- data/app/controllers/lookout/stats/total_visits_controller.rb +10 -0
- data/app/controllers/lookout/stats/unique_visitors_controller.rb +11 -0
- data/app/controllers/lookout/stats/views_per_visits_controller.rb +11 -0
- data/app/controllers/lookout/stats/visit_durations_controller.rb +10 -0
- data/app/controllers/lookout/stats_controller.rb +7 -0
- data/app/controllers/lookout/top_pages_controller.rb +20 -0
- data/app/decorators/lookout/application_decorator.rb +58 -0
- data/app/decorators/lookout/campaign_decorator.rb +27 -0
- data/app/decorators/lookout/city_decorator.rb +24 -0
- data/app/decorators/lookout/country_decorator.rb +38 -0
- data/app/decorators/lookout/device_decorator.rb +27 -0
- data/app/decorators/lookout/entry_page_decorator.rb +7 -0
- data/app/decorators/lookout/exit_page_decorator.rb +7 -0
- data/app/decorators/lookout/page_decorator.rb +27 -0
- data/app/decorators/lookout/region_decorator.rb +28 -0
- data/app/decorators/lookout/source_decorator.rb +27 -0
- data/app/decorators/lookout/top_page_decorator.rb +7 -0
- data/app/helpers/lookout/application_helper.rb +124 -0
- data/app/models/concerns/lookout/compare_mode.rb +19 -0
- data/app/models/concerns/lookout/limitable.rb +17 -0
- data/app/models/concerns/lookout/range_options.rb +8 -0
- data/app/models/lookout/comparison_mode.rb +72 -0
- data/app/models/lookout/export.rb +48 -0
- data/app/models/lookout/filter_parser.rb +82 -0
- data/app/models/lookout/range_from_params.rb +78 -0
- data/app/models/lookout/rangeable.rb +7 -0
- data/app/models/lookout/widget.rb +15 -0
- data/app/presenters/lookout/dashboard_presenter.rb +53 -0
- data/app/presenters/lookout/funnel_presenter.rb +75 -0
- data/app/presenters/lookout/goals_presenter.rb +72 -0
- data/app/queries/concerns/lookout/comparable_queries.rb +25 -0
- data/app/queries/concerns/lookout/comparable_query.rb +152 -0
- data/app/queries/concerns/lookout/lazy_comparable_query.rb +42 -0
- data/app/queries/lookout/application_query.rb +186 -0
- data/app/queries/lookout/campaign_query.rb +14 -0
- data/app/queries/lookout/city_query.rb +14 -0
- data/app/queries/lookout/country_query.rb +10 -0
- data/app/queries/lookout/device_query.rb +10 -0
- data/app/queries/lookout/entry_pages_query.rb +18 -0
- data/app/queries/lookout/event_query.rb +42 -0
- data/app/queries/lookout/exit_pages_query.rb +19 -0
- data/app/queries/lookout/region_query.rb +14 -0
- data/app/queries/lookout/source_query.rb +11 -0
- data/app/queries/lookout/stats/average_views_per_visit_query.rb +20 -0
- data/app/queries/lookout/stats/average_visit_duration_query.rb +34 -0
- data/app/queries/lookout/stats/base_query.rb +18 -0
- data/app/queries/lookout/stats/bounce_rates_query.rb +33 -0
- data/app/queries/lookout/stats/total_pageviews_query.rb +9 -0
- data/app/queries/lookout/stats/total_visitors_query.rb +9 -0
- data/app/queries/lookout/stats/unique_visitors_query.rb +9 -0
- data/app/queries/lookout/stats/views_per_visit_query.rb +17 -0
- data/app/queries/lookout/stats/visit_duration_query.rb +19 -0
- data/app/queries/lookout/top_page_query.rb +13 -0
- data/app/queries/lookout/visit_query.rb +42 -0
- data/app/views/lookout/campaigns/index.html+details.erb +4 -0
- data/app/views/lookout/campaigns/index.html.erb +3 -0
- data/app/views/lookout/devices/_table.html.erb +2 -0
- data/app/views/lookout/devices/index.html+details.erb +4 -0
- data/app/views/lookout/devices/index.html.erb +3 -0
- data/app/views/lookout/entry_pages/index.html+details.erb +4 -0
- data/app/views/lookout/entry_pages/index.html.erb +3 -0
- data/app/views/lookout/exit_pages/index.html+details.erb +4 -0
- data/app/views/lookout/exit_pages/index.html.erb +3 -0
- data/app/views/lookout/funnels/index.html.erb +7 -0
- data/app/views/lookout/funnels/show.html.erb +15 -0
- data/app/views/lookout/goals/index.html.erb +4 -0
- data/app/views/lookout/layouts/application.html.erb +144 -0
- data/app/views/lookout/layouts/shared/_tile_loader.html.erb +5 -0
- data/app/views/lookout/layouts/shared/_widget_disabled.html+details.erb +3 -0
- data/app/views/lookout/layouts/shared/_widget_disabled.html.erb +3 -0
- data/app/views/lookout/locations/cities/index.html+details.erb +4 -0
- data/app/views/lookout/locations/cities/index.html.erb +3 -0
- data/app/views/lookout/locations/countries/index.html+details.erb +5 -0
- data/app/views/lookout/locations/countries/index.html.erb +3 -0
- data/app/views/lookout/locations/maps/_simple_map.html.erb +26 -0
- data/app/views/lookout/locations/maps/show.html.erb +106 -0
- data/app/views/lookout/locations/regions/index.html+details.erb +4 -0
- data/app/views/lookout/locations/regions/index.html.erb +3 -0
- data/app/views/lookout/properties/_form.html.erb +6 -0
- data/app/views/lookout/properties/index.html.erb +3 -0
- data/app/views/lookout/properties/show.html.erb +6 -0
- data/app/views/lookout/realtimes/show.html.erb +9 -0
- data/app/views/lookout/roots/_filters.html.erb +80 -0
- data/app/views/lookout/roots/show.html.erb +191 -0
- data/app/views/lookout/sources/index.html+details.erb +4 -0
- data/app/views/lookout/sources/index.html.erb +3 -0
- data/app/views/lookout/stats/base/index.html.erb +40 -0
- data/app/views/lookout/stats/show.html.erb +15 -0
- data/app/views/lookout/top_pages/index.html+details.erb +4 -0
- data/app/views/lookout/top_pages/index.html.erb +3 -0
- data/config/routes.rb +69 -0
- data/lib/generators/lookout/install_generator.rb +31 -0
- data/lib/generators/lookout/migration_generator.rb +21 -0
- data/lib/generators/lookout/templates/config.rb.tt +185 -0
- data/lib/generators/lookout/templates/migration.rb.tt +7 -0
- data/lib/lookout/active_record.rb +108 -0
- data/lib/lookout/ahoy/event_methods.rb +75 -0
- data/lib/lookout/ahoy/visit_methods.rb +24 -0
- data/lib/lookout/configuration.rb +58 -0
- data/lib/lookout/database_adapter.rb +168 -0
- data/lib/lookout/engine.rb +47 -0
- data/lib/lookout/filter_configuration/filter.rb +16 -0
- data/lib/lookout/filter_configuration/filter_collection.rb +48 -0
- data/lib/lookout/filters_configuration.rb +77 -0
- data/lib/lookout/funnels.rb +44 -0
- data/lib/lookout/goals.rb +51 -0
- data/lib/lookout/period_collection.rb +115 -0
- data/lib/lookout/predicate_label.rb +7 -0
- data/lib/lookout/railtie.rb +9 -0
- data/lib/lookout/version.rb +3 -0
- data/lib/lookout.rb +78 -0
- metadata +673 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Lookout
|
|
2
|
+
module CompareMode
|
|
3
|
+
def self.included(klass)
|
|
4
|
+
if klass < ActionController::Base
|
|
5
|
+
klass.helper_method :compare_mode?
|
|
6
|
+
klass.helper_method :comparison_label
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# doesn't work for realtime and realtime doesn't need a secondary range
|
|
11
|
+
def compare_mode?
|
|
12
|
+
comparison_mode.enabled?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def comparison_mode
|
|
16
|
+
@comparison_mode ||= ComparisonMode.new(params)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Lookout
|
|
2
|
+
class ComparisonMode
|
|
3
|
+
VALID_COMPARISONS = %w{previous year true}
|
|
4
|
+
|
|
5
|
+
include RangeOptions
|
|
6
|
+
include Rangeable
|
|
7
|
+
|
|
8
|
+
attr_reader :params
|
|
9
|
+
def initialize(params)
|
|
10
|
+
@params = params
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# can't compare realtime
|
|
14
|
+
def enabled?(strict = true)
|
|
15
|
+
comparing = (params[:comparison].in?(VALID_COMPARISONS) || custom_compare?)
|
|
16
|
+
if strict
|
|
17
|
+
comparing && !range.realtime?
|
|
18
|
+
else
|
|
19
|
+
comparing
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def label
|
|
24
|
+
if custom_compare?
|
|
25
|
+
return "Custom period"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if type == :true || type == :previous
|
|
29
|
+
"Previous period"
|
|
30
|
+
elsif type == :year
|
|
31
|
+
"Year-over-year"
|
|
32
|
+
else
|
|
33
|
+
raise ArgumentError
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def compared_to_range
|
|
38
|
+
return custom_compare if custom_compare?
|
|
39
|
+
|
|
40
|
+
if type == :true || type == :previous
|
|
41
|
+
RangeFromParams.new(period: nil, start_date: (range[0] - (range[1] - range[0])).utc.to_s, end_date: range[0].utc.to_s).build
|
|
42
|
+
elsif type == :year
|
|
43
|
+
RangeFromParams.new(period: nil, start_date: range[0].change(year: range[0].year - 1).utc.to_s, end_date: range[1].change(year: range[1].year - 1).utc.to_s).build
|
|
44
|
+
else
|
|
45
|
+
raise ArgumentError
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def type
|
|
50
|
+
return params[:comparison].to_sym if params[:comparison].in?(VALID_COMPARISONS)
|
|
51
|
+
return :custom if custom_compare?
|
|
52
|
+
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def match_to
|
|
57
|
+
return params[:compare_to].to_sym if params[:compare_to].in?(%w{dow date})
|
|
58
|
+
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def custom_compare
|
|
63
|
+
return nil unless (params[:compare_to_start_date].present? && params[:compare_to_end_date].present?)
|
|
64
|
+
|
|
65
|
+
RangeFromParams.new(period: nil, start_date: params[:compare_to_start_date], end_date: params[:compare_to_end_date]).build
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def custom_compare?
|
|
69
|
+
custom_compare
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Lookout
|
|
2
|
+
class Export
|
|
3
|
+
def initialize(params, context)
|
|
4
|
+
@params = params
|
|
5
|
+
@context = context
|
|
6
|
+
@files = {}
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def build
|
|
10
|
+
@files["browsers.csv"] = to_csv(DeviceQuery.call(merged_params(devices_type: "browser")), DeviceDecorator)
|
|
11
|
+
@files["cities.csv"] = to_csv(CityQuery.call(merged_params), CityDecorator)
|
|
12
|
+
@files["countries.csv"] = to_csv(CountryQuery.call(merged_params), CountryDecorator)
|
|
13
|
+
@files["devices.csv"] = to_csv(DeviceQuery.call(merged_params(devices_type: :device_type)), DeviceDecorator)
|
|
14
|
+
@files["entry_pages.csv"] = to_csv(EntryPagesQuery.call(merged_params), EntryPageDecorator)
|
|
15
|
+
@files["exit_pages.csv"] = to_csv(ExitPagesQuery.call(merged_params), ExitPageDecorator)
|
|
16
|
+
@files["operating_systems.csv"] = to_csv(DeviceQuery.call(merged_params(devices_type: "os")), DeviceDecorator)
|
|
17
|
+
@files["top_pages.csv"] = to_csv(TopPageQuery.call(merged_params), TopPageDecorator)
|
|
18
|
+
@files["regions.csv"] = to_csv(RegionQuery.call(merged_params), RegionDecorator)
|
|
19
|
+
@files["sources.csv"] = to_csv(SourceQuery.call(merged_params), SourceDecorator)
|
|
20
|
+
["campaign", "content", "medium", "source", "term"].each do |utm|
|
|
21
|
+
@files["utm_#{utm.pluralize}.csv"] = to_csv(CampaignQuery.call(merged_params(campaigns_type: "utm_#{utm}")), CampaignDecorator)
|
|
22
|
+
end
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_zip
|
|
27
|
+
zip_stream = Zip::OutputStream.write_buffer do |zip|
|
|
28
|
+
@files.each do |filename, csv|
|
|
29
|
+
zip.put_next_entry(filename)
|
|
30
|
+
zip.write(csv)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
zip_stream.rewind
|
|
35
|
+
zip_stream
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def to_csv(query, decorator)
|
|
41
|
+
decorator.to_csv(query, @context)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def merged_params(params_to_merge = {})
|
|
45
|
+
@params.dup.merge(params_to_merge)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Lookout
|
|
2
|
+
class FilterParser
|
|
3
|
+
FILTER_MENU_MAX_SIZE = 2
|
|
4
|
+
class Item
|
|
5
|
+
attr_accessor :name, :column, :description, :values, :predicate, :url, :modal, :label
|
|
6
|
+
|
|
7
|
+
def title
|
|
8
|
+
column.titleize
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.parse(request)
|
|
13
|
+
new(request).tap do |instance|
|
|
14
|
+
instance.parse
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
delegate_missing_to :@items
|
|
19
|
+
|
|
20
|
+
def initialize(request)
|
|
21
|
+
@request = request
|
|
22
|
+
@params = @request.params
|
|
23
|
+
@filter_params = @request.params[:q] || {}
|
|
24
|
+
@items = {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def parse
|
|
28
|
+
@filter_params.each do |key, values|
|
|
29
|
+
next if ::Lookout.event.ransackable_scopes.include?(key.to_sym)
|
|
30
|
+
|
|
31
|
+
item = build_item(key, values)
|
|
32
|
+
@items[key] = item
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@items
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def build_item(key, values)
|
|
41
|
+
item = Item.new
|
|
42
|
+
item.values = Array(values)
|
|
43
|
+
|
|
44
|
+
item.predicate = Ransack::Predicate.detect_and_strip_from_string!(key.dup)
|
|
45
|
+
item.column = key.delete_suffix("_#{item.predicate}")
|
|
46
|
+
modal_name = Lookout.config.filters.detect { |_, filters| filters.include?(item.column) }
|
|
47
|
+
if modal_name
|
|
48
|
+
item.modal = modal_name[1].modal_name
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
label = if item.column == "goal"
|
|
52
|
+
item.values.map { |value| Lookout.config.goals[value].title }
|
|
53
|
+
else
|
|
54
|
+
item.values
|
|
55
|
+
end.to_sentence(last_word_connector: " or ")
|
|
56
|
+
|
|
57
|
+
item.label = if key.start_with?("properties.")
|
|
58
|
+
item.modal = "customPropertyFilterModal"
|
|
59
|
+
item.column = item.column.dup.delete_prefix("properties.")
|
|
60
|
+
label
|
|
61
|
+
else
|
|
62
|
+
label
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
item.description = "#{item.label} #{::Lookout::PredicateLabel[item.predicate]} #{label}"
|
|
66
|
+
item.url = build_url(key, values)
|
|
67
|
+
item
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def build_url(name, values)
|
|
71
|
+
search_params = @request.query_parameters.deep_dup
|
|
72
|
+
if search_params["q"][name].is_a?(Array)
|
|
73
|
+
search_params["q"][name] = search_params["q"][name] - Array(values)
|
|
74
|
+
else
|
|
75
|
+
search_params["q"].delete(name)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
@request.path + "?" + search_params.to_query
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module Lookout
|
|
2
|
+
class RangeFromParams
|
|
3
|
+
def self.from_params(params)
|
|
4
|
+
compare = ComparisonMode.new(params)
|
|
5
|
+
new(period: params[:period], start_date: params[:start_date], end_date: params[:end_date], date: params[:date], comparison: compare.enabled?(false), raw: params).tap { |instance| instance.build }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
attr_reader :params, :range
|
|
9
|
+
def initialize(period: Lookout.config.ranges.default, start_date: nil, end_date: nil, date: nil, comparison: false, raw: {})
|
|
10
|
+
@period = period || Lookout.config.ranges.default
|
|
11
|
+
@start_date = start_date
|
|
12
|
+
@end_date = end_date
|
|
13
|
+
@date = date
|
|
14
|
+
@range = nil
|
|
15
|
+
@comparison = comparison
|
|
16
|
+
@raw = raw
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def build
|
|
20
|
+
if (@start_date.present? && @end_date.present?) || @date.present?
|
|
21
|
+
if @date
|
|
22
|
+
@start_date ||= @date.to_datetime.beginning_of_day
|
|
23
|
+
@end_date ||= @date.to_datetime.end_of_day
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
custom = [@start_date.to_datetime, @end_date.to_datetime].sort
|
|
27
|
+
duration = (custom[1].to_date - custom[0].to_date)
|
|
28
|
+
|
|
29
|
+
# if Lookout.config.ranges.max && (duration.days <= Lookout.config.ranges.max)
|
|
30
|
+
@range = Range.new(custom[0].utc, custom[1].utc)
|
|
31
|
+
return self
|
|
32
|
+
# end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@range = Range.new(selected_period[0], selected_period[1])
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def starts_at
|
|
40
|
+
Time.at(@range.min)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def ends_at
|
|
44
|
+
if realtime?
|
|
45
|
+
Time.current
|
|
46
|
+
else
|
|
47
|
+
Time.at(@range.max)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def realtime?
|
|
52
|
+
@range.end.nil?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def custom?
|
|
56
|
+
@raw[:start_date].present? && @raw[:end_date].present?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def [](value)
|
|
60
|
+
if value == 0
|
|
61
|
+
starts_at
|
|
62
|
+
elsif value == 1
|
|
63
|
+
ends_at
|
|
64
|
+
else
|
|
65
|
+
raise NoMethodError
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# return an integer-based range which works with step
|
|
70
|
+
def numeric
|
|
71
|
+
@numeric ||= Range.new(starts_at.to_i, ends_at.to_i)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def selected_period
|
|
75
|
+
Lookout.config.ranges.for(@period) || Lookout.config.ranges.default
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Lookout
|
|
2
|
+
class Widget
|
|
3
|
+
class WidgetDisabled < StandardError
|
|
4
|
+
attr_reader :frame
|
|
5
|
+
def initialize(msg, frame = nil)
|
|
6
|
+
@frame = frame
|
|
7
|
+
super(msg)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.disabled?(*names)
|
|
12
|
+
Lookout.config.disabled_widgets.include?(names.join("."))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Lookout
|
|
2
|
+
class DashboardPresenter
|
|
3
|
+
include Rangeable
|
|
4
|
+
include RangeOptions
|
|
5
|
+
include CompareMode
|
|
6
|
+
|
|
7
|
+
attr_reader :params
|
|
8
|
+
|
|
9
|
+
def initialize(params)
|
|
10
|
+
@params = params
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def unique_visitors
|
|
14
|
+
Stats::UniqueVisitorsQuery.call(params).with_comparison(compare_mode?).count
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def total_visits
|
|
18
|
+
Stats::TotalVisitorsQuery.call(params).with_comparison(compare_mode?).count
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def total_pageviews
|
|
22
|
+
Stats::TotalPageviewsQuery.call(params).with_comparison(compare_mode?).count
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def views_per_visit
|
|
26
|
+
Stats::AverageViewsPerVisitQuery.call(params).with_comparison(compare_mode?).average("count")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def bounce_rate
|
|
30
|
+
query = Stats::BounceRatesQuery.call(params)
|
|
31
|
+
if compare_mode?
|
|
32
|
+
query.with_comparison(true).average("bounce_rate")
|
|
33
|
+
else
|
|
34
|
+
query.average("bounce_rate").try(:round, 2) || 0
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def visit_duration
|
|
39
|
+
query = Stats::AverageVisitDurationQuery.call(params)
|
|
40
|
+
if compare_mode?
|
|
41
|
+
query.with_comparison(true)
|
|
42
|
+
else
|
|
43
|
+
query[0].average_visit_duration
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def compare_mode?
|
|
50
|
+
params[:comparison] != 'false'
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module Lookout
|
|
2
|
+
# this is incredibly naive and needs some tlc
|
|
3
|
+
class FunnelPresenter
|
|
4
|
+
|
|
5
|
+
attr_reader :steps
|
|
6
|
+
def initialize(funnel, event_query)
|
|
7
|
+
@funnel = funnel
|
|
8
|
+
@event_query = event_query.joins(:visit)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def build
|
|
12
|
+
if Lookout.config.goals.none? || @funnel.goals.none?
|
|
13
|
+
@steps = []
|
|
14
|
+
return self
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
queries = {
|
|
18
|
+
totals: @event_query.select("count(distinct(#{Lookout.event.table_name}.visit_id)) as unique_visits, '_internal_total_visits_' as name, count(distinct #{Lookout.event.table_name}.id) as total_events, 0 as sort_order")
|
|
19
|
+
}
|
|
20
|
+
selects = ["SELECT unique_visits, name, total_events, sort_order from totals"]
|
|
21
|
+
last_goal = nil
|
|
22
|
+
map = {}.with_indifferent_access
|
|
23
|
+
|
|
24
|
+
# Use funnel's goals in order, not all configured goals
|
|
25
|
+
@funnel.goals.each_with_index do |goal, index|
|
|
26
|
+
queries[goal.id] = @event_query.select("count(distinct(#{Lookout.event.table_name}.visit_id)) as unique_visits, '#{goal.id}' as name, count(distinct #{Lookout.event.table_name}.id) as total_events, #{index + 1} as sort_order").merge(goal.event_query.call).group("#{Lookout.event.table_name}.name")
|
|
27
|
+
selects << ["SELECT unique_visits, name, total_events, sort_order from #{goal.id}"]
|
|
28
|
+
map[goal.id] = goal
|
|
29
|
+
last_goal = goal
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# activerecord quirk / with bug
|
|
33
|
+
select = selects.join(" UNION ").delete_suffix(" from #{last_goal.id}")
|
|
34
|
+
select = select.delete_prefix("SELECT ")
|
|
35
|
+
steps = ::Ahoy::Event.with(
|
|
36
|
+
queries,
|
|
37
|
+
).select(select).from("#{last_goal.id}").order("sort_order asc")
|
|
38
|
+
|
|
39
|
+
# Cast to numeric/real for division depending on database
|
|
40
|
+
cast_type = Lookout::DatabaseAdapter.postgresql? ? "::numeric" : ""
|
|
41
|
+
cast_division = Lookout::DatabaseAdapter.postgresql? ?
|
|
42
|
+
"total_events::numeric/lag(total_events, 1) over ()" :
|
|
43
|
+
"CAST(total_events AS REAL)/lag(total_events, 1) over ()"
|
|
44
|
+
|
|
45
|
+
items = ::Ahoy::Event.with(steps: steps).select("total_events, unique_visits, name, round((#{cast_division}),2) as drop_off").from("steps").order("sort_order asc").index_by(&:name)
|
|
46
|
+
items.delete("_internal_total_visits_")
|
|
47
|
+
@steps = []
|
|
48
|
+
|
|
49
|
+
items.values.each do |item|
|
|
50
|
+
if map[item.name]
|
|
51
|
+
item.name = map[item.name].title
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@steps = items.values
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def total
|
|
61
|
+
@event_query.distinct(:visitor_token).count
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def as_json
|
|
65
|
+
{
|
|
66
|
+
steps: @steps.as_json,
|
|
67
|
+
total: total
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def to_json
|
|
72
|
+
as_json.to_json
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Lookout
|
|
2
|
+
class GoalsPresenter
|
|
3
|
+
attr_reader :goals
|
|
4
|
+
def initialize(event_query)
|
|
5
|
+
@event_query = event_query
|
|
6
|
+
@goals = nil
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# this is a dumpster fire
|
|
10
|
+
def build
|
|
11
|
+
if Lookout.config.goals.none?
|
|
12
|
+
@goals = []
|
|
13
|
+
return self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
queries = {
|
|
17
|
+
totals: @event_query.select("count(distinct(#{Lookout.event.table_name}.visit_id)) as unique_visits, '_internal_total_visits_' as name, count(distinct #{Lookout.event.table_name}.id) as total_events, 0 as sort_order")
|
|
18
|
+
}
|
|
19
|
+
selects = ["SELECT unique_visits, name, total_events, sort_order, 0 as cr, '' as goal_id from totals"]
|
|
20
|
+
last_goal = nil
|
|
21
|
+
map = {}.with_indifferent_access
|
|
22
|
+
|
|
23
|
+
Lookout.config.goals.each_with_index do |goal, index|
|
|
24
|
+
queries[goal.id] = @event_query.select(
|
|
25
|
+
[
|
|
26
|
+
"count(distinct(#{Lookout.event.table_name}.visit_id)) as unique_visits" ,
|
|
27
|
+
"'#{goal.id}' as name",
|
|
28
|
+
"count(distinct #{Lookout.event.table_name}.id) as total_events",
|
|
29
|
+
"#{index + 1} as sort_order",
|
|
30
|
+
"'#{goal.id}' as goal_id"
|
|
31
|
+
]
|
|
32
|
+
).merge(goal.event_query.call).group("#{Lookout.event.table_name}.name")
|
|
33
|
+
# Cast 0 to decimal/real depending on database
|
|
34
|
+
zero_decimal = Lookout::DatabaseAdapter.postgresql? ? "0::decimal" : "0.0"
|
|
35
|
+
selects << ["SELECT unique_visits, name, total_events, sort_order, #{zero_decimal} as cr, '#{goal.id}' as goal_id from #{goal.id}"]
|
|
36
|
+
map[goal.id] = goal
|
|
37
|
+
last_goal = goal
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# activerecord quirk / with bug
|
|
41
|
+
select = selects.join(" UNION ").delete_suffix(" from #{last_goal.id}")
|
|
42
|
+
select = select.delete_prefix("SELECT ")
|
|
43
|
+
steps = ::Ahoy::Event.with(
|
|
44
|
+
queries,
|
|
45
|
+
).select(select).from("#{last_goal.id}").order("sort_order asc").index_by(&:name)
|
|
46
|
+
totals = steps.delete("_internal_total_visits_")
|
|
47
|
+
|
|
48
|
+
@goals = steps.keys.collect do |name|
|
|
49
|
+
step = steps[name]
|
|
50
|
+
step.name = map[name].title
|
|
51
|
+
step.cr = ((step.total_events.to_d / totals.total_events.to_d) * 100).round(2)
|
|
52
|
+
step
|
|
53
|
+
end
|
|
54
|
+
self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def total_visitors
|
|
58
|
+
@total_visitors ||= @event_query.select(:visit_id).distinct.count
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def as_json
|
|
62
|
+
{
|
|
63
|
+
steps: @steps.as_json,
|
|
64
|
+
total: total
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_json
|
|
69
|
+
as_json.to_json
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Lookout
|
|
2
|
+
module ComparableQueries
|
|
3
|
+
def compare_range
|
|
4
|
+
@compare_range ||= begin
|
|
5
|
+
ComparisonMode.new(@params).compared_to_range
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def range
|
|
10
|
+
@range ||= @query.send(:range)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def comparison_params
|
|
14
|
+
params = @params.deep_dup
|
|
15
|
+
params.delete("period")
|
|
16
|
+
|
|
17
|
+
params[:start_date] = compare_range[0]
|
|
18
|
+
params[:end_date] = compare_range[1]
|
|
19
|
+
params
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
require_relative './lazy_comparable_query'
|
|
25
|
+
require_relative './comparable_query'
|