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.
Files changed (223) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +99 -0
  4. data/Rakefile +24 -0
  5. data/app/assets/images/lookout/apple-touch-icon.png +0 -0
  6. data/app/assets/images/lookout/favicon-16x16.png +0 -0
  7. data/app/assets/images/lookout/favicon-32x32.png +0 -0
  8. data/app/assets/images/lookout/logo.png +0 -0
  9. data/app/assets/images/lookout/safari-pinned-tab.png +0 -0
  10. data/app/assets/images/lookout/safari-pinned-tab.svg +199 -0
  11. data/app/assets/javascript/lookout/application.js +2 -0
  12. data/app/assets/javascript/lookout/controllers/application.js +9 -0
  13. data/app/assets/javascript/lookout/controllers/application_controller.js +33 -0
  14. data/app/assets/javascript/lookout/controllers/combobox_controller.js +371 -0
  15. data/app/assets/javascript/lookout/controllers/details_modal_controller.js +18 -0
  16. data/app/assets/javascript/lookout/controllers/dropdown_label_controller.js +39 -0
  17. data/app/assets/javascript/lookout/controllers/filter/item_controller.js +12 -0
  18. data/app/assets/javascript/lookout/controllers/filter_form_controller.js +13 -0
  19. data/app/assets/javascript/lookout/controllers/filter_modal_controller.js +45 -0
  20. data/app/assets/javascript/lookout/controllers/frame_link_controller.js +20 -0
  21. data/app/assets/javascript/lookout/controllers/funnel_chart_controller.js +159 -0
  22. data/app/assets/javascript/lookout/controllers/index.js +4 -0
  23. data/app/assets/javascript/lookout/controllers/interval_controller.js +15 -0
  24. data/app/assets/javascript/lookout/controllers/line_chart_controller.js +251 -0
  25. data/app/assets/javascript/lookout/controllers/predicate_select_controller.js +10 -0
  26. data/app/assets/javascript/lookout/controllers/properties_controller.js +8 -0
  27. data/app/assets/javascript/lookout/controllers/property_filter_controller.js +45 -0
  28. data/app/assets/javascript/lookout/controllers/realtime_controller.js +30 -0
  29. data/app/assets/javascript/lookout/controllers/sparkline_controller.js +64 -0
  30. data/app/assets/javascript/lookout/controllers/tile_controller.js +33 -0
  31. data/app/assets/javascript/lookout/controllers/toggle_controller.js +17 -0
  32. data/app/assets/javascript/lookout/helpers/chart_utils.js +156 -0
  33. data/app/assets/javascript/lookout/helpers/countries.js +2261 -0
  34. data/app/assets/javascript/lookout/helpers/number_formatters.js +55 -0
  35. data/app/assets/manifest/lookout/manifest.js +2 -0
  36. data/app/components/lookout/combobox_component.html.erb +33 -0
  37. data/app/components/lookout/combobox_component.rb +13 -0
  38. data/app/components/lookout/comparison_link_component.html.erb +17 -0
  39. data/app/components/lookout/comparison_link_component.rb +44 -0
  40. data/app/components/lookout/dropdown_button_component.html.erb +16 -0
  41. data/app/components/lookout/dropdown_button_component.rb +14 -0
  42. data/app/components/lookout/dropdown_link_component.html.erb +17 -0
  43. data/app/components/lookout/dropdown_link_component.rb +19 -0
  44. data/app/components/lookout/filter/dropdown_component.html.erb +50 -0
  45. data/app/components/lookout/filter/dropdown_component.rb +51 -0
  46. data/app/components/lookout/filter/modal_component.html.erb +16 -0
  47. data/app/components/lookout/filter/modal_component.rb +13 -0
  48. data/app/components/lookout/filter/select_component.html.erb +25 -0
  49. data/app/components/lookout/filter/select_component.rb +64 -0
  50. data/app/components/lookout/filter/tag_component.html.erb +13 -0
  51. data/app/components/lookout/filter/tag_component.rb +14 -0
  52. data/app/components/lookout/filter/tag_container_component.html.erb +4 -0
  53. data/app/components/lookout/filter/tag_container_component.rb +6 -0
  54. data/app/components/lookout/previous_next_component.html.erb +8 -0
  55. data/app/components/lookout/previous_next_component.rb +11 -0
  56. data/app/components/lookout/stats/comparable_container_component.html.erb +25 -0
  57. data/app/components/lookout/stats/comparable_container_component.rb +103 -0
  58. data/app/components/lookout/stats/container_component.html.erb +23 -0
  59. data/app/components/lookout/stats/container_component.rb +28 -0
  60. data/app/components/lookout/sticky_nav_component.html.erb +32 -0
  61. data/app/components/lookout/sticky_nav_component.rb +24 -0
  62. data/app/components/lookout/table_component.html.erb +16 -0
  63. data/app/components/lookout/table_component.rb +48 -0
  64. data/app/components/lookout/tables/devices_table_component.rb +11 -0
  65. data/app/components/lookout/tables/dynamic_table.rb +13 -0
  66. data/app/components/lookout/tables/dynamic_table_component.rb +207 -0
  67. data/app/components/lookout/tables/goals_table_component.rb +17 -0
  68. data/app/components/lookout/tables/header_component.html.erb +6 -0
  69. data/app/components/lookout/tables/header_component.rb +18 -0
  70. data/app/components/lookout/tables/headers/header_component.html.erb +5 -0
  71. data/app/components/lookout/tables/headers/header_component.rb +16 -0
  72. data/app/components/lookout/tables/properties_table_component.rb +27 -0
  73. data/app/components/lookout/tables/row_component.html.erb +4 -0
  74. data/app/components/lookout/tables/rows/row_component.html.erb +6 -0
  75. data/app/components/lookout/tables/rows/row_component.rb +40 -0
  76. data/app/components/lookout/tile_component.html.erb +24 -0
  77. data/app/components/lookout/tile_component.rb +24 -0
  78. data/app/components/lookout/tooltip_component.html.erb +3 -0
  79. data/app/components/lookout/tooltip_component.rb +18 -0
  80. data/app/controllers/lookout/application_controller.rb +83 -0
  81. data/app/controllers/lookout/campaigns_controller.rb +19 -0
  82. data/app/controllers/lookout/devices_controller.rb +20 -0
  83. data/app/controllers/lookout/entry_pages_controller.rb +19 -0
  84. data/app/controllers/lookout/exit_pages_controller.rb +19 -0
  85. data/app/controllers/lookout/exports_controller.rb +14 -0
  86. data/app/controllers/lookout/filters/base_controller.rb +15 -0
  87. data/app/controllers/lookout/filters/goals_controller.rb +9 -0
  88. data/app/controllers/lookout/filters/locations_controller.rb +11 -0
  89. data/app/controllers/lookout/filters/operating_systems/names_controller.rb +13 -0
  90. data/app/controllers/lookout/filters/operating_systems/versions_controller.rb +13 -0
  91. data/app/controllers/lookout/filters/pages/actions_controller.rb +13 -0
  92. data/app/controllers/lookout/filters/pages/entry_pages_controller.rb +14 -0
  93. data/app/controllers/lookout/filters/pages/exit_pages_controller.rb +15 -0
  94. data/app/controllers/lookout/filters/properties/names_controller.rb +29 -0
  95. data/app/controllers/lookout/filters/properties/values_controller.rb +15 -0
  96. data/app/controllers/lookout/filters/screens_controller.rb +11 -0
  97. data/app/controllers/lookout/filters/sources_controller.rb +11 -0
  98. data/app/controllers/lookout/filters/utms_controller.rb +10 -0
  99. data/app/controllers/lookout/funnels_controller.rb +8 -0
  100. data/app/controllers/lookout/goals_controller.rb +7 -0
  101. data/app/controllers/lookout/locations/cities_controller.rb +22 -0
  102. data/app/controllers/lookout/locations/countries_controller.rb +22 -0
  103. data/app/controllers/lookout/locations/maps_controller.rb +24 -0
  104. data/app/controllers/lookout/locations/regions_controller.rb +22 -0
  105. data/app/controllers/lookout/properties_controller.rb +73 -0
  106. data/app/controllers/lookout/realtimes_controller.rb +7 -0
  107. data/app/controllers/lookout/roots_controller.rb +6 -0
  108. data/app/controllers/lookout/sources_controller.rb +21 -0
  109. data/app/controllers/lookout/stats/base_controller.rb +148 -0
  110. data/app/controllers/lookout/stats/bounce_rates_controller.rb +12 -0
  111. data/app/controllers/lookout/stats/total_pageviews_controller.rb +10 -0
  112. data/app/controllers/lookout/stats/total_visits_controller.rb +10 -0
  113. data/app/controllers/lookout/stats/unique_visitors_controller.rb +11 -0
  114. data/app/controllers/lookout/stats/views_per_visits_controller.rb +11 -0
  115. data/app/controllers/lookout/stats/visit_durations_controller.rb +10 -0
  116. data/app/controllers/lookout/stats_controller.rb +7 -0
  117. data/app/controllers/lookout/top_pages_controller.rb +20 -0
  118. data/app/decorators/lookout/application_decorator.rb +58 -0
  119. data/app/decorators/lookout/campaign_decorator.rb +27 -0
  120. data/app/decorators/lookout/city_decorator.rb +24 -0
  121. data/app/decorators/lookout/country_decorator.rb +38 -0
  122. data/app/decorators/lookout/device_decorator.rb +27 -0
  123. data/app/decorators/lookout/entry_page_decorator.rb +7 -0
  124. data/app/decorators/lookout/exit_page_decorator.rb +7 -0
  125. data/app/decorators/lookout/page_decorator.rb +27 -0
  126. data/app/decorators/lookout/region_decorator.rb +28 -0
  127. data/app/decorators/lookout/source_decorator.rb +27 -0
  128. data/app/decorators/lookout/top_page_decorator.rb +7 -0
  129. data/app/helpers/lookout/application_helper.rb +124 -0
  130. data/app/models/concerns/lookout/compare_mode.rb +19 -0
  131. data/app/models/concerns/lookout/limitable.rb +17 -0
  132. data/app/models/concerns/lookout/range_options.rb +8 -0
  133. data/app/models/lookout/comparison_mode.rb +72 -0
  134. data/app/models/lookout/export.rb +48 -0
  135. data/app/models/lookout/filter_parser.rb +82 -0
  136. data/app/models/lookout/range_from_params.rb +78 -0
  137. data/app/models/lookout/rangeable.rb +7 -0
  138. data/app/models/lookout/widget.rb +15 -0
  139. data/app/presenters/lookout/dashboard_presenter.rb +53 -0
  140. data/app/presenters/lookout/funnel_presenter.rb +75 -0
  141. data/app/presenters/lookout/goals_presenter.rb +72 -0
  142. data/app/queries/concerns/lookout/comparable_queries.rb +25 -0
  143. data/app/queries/concerns/lookout/comparable_query.rb +152 -0
  144. data/app/queries/concerns/lookout/lazy_comparable_query.rb +42 -0
  145. data/app/queries/lookout/application_query.rb +186 -0
  146. data/app/queries/lookout/campaign_query.rb +14 -0
  147. data/app/queries/lookout/city_query.rb +14 -0
  148. data/app/queries/lookout/country_query.rb +10 -0
  149. data/app/queries/lookout/device_query.rb +10 -0
  150. data/app/queries/lookout/entry_pages_query.rb +18 -0
  151. data/app/queries/lookout/event_query.rb +42 -0
  152. data/app/queries/lookout/exit_pages_query.rb +19 -0
  153. data/app/queries/lookout/region_query.rb +14 -0
  154. data/app/queries/lookout/source_query.rb +11 -0
  155. data/app/queries/lookout/stats/average_views_per_visit_query.rb +20 -0
  156. data/app/queries/lookout/stats/average_visit_duration_query.rb +34 -0
  157. data/app/queries/lookout/stats/base_query.rb +18 -0
  158. data/app/queries/lookout/stats/bounce_rates_query.rb +33 -0
  159. data/app/queries/lookout/stats/total_pageviews_query.rb +9 -0
  160. data/app/queries/lookout/stats/total_visitors_query.rb +9 -0
  161. data/app/queries/lookout/stats/unique_visitors_query.rb +9 -0
  162. data/app/queries/lookout/stats/views_per_visit_query.rb +17 -0
  163. data/app/queries/lookout/stats/visit_duration_query.rb +19 -0
  164. data/app/queries/lookout/top_page_query.rb +13 -0
  165. data/app/queries/lookout/visit_query.rb +42 -0
  166. data/app/views/lookout/campaigns/index.html+details.erb +4 -0
  167. data/app/views/lookout/campaigns/index.html.erb +3 -0
  168. data/app/views/lookout/devices/_table.html.erb +2 -0
  169. data/app/views/lookout/devices/index.html+details.erb +4 -0
  170. data/app/views/lookout/devices/index.html.erb +3 -0
  171. data/app/views/lookout/entry_pages/index.html+details.erb +4 -0
  172. data/app/views/lookout/entry_pages/index.html.erb +3 -0
  173. data/app/views/lookout/exit_pages/index.html+details.erb +4 -0
  174. data/app/views/lookout/exit_pages/index.html.erb +3 -0
  175. data/app/views/lookout/funnels/index.html.erb +7 -0
  176. data/app/views/lookout/funnels/show.html.erb +15 -0
  177. data/app/views/lookout/goals/index.html.erb +4 -0
  178. data/app/views/lookout/layouts/application.html.erb +144 -0
  179. data/app/views/lookout/layouts/shared/_tile_loader.html.erb +5 -0
  180. data/app/views/lookout/layouts/shared/_widget_disabled.html+details.erb +3 -0
  181. data/app/views/lookout/layouts/shared/_widget_disabled.html.erb +3 -0
  182. data/app/views/lookout/locations/cities/index.html+details.erb +4 -0
  183. data/app/views/lookout/locations/cities/index.html.erb +3 -0
  184. data/app/views/lookout/locations/countries/index.html+details.erb +5 -0
  185. data/app/views/lookout/locations/countries/index.html.erb +3 -0
  186. data/app/views/lookout/locations/maps/_simple_map.html.erb +26 -0
  187. data/app/views/lookout/locations/maps/show.html.erb +106 -0
  188. data/app/views/lookout/locations/regions/index.html+details.erb +4 -0
  189. data/app/views/lookout/locations/regions/index.html.erb +3 -0
  190. data/app/views/lookout/properties/_form.html.erb +6 -0
  191. data/app/views/lookout/properties/index.html.erb +3 -0
  192. data/app/views/lookout/properties/show.html.erb +6 -0
  193. data/app/views/lookout/realtimes/show.html.erb +9 -0
  194. data/app/views/lookout/roots/_filters.html.erb +80 -0
  195. data/app/views/lookout/roots/show.html.erb +191 -0
  196. data/app/views/lookout/sources/index.html+details.erb +4 -0
  197. data/app/views/lookout/sources/index.html.erb +3 -0
  198. data/app/views/lookout/stats/base/index.html.erb +40 -0
  199. data/app/views/lookout/stats/show.html.erb +15 -0
  200. data/app/views/lookout/top_pages/index.html+details.erb +4 -0
  201. data/app/views/lookout/top_pages/index.html.erb +3 -0
  202. data/config/routes.rb +69 -0
  203. data/lib/generators/lookout/install_generator.rb +31 -0
  204. data/lib/generators/lookout/migration_generator.rb +21 -0
  205. data/lib/generators/lookout/templates/config.rb.tt +185 -0
  206. data/lib/generators/lookout/templates/migration.rb.tt +7 -0
  207. data/lib/lookout/active_record.rb +108 -0
  208. data/lib/lookout/ahoy/event_methods.rb +75 -0
  209. data/lib/lookout/ahoy/visit_methods.rb +24 -0
  210. data/lib/lookout/configuration.rb +58 -0
  211. data/lib/lookout/database_adapter.rb +168 -0
  212. data/lib/lookout/engine.rb +47 -0
  213. data/lib/lookout/filter_configuration/filter.rb +16 -0
  214. data/lib/lookout/filter_configuration/filter_collection.rb +48 -0
  215. data/lib/lookout/filters_configuration.rb +77 -0
  216. data/lib/lookout/funnels.rb +44 -0
  217. data/lib/lookout/goals.rb +51 -0
  218. data/lib/lookout/period_collection.rb +115 -0
  219. data/lib/lookout/predicate_label.rb +7 -0
  220. data/lib/lookout/railtie.rb +9 -0
  221. data/lib/lookout/version.rb +3 -0
  222. data/lib/lookout.rb +78 -0
  223. metadata +673 -0
@@ -0,0 +1,23 @@
1
+ <a href="<%= @url %>" class="relative px-4 md:px-6 w-1/2 my-4 lg:w-auto group cursor-pointer" data-controller="frame-link" data-turbo-frame="chart">
2
+ <div>
3
+ <h5 class="text-sm font-bold uppercase whitespace-nowrap flex w-content border-transparent tooltip tooltip-bottom "
4
+ data-active-links-target="link">
5
+ <%= @label %>
6
+ </h5>
7
+ <div class="my-1 space-y-2">
8
+ <div>
9
+ <span class="flex items-center justify-between whitespace-nowrap">
10
+ <p class="font-bold text-xl"><%= formatted(@value) %></p>
11
+ </span>
12
+ </div>
13
+ <div style="height: 30px; width: 100px;">
14
+ <canvas
15
+ data-controller="sparkline"
16
+ data-sparkline-label-value="<%= @label %>"
17
+ width="100"
18
+ height="30">
19
+ </canvas>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </a>
@@ -0,0 +1,28 @@
1
+ module Lookout
2
+ module Stats
3
+ class ContainerComponent < ViewComponent::Base
4
+ def initialize(url, label, value, formatter, selected = false)
5
+ @url = url
6
+ @label = label
7
+ @value = value
8
+ @formatter = formatter
9
+ @selected = selected
10
+ end
11
+
12
+ def formatted(value)
13
+ public_send(@formatter, value)
14
+ end
15
+
16
+
17
+ def number_to_duration(duration)
18
+ if duration && duration > 0
19
+ minutes = (duration / 60).to_i
20
+ seconds = (duration % 60).to_i
21
+ "#{minutes}m #{seconds}s"
22
+ else
23
+ "0m 0s"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ <div class=" sticky top-0 min-h-sm z-[99999] py-6 bg-base-100">
2
+ <div class="max-w-6xl mx-auto max-w-6xl flex justify-between ">
3
+ <div class="flex items-center">
4
+ <a href="<%= Lookout.config.return_path %>" class="text-lg font-medium hover:text-primary transition-colors py-2">
5
+ <%= Lookout.config.return_copy %>
6
+ </a>
7
+ <% if tag_list_hidden? %>
8
+ <%= render Lookout::Filter::TagContainerComponent.new %>
9
+ <% else %>
10
+ <%= realtime_update %>
11
+ <% end %>
12
+ </div>
13
+ <div class="flex flex-row-reverse col-span-2 items-center gap-3">
14
+ <%= render Lookout::ComparisonLinkComponent.new %>
15
+ <%= render Lookout::PreviousNextComponent.new(range) %>
16
+
17
+ <%= render Lookout::DropdownLinkComponent.new(title: params[:start_date] ? custom_range_label : (Lookout.config.ranges.find(params[:period] || Lookout.config.ranges.default).try(:label) || "Period"), classes: 'btn btn-sm btn-base-100 no-underline hover:bg-base-100') do |dropdown| %>
18
+ <% dropdown.with_option do %>
19
+ <% Lookout.config.ranges.each do |param, range| %>
20
+ <a class='link no-underline' href="<%= request.path %>?<%= request.query_parameters.except("start_date", "end_date", "date", "compare_to_start_date", "compare_to_end_date").merge("period" => param).to_query %>"><%= range.label %></a>
21
+ <% end %>
22
+
23
+ <a class='link no-underline ' href='#' onclick="event.preventDefault(); customRangeModal.showModal()">Custom Range</a>
24
+ <a class='link no-underline ' href='<%= Lookout::Engine.routes.url_helpers.root_path(**helpers.search_params.merge(comparison: !compare_mode?)) %>'><%= compare_mode? ? "Disable Comparison" : "Compare" %></a>
25
+ <% end %>
26
+ <% end %>
27
+
28
+ <%= render Lookout::Filter::DropdownComponent.new(filters: filters) %>
29
+ </div>
30
+ </div>
31
+
32
+ </div>
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Lookout::StickyNavComponent < ViewComponent::Base
4
+ renders_one :realtime_update
5
+ include ::Lookout::CompareMode
6
+ include ::Lookout::RangeOptions
7
+ include ::Lookout::Rangeable
8
+
9
+ def filters
10
+ @filters ||= ::Lookout::FilterParser.parse(request)
11
+ end
12
+
13
+ def custom_range_label
14
+ if range.custom?
15
+ [range.starts_at, range.ends_at].map { |date| date.strftime('%b %d, %Y') }.join("-")
16
+ else
17
+ "Custom Range"
18
+ end
19
+ end
20
+
21
+ def tag_list_hidden?
22
+ filters.values.map(&:values).flatten.size < ::Lookout::FilterParser::FILTER_MENU_MAX_SIZE
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ <div class="flex flex-col <%= 'min-h-[380px]' if fixed_height? %> w-full pt-4">
2
+ <%= render @header %>
3
+ <div class='<%= 'min-h-[420px]' if fixed_height? %>'>
4
+ <div class="grow">
5
+ <% if items.respond_to?(:each) && items.any? %>
6
+ <% items.each do |item| %>
7
+ <div class='flex items-center relative rounded-sm overflow-hidden h-8 my-2 bg-gray-400'>
8
+ <%= render_row(item) %>
9
+ </div>
10
+ <% end %>
11
+ <% else %>
12
+ <p>No data found</p>
13
+ <% end %>
14
+ </div>
15
+ </div>
16
+ </div>
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Lookout::TableComponent < ViewComponent::Base
4
+ DEFAULT_HEADER = Lookout::Tables::Headers::HeaderComponent
5
+ DEFAULT_ROW = Lookout::Tables::Rows::RowComponent
6
+
7
+ def initialize(items:, category_name: nil, unit_name: nil, header: nil, row: DEFAULT_ROW, table: nil)
8
+ @items = items
9
+ @category_name = category_name
10
+ @unit_name = unit_name
11
+ if header.nil?
12
+ @header = DEFAULT_HEADER.new(category_name: category_name, unit_name: unit_name)
13
+ else
14
+ @header = header.new
15
+ end
16
+
17
+ @row = row
18
+
19
+ if table
20
+ @table = table.table
21
+ @header = @table.headers
22
+ end
23
+ end
24
+
25
+ def render_row(item)
26
+ if @table
27
+ @table.row(item, view_context)
28
+ else
29
+ @row.new(table: self, item: item).render_in(view_context)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :items, :category_name, :unit_name, :additional_cols
36
+
37
+ def max_amount
38
+ @max_amount ||= items.first.unit_amount
39
+ end
40
+
41
+ def total
42
+ @total ||= items.first.total_count
43
+ end
44
+
45
+ def fixed_height?
46
+ @header.fixed_height?
47
+ end
48
+ end
@@ -0,0 +1,11 @@
1
+ module Lookout
2
+ module Tables
3
+ class DevicesTableComponent < DynamicTable
4
+ register do
5
+ progress_bar :display_name, value: :count, max: :total_count, title: "Device"
6
+ number :count, title: "Visitors"
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module Lookout
2
+ module Tables
3
+ class DynamicTable
4
+ def self.register(options = {}, &block)
5
+ @table ||= DynamicTableComponent.build(self, options, &block)
6
+ end
7
+
8
+ def self.table
9
+ @table
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,207 @@
1
+ module Lookout
2
+ module Tables
3
+ class DynamicTableComponent < ViewComponent::Base
4
+ class TableDefinition
5
+ attr_reader :rows
6
+ class Row
7
+ attr_reader :attribute, :view_context
8
+ attr_reader :item
9
+ def initialize(attribute, options = {}, &block)
10
+ @attribute = attribute
11
+ @options = options
12
+ @block = block
13
+ @view_context = nil
14
+ end
15
+
16
+ def render(item, view_context, is_last = false)
17
+ @view_context = view_context
18
+ @item = item
19
+ @is_last = is_last
20
+ if @options[:type] == :progress_bar
21
+ progress_bar(item)
22
+ else
23
+ if @block
24
+ column(@block.call(item))
25
+ else
26
+ column(item.public_send(attribute).presence)
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ def header
33
+ @options[:title] || @attribute.to_s.titleize
34
+ end
35
+
36
+ def link_to(*args)
37
+ @view_context.link_to(*args)
38
+ end
39
+
40
+ def request
41
+ @view_context.request
42
+ end
43
+
44
+ def h
45
+ @view_context.helpers
46
+ end
47
+
48
+ def search_params
49
+ @view_context.search_params
50
+ end
51
+
52
+ # needed for tricky instance_eval with blocks and view_context
53
+ def method_missing(method, *args, &block)
54
+ if klass.respond_to?(method)
55
+ klass.send(method, *args, &block)
56
+ else
57
+ @item.send method, *args, &block
58
+ end
59
+ end
60
+
61
+ def klass
62
+ @options[:klass]
63
+ end
64
+
65
+ def progress_bar(item)
66
+ @item = item
67
+ value = if @options[:value]
68
+ build_option(item, @options[:value]).to_s
69
+ else
70
+ item.public_send(@attribute)
71
+ end
72
+
73
+ max = build_option(item, @options[:max]).to_s
74
+ label = if @block
75
+ instance_eval(&@block)
76
+ else
77
+ item.public_send(@attribute)
78
+ end.to_s
79
+
80
+ items = []
81
+ items << view_context.content_tag(:progress, "", class: "progress-primary bg-gray-800 h-8 grow max-w-[calc(100%-2rem)] opacity-60", value: value, max: max)
82
+ items << view_context.content_tag(:span, class: "grow text-elipsis overflow-hidden absolute left-4 flex items-center h-8 text-primary-content") do
83
+ label
84
+ end
85
+
86
+ items.join.html_safe
87
+ end
88
+
89
+ def build_option(item, value)
90
+ if value.is_a?(Symbol)
91
+ item.public_send(value)
92
+ else
93
+ value
94
+ end
95
+ end
96
+
97
+ def column(value = nil, &block)
98
+ padding_class = @is_last ? 'pl-4' : 'pl-6'
99
+ view_context.content_tag(:span, class: "w-16 text-right border-l-gray border-base-300 #{padding_class} pr-2") do
100
+ if value
101
+ if @options[:formatter]
102
+ if respond_to?(@options[:formatter])
103
+ public_send(@options[:formatter], value)
104
+ else
105
+ view_context.public_send(@options[:formatter], value)
106
+ end
107
+ else
108
+ value.to_s
109
+ end
110
+ else
111
+ view_context.capture(&block).to_s
112
+ end
113
+ end
114
+ end
115
+
116
+ def number_to_human(amount)
117
+ tooltip(amount) do
118
+ view_context.number_to_human(amount, format: '%n%u', precision: 2, units: { thousand: 'k', million: 'm', billion: 'b' })
119
+ end
120
+ end
121
+
122
+ def percent_total(item)
123
+ '%.1f' % ((item.unit_amount.to_i * 1.0 / total)*100.0)
124
+ end
125
+
126
+ def tooltip(value)
127
+ Lookout::TooltipComponent.new(amount: value).render_in(view_context)
128
+ end
129
+ end
130
+
131
+ def initialize(klass)
132
+ @klass = klass
133
+ @rows = []
134
+ end
135
+
136
+ def column(key = nil, options = {}, &block)
137
+ options[:klass] = @klass
138
+ @rows << Row.new(key, options, &block)
139
+
140
+ self
141
+ end
142
+
143
+ def progress_bar(*args, &block)
144
+ options = args.extract_options!
145
+ options.assert_valid_keys(:value, :max, :title)
146
+ options[:klass] = @klass
147
+
148
+ @rows << Row.new(args[0], options.merge(type: :progress_bar), &block)
149
+
150
+ self
151
+ end
152
+
153
+ def number(key = nil, options = {}, &block)
154
+ options[:klass] = @klass
155
+
156
+ @rows << Row.new(key, options.merge(formatter: :number_to_human), &block)
157
+
158
+ self
159
+ end
160
+
161
+ def percent(key = nil, options = {}, &block)
162
+ options[:klass] = @klass
163
+
164
+ @rows << Row.new(key, options.merge(formatter: :number_to_percentage), &block)
165
+
166
+ self
167
+ end
168
+
169
+ def row(item, view_context)
170
+ items = []
171
+ @rows.each_with_index do |row, index|
172
+ is_last = index == @rows.length - 1
173
+ items << row.render(item, view_context, is_last)
174
+ end
175
+ items.join.html_safe
176
+ end
177
+ end
178
+
179
+ def self.build(klass, options = {}, &block)
180
+ if options.key?(:fixed_height)
181
+ options[:header_options] = { fixed_height: options.delete(:fixed_height) }
182
+ end
183
+ table = TableDefinition.new(klass).instance_exec(&block)
184
+ new(klass, table, options)
185
+ end
186
+
187
+ def initialize(klass, table, options = {})
188
+ @klass = klass
189
+ @table = table
190
+ @options = options
191
+ end
192
+
193
+ def for(item)
194
+ @item = item
195
+ self
196
+ end
197
+
198
+ def row(item, view_context)
199
+ @table.row(item, view_context)
200
+ end
201
+
202
+ def headers
203
+ HeaderComponent.new(@table.rows.map.with_index { |row, index| { label: row.header, grow: index.zero? } }, @options[:header_options] || {})
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,17 @@
1
+ module Lookout
2
+ module Tables
3
+ class GoalsTableComponent < DynamicTable
4
+ register fixed_height: false do
5
+ progress_bar value: :cr, max: 100, title: "Name" do |row|
6
+ search_params = view_context.search_params
7
+ query = search_params.dup.merge(q: { goal_in: row.goal_id}).to_query
8
+ url = Lookout::Engine.app.url_helpers.root_path + "?#{query}"
9
+ link_to row.item.name, url, target: :_top
10
+ end
11
+ number :unique_visits, title: "Uniq"
12
+ number :total_events, title: "All"
13
+ percent :cr, title: "CR"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ <div class="flex text-sm font-bold mb-4">
2
+ <% @headers.each_with_index do |header, index| %>
3
+ <% is_last = index == @headers.length - 1 %>
4
+ <span class="<%= header[:grow] ? 'grow pl-4' : "w-16 text-right #{is_last ? 'pl-4' : 'pl-6'} pr-2 border-l-gray border-base-300" %>"><%= header[:label] %></span>
5
+ <% end %>
6
+ </div>
@@ -0,0 +1,18 @@
1
+ module Lookout
2
+ module Tables
3
+ class HeaderComponent < ViewComponent::Base
4
+ def initialize(headers, options = {})
5
+ @headers = headers.flatten
6
+ @options = options
7
+ end
8
+
9
+ def fixed_height?
10
+ if @options.key?(:fixed_height)
11
+ @options[:fixed_height]
12
+ else
13
+ true
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ <div class=" flex text-sm font-bold mb-4">
2
+ <span class="grow"><%= @category_name %></span>
3
+ <span ><%= @unit_name %></span>
4
+ <%= content %>
5
+ </div>
@@ -0,0 +1,16 @@
1
+ module Lookout
2
+ module Tables
3
+ module Headers
4
+ class HeaderComponent < ViewComponent::Base
5
+ def initialize(category_name:, unit_name:)
6
+ @category_name = category_name
7
+ @unit_name = unit_name
8
+ end
9
+
10
+ def fixed_height?
11
+ true
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ module Lookout
2
+ module Tables
3
+ class PropertiesTableComponent < DynamicTable
4
+ register do
5
+ progress_bar :label, value: :percentage, max: 100, title: "Name" do |item|
6
+ link_to item.label, url(item), target: :_top
7
+ end
8
+ number :unique_visitors_count, title: "Visitors"
9
+ number :events_count, title: "Events"
10
+ percent :percentage, title: "%"
11
+ end
12
+
13
+ def self.url(item)
14
+ name = Base64.decode64(item.request.params[:id])
15
+ params = item.search_params.dup
16
+ if params["q"] && params["q"].key?("properties_json_cont")
17
+ json = JSON.parse(params["q"]["properties_json_cont"])
18
+ json[name] = item.item.label
19
+ params["q"]["properties_json_cont"] = json.to_json
20
+ else
21
+ params[:q] = { "properties_json_cont" => { name => item.item.label }.to_json }
22
+ end
23
+ params
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,4 @@
1
+ <% @rows.each do |row| %>
2
+ <%= row.render(@item, view_context) %>
3
+
4
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <%= progress_bar(@item.count, @item.total_count, @item.display_name) %>
2
+ <%=
3
+ item do
4
+ tooltip(@item.count)
5
+ end
6
+ %>
@@ -0,0 +1,40 @@
1
+ module Lookout
2
+ module Tables
3
+ module Rows
4
+ class RowComponent < ViewComponent::Base
5
+ def initialize(table:, item:)
6
+ @table = table
7
+ @item = item
8
+ end
9
+
10
+ def progress_bar(value, max, label)
11
+ items = []
12
+ items << view_context.content_tag(:progress, "", class: "progress-primary bg-gray-800 h-8 grow rounded-md opacity-60", value: value, max: max)
13
+ items << view_context.content_tag(:span, class: "grow text-elipsis overflow-hidden absolute left-4 flex items-center h-8 text-primary-content") do
14
+ label
15
+ end
16
+
17
+ items.join.html_safe
18
+ end
19
+
20
+ def item(value = nil, &block)
21
+ view_context.content_tag(:span, class: "w-8 text-right border-l-gray border-base-300") do
22
+ if value
23
+ value
24
+ else
25
+ capture(&block)
26
+ end
27
+ end
28
+ end
29
+
30
+ def percent_total(item)
31
+ '%.1f' % ((item.unit_amount.to_i * 1.0 / total)*100.0)
32
+ end
33
+
34
+ def tooltip(value)
35
+ Lookout::TooltipComponent.new(amount: value).render_in(self)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ <div
2
+ data-controller="tile"
3
+ class="card card-compact <%= 'lg:col-span-2' if wide %> col-span-1 shadow-xl rounded-md lg:mx-0 bg-base-200 <%= @classes %>">
4
+ <% if title.present? || display_links.present? %>
5
+ <div class="flex justify-between items-start">
6
+ <div class="flex items-baseline gap-2">
7
+ <% if title.present? %>
8
+ <h2 class="card-title" data-tile-target="title"><%= title %></h2>
9
+ <% end %>
10
+ <% if details_cta.present? %>
11
+ <span class="text-sm text-gray-400 font-normal">|</span>
12
+ <span class="text-xs text-gray-400 font-normal"><%= details_cta %></span>
13
+ <% end %>
14
+ </div>
15
+ <% if display_links.present? %>
16
+ <div class="flex self-center lg:gap-3">
17
+ <%= display_links %>
18
+ </div>
19
+ <% end %>
20
+ </div>
21
+ <% end %>
22
+
23
+ <%= statistic_display %>
24
+ </div>
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Lookout::TileComponent < ViewComponent::Base
4
+ renders_one :statistic_display
5
+ renders_one :display_links
6
+ renders_one :details_cta
7
+
8
+ def initialize(title: nil, wide: false, classes: "p-8 mx-4")
9
+ @classes = classes
10
+ @title = title
11
+ @wide = wide
12
+ end
13
+
14
+ def link_to(name, url, **options)
15
+ options[:class] = "inline-block h-5 font-semibold"
16
+ options[:data] ||= {}
17
+ options[:data].merge!(controller: "frame-link")
18
+ view_context.link_to name, url, **options
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :title, :wide
24
+ end
@@ -0,0 +1,3 @@
1
+ <div class="tooltip mr-2" data-tip=<%= amount %>>
2
+ <p><%= abbreviate %></p>
3
+ </div>
@@ -0,0 +1,18 @@
1
+ class Lookout::TooltipComponent < ViewComponent::Base
2
+ def initialize(amount:)
3
+ @amount = amount
4
+ end
5
+
6
+ def abbreviate
7
+
8
+ if amount.to_i >= 1000
9
+ number_to_human(amount, format: '%n%u', precision: 2, units: { thousand: 'k', million: 'm', billion: 'b' })
10
+ else
11
+ amount.to_s
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :amount
18
+ end