mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Optimize stats page performance
This commit is contained in:
parent
7c1c42dfc1
commit
88909b3e9f
14 changed files with 56 additions and 12 deletions
1
Gemfile
1
Gemfile
|
|
@ -77,4 +77,5 @@ group :development do
|
||||||
gem 'database_consistency', require: false
|
gem 'database_consistency', require: false
|
||||||
gem 'foreman'
|
gem 'foreman'
|
||||||
gem 'rubocop-rails', require: false
|
gem 'rubocop-rails', require: false
|
||||||
|
gem 'bullet'
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,9 @@ GEM
|
||||||
brakeman (7.0.2)
|
brakeman (7.0.2)
|
||||||
racc
|
racc
|
||||||
builder (3.3.0)
|
builder (3.3.0)
|
||||||
|
bullet (8.0.8)
|
||||||
|
activesupport (>= 3.0.0)
|
||||||
|
uniform_notifier (~> 1.11)
|
||||||
bundler-audit (0.9.2)
|
bundler-audit (0.9.2)
|
||||||
bundler (>= 1.2.0, < 3)
|
bundler (>= 1.2.0, < 3)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
|
|
@ -486,6 +489,7 @@ GEM
|
||||||
unicode-display_width (3.1.4)
|
unicode-display_width (3.1.4)
|
||||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||||
unicode-emoji (4.0.4)
|
unicode-emoji (4.0.4)
|
||||||
|
uniform_notifier (1.17.0)
|
||||||
uri (1.0.3)
|
uri (1.0.3)
|
||||||
useragent (0.16.11)
|
useragent (0.16.11)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
|
|
@ -519,6 +523,7 @@ DEPENDENCIES
|
||||||
aws-sdk-s3 (~> 1.177.0)
|
aws-sdk-s3 (~> 1.177.0)
|
||||||
bootsnap
|
bootsnap
|
||||||
brakeman
|
brakeman
|
||||||
|
bullet
|
||||||
bundler-audit
|
bundler-audit
|
||||||
capybara
|
capybara
|
||||||
chartkick
|
chartkick
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,30 @@ class StatsController < ApplicationController
|
||||||
before_action :authenticate_active_user!, only: %i[update update_all]
|
before_action :authenticate_active_user!, only: %i[update update_all]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@stats = current_user.stats.group_by(&:year).transform_values { |stats| stats.sort_by(&:updated_at).reverse }.sort.reverse
|
@stats = current_user.stats.group_by(&:year).transform_values do |stats|
|
||||||
@points_total = current_user.tracked_points.count
|
stats.sort_by(&:updated_at).reverse
|
||||||
@points_reverse_geocoded = current_user.total_reverse_geocoded_points
|
end.sort.reverse
|
||||||
@points_reverse_geocoded_without_data = current_user.total_reverse_geocoded_points_without_data
|
|
||||||
|
# Single aggregated query to replace 3 separate COUNT queries
|
||||||
|
result = current_user.tracked_points.connection.execute(<<~SQL.squish)
|
||||||
|
SELECT#{' '}
|
||||||
|
COUNT(*) as total,
|
||||||
|
COUNT(reverse_geocoded_at) as geocoded,
|
||||||
|
COUNT(CASE WHEN geodata = '{}' THEN 1 END) as without_data
|
||||||
|
FROM points#{' '}
|
||||||
|
WHERE user_id = #{current_user.id}
|
||||||
|
SQL
|
||||||
|
|
||||||
|
row = result.first
|
||||||
|
@points_total = row['total'].to_i
|
||||||
|
@points_reverse_geocoded = row['geocoded'].to_i
|
||||||
|
@points_reverse_geocoded_without_data = row['without_data'].to_i
|
||||||
|
|
||||||
|
# Precompute year distance data to avoid N+1 queries in view
|
||||||
|
@year_distances = {}
|
||||||
|
@stats.each do |year, _stats|
|
||||||
|
@year_distances[year] = Stat.year_distance(year, current_user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Trips::CalculateAllJob < ApplicationJob
|
class Trips::CalculateAllJob < ApplicationJob
|
||||||
queue_as :default
|
queue_as :trips
|
||||||
|
|
||||||
def perform(trip_id, distance_unit = 'km')
|
def perform(trip_id, distance_unit = 'km')
|
||||||
Trips::CalculatePathJob.perform_later(trip_id)
|
Trips::CalculatePathJob.perform_later(trip_id)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Trips::CalculateCountriesJob < ApplicationJob
|
class Trips::CalculateCountriesJob < ApplicationJob
|
||||||
queue_as :default
|
queue_as :trips
|
||||||
|
|
||||||
def perform(trip_id, distance_unit)
|
def perform(trip_id, distance_unit)
|
||||||
trip = Trip.find(trip_id)
|
trip = Trip.find(trip_id)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Trips::CalculateDistanceJob < ApplicationJob
|
class Trips::CalculateDistanceJob < ApplicationJob
|
||||||
queue_as :default
|
queue_as :trips
|
||||||
|
|
||||||
def perform(trip_id, distance_unit)
|
def perform(trip_id, distance_unit)
|
||||||
trip = Trip.find(trip_id)
|
trip = Trip.find(trip_id)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Trips::CalculatePathJob < ApplicationJob
|
class Trips::CalculatePathJob < ApplicationJob
|
||||||
queue_as :default
|
queue_as :trips
|
||||||
|
|
||||||
def perform(trip_id)
|
def perform(trip_id)
|
||||||
trip = Trip.find(trip_id)
|
trip = Trip.find(trip_id)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ class Country < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.names_to_iso_a2
|
def self.names_to_iso_a2
|
||||||
pluck(:name, :iso_a2).to_h
|
Rails.cache.fetch('countries_names_to_iso_a2', expires_in: 1.day) do
|
||||||
|
pluck(:name, :iso_a2).to_h
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
</h2>
|
</h2>
|
||||||
<div class='my-10'>
|
<div class='my-10'>
|
||||||
<%= column_chart(
|
<%= column_chart(
|
||||||
Stat.year_distance(year, current_user),
|
@year_distances[year],
|
||||||
height: '200px',
|
height: '200px',
|
||||||
suffix: " #{current_user.safe_settings.distance_unit}",
|
suffix: " #{current_user.safe_settings.distance_unit}",
|
||||||
xtitle: 'Days',
|
xtitle: 'Days',
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= column_chart(
|
<%= column_chart(
|
||||||
Stat.year_distance(year, current_user).map { |month_name, distance_meters|
|
@year_distances[year].map { |month_name, distance_meters|
|
||||||
[month_name, Stat.convert_distance(distance_meters, current_user.safe_settings.distance_unit).round(2)]
|
[month_name, Stat.convert_distance(distance_meters, current_user.safe_settings.distance_unit).round(2)]
|
||||||
},
|
},
|
||||||
height: '200px',
|
height: '200px',
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,15 @@
|
||||||
require 'active_support/core_ext/integer/time'
|
require 'active_support/core_ext/integer/time'
|
||||||
|
|
||||||
Rails.application.configure do
|
Rails.application.configure do
|
||||||
|
config.after_initialize do
|
||||||
|
Bullet.enable = true
|
||||||
|
Bullet.alert = true
|
||||||
|
Bullet.bullet_logger = true
|
||||||
|
Bullet.console = true
|
||||||
|
Bullet.rails_logger = true
|
||||||
|
Bullet.add_footer = true
|
||||||
|
end
|
||||||
|
|
||||||
# Settings specified here will take precedence over those in config/application.rb.
|
# Settings specified here will take precedence over those in config/application.rb.
|
||||||
|
|
||||||
# In the development environment your application's code is reloaded any time
|
# In the development environment your application's code is reloaded any time
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ Rails.application.configure do
|
||||||
# config.assets.css_compressor = :sass
|
# config.assets.css_compressor = :sass
|
||||||
|
|
||||||
# Do not fallback to assets pipeline if a precompiled asset is missed.
|
# Do not fallback to assets pipeline if a precompiled asset is missed.
|
||||||
config.assets.compile = true
|
config.assets.compile = false
|
||||||
|
|
||||||
config.assets.content_type = {
|
config.assets.content_type = {
|
||||||
geojson: 'application/geo+json'
|
geojson: 'application/geo+json'
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,12 @@ require 'active_support/core_ext/integer/time'
|
||||||
# and recreated between test runs. Don't rely on the data there!
|
# and recreated between test runs. Don't rely on the data there!
|
||||||
|
|
||||||
Rails.application.configure do
|
Rails.application.configure do
|
||||||
|
config.after_initialize do
|
||||||
|
Bullet.enable = true
|
||||||
|
Bullet.bullet_logger = true
|
||||||
|
Bullet.raise = true # raise an error if n+1 query occurs
|
||||||
|
end
|
||||||
|
|
||||||
# Settings specified here will take precedence over those in config/application.rb.
|
# Settings specified here will take precedence over those in config/application.rb.
|
||||||
|
|
||||||
# While tests run files are not watched, reloading is not necessary.
|
# While tests run files are not watched, reloading is not necessary.
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
- imports
|
- imports
|
||||||
- exports
|
- exports
|
||||||
- stats
|
- stats
|
||||||
|
- trips
|
||||||
- tracks
|
- tracks
|
||||||
- reverse_geocoding
|
- reverse_geocoding
|
||||||
- visit_suggesting
|
- visit_suggesting
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue