Map v2 will no longer block the UI when Immich/Photoprism integration has a bad URL or is unreachable

This commit is contained in:
Eugene Burmakin 2026-01-03 13:43:37 +01:00
parent 8ecb2e3765
commit 0bd1a2038e
6 changed files with 43 additions and 17 deletions

View file

@ -8,8 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## Fixed
- Months are now correctly ordered (Jan-Dec) in the year-end digest chart instead of being sorted alphabetically.
- Time spent in a country and city is now calculated correctly for the year-end digest email. #2104
- Updated Trix to fix a XSS vulnerability. #2102
- Map v2 UI no longer blocks when Immich/Photoprism integration has a bad URL or is unreachable. Added 10-second timeout to photo API requests and improved error handling to prevent UI freezing during initial load. #2085
# [0.37.1] - 2025-12-30

View file

@ -56,22 +56,36 @@ export class DataLoader {
}
data.visitsGeoJSON = this.visitsToGeoJSON(data.visits)
// Fetch photos
try {
console.log('[Photos] Fetching photos from:', startDate, 'to', endDate)
data.photos = await this.api.fetchPhotos({
start_at: startDate,
end_at: endDate
})
console.log('[Photos] Fetched photos:', data.photos.length, 'photos')
console.log('[Photos] Sample photo:', data.photos[0])
} catch (error) {
console.error('[Photos] Failed to fetch photos:', error)
// Fetch photos - only if photos layer is enabled and integration is configured
// Skip API call if photos are disabled to avoid blocking on failed integrations
if (this.settings.photosEnabled) {
try {
console.log('[Photos] Fetching photos from:', startDate, 'to', endDate)
// Use Promise.race to enforce a client-side timeout
const photosPromise = this.api.fetchPhotos({
start_at: startDate,
end_at: endDate
})
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Photo fetch timeout')), 15000) // 15 second timeout
)
data.photos = await Promise.race([photosPromise, timeoutPromise])
console.log('[Photos] Fetched photos:', data.photos.length, 'photos')
console.log('[Photos] Sample photo:', data.photos[0])
} catch (error) {
console.warn('[Photos] Failed to fetch photos (non-blocking):', error.message)
data.photos = []
}
} else {
console.log('[Photos] Photos layer disabled, skipping fetch')
data.photos = []
}
data.photosGeoJSON = this.photosToGeoJSON(data.photos)
console.log('[Photos] Converted to GeoJSON:', data.photosGeoJSON.features.length, 'features')
console.log('[Photos] Sample feature:', data.photosGeoJSON.features[0])
if (data.photosGeoJSON.features.length > 0) {
console.log('[Photos] Sample feature:', data.photosGeoJSON.features[0])
}
// Fetch areas
try {

View file

@ -31,7 +31,10 @@ class Immich::RequestPhotos
while page <= max_pages
response = JSON.parse(
HTTParty.post(
immich_api_base_url, headers: headers, body: request_body(page)
immich_api_base_url,
headers: headers,
body: request_body(page),
timeout: 10
).body
)
Rails.logger.debug('==== IMMICH RESPONSE ====')
@ -46,6 +49,9 @@ class Immich::RequestPhotos
end
data.flatten
rescue HTTParty::Error, Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error("Immich photo fetch failed: #{e.message}")
[]
end
def headers

View file

@ -43,13 +43,17 @@ class Photoprism::RequestPhotos
end
data.flatten
rescue HTTParty::Error, Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error("Photoprism photo fetch failed: #{e.message}")
[]
end
def fetch_page(offset)
response = HTTParty.get(
photoprism_api_base_url,
headers: headers,
query: request_params(offset)
query: request_params(offset),
timeout: 10
)
if response.code != 200

View file

@ -79,7 +79,7 @@
</h2>
<div class="w-full h-48 bg-base-200 rounded-lg p-4 relative">
<%= column_chart(
@digest.monthly_distances.sort.map { |month, distance_meters|
@digest.monthly_distances.sort_by { |month, _| month.to_i }.map { |month, distance_meters|
[Date::ABBR_MONTHNAMES[month.to_i], Users::Digest.convert_distance(distance_meters.to_i, @distance_unit).round]
},
height: '200px',

View file

@ -101,7 +101,7 @@
</h2>
<div class="w-full h-64 bg-base-100 rounded-lg p-4">
<%= column_chart(
@digest.monthly_distances.sort.map { |month, distance_meters|
@digest.monthly_distances.sort_by { |month, _| month.to_i }.map { |month, distance_meters|
[Date::ABBR_MONTHNAMES[month.to_i], Users::Digest.convert_distance(distance_meters.to_i, @distance_unit).round]
},
height: '250px',