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,191 @@
|
|
|
1
|
+
<main class='w-screen overflow-hidden' data-action="combobox:init@window->application#comboboxInit">
|
|
2
|
+
<script id="tile-loader-template" type="template/html">
|
|
3
|
+
<%= render '/lookout/layouts/shared/tile_loader' %>
|
|
4
|
+
</script>
|
|
5
|
+
<%= render Lookout::StickyNavComponent.new do |nav| %>
|
|
6
|
+
<% nav.with_realtime_update do %>
|
|
7
|
+
<%= turbo_frame_tag :realtime, src: realtime_path, data: { controller: "realtime", "realtime-interval-value" => Lookout.config.realtime_interval.to_i }, loading: :lazy %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% end %>
|
|
10
|
+
|
|
11
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 grid-flow-row gap-4 min-h-screen pb-4 max-w-6xl mx-auto">
|
|
12
|
+
<%= render Lookout::TileComponent.new(wide: true, classes: "p-4 m-2") do |component| %>
|
|
13
|
+
<% component.with_statistic_display do %>
|
|
14
|
+
<%= turbo_frame_tag :stats, src: stats_path(search_params), loading: :lazy, skeleton: false do %>
|
|
15
|
+
<div class="grid grid-cols-1 divide-y divide-base-200 overflow-hidden rounded-lg grid-cols-2 md:grid-cols-6 md:divide-y-0">
|
|
16
|
+
<% 6.times do %>
|
|
17
|
+
<div class="relative px-4 md:px-6 w-1/2 my-4 w-auto group cursor-pointer" >
|
|
18
|
+
<div role="status" class="max-w-sm animate-pulse opacity-40">
|
|
19
|
+
<div class="h-2 bg-gray-100 rounded-full dark:bg-gray-800 max-w-[120px] mb-2.5"></div>
|
|
20
|
+
<div class="h-2 bg-gray-100 rounded-full dark:bg-gray-800 mb-2.5 max-w-[60px]"></div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
<% end %>
|
|
24
|
+
</div>
|
|
25
|
+
<% end %>
|
|
26
|
+
<%= turbo_frame_tag :chart, src: stats_unique_visitors_path(search_params) do %>
|
|
27
|
+
<div role="status" class="p-4 animate-pulse opacity-30 md:p-6">
|
|
28
|
+
<div class="flex items-baseline mt-4 space-x-6">
|
|
29
|
+
<div class="w-full bg-gray-100 rounded-t-lg h-72 dark:bg-gray-800"></div>
|
|
30
|
+
<div class="w-full h-56 bg-gray-100 rounded-t-lg dark:bg-gray-800"></div>
|
|
31
|
+
<div class="w-full bg-gray-100 rounded-t-lg h-72 dark:bg-gray-800"></div>
|
|
32
|
+
<div class="w-full h-64 bg-gray-100 rounded-t-lg dark:bg-gray-800"></div>
|
|
33
|
+
<div class="w-full bg-gray-100 rounded-t-lg h-80 dark:bg-gray-800"></div>
|
|
34
|
+
<div class="w-full bg-gray-100 rounded-t-lg h-72 dark:bg-gray-800"></div>
|
|
35
|
+
<div class="w-full bg-gray-100 rounded-t-lg h-80 dark:bg-gray-800"></div>
|
|
36
|
+
</div>
|
|
37
|
+
<span class="sr-only">Loading...</span>
|
|
38
|
+
</div>
|
|
39
|
+
<% end %>
|
|
40
|
+
<% end %>
|
|
41
|
+
|
|
42
|
+
<% end %>
|
|
43
|
+
|
|
44
|
+
<%= render Lookout::TileComponent.new(title: 'Top Sources') do |component| %>
|
|
45
|
+
<% component.with_display_links do %>
|
|
46
|
+
<div class="flex text-xs font-medium text-gray-400 space-x-2">
|
|
47
|
+
<div class="relative inline-block text-left">
|
|
48
|
+
<%= component.link_to "All", sources_path(search_params), data: { turbo_frame: "sources" } %>
|
|
49
|
+
<%= render Lookout::DropdownLinkComponent.new(title: "Campaign") do |dropdown| %>
|
|
50
|
+
<% %w{utm_source utm_medium utm_term utm_content utm_campaign}.each do |source| %>
|
|
51
|
+
<%= dropdown.link_to source.titleize.gsub("Utm", "UTM"), public_send("campaign_#{source}_path".to_sym, **search_params), data: { turbo_frame: "sources" } %>
|
|
52
|
+
<% end %>
|
|
53
|
+
<% end %>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
<% end %>
|
|
59
|
+
<% component.with_statistic_display do %>
|
|
60
|
+
<%= turbo_frame_tag :sources, src: sources_path(search_params), loading: :lazy do %>
|
|
61
|
+
<%= render '/lookout/layouts/shared/tile_loader' %>
|
|
62
|
+
<% end %>
|
|
63
|
+
<% end %>
|
|
64
|
+
<% component.with_details_cta do %>
|
|
65
|
+
<button data-action="click->details-modal#openModal" data-controller="details-modal" data-details-modal-target-value="#sources" class="no-underline text-xs text-gray-400 font-normal hover:text-gray-500">Details</button>
|
|
66
|
+
<% end %>
|
|
67
|
+
<% end %>
|
|
68
|
+
|
|
69
|
+
<%= render Lookout::TileComponent.new(title: 'Top Pages') do |component| %>
|
|
70
|
+
<% component.with_display_links do %>
|
|
71
|
+
<div class="flex text-xs font-medium text-gray-400 space-x-2">
|
|
72
|
+
<div class="relative inline-block text-left"><div>
|
|
73
|
+
<%= component.link_to "Top Pages", top_pages_path(search_params), data: { action: "click->tile#setTitle", turbo_frame: "pages" } %>
|
|
74
|
+
<%= component.link_to "Entry Pages", entry_pages_path(search_params), data: { action: "click->tile#setTitle", turbo_frame: "pages" } %>
|
|
75
|
+
<%= component.link_to "Exit Pages", exit_pages_path(search_params), data: { action: "click->tile#setTitle", turbo_frame: "pages" } %>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<% end %>
|
|
81
|
+
<% component.with_statistic_display do %>
|
|
82
|
+
<%= turbo_frame_tag :pages, src: top_pages_path(search_params), loading: :lazy do %>
|
|
83
|
+
<%= render '/lookout/layouts/shared/tile_loader' %>
|
|
84
|
+
<% end %>
|
|
85
|
+
<% end %>
|
|
86
|
+
<% component.with_details_cta do %>
|
|
87
|
+
<button data-action="click->details-modal#openModal" data-controller="details-modal" data-details-modal-target-value="#pages" class="no-underline text-xs text-gray-400 font-normal hover:text-gray-500">Details</button>
|
|
88
|
+
<% end %>
|
|
89
|
+
<% end %>
|
|
90
|
+
|
|
91
|
+
<%= render Lookout::TileComponent.new(title: 'Map') do |component| %>
|
|
92
|
+
<% component.with_display_links do %>
|
|
93
|
+
<div class="flex text-xs font-medium text-gray-400 space-x-2">
|
|
94
|
+
<div class="relative inline-block text-left">
|
|
95
|
+
<%= component.link_to "Map", locations_map_path(search_params), data: { action: "click->tile#setTitle", turbo_frame: "geography" } %>
|
|
96
|
+
<%= component.link_to "Countries", locations_countries_path(search_params), data: { action: "click->tile#setTitle", turbo_frame: "geography" } %>
|
|
97
|
+
<%= component.link_to "Regions", locations_regions_path(search_params), data: { action: "click->tile#setTitle", turbo_frame: "geography" } %>
|
|
98
|
+
<%= component.link_to "Cities", locations_cities_path(search_params), data: { action: "click->tile#setTitle", turbo_frame: "geography" } %>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
<% end %>
|
|
102
|
+
<% component.with_statistic_display do %>
|
|
103
|
+
<%= turbo_frame_tag :geography, src: locations_map_path(search_params), loading: :lazy do %>
|
|
104
|
+
<%= render '/lookout/layouts/shared/tile_loader' %>
|
|
105
|
+
|
|
106
|
+
<% end %>
|
|
107
|
+
<% end %>
|
|
108
|
+
<% component.with_details_cta do %>
|
|
109
|
+
<button data-action="click->details-modal#openModal" data-controller="details-modal" data-details-modal-target-value="#geography" class="no-underline text-xs text-gray-400 font-normal hover:text-gray-500">Details</button>
|
|
110
|
+
<% end %>
|
|
111
|
+
<% end %>
|
|
112
|
+
|
|
113
|
+
<%= render Lookout::TileComponent.new(title: 'Devices') do |component| %>
|
|
114
|
+
<% component.with_display_links do %>
|
|
115
|
+
<div class="flex text-xs font-medium text-gray-400 space-x-2">
|
|
116
|
+
<div class="relative inline-block text-left">
|
|
117
|
+
<%= component.link_to "Browser", devices_browsers_path(search_params), data: { turbo_frame: "devices" } %>
|
|
118
|
+
<%= component.link_to "OS", devices_operating_systems_path(search_params), data: { turbo_frame: "devices" } %>
|
|
119
|
+
<%= component.link_to "Size", devices_device_types_path(search_params), data: { turbo_frame: "devices" } %>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
<% end %>
|
|
123
|
+
<% component.with_statistic_display do %>
|
|
124
|
+
<%= turbo_frame_tag :devices, src: devices_browsers_path(search_params), loading: :lazy do %>
|
|
125
|
+
<%= render '/lookout/layouts/shared/tile_loader' %>
|
|
126
|
+
|
|
127
|
+
<% end %>
|
|
128
|
+
<% end %>
|
|
129
|
+
<% component.with_details_cta do %>
|
|
130
|
+
<button data-action="click->details-modal#openModal" data-controller="details-modal" data-details-modal-target-value="#devices" class="no-underline text-xs text-gray-400 font-normal hover:text-gray-500">Details</button>
|
|
131
|
+
<% end %>
|
|
132
|
+
<% end %>
|
|
133
|
+
<%= render Lookout::TileComponent.new(title: "Funnels", wide: true, classes: "p-4 m-2") do |component| %>
|
|
134
|
+
<% component.with_display_links do %>
|
|
135
|
+
<div>
|
|
136
|
+
<div >
|
|
137
|
+
<div class="flex text-xs font-medium text-gray-400 space-x-2">
|
|
138
|
+
<div class="relative inline-block text-left">
|
|
139
|
+
<a href="<%= goals_path(search_params) %>" data-turbo-frame="goals" class="inline-block h-5 font-semibold" data-controller="frame-link" data-action="click->tile#setTitle">Goals</a>
|
|
140
|
+
<a href="<%= properties_path(search_params) %>" data-turbo-frame="goals" class="inline-block h-5 font-semibold" data-controller="frame-link" data-action="click->tile#setTitle">Properties</a>
|
|
141
|
+
<div class="dropdown dropdown-end inline-block" data-controller='dropdown-label'>
|
|
142
|
+
<label
|
|
143
|
+
tabindex="0"
|
|
144
|
+
class="cursor-pointer inline-block h-5 font-semibold"
|
|
145
|
+
data-action='click->dropdown-label#removeHidden'
|
|
146
|
+
data-funnel-dropdown-label
|
|
147
|
+
data-turbo-frame="goals"
|
|
148
|
+
>
|
|
149
|
+
<span data-dropdown-label-target="label">Funnels</span>
|
|
150
|
+
</label>
|
|
151
|
+
<ul class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52" data-dropdown-label-target="close">
|
|
152
|
+
<% Lookout.config.funnels.each do |id, funnel| %>
|
|
153
|
+
<li data-action="click->dropdown-label#setLabel">
|
|
154
|
+
<a href="<%= funnel_path(id, search_params) %>" data-turbo-frame="goals" class="link" data-action="click->tile#setTitle" data-funnel-link title="<%= funnel.title %>">
|
|
155
|
+
<%= funnel.title %>
|
|
156
|
+
</a>
|
|
157
|
+
</li>
|
|
158
|
+
<% end %>
|
|
159
|
+
</ul>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
<% end %>
|
|
167
|
+
<% component.with_statistic_display do %>
|
|
168
|
+
<div class="pl-4 pb-4">
|
|
169
|
+
<%= turbo_frame_tag :goals, src: goals_path(search_params), loading: :lazy do %>
|
|
170
|
+
<%= render '/lookout/layouts/shared/tile_loader' %>
|
|
171
|
+
<% end %>
|
|
172
|
+
|
|
173
|
+
</div>
|
|
174
|
+
<% end %>
|
|
175
|
+
<% component.with_details_cta do %>
|
|
176
|
+
<button data-action="click->details-modal#openModal" data-controller="details-modal" data-details-modal-target-value="#goals" class="no-underline text-xs text-gray-400 font-normal hover:text-gray-500">Details</button>
|
|
177
|
+
<% end %>
|
|
178
|
+
<% end %>
|
|
179
|
+
</div>
|
|
180
|
+
</main>
|
|
181
|
+
|
|
182
|
+
<%= render 'lookout/roots/filters' %>
|
|
183
|
+
|
|
184
|
+
<dialog id="detailsModal" class="modal">
|
|
185
|
+
<div class="modal-box w-11/12 max-w-5xl">
|
|
186
|
+
<%= turbo_frame_tag :details %>
|
|
187
|
+
</div>
|
|
188
|
+
<form method="dialog" class="modal-backdrop">
|
|
189
|
+
<button>close</button>
|
|
190
|
+
</form>
|
|
191
|
+
</dialog>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<%= turbo_frame_tag :chart do %>
|
|
2
|
+
<div class="p-4">
|
|
3
|
+
<div class="flex justify-end gap-3 items-center ">
|
|
4
|
+
<a href="<%= export_path(request.query_parameters) %>" class="link text-sm" target="_blank" data-turbo-frame="false">Download</a>
|
|
5
|
+
<%= form_with url: url_for(params.permit!), method: :get, data: { controller: "interval" } do %>
|
|
6
|
+
<%= select_tag :interval, options_for_select(available_intervals.collect { |interval| [interval.titleize, interval] }, selected: selected_interval), class: "select text-accent select-sm w-full max-w-sm", 'data-action': "change->interval#handleChange" %>
|
|
7
|
+
<% end %>
|
|
8
|
+
</div>
|
|
9
|
+
<div>
|
|
10
|
+
<canvas id="overlay" width="600" height="400" style="position:absolute;pointer-events:none;"></canvas>
|
|
11
|
+
|
|
12
|
+
<% if compare_mode? %>
|
|
13
|
+
<canvas class="h-[300px] w-full"
|
|
14
|
+
data-controller="line-chart"
|
|
15
|
+
data-line-chart-label-value="<%= @label %>"
|
|
16
|
+
data-line-chart-interval-value="<%= selected_interval %>"
|
|
17
|
+
data-line-chart-current-value="<%= @stats.current.to_json %>"
|
|
18
|
+
data-line-chart-comparison-value="<%= params[:comparison] %>"
|
|
19
|
+
data-line-chart-compared-to-value="<%= @stats.compared_to.to_json %>"
|
|
20
|
+
data-line-chart-metric-value="<%= metric_type(@stats) %>"
|
|
21
|
+
data-action="resize@window->line-chart#resize"
|
|
22
|
+
data-action="mouseenter->line-chart#hover"
|
|
23
|
+
data-action="mouseleave->line-chart#hover"
|
|
24
|
+
></canvas>
|
|
25
|
+
<% else %>
|
|
26
|
+
<canvas class="h-[300px] w-full"
|
|
27
|
+
data-controller="line-chart"
|
|
28
|
+
data-line-chart-label-value="<%= @label %>"
|
|
29
|
+
data-line-chart-interval-value="<%= selected_interval %>"
|
|
30
|
+
data-line-chart-current-value="<%= @stats.to_json %>"
|
|
31
|
+
data-line-chart-metric-value="<%= metric_type(@stats) %>"
|
|
32
|
+
data-action="resize@window->line-chart#resize"
|
|
33
|
+
data-action="mouseenter->line-chart#hover"
|
|
34
|
+
data-action="mouseleave->line-chart#hover"
|
|
35
|
+
></canvas>
|
|
36
|
+
<% end %>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<% end %>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<%= turbo_frame_tag :stats, data: { controller: "active-frame-link" } do %>
|
|
2
|
+
<div class="grid grid-cols-1 divide-y divide-base-200 overflow-hidden rounded-lg grid-cols-2 md:grid-cols-6 md:divide-y-0" data-controller="active-links" data-active-links-classes-value='["text-primary"]'>
|
|
3
|
+
<% if @presenter.send(:range).realtime? %>
|
|
4
|
+
<%= render stats_container(@presenter.unique_visitors, stats_unique_visitors_url(search_params), "Unique Visits (30 min)", :number_with_delimiter, true) %>
|
|
5
|
+
<%= render stats_container(@presenter.total_pageviews, stats_total_pageviews_path(search_params), "Total Pageviews (30 min)", :number_with_delimiter) %>
|
|
6
|
+
<% else %>
|
|
7
|
+
<%= render stats_container(@presenter.unique_visitors, stats_unique_visitors_url(search_params), "Unique Visits", :number_with_delimiter, true) %>
|
|
8
|
+
<%= render stats_container(@presenter.total_visits, stats_total_visits_path(search_params), "Total Visits", :number_with_delimiter) %>
|
|
9
|
+
<%= render stats_container(@presenter.total_pageviews, stats_total_pageviews_path(search_params), "Total Pageviews", :number_with_delimiter) %>
|
|
10
|
+
<%= render stats_container(@presenter.views_per_visit, stats_views_per_visits_path(search_params), "Views per Visit", :number_with_delimiter) %>
|
|
11
|
+
<%= render stats_container(@presenter.bounce_rate, stats_bounce_rates_path(search_params), "Bounce Rate", :number_with_delimiter) %>
|
|
12
|
+
<%= render stats_container(@presenter.visit_duration, stats_visit_durations_url(search_params), "Visit Duration", :number_to_duration) %>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
15
|
+
<% end %>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Lookout::Engine.routes.draw do
|
|
2
|
+
root to: 'roots#show'
|
|
3
|
+
%w{utm_source utm_medium utm_term utm_content utm_campaign}.each do |utm|
|
|
4
|
+
get "campaigns/#{utm}" => "campaigns#index", defaults: { campaigns_type: utm }, as: "campaign_#{utm}"
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
browsers: :browser,
|
|
9
|
+
operating_systems: :os,
|
|
10
|
+
device_types: :device_type
|
|
11
|
+
}.each do |k,v|
|
|
12
|
+
get "/devices/#{k}" => 'devices#index', defaults: { devices_type: v }, as: "devices_#{k}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
namespace :locations do
|
|
16
|
+
resource :map, only: [:show]
|
|
17
|
+
resources :countries, only: [:index]
|
|
18
|
+
resources :regions, only: [:index]
|
|
19
|
+
resources :cities, only: [:index]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
resources :properties, only: [:index, :show]
|
|
23
|
+
resource :export, only: [:show]
|
|
24
|
+
resource :realtime, only: [:show]
|
|
25
|
+
resources :funnels, only: [:show]
|
|
26
|
+
resources :goals, only: [:index]
|
|
27
|
+
resource :stats, only: [:show]
|
|
28
|
+
resources :campaigns, only: [:index]
|
|
29
|
+
resources :sources, only: [:index]
|
|
30
|
+
resources :exit_pages, only: [:index]
|
|
31
|
+
resources :top_pages, only: [:index]
|
|
32
|
+
resources :entry_pages, only: [:index]
|
|
33
|
+
|
|
34
|
+
namespace :stats do
|
|
35
|
+
resources :unique_visitors, only: [:index]
|
|
36
|
+
resources :total_visits, only: [:index]
|
|
37
|
+
resources :total_pageviews, only: [:index]
|
|
38
|
+
resources :views_per_visits, only: [:index]
|
|
39
|
+
resources :bounce_rates, only: [:index]
|
|
40
|
+
resources :visit_durations, only: [:index]
|
|
41
|
+
end
|
|
42
|
+
namespace :filters do
|
|
43
|
+
%w{source medium term content campaign}.each do |utm|
|
|
44
|
+
get "utm/#{utm}s" => "utms#index", defaults: { type: "utm_#{utm}" }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
%w{country region city}.each do |type|
|
|
48
|
+
get "locations/#{type.pluralize}" => "locations#index", defaults: { type: type }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
namespace :properties do
|
|
52
|
+
resources :names, only: [:index]
|
|
53
|
+
resources :values, only: [:index]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
resources :goals, only: [:index]
|
|
57
|
+
resources :sources, only: [:index]
|
|
58
|
+
resources :screens, only: [:index]
|
|
59
|
+
scope :operating_systems, module: :operating_systems do
|
|
60
|
+
resources :names, only: [:index]
|
|
61
|
+
resources :versions, only: [:index]
|
|
62
|
+
end
|
|
63
|
+
scope :pages, module: :pages do
|
|
64
|
+
resources :actions, only: [:index]
|
|
65
|
+
resources :entry_pages, only: [:index]
|
|
66
|
+
resources :exit_pages, only: [:index]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
|
|
3
|
+
module Lookout
|
|
4
|
+
module Generators
|
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
|
6
|
+
source_root File.join(__dir__, "templates")
|
|
7
|
+
|
|
8
|
+
def copy_templates
|
|
9
|
+
insert_into_file ::Rails.root.join("app/models/ahoy/event.rb").to_s, " include Lookout::Ahoy::EventMethods\n", after: "class Ahoy::Event < ApplicationRecord\n"
|
|
10
|
+
insert_into_file ::Rails.root.join("app/models/ahoy/visit.rb").to_s, " include Lookout::Ahoy::VisitMethods\n", after: "class Ahoy::Visit < ApplicationRecord\n"
|
|
11
|
+
|
|
12
|
+
template "config.rb", "config/initializers/lookout.rb"
|
|
13
|
+
|
|
14
|
+
route "mount Lookout::Engine => '/lookout'"
|
|
15
|
+
|
|
16
|
+
# Importmap (Rails 8+): add one-liner to host app if importmap.rb exists and hasn't been configured yet
|
|
17
|
+
importmap_path = ::Rails.root.join("config/importmap.rb")
|
|
18
|
+
if File.exist?(importmap_path)
|
|
19
|
+
content = File.read(importmap_path)
|
|
20
|
+
unless content.include?("Lookout.importmap(self)")
|
|
21
|
+
append_to_file importmap_path.to_s, <<~RUBY
|
|
22
|
+
|
|
23
|
+
# Lookout (analytics dashboard)
|
|
24
|
+
Lookout.importmap(self)
|
|
25
|
+
RUBY
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
require "rails/generators/active_record"
|
|
3
|
+
|
|
4
|
+
module Lookout
|
|
5
|
+
module Generators
|
|
6
|
+
class MigrationGenerator < Rails::Generators::Base
|
|
7
|
+
include ActiveRecord::Generators::Migration
|
|
8
|
+
source_root File.join(__dir__, "templates")
|
|
9
|
+
|
|
10
|
+
def copy_templates
|
|
11
|
+
migration_template "migration.rb", "db/migrate/create_lookout_indexes.rb", migration_version: migration_version
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def migration_version
|
|
17
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Lookout.configure do |config|
|
|
2
|
+
# ==> Navigation
|
|
3
|
+
#
|
|
4
|
+
# Return path for the back link in the dashboard header
|
|
5
|
+
# Default: "/"
|
|
6
|
+
# config.return_path = "/admin"
|
|
7
|
+
|
|
8
|
+
# Return link text (can include HTML entities)
|
|
9
|
+
# Default: "← Back"
|
|
10
|
+
# config.return_copy = "← Dashboard"
|
|
11
|
+
# config.return_copy = "⟵ Admin Panel"
|
|
12
|
+
|
|
13
|
+
# ==> Event tracking
|
|
14
|
+
#
|
|
15
|
+
# View name
|
|
16
|
+
# The event you use to dictate if a page view occurred
|
|
17
|
+
# config.event.view_name = "$view"
|
|
18
|
+
#
|
|
19
|
+
# URL column
|
|
20
|
+
# The properties that indicate what URL was viewed. Ahoy suggested tracking the
|
|
21
|
+
# controller and action for each view by default, so we use that here.
|
|
22
|
+
# This is automatically adapted for your database (PostgreSQL or SQLite).
|
|
23
|
+
# Default configuration extracts: properties['controller'] + '#' + properties['action']
|
|
24
|
+
#
|
|
25
|
+
# If you have a `url` key in your `properties`, you can customize it:
|
|
26
|
+
# For PostgreSQL:
|
|
27
|
+
# config.event.url_column = "properties->>'url'"
|
|
28
|
+
# For SQLite:
|
|
29
|
+
# config.event.url_column = "JSON_EXTRACT(properties, '$.url')"
|
|
30
|
+
#
|
|
31
|
+
# URL exists
|
|
32
|
+
# A query that indicates if a view event has the correct properties for a page view.
|
|
33
|
+
# This is also automatically adapted for your database.
|
|
34
|
+
#
|
|
35
|
+
# ==> Models
|
|
36
|
+
#
|
|
37
|
+
# Ahoy::Event model
|
|
38
|
+
# config.models.event = '::Ahoy::Event'
|
|
39
|
+
#
|
|
40
|
+
# Ahoy::Visit model
|
|
41
|
+
# config.models.visit = '::Ahoy::Visit'
|
|
42
|
+
#
|
|
43
|
+
#
|
|
44
|
+
# ==> Theme
|
|
45
|
+
#
|
|
46
|
+
# https://daisyui.com/docs/themes/
|
|
47
|
+
# config.theme = "dark"
|
|
48
|
+
|
|
49
|
+
# ==> Disabled widgets
|
|
50
|
+
# Some widgets are more expensive than others. You can disable them here.
|
|
51
|
+
#
|
|
52
|
+
# Here's the list of widgets:
|
|
53
|
+
# * sources
|
|
54
|
+
# * campaigns.utm_medium
|
|
55
|
+
# * campaigns.utm_source
|
|
56
|
+
# * campaigns.utm_term
|
|
57
|
+
# * campaigns.utm_content
|
|
58
|
+
# * campaigns.utm_campaign
|
|
59
|
+
# * top_pages
|
|
60
|
+
# * entry_pages
|
|
61
|
+
# * landing_pages
|
|
62
|
+
# * locations.countries
|
|
63
|
+
# * locations.regions
|
|
64
|
+
# * locations.cities
|
|
65
|
+
# * devices.browsers
|
|
66
|
+
# * devices.operating_systems
|
|
67
|
+
# * devices.device_types
|
|
68
|
+
#
|
|
69
|
+
# config.disabled_widgets = []
|
|
70
|
+
|
|
71
|
+
# ==> Time periods
|
|
72
|
+
#
|
|
73
|
+
# Defaults come from lib/lookout/period_collection.rb
|
|
74
|
+
#
|
|
75
|
+
# If you want your own entirely, first call reset.
|
|
76
|
+
# config.ranges.reset
|
|
77
|
+
#
|
|
78
|
+
# Then you can add your own.
|
|
79
|
+
# config.ranges.add :param_name, "Label", -> { [3.days.ago, Date.today] }
|
|
80
|
+
#
|
|
81
|
+
# You can also remove an existing one:
|
|
82
|
+
# config.ranges.delete(:mtd)
|
|
83
|
+
#
|
|
84
|
+
# Or add to the defaults:
|
|
85
|
+
# config.ranges.add :custom, "Custom", -> { [6.hours.ago, 2.minutes.ago] }
|
|
86
|
+
#
|
|
87
|
+
# Or overwrite the defaults:
|
|
88
|
+
# config.ranges.add :mtd, "Custom MTD", -> { [2.weeks.ago, Time.current] }
|
|
89
|
+
#
|
|
90
|
+
# And handle the default range, which will be used if no range is given:
|
|
91
|
+
# config.ranges.default = '3d'
|
|
92
|
+
#
|
|
93
|
+
# The max range if a custom range is sent
|
|
94
|
+
# config.ranges.max = 180.days
|
|
95
|
+
#
|
|
96
|
+
# Set to false to disable custom ranges
|
|
97
|
+
# config.ranges.custom = true
|
|
98
|
+
#
|
|
99
|
+
# For an interval to be considered "realtime" it must not have a secondary item in the range
|
|
100
|
+
|
|
101
|
+
# ==> Filters
|
|
102
|
+
#
|
|
103
|
+
# Defaults come from lib/lookout/filters_configuration.rb
|
|
104
|
+
#
|
|
105
|
+
# If you want your own entirely, first call reset.
|
|
106
|
+
# config.filters.reset
|
|
107
|
+
#
|
|
108
|
+
# Then you can add your own.
|
|
109
|
+
#
|
|
110
|
+
# config.filters.register "Group label" do
|
|
111
|
+
# filter label: "Some label", column: :column_name, url: :url_for_options, predicates: [:in, :not_in], multiple: true
|
|
112
|
+
# end
|
|
113
|
+
#
|
|
114
|
+
# You can also remove an existing group:
|
|
115
|
+
#
|
|
116
|
+
# config.filters.delete("Group label")
|
|
117
|
+
#
|
|
118
|
+
# Remove a specific filter from a group:
|
|
119
|
+
#
|
|
120
|
+
# config.filters["Group label"].delete(:column_name)
|
|
121
|
+
#
|
|
122
|
+
# You can add to an existing group:
|
|
123
|
+
#
|
|
124
|
+
# config.filters["Group label"].filter label: "Some label", column: :column_name, url: :url_for_options, predicates: [:in, :not_in], multiple: true
|
|
125
|
+
|
|
126
|
+
# ==> Caching
|
|
127
|
+
# config.cache.enabled = false
|
|
128
|
+
#
|
|
129
|
+
# Cache store should be an ActiveSupport::Cache::Store instance
|
|
130
|
+
# config.cache.store = Rails.cache
|
|
131
|
+
#
|
|
132
|
+
# TTL
|
|
133
|
+
# config.cache.ttl = 1.minute
|
|
134
|
+
|
|
135
|
+
#==> Goal tracking
|
|
136
|
+
# Your mother told you to have goals. Track those goals.
|
|
137
|
+
#
|
|
138
|
+
# Basically:
|
|
139
|
+
#
|
|
140
|
+
# config.goal :unique_id do
|
|
141
|
+
# label "Some label here"
|
|
142
|
+
# name "The event name you're tracking in your Ahoy::Event table"
|
|
143
|
+
# end
|
|
144
|
+
#
|
|
145
|
+
# Real-world example:
|
|
146
|
+
#
|
|
147
|
+
# config.goal :appointment_paid do
|
|
148
|
+
# label "Appointment Paid"
|
|
149
|
+
# name "$appointment.paid"
|
|
150
|
+
# end
|
|
151
|
+
#
|
|
152
|
+
# You can also use queries:
|
|
153
|
+
#
|
|
154
|
+
# config.goal :appointment_paid do
|
|
155
|
+
# label "Appointment Paid"
|
|
156
|
+
# query do
|
|
157
|
+
# ::Ahoy::Event.where(...)
|
|
158
|
+
# end
|
|
159
|
+
# end
|
|
160
|
+
|
|
161
|
+
# ==> Funnels
|
|
162
|
+
# Your mother definitely didn't tell you about conversion rate.
|
|
163
|
+
# Except, you're here, so...
|
|
164
|
+
#
|
|
165
|
+
# Basically:
|
|
166
|
+
#
|
|
167
|
+
# config.funnel :id do
|
|
168
|
+
# label "Some label"
|
|
169
|
+
# goal :goal_id_1
|
|
170
|
+
# goal :goal_id_2
|
|
171
|
+
# end
|
|
172
|
+
#
|
|
173
|
+
# Real-world example:
|
|
174
|
+
#
|
|
175
|
+
# config.funnel :appointments do
|
|
176
|
+
# label "Appointment Workflow"
|
|
177
|
+
# goal :appointment_created
|
|
178
|
+
# goal :appointment_paid
|
|
179
|
+
# end
|
|
180
|
+
#
|
|
181
|
+
# => Realtime interval
|
|
182
|
+
# config.realtime_interval = 30.seconds
|
|
183
|
+
#
|
|
184
|
+
# How frequently the page should refresh if the interval is realtime
|
|
185
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
+
def change
|
|
3
|
+
add_index :<%= ::Lookout.visit.table_name %>, :started_at unless index_exists?(:<%= ::Lookout.visit.table_name %>, :started_at)
|
|
4
|
+
add_index :<%= ::Lookout.event.table_name %>, :visit_id unless index_exists?(:<%= ::Lookout.event.table_name %>, :visit_id)
|
|
5
|
+
add_index :<%= ::Lookout.event.table_name %>, :time unless index_exists?(:<%= ::Lookout.event.table_name %>, :time)
|
|
6
|
+
end
|
|
7
|
+
end
|