From 088d8b14c2f8b7a8e0821a97ace9b0d6b6755ca1 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Thu, 15 May 2025 21:33:01 +0200 Subject: [PATCH] Calculate trip data in the background --- .rspec | 1 + app/controllers/trips_controller.rb | 6 +++- app/jobs/trips/calculate_all_job.rb | 11 +++++++ app/jobs/trips/calculate_countries_job.rb | 25 +++++++++++++++ app/jobs/trips/calculate_distance_job.rb | 25 +++++++++++++++ app/jobs/trips/calculate_path_job.rb | 25 +++++++++++++++ app/jobs/trips/create_path_job.rb | 13 -------- app/models/trip.rb | 38 +++++++++++++---------- app/views/trips/_countries.html.erb | 14 +++++++++ app/views/trips/_distance.html.erb | 6 ++++ app/views/trips/_path.html.erb | 24 ++++++++++++++ app/views/trips/show.html.erb | 35 +++++++++------------ docker/docker-compose.yml | 4 +-- 13 files changed, 175 insertions(+), 52 deletions(-) create mode 100644 app/jobs/trips/calculate_all_job.rb create mode 100644 app/jobs/trips/calculate_countries_job.rb create mode 100644 app/jobs/trips/calculate_distance_job.rb create mode 100644 app/jobs/trips/calculate_path_job.rb delete mode 100644 app/jobs/trips/create_path_job.rb create mode 100644 app/views/trips/_countries.html.erb create mode 100644 app/views/trips/_distance.html.erb create mode 100644 app/views/trips/_path.html.erb diff --git a/.rspec b/.rspec index c99d2e73..d855298e 100644 --- a/.rspec +++ b/.rspec @@ -1 +1,2 @@ --require spec_helper +--profile diff --git a/app/controllers/trips_controller.rb b/app/controllers/trips_controller.rb index f9e57e1d..5ff6adfe 100644 --- a/app/controllers/trips_controller.rb +++ b/app/controllers/trips_controller.rb @@ -15,6 +15,9 @@ class TripsController < ApplicationController @trip.photo_previews end @photo_sources = @trip.photo_sources + + # Trigger calculation jobs if data is missing + Trips::CalculateAllJob.perform_later(@trip.id) unless @trip.path.present? && @trip.distance.present? && @trip.visited_countries.present? end def new @@ -28,7 +31,7 @@ class TripsController < ApplicationController @trip = current_user.trips.build(trip_params) if @trip.save - redirect_to @trip, notice: 'Trip was successfully created.' + redirect_to @trip, notice: 'Trip was successfully created. Data is being calculated in the background.' else render :new, status: :unprocessable_entity end @@ -36,6 +39,7 @@ class TripsController < ApplicationController def update if @trip.update(trip_params) + # Only recalculate if date range changed (handled by model callback) redirect_to @trip, notice: 'Trip was successfully updated.', status: :see_other else render :edit, status: :unprocessable_entity diff --git a/app/jobs/trips/calculate_all_job.rb b/app/jobs/trips/calculate_all_job.rb new file mode 100644 index 00000000..1564c97d --- /dev/null +++ b/app/jobs/trips/calculate_all_job.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class Trips::CalculateAllJob < ApplicationJob + queue_as :default + + def perform(trip_id) + Trips::CalculatePathJob.perform_later(trip_id) + Trips::CalculateDistanceJob.perform_later(trip_id) + Trips::CalculateCountriesJob.perform_later(trip_id) + end +end diff --git a/app/jobs/trips/calculate_countries_job.rb b/app/jobs/trips/calculate_countries_job.rb new file mode 100644 index 00000000..d6042e4b --- /dev/null +++ b/app/jobs/trips/calculate_countries_job.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Trips::CalculateCountriesJob < ApplicationJob + queue_as :default + + def perform(trip_id) + trip = Trip.find(trip_id) + + trip.calculate_countries + trip.save! + + broadcast_update(trip) + end + + private + + def broadcast_update(trip) + Turbo::StreamsChannel.broadcast_update_to( + "trip_#{trip.id}", + target: "trip_countries", + partial: "trips/countries", + locals: { trip: trip } + ) + end +end diff --git a/app/jobs/trips/calculate_distance_job.rb b/app/jobs/trips/calculate_distance_job.rb new file mode 100644 index 00000000..b2e7b0d9 --- /dev/null +++ b/app/jobs/trips/calculate_distance_job.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Trips::CalculateDistanceJob < ApplicationJob + queue_as :default + + def perform(trip_id) + trip = Trip.find(trip_id) + + trip.calculate_distance + trip.save! + + broadcast_update(trip) + end + + private + + def broadcast_update(trip) + Turbo::StreamsChannel.broadcast_update_to( + "trip_#{trip.id}", + target: "trip_distance", + partial: "trips/distance", + locals: { trip: trip } + ) + end +end diff --git a/app/jobs/trips/calculate_path_job.rb b/app/jobs/trips/calculate_path_job.rb new file mode 100644 index 00000000..711cfef8 --- /dev/null +++ b/app/jobs/trips/calculate_path_job.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Trips::CalculatePathJob < ApplicationJob + queue_as :default + + def perform(trip_id) + trip = Trip.find(trip_id) + + trip.calculate_path + trip.save! + + broadcast_update(trip) + end + + private + + def broadcast_update(trip) + Turbo::StreamsChannel.broadcast_update_to( + "trip_#{trip.id}", + target: "trip_path", + partial: "trips/path", + locals: { trip: trip } + ) + end +end diff --git a/app/jobs/trips/create_path_job.rb b/app/jobs/trips/create_path_job.rb deleted file mode 100644 index 5c73dc90..00000000 --- a/app/jobs/trips/create_path_job.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -class Trips::CreatePathJob < ApplicationJob - queue_as :default - - def perform(trip_id) - trip = Trip.find(trip_id) - - trip.calculate_trip_data - - trip.save! - end -end diff --git a/app/models/trip.rb b/app/models/trip.rb index caab72ab..a42b7039 100644 --- a/app/models/trip.rb +++ b/app/models/trip.rb @@ -7,7 +7,8 @@ class Trip < ApplicationRecord validates :name, :started_at, :ended_at, presence: true - before_save :calculate_trip_data + after_create :enqueue_calculation_jobs + after_update :enqueue_calculation_jobs, if: -> { saved_change_to_started_at? || saved_change_to_ended_at? } def calculate_trip_data calculate_path @@ -15,6 +16,10 @@ class Trip < ApplicationRecord calculate_countries end + def enqueue_calculation_jobs + Trips::CalculateAllJob.perform_later(id) + end + def points user.tracked_points.where(timestamp: started_at.to_i..ended_at.to_i).order(:timestamp) end @@ -33,21 +38,7 @@ class Trip < ApplicationRecord @photo_sources ||= photos.map { _1[:source] }.uniq end - private - - def photos - @photos ||= Trips::Photos.new(self, user).call - end - - def select_dominant_orientation(photos) - vertical_photos = photos.select { |photo| photo[:orientation] == 'portrait' } - horizontal_photos = photos.select { |photo| photo[:orientation] == 'landscape' } - - # this is ridiculous, but I couldn't find my way around frontend - # to show all photos in the same height - vertical_photos.count > horizontal_photos.count ? vertical_photos : horizontal_photos - end - + # These methods are now public since they're called from jobs def calculate_path trip_path = Tracks::BuildPath.new(points.pluck(:lonlat)).call @@ -65,4 +56,19 @@ class Trip < ApplicationRecord self.visited_countries = countries end + + private + + def photos + @photos ||= Trips::Photos.new(self, user).call + end + + def select_dominant_orientation(photos) + vertical_photos = photos.select { |photo| photo[:orientation] == 'portrait' } + horizontal_photos = photos.select { |photo| photo[:orientation] == 'landscape' } + + # this is ridiculous, but I couldn't find my way around frontend + # to show all photos in the same height + vertical_photos.count > horizontal_photos.count ? vertical_photos : horizontal_photos + end end diff --git a/app/views/trips/_countries.html.erb b/app/views/trips/_countries.html.erb new file mode 100644 index 00000000..f80415ac --- /dev/null +++ b/app/views/trips/_countries.html.erb @@ -0,0 +1,14 @@ +<% if trip.countries.any? %> +

+ <%= "#{trip.countries.join(', ')} (#{trip.distance} #{DISTANCE_UNIT})" %> +

+<% elsif trip.visited_countries.present? %> +

+ <%= "#{trip.visited_countries.join(', ')} (#{trip.distance} #{DISTANCE_UNIT})" %> +

+<% else %> +

+ Countries are being calculated... + +

+<% end %> diff --git a/app/views/trips/_distance.html.erb b/app/views/trips/_distance.html.erb new file mode 100644 index 00000000..51d61714 --- /dev/null +++ b/app/views/trips/_distance.html.erb @@ -0,0 +1,6 @@ +<% if trip.distance.present? %> + <%= trip.distance %> <%= DISTANCE_UNIT %> +<% else %> + Calculating... + +<% end %> diff --git a/app/views/trips/_path.html.erb b/app/views/trips/_path.html.erb new file mode 100644 index 00000000..2382b0c2 --- /dev/null +++ b/app/views/trips/_path.html.erb @@ -0,0 +1,24 @@ +<% if trip.path.present? %> +
+
+
+
+<% else %> +
+
+

Trip path is being calculated...

+
+
+
+<% end %> diff --git a/app/views/trips/show.html.erb b/app/views/trips/show.html.erb index f4709aa5..85ee31ce 100644 --- a/app/views/trips/show.html.erb +++ b/app/views/trips/show.html.erb @@ -1,36 +1,31 @@ <% content_for :title, @trip.name %> +<%= turbo_stream_from "trip_#{@trip.id}" %> +

<%= @trip.name %>

<%= human_date(@trip.started_at) %> - <%= human_date(@trip.ended_at) %>

- <% if @trip.countries.any? %> -

- <%= "#{@trip.countries.join(', ')} (#{@trip.distance} #{DISTANCE_UNIT})" %> -

+ <% if @trip.countries.any? || @trip.visited_countries.present? %> +
+ <%= render "trips/countries", trip: @trip %> +
+ <% else %> +
+

+ Countries are being calculated... + +

+
<% end %>
-
-
-
-
-
+
+ <%= render "trips/path", trip: @trip, current_user: current_user %>
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 97070e16..86dacc53 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -70,7 +70,7 @@ services: PROMETHEUS_EXPORTER_HOST: 0.0.0.0 PROMETHEUS_EXPORTER_PORT: 9394 SELF_HOSTED: "true" - STORE_GEODATA: "false" + STORE_GEODATA: "true" logging: driver: "json-file" options: @@ -123,7 +123,7 @@ services: PROMETHEUS_EXPORTER_HOST: dawarich_app PROMETHEUS_EXPORTER_PORT: 9394 SELF_HOSTED: "true" - STORE_GEODATA: "false" + STORE_GEODATA: "true" logging: driver: "json-file" options: