Merge pull request #235 from Freika/fix/immich-import-last-year

Fix immich import last year
This commit is contained in:
Evgenii Burmakin 2024-09-08 18:45:20 +03:00 committed by GitHub
commit c42f12ab4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 108 additions and 68 deletions

View file

@ -1 +1 @@
0.13.4
0.13.5

View file

@ -5,4 +5,4 @@ DATABASE_NAME=dawarich_development
DATABASE_PORT=5432
REDIS_URL=redis://localhost:6379/1
PHOTON_API_HOST='photon.komoot.io'
DISTANCE_UNIT='mi'
DISTANCE_UNIT='km'

View file

@ -7,9 +7,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.13.5] — 2024-09-08
### Added
- Links to view import points on the map and on the Points page on the Imports page.
### Fixed
- The Imports page now loading faster.
### Changed
- Default value for `RAILS_MAX_THREADS` was changed to 10.
- Visit suggestions background job was moved to its own low priority queue to prevent it from blocking other jobs.
## [0.13.4] — 2024-09-06

File diff suppressed because one or more lines are too long

View file

@ -5,7 +5,12 @@ class ImportsController < ApplicationController
before_action :set_import, only: %i[show destroy]
def index
@imports = current_user.imports.order(created_at: :desc).page(params[:page])
@imports =
current_user
.imports
.select(:id, :name, :source, :created_at, :points_count)
.order(created_at: :desc)
.page(params[:page])
end
def show; end

View file

@ -4,8 +4,10 @@ class MapController < ApplicationController
before_action :authenticate_user!
def index
@points = current_user.tracked_points.without_raw_data.where('timestamp >= ? AND timestamp <= ?', start_at,
end_at).order(timestamp: :asc)
@points = points
.without_raw_data
.where('timestamp >= ? AND timestamp <= ?', start_at, end_at)
.order(timestamp: :asc)
@countries_and_cities = CountriesAndCities.new(@points).call
@coordinates =
@ -42,4 +44,16 @@ class MapController < ApplicationController
@distance.round(1)
end
def points
params[:import_id] ? points_from_import : points_from_user
end
def points_from_import
current_user.imports.find(params[:import_id]).points
end
def points_from_user
current_user.tracked_points
end
end

View file

@ -6,9 +6,7 @@ class PointsController < ApplicationController
def index
order_by = params[:order_by] || 'desc'
@points =
current_user
.tracked_points
@points = points
.without_raw_data
.where(timestamp: start_at..end_at)
.order(timestamp: order_by)
@ -19,6 +17,7 @@ class PointsController < ApplicationController
@end_at = Time.zone.at(end_at)
@points_number = @points.except(:limit, :offset).size
@imports = current_user.imports.order(created_at: :desc)
end
def bulk_destroy
@ -44,4 +43,16 @@ class PointsController < ApplicationController
Time.zone.parse(params[:end_at]).to_i
end
def points
params[:import_id] ? points_from_import : points_from_user
end
def points_from_import
current_user.imports.find(params[:import_id]).points
end
def points_from_user
current_user.tracked_points
end
end

View file

@ -106,4 +106,10 @@ module ApplicationHelper
def active_tab?(link_path)
'tab-active' if current_page?(link_path)
end
def notification_link_color(notification)
return 'text-gray-600' if notification.read?
'text-blue-600'
end
end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class VisitSuggestingJob < ApplicationJob
queue_as :default
queue_as :visit_suggesting
def perform(user_ids: [], start_at: 1.day.ago, end_at: Time.current)
users = user_ids.any? ? User.where(id: user_ids) : User.all

View file

@ -8,4 +8,8 @@ class Notification < ApplicationRecord
enum :kind, { info: 0, warning: 1, error: 2 }
scope :unread, -> { where(read_at: nil) }
def read?
read_at.present?
end
end

View file

@ -31,7 +31,7 @@ class Areas::Visits::Create
def area_points(area)
area_radius =
if DISTANCE_UNIT.to_sym == :km
if ::DISTANCE_UNIT.to_sym == :km
area.radius / 1000.0
else
area.radius / 1609.344

View file

@ -35,9 +35,9 @@ class Immich::ImportGeodata
end
def retrieve_immich_data
(1..12).flat_map do |month_number|
(1..31).map do |day|
url = "#{immich_api_base_url}/assets/memory-lane?day=#{day}&month=#{month_number}"
1970.upto(Date.today.year).flat_map do |year|
(1..12).map do |month_number|
url = "#{immich_api_base_url}/timeline/bucket?size=MONTH&timeBucket=#{year}-#{month_number}-01"
JSON.parse(HTTParty.get(url, headers:).body)
end
@ -51,25 +51,18 @@ class Immich::ImportGeodata
end
def parse_immich_data(immich_data)
geodata = []
geodata = immich_data.map do |asset|
log_no_data and next if asset_invalid?(asset)
next unless valid?(asset)
immich_data.each do |memory_lane|
log_no_data and next if memory_lane_invalid?(memory_lane)
assets = extract_assets(memory_lane)
assets.each { |asset| geodata << extract_geodata(asset) if valid?(asset) }
extract_geodata(asset)
end
geodata.sort_by { |data| data[:timestamp] }
geodata.compact.sort_by { |data| data[:timestamp] }
end
def memory_lane_invalid?(memory_lane)
memory_lane.is_a?(Hash) && memory_lane['statusCode'] == 404
end
def extract_assets(memory_lane)
memory_lane.flat_map { |lane| lane['assets'] }.compact
def asset_invalid?(bucket)
bucket.is_a?(Hash) && bucket['statusCode'] == 404
end
def extract_geodata(asset)

View file

@ -44,6 +44,10 @@
<tr>
<td>
<%= link_to import.name, import, class: 'underline hover:no-underline' %> (<%= import.source %>)
&nbsp
<%= link_to '🗺️', map_path(import_id: import.id) %>
&nbsp
<%= link_to '📋', points_path(import_id: import.id) %>
</td>
<td>
<%= "#{number_with_delimiter import.points_count}" %>

View file

@ -2,7 +2,7 @@
<div class='w-4/5 mt-8'>
<div class="flex flex-col space-y-4 mb-4 w-full">
<%= form_with url: map_path, method: :get do |f| %>
<%= form_with url: map_path(import_id: params[:import_id]), method: :get do |f| %>
<div class="flex flex-col md:flex-row md:space-x-4 md:items-end">
<div class="w-full md:w-2/12">
<div class="flex flex-col space-y-2">
@ -23,17 +23,17 @@
</div>
<div class="w-full md:w-2/12">
<div class="flex flex-col space-y-2 text-center">
<%= link_to "Yesterday", map_path(start_at: Date.yesterday.beginning_of_day, end_at: Date.yesterday.end_of_day), class: "px-4 py-2 bg-gray-500 text-white rounded-md" %>
<%= link_to "Yesterday", map_path(start_at: Date.yesterday.beginning_of_day, end_at: Date.yesterday.end_of_day, import_id: params[:import_id]), class: "px-4 py-2 bg-gray-500 text-white rounded-md" %>
</div>
</div>
<div class="w-full md:w-2/12">
<div class="flex flex-col space-y-2 text-center">
<%= link_to "Last 7 days", map_path(start_at: 1.week.ago.beginning_of_day, end_at: Time.current.end_of_day), class: "px-4 py-2 bg-gray-500 text-white rounded-md" %>
<%= link_to "Last 7 days", map_path(start_at: 1.week.ago.beginning_of_day, end_at: Time.current.end_of_day, import_id: params[:import_id]), class: "px-4 py-2 bg-gray-500 text-white rounded-md" %>
</div>
</div>
<div class="w-full md:w-2/12">
<div class="flex flex-col space-y-2 text-center">
<%= link_to "Last month", map_path(start_at: 1.month.ago.beginning_of_day, end_at: Time.current.end_of_day), class: "px-4 py-2 bg-gray-500 text-white rounded-md" %>
<%= link_to "Last month", map_path(start_at: 1.month.ago.beginning_of_day, end_at: Time.current.end_of_day, import_id: params[:import_id]), class: "px-4 py-2 bg-gray-500 text-white rounded-md" %>
</div>
</div>
</div>

View file

@ -1,7 +1,7 @@
<div role="<%= notification.kind %>" class="<%= notification.kind %> shadow-lg p-5 flex justify-between items-center mb-4 rounded-lg bg-base-200" id="<%= dom_id notification %>">
<div class="flex-1">
<h3 class="font-bold text-xl">
<%= link_to notification.title, notification, class: 'link hover:no-underline text-blue-600' %>
<%= link_to notification.title, notification, class: "link hover:no-underline #{notification_link_color(notification)}" %>
</h3>
<div class="text-sm text-gray-500"><%= time_ago_in_words notification.created_at %> ago</div>

View file

@ -3,7 +3,9 @@
<div class="text-center mb-6">
<h1 class="font-bold text-4xl mb-4">Notifications</h1>
<div class="flex items-center justify-center mb-4">
<% if @notifications.unread.any? %>
<%= link_to "Mark all as read", mark_notifications_as_read_path, method: :post, data: { turbo_method: :post }, class: "btn btn-sm btn-primary" %>
<% end %>
</div>
<div class="mb-4">
<%= paginate @notifications %>

View file

@ -1,20 +1,26 @@
<% content_for :title, 'Points' %>
<div class="w-full">
<%= form_with url: points_path, method: :get do |f| %>
<%= form_with url: points_path(import_id: params[:import_id]), method: :get do |f| %>
<div class="flex flex-col md:flex-row md:space-x-4 md:items-end">
<div class="w-full md:w-3/12">
<div class="w-full md:w-2/12">
<div class="flex flex-col space-y-2">
<%= f.label :start_at, class: "text-sm font-semibold" %>
<%= f.datetime_local_field :start_at, class: "rounded-md w-full", value: @start_at %>
</div>
</div>
<div class="w-full md:w-3/12">
<div class="w-full md:w-2/12">
<div class="flex flex-col space-y-2">
<%= f.label :end_at, class: "text-sm font-semibold" %>
<%= f.datetime_local_field :end_at, class: "rounded-md w-full", value: @end_at %>
</div>
</div>
<div class="w-full md:w-2/12">
<div class="flex flex-col space-y-2">
<%= f.label :import, class: "text-sm font-semibold" %>
<%= f.select :import_id, options_for_select(@imports.map { |i| [i.name, i.id] }, params[:import_id]), { include_blank: true }, class: "rounded-md w-full" %>
</div>
</div>
<div class="w-full md:w-1/12">
<div class="flex flex-col space-y-2">
<%= f.submit "Search", class: "px-4 py-2 bg-blue-500 text-white rounded-md" %>
@ -48,8 +54,8 @@
</div>
<div class="flex justify-end">
<span class="mr-2">Order by:</span>
<%= link_to 'Newest', points_path(order_by: :desc), class: 'btn btn-xs btn-primary mx-1' %>
<%= link_to 'Oldest', points_path(order_by: :asc), class: 'btn btn-xs btn-primary mx-1' %>
<%= link_to 'Newest', points_path(order_by: :desc, import_id: params[:import_id]), class: 'btn btn-xs btn-primary mx-1' %>
<%= link_to 'Oldest', points_path(order_by: :asc, import_id: params[:import_id]), class: 'btn btn-xs btn-primary mx-1' %>
</div>
</div>

View file

@ -5,7 +5,7 @@
<div tabindex="0" role="button" class="btn m-1">Select year</div>
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
<% current_user.stats.years.each do |year| %>
<li><%= link_to year, map_url(year_timespan(year).merge(year: year)) %></li>
<li><%= link_to year, map_url(year_timespan(year).merge(year: year, import_id: params[:import_id])) %></li>
<% end %>
</ul>
</div>
@ -19,7 +19,7 @@
<% (1..12).to_a.each_slice(3) do |months| %>
<% months.each do |month_number| %>
<% if past?(year, month_number) && points_exist?(year, month_number, current_user) %>
<%= link_to Date::ABBR_MONTHNAMES[month_number], map_url(timespan(month_number, year)), class: 'btn btn-default' %>
<%= link_to Date::ABBR_MONTHNAMES[month_number], map_url(timespan(month_number, year).merge(import_id: params[:import_id])), class: 'btn btn-default' %>
<% else %>
<div class='btn btn-disabled'><%= Date::ABBR_MONTHNAMES[month_number] %></div>
<% end %>

View file

@ -18,8 +18,8 @@
</div>
<div class="flex items-center">
<span class="mr-2">Order by:</span>
<%= link_to 'Newest', visits_path(order_by: :desc), class: 'btn btn-xs btn-primary mx-1' %>
<%= link_to 'Oldest', visits_path(order_by: :asc), class: 'btn btn-xs btn-primary mx-1' %>
<%= link_to 'Newest', visits_path(order_by: :desc, status: params[:status]), class: 'btn btn-xs btn-primary mx-1' %>
<%= link_to 'Oldest', visits_path(order_by: :asc, status: params[:status]), class: 'btn btn-xs btn-primary mx-1' %>
</div>
</div>

View file

@ -6,3 +6,4 @@
- exports
- stats
- reverse_geocoding
- visit_suggesting

View file

@ -10,35 +10,20 @@ RSpec.describe Immich::ImportGeodata do
create(:user, settings: { 'immich_url' => 'http://immich.app', 'immich_api_key' => '123456' })
end
let(:immich_data) do
[
{
"assets": [
{
"exifInfo": {
"dateTimeOriginal": '2022-12-31T23:17:06.170Z',
"latitude": 52.0000,
"longitude": 13.0000
}
},
{
"exifInfo": {
"dateTimeOriginal": '2022-12-31T23:21:53.140Z',
"latitude": 52.0000,
"longitude": 13.0000
}
}
],
"title": '1 year ago',
"yearsAgo": 1
}
].to_json
}.to_json
end
context 'when user has immich_url and immich_api_key' do
before do
stub_request(
:any,
%r{http://immich\.app/api/assets/memory-lane\?day=(1[0-9]|2[0-9]|3[01]|[1-9])&month=(1[0-2]|[1-9])}
%r{http://immich\.app/api/timeline/bucket\?size=MONTH&timeBucket=(19[7-9][0-9]|20[0-9]{2})-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])}
).to_return(status: 200, body: immich_data, headers: {})
end