Compare commits

...

24 commits

Author SHA1 Message Date
Diogo Correia
9b84d8e738
Merge 35f4c0f1f6 into bd2558ed29 2025-07-22 20:53:52 +02:00
Eugene Burmakin
bd2558ed29 Enable assets compilation in production 2025-07-22 20:35:45 +02:00
Evgenii Burmakin
685f7eebd2
Merge pull request #1551 from Freika/chore/disable-tracks-generation
Temporary disable track creation
2025-07-22 20:29:03 +02:00
Eugene Burmakin
0bfddd932f Disable specs for track generation 2025-07-22 20:28:46 +02:00
Eugene Burmakin
27857ba078 Disable tracks panel on the map 2025-07-22 20:26:58 +02:00
Eugene Burmakin
7c8a7e7f38 Temporary disable track creation 2025-07-22 20:25:44 +02:00
Evgenii Burmakin
962983aa82
Merge pull request #1498 from Freika/dependabot/bundler/chartkick-5.2.0
Bump chartkick from 5.1.5 to 5.2.0
2025-07-22 20:24:54 +02:00
Evgenii Burmakin
c22b260e28
Merge pull request #1497 from Freika/dependabot/bundler/debug-1.11.0
Bump debug from 1.10.0 to 1.11.0
2025-07-22 20:24:27 +02:00
Evgenii Burmakin
1158444c0a
Merge pull request #1496 from Freika/dependabot/bundler/super_diff-0.16.0
Bump super_diff from 0.15.0 to 0.16.0
2025-07-22 20:23:37 +02:00
Evgenii Burmakin
bf9b0d037a
Merge pull request #1550 from Freika/fix/stats-page-performance
Fix/stats page performance
2025-07-22 20:17:13 +02:00
Eugene Burmakin
c14054fdc3 Disable track generation failure notification for self-hosted users 2025-07-22 20:15:52 +02:00
Eugene Burmakin
cbdef5fa43 Parameterize stats query 2025-07-22 19:56:12 +02:00
Eugene Burmakin
6e5dd4bed6 Update stats query 2025-07-22 19:52:24 +02:00
Eugene Burmakin
58ffca74f6 Remove bullet 2025-07-22 19:44:50 +02:00
Evgenii Burmakin
18aed4a10c
Merge pull request #1542 from Freika/dependabot/bundler/bundler-f02c9c4a61
Bump the bundler group with 2 updates
2025-07-22 19:44:27 +02:00
Eugene Burmakin
da38c12819 Extract stats query 2025-07-22 19:43:27 +02:00
Eugene Burmakin
88909b3e9f Optimize stats page performance 2025-07-22 19:17:28 +02:00
dependabot[bot]
97d6037448
Bump the bundler group with 2 updates
Bumps the bundler group with 2 updates: [nokogiri](https://github.com/sparklemotion/nokogiri) and [thor](https://github.com/rails/thor).


Updates `nokogiri` from 1.18.8 to 1.18.9
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.18.8...v1.18.9)

Updates `thor` from 1.3.2 to 1.4.0
- [Release notes](https://github.com/rails/thor/releases)
- [Commits](https://github.com/rails/thor/compare/v1.3.2...v1.4.0)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-version: 1.18.9
  dependency-type: indirect
  dependency-group: bundler
- dependency-name: thor
  dependency-version: 1.4.0
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-22 05:39:51 +00:00
Evgenii Burmakin
3f22162cf0
Merge pull request #1527 from Freika/dev
0.29.2
2025-07-21 20:36:11 +02:00
Eugene Burmakin
f5c399a8cc Fix domain in development and production 2025-07-20 17:11:11 +02:00
Diogo Correia
35f4c0f1f6
fix: use db parameter when constructing redis client
Fixes #1507
2025-07-09 19:39:08 +01:00
dependabot[bot]
4e35cdd305
Bump chartkick from 5.1.5 to 5.2.0
Bumps [chartkick](https://github.com/ankane/chartkick) from 5.1.5 to 5.2.0.
- [Changelog](https://github.com/ankane/chartkick/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ankane/chartkick/compare/v5.1.5...v5.2.0)

---
updated-dependencies:
- dependency-name: chartkick
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-07 16:47:02 +00:00
dependabot[bot]
d0aaa3c674
Bump debug from 1.10.0 to 1.11.0
Bumps [debug](https://github.com/ruby/debug) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/ruby/debug/releases)
- [Commits](https://github.com/ruby/debug/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: debug
  dependency-version: 1.11.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-07 16:31:00 +00:00
dependabot[bot]
90efb5b0bb
Bump super_diff from 0.15.0 to 0.16.0
Bumps [super_diff](https://github.com/splitwise/super_diff) from 0.15.0 to 0.16.0.
- [Release notes](https://github.com/splitwise/super_diff/releases)
- [Changelog](https://github.com/splitwise/super_diff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/splitwise/super_diff/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: super_diff
  dependency-version: 0.16.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-07 16:30:16 +00:00
24 changed files with 291 additions and 45 deletions

View file

@ -4,13 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
# [0.30.1] - 2025-07-21
# [0.30.1] - 2025-07-22
## Fixed
- Points limit exceeded check is now cached.
- Reverse geocoding for places is now significantly faster.
## Changed
- Stats page should load faster now.
- Track creation is temporarily disabled.
# [0.30.0] - 2025-07-21

View file

@ -126,7 +126,7 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
chartkick (5.1.5)
chartkick (5.2.0)
coderay (1.1.3)
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
@ -144,7 +144,7 @@ GEM
database_consistency (2.0.4)
activerecord (>= 3.2)
date (3.4.1)
debug (1.10.0)
debug (1.11.0)
irb (~> 1.10)
reline (>= 0.3.8)
devise (4.9.4)
@ -160,7 +160,7 @@ GEM
dotenv (= 3.1.8)
railties (>= 6.1)
drb (2.2.3)
erb (5.0.1)
erb (5.0.2)
erubi (1.13.1)
et-orbi (1.2.11)
tzinfo
@ -194,7 +194,7 @@ GEM
actionpack (>= 6.0.0)
activesupport (>= 6.0.0)
railties (>= 6.0.0)
io-console (0.8.0)
io-console (0.8.1)
irb (1.15.2)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
@ -243,7 +243,7 @@ GEM
multi_json (1.15.0)
multi_xml (0.7.1)
bigdecimal (~> 3.1)
net-imap (0.5.8)
net-imap (0.5.9)
date
net-protocol
net-pop (0.1.2)
@ -253,23 +253,23 @@ GEM
net-smtp (0.5.1)
net-protocol
nio4r (2.7.4)
nokogiri (1.18.8)
nokogiri (1.18.9)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.18.8-aarch64-linux-gnu)
nokogiri (1.18.9-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.8-arm-linux-gnu)
nokogiri (1.18.9-arm-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.8-arm64-darwin)
nokogiri (1.18.9-arm64-darwin)
racc (~> 1.4)
nokogiri (1.18.8-x86_64-darwin)
nokogiri (1.18.9-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.18.8-x86_64-linux-gnu)
nokogiri (1.18.9-x86_64-linux-gnu)
racc (~> 1.4)
oj (3.16.11)
bigdecimal (>= 3.0)
ostruct (>= 0.2)
optimist (3.2.0)
optimist (3.2.1)
orm_adapter (0.5.0)
ostruct (0.6.1)
parallel (1.27.0)
@ -342,7 +342,7 @@ GEM
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.3.0)
rdoc (6.14.1)
rdoc (6.14.2)
erb
psych (>= 4.0.0)
redis (5.4.0)
@ -350,7 +350,7 @@ GEM
redis-client (0.24.0)
connection_pool
regexp_parser (2.10.0)
reline (0.6.1)
reline (0.6.2)
io-console (~> 0.5)
request_store (1.7.0)
rack (>= 1.4)
@ -462,7 +462,7 @@ GEM
stringio (3.1.7)
strong_migrations (2.3.0)
activerecord (>= 7)
super_diff (0.15.0)
super_diff (0.16.0)
attr_extras (>= 6.2.4)
diff-lcs
patience_diff
@ -475,7 +475,7 @@ GEM
tailwindcss-ruby (3.4.17-arm64-darwin)
tailwindcss-ruby (3.4.17-x86_64-darwin)
tailwindcss-ruby (3.4.17-x86_64-linux)
thor (1.3.2)
thor (1.4.0)
timeout (0.4.3)
turbo-rails (2.0.16)
actionpack (>= 7.1.0)
@ -496,7 +496,7 @@ GEM
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.9.1)
websocket (1.2.11)
websocket-driver (0.7.7)
websocket-driver (0.8.0)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)

View file

@ -5,10 +5,9 @@ class StatsController < ApplicationController
before_action :authenticate_active_user!, only: %i[update update_all]
def index
@stats = current_user.stats.group_by(&:year).transform_values { |stats| stats.sort_by(&:updated_at).reverse }.sort.reverse
@points_total = current_user.tracked_points.count
@points_reverse_geocoded = current_user.total_reverse_geocoded_points
@points_reverse_geocoded_without_data = current_user.total_reverse_geocoded_points_without_data
@stats = build_stats
assign_points_statistics
@year_distances = precompute_year_distances
end
def show
@ -43,4 +42,30 @@ class StatsController < ApplicationController
redirect_to stats_path, notice: 'Stats are being updated', status: :see_other
end
private
def assign_points_statistics
points_stats = ::StatsQuery.new(current_user).points_stats
@points_total = points_stats[:total]
@points_reverse_geocoded = points_stats[:geocoded]
@points_reverse_geocoded_without_data = points_stats[:without_data]
end
def precompute_year_distances
year_distances = {}
@stats.each do |year, _stats|
year_distances[year] = Stat.year_distance(year, current_user)
end
year_distances
end
def build_stats
current_user.stats.group_by(&:year).transform_values do |stats|
stats.sort_by(&:updated_at).reverse
end.sort.reverse
end
end

View file

@ -219,7 +219,7 @@ export default class extends BaseController {
this.setupTracksSubscription();
// Handle routes/tracks mode selection
this.addRoutesTracksSelector();
// this.addRoutesTracksSelector(); # Temporarily disabled
this.switchRouteMode('routes', true);
// Initialize layers based on settings

View file

@ -27,6 +27,8 @@ class Tracks::CreateJob < ApplicationJob
end
def create_error_notification(user, error)
return unless DawarichSettings.self_hosted?
Notifications::Create.new(
user: user,
kind: :error,

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Trips::CalculateAllJob < ApplicationJob
queue_as :default
queue_as :trips
def perform(trip_id, distance_unit = 'km')
Trips::CalculatePathJob.perform_later(trip_id)

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Trips::CalculateCountriesJob < ApplicationJob
queue_as :default
queue_as :trips
def perform(trip_id, distance_unit)
trip = Trip.find(trip_id)

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Trips::CalculateDistanceJob < ApplicationJob
queue_as :default
queue_as :trips
def perform(trip_id, distance_unit)
trip = Trip.find(trip_id)

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Trips::CalculatePathJob < ApplicationJob
queue_as :default
queue_as :trips
def perform(trip_id)
trip = Trip.find(trip_id)

View file

@ -12,6 +12,8 @@ class Country < ApplicationRecord
end
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

View file

@ -33,8 +33,8 @@ class Point < ApplicationRecord
after_create :async_reverse_geocode, if: -> { DawarichSettings.store_geodata? && !reverse_geocoded? }
after_create :set_country
after_create_commit :broadcast_coordinates
after_create_commit :trigger_incremental_track_generation, if: -> { import_id.nil? }
after_commit :recalculate_track, on: :update, if: -> { track.present? }
# after_create_commit :trigger_incremental_track_generation, if: -> { import_id.nil? }
# after_commit :recalculate_track, on: :update, if: -> { track.present? }
def self.without_raw_data
select(column_names - ['raw_data'])

View file

@ -0,0 +1,33 @@
# frozen_string_literal: true
class StatsQuery
def initialize(user)
@user = user
end
def points_stats
sql = ActiveRecord::Base.sanitize_sql_array([
<<~SQL.squish,
SELECT
COUNT(id) as total,
COUNT(reverse_geocoded_at) as geocoded,
COUNT(CASE WHEN geodata = '{}'::jsonb THEN 1 END) as without_data
FROM points
WHERE user_id = ?
SQL
user.id
])
result = Point.connection.select_one(sql)
{
total: result['total'].to_i,
geocoded: result['geocoded'].to_i,
without_data: result['without_data'].to_i
}
end
private
attr_reader :user
end

View file

@ -4,7 +4,7 @@
</h2>
<div class='my-10'>
<%= column_chart(
Stat.year_distance(year, current_user),
@year_distances[year],
height: '200px',
suffix: " #{current_user.safe_settings.distance_unit}",
xtitle: 'Days',

View file

@ -82,7 +82,7 @@
</div>
<% end %>
<%= 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)]
},
height: '200px',

View file

@ -1,11 +1,13 @@
development:
adapter: redis
url: <%= "#{ENV.fetch("REDIS_URL")}/#{ENV.fetch('RAILS_WS_DB', 2)}" %>
url: <%= "#{ENV.fetch("REDIS_URL")}" %>
db: <%= "#{ENV.fetch('RAILS_WS_DB', 2)}" %>
test:
adapter: test
production:
adapter: redis
url: <%= "#{ENV.fetch("REDIS_URL")}/#{ENV.fetch('RAILS_WS_DB', 2)}" %>
url: <%= "#{ENV.fetch("REDIS_URL")}" %>
db: <%= "#{ENV.fetch('RAILS_WS_DB', 2)}" %>
channel_prefix: dawarich_production

View file

@ -26,7 +26,7 @@ Rails.application.configure do
# Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching.
config.cache_store = :redis_cache_store, { url: "#{ENV['REDIS_URL']}/#{ENV.fetch('RAILS_CACHE_DB', 0)}" }
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'], db: ENV.fetch('RAILS_CACHE_DB', 0) }
if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true

View file

@ -73,7 +73,7 @@ Rails.application.configure do
config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'info')
# Use a different cache store in production.
config.cache_store = :redis_cache_store, { url: "#{ENV['REDIS_URL']}/#{ENV.fetch('RAILS_CACHE_DB', 0)}" }
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'], db: ENV.fetch('RAILS_CACHE_DB', 0) }
# Use a real queuing backend for Active Job (and separate queues per environment).
config.active_job.queue_adapter = :sidekiq

View file

@ -4,7 +4,7 @@ settings = {
debug_mode: true,
timeout: 5,
units: :km,
cache: Redis.new(url: "#{ENV['REDIS_URL']}/#{ENV.fetch('RAILS_CACHE_DB', 0)}"),
cache: Redis.new(url: ENV['REDIS_URL'], db: ENV.fetch('RAILS_CACHE_DB', 0)),
always_raise: :all,
http_headers: {
'User-Agent' => "Dawarich #{APP_VERSION} (https://dawarich.app)"

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
Sidekiq.configure_server do |config|
config.redis = { url: "#{ENV['REDIS_URL']}/#{ENV.fetch('RAILS_JOB_QUEUE_DB', 1)}" }
config.redis = { url: ENV['REDIS_URL'], db: ENV.fetch('RAILS_JOB_QUEUE_DB', 1) }
config.logger = Sidekiq::Logger.new($stdout)
if ENV['PROMETHEUS_EXPORTER_ENABLED'].to_s == 'true'
@ -24,7 +24,7 @@ Sidekiq.configure_server do |config|
end
Sidekiq.configure_client do |config|
config.redis = { url: "#{ENV['REDIS_URL']}/#{ENV.fetch('RAILS_JOB_QUEUE_DB', 1)}" }
config.redis = { url: ENV['REDIS_URL'], db: ENV.fetch('RAILS_JOB_QUEUE_DB', 1) }
end
Sidekiq::Queue['reverse_geocoding'].limit = 1 if Sidekiq.server? && DawarichSettings.photon_uses_komoot_io?

View file

@ -30,10 +30,10 @@ cache_preheating_job:
class: "Cache::PreheatingJob"
queue: default
tracks_cleanup_job:
cron: "0 2 * * 0" # every Sunday at 02:00
class: "Tracks::CleanupJob"
queue: tracks
# tracks_cleanup_job:
# cron: "0 2 * * 0" # every Sunday at 02:00
# class: "Tracks::CleanupJob"
# queue: tracks
place_name_fetching_job:
cron: "30 0 * * *" # every day at 00:30

View file

@ -6,6 +6,7 @@
- imports
- exports
- stats
- trips
- tracks
- reverse_geocoding
- visit_suggesting

View file

@ -151,4 +151,50 @@ RSpec.describe Tracks::CreateJob, type: :job do
expect(described_class.new.queue_name).to eq('tracks')
end
end
context 'when self-hosted' do
let(:generator_instance) { instance_double(Tracks::Generator) }
let(:notification_service) { instance_double(Notifications::Create) }
let(:error_message) { 'Something went wrong' }
before do
allow(DawarichSettings).to receive(:self_hosted?).and_return(true)
allow(Tracks::Generator).to receive(:new).and_return(generator_instance)
allow(generator_instance).to receive(:call).and_raise(StandardError, error_message)
allow(Notifications::Create).to receive(:new).and_return(notification_service)
allow(notification_service).to receive(:call)
end
it 'creates a failure notification when self-hosted' do
described_class.new.perform(user.id)
expect(Notifications::Create).to have_received(:new).with(
user: user,
kind: :error,
title: 'Track Generation Failed',
content: "Failed to generate tracks from your location data: #{error_message}"
)
expect(notification_service).to have_received(:call)
end
end
context 'when not self-hosted' do
let(:generator_instance) { instance_double(Tracks::Generator) }
let(:notification_service) { instance_double(Notifications::Create) }
let(:error_message) { 'Something went wrong' }
before do
allow(DawarichSettings).to receive(:self_hosted?).and_return(false)
allow(Tracks::Generator).to receive(:new).and_return(generator_instance)
allow(generator_instance).to receive(:call).and_raise(StandardError, error_message)
allow(Notifications::Create).to receive(:new).and_return(notification_service)
allow(notification_service).to receive(:call)
end
it 'does not create a failure notification' do
described_class.new.perform(user.id)
expect(notification_service).not_to have_received(:call)
end
end
end

View file

@ -30,7 +30,7 @@ RSpec.describe Point, type: :model do
end
end
describe '#recalculate_track' do
xdescribe '#recalculate_track' do
let(:point) { create(:point, track: track) }
let(:track) { create(:track) }
@ -121,7 +121,7 @@ RSpec.describe Point, type: :model do
end
end
describe '#trigger_incremental_track_generation' do
xdescribe '#trigger_incremental_track_generation' do
let(:point) do
create(:point, track: track, import_id: nil, timestamp: 1.hour.ago.to_i, reverse_geocoded_at: 1.hour.ago)
end

View file

@ -0,0 +1,130 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe StatsQuery do
describe '#points_stats' do
subject(:points_stats) { described_class.new(user).points_stats }
let(:user) { create(:user) }
let!(:import) { create(:import, user: user) }
context 'when user has no points' do
it 'returns zero counts for all statistics' do
expect(points_stats).to eq({
total: 0,
geocoded: 0,
without_data: 0
})
end
end
context 'when user has points' do
let!(:geocoded_point_with_data) do
create(:point,
user: user,
import: import,
reverse_geocoded_at: Time.current,
geodata: { 'address' => '123 Main St' })
end
let!(:geocoded_point_without_data) do
create(:point,
user: user,
import: import,
reverse_geocoded_at: Time.current,
geodata: {})
end
let!(:non_geocoded_point) do
create(:point,
user: user,
import: import,
reverse_geocoded_at: nil,
geodata: { 'some' => 'data' })
end
it 'returns correct counts for all statistics' do
expect(points_stats).to eq({
total: 3,
geocoded: 2,
without_data: 1
})
end
context 'when another user has points' do
let(:other_user) { create(:user) }
let!(:other_import) { create(:import, user: other_user) }
let!(:other_point) do
create(:point,
user: other_user,
import: other_import,
reverse_geocoded_at: Time.current,
geodata: { 'address' => 'Other Address' })
end
it 'only counts points for the specified user' do
expect(points_stats).to eq({
total: 3,
geocoded: 2,
without_data: 1
})
end
end
end
context 'when all points are geocoded with data' do
before do
create_list(:point, 5,
user: user,
import: import,
reverse_geocoded_at: Time.current,
geodata: { 'address' => 'Some Address' })
end
it 'returns correct statistics' do
expect(points_stats).to eq({
total: 5,
geocoded: 5,
without_data: 0
})
end
end
context 'when all points are without geodata' do
before do
create_list(:point, 3,
user: user,
import: import,
reverse_geocoded_at: Time.current,
geodata: {})
end
it 'returns correct statistics' do
expect(points_stats).to eq({
total: 3,
geocoded: 3,
without_data: 3
})
end
end
context 'when all points are not geocoded' do
before do
create_list(:point, 4,
user: user,
import: import,
reverse_geocoded_at: nil,
geodata: { 'some' => 'data' })
end
it 'returns correct statistics' do
expect(points_stats).to eq({
total: 4,
geocoded: 0,
without_data: 0
})
end
end
end
end