From 82ad94fd6c52c0075c0c79e9873e9a32e31364a3 Mon Sep 17 00:00:00 2001 From: Evgenii Burmakin Date: Sat, 31 Jan 2026 22:40:14 +0100 Subject: [PATCH] 1.0.2-2 (#2208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: move foreman to global gems to fix startup crash (#1971) * Update exporting code to stream points data to file in batches to red… (#1980) * Update exporting code to stream points data to file in batches to reduce memory usage * Update changelog * Update changelog * Feature/maplibre frontend (#1953) * Add a plan to use MapLibre GL JS for the frontend map rendering, replacing Leaflet * Implement phase 1 * Phases 1-3 + part of 4 * Fix e2e tests * Phase 6 * Implement fog of war * Phase 7 * Next step: fix specs, phase 7 done * Use our own map tiles * Extract v2 map logic to separate manager classes * Update settings panel on v2 map * Update v2 e2e tests structure * Reimplement location search in maps v2 * Update speed routes * Implement visits and places creation in v2 * Fix last failing test * Implement visits merging * Fix a routes e2e test and simplify the routes layer styling. * Extract js to modules from maps_v2_controller.js * Implement area creation * Fix spec problem * Fix some e2e tests * Implement live mode in v2 map * Update icons and panel * Extract some styles * Remove unused file * Start adding dark theme to popups on MapLibre maps * Make popups respect dark theme * Move v2 maps to maplibre namespace * Update v2 references to maplibre * Put place, area and visit info into side panel * Update API to use safe settings config method * Fix specs * Fix method name to config in SafeSettings and update usages accordingly * Add missing public files * Add handling for real time points * Fix remembering enabled/disabled layers of the v2 map * Fix lots of e2e tests * Add settings to select map version * Use maps/v2 as main path for MapLibre maps * Update routing * Update live mode * Update maplibre controller * Update changelog * Remove some console.log statements * Pull only necessary data for map v2 points * Feature/raw data archive (#2009) * 0.36.2 (#2007) * fix: move foreman to global gems to fix startup crash (#1971) * Update exporting code to stream points data to file in batches to red… (#1980) * Update exporting code to stream points data to file in batches to reduce memory usage * Update changelog * Update changelog * Feature/maplibre frontend (#1953) * Add a plan to use MapLibre GL JS for the frontend map rendering, replacing Leaflet * Implement phase 1 * Phases 1-3 + part of 4 * Fix e2e tests * Phase 6 * Implement fog of war * Phase 7 * Next step: fix specs, phase 7 done * Use our own map tiles * Extract v2 map logic to separate manager classes * Update settings panel on v2 map * Update v2 e2e tests structure * Reimplement location search in maps v2 * Update speed routes * Implement visits and places creation in v2 * Fix last failing test * Implement visits merging * Fix a routes e2e test and simplify the routes layer styling. * Extract js to modules from maps_v2_controller.js * Implement area creation * Fix spec problem * Fix some e2e tests * Implement live mode in v2 map * Update icons and panel * Extract some styles * Remove unused file * Start adding dark theme to popups on MapLibre maps * Make popups respect dark theme * Move v2 maps to maplibre namespace * Update v2 references to maplibre * Put place, area and visit info into side panel * Update API to use safe settings config method * Fix specs * Fix method name to config in SafeSettings and update usages accordingly * Add missing public files * Add handling for real time points * Fix remembering enabled/disabled layers of the v2 map * Fix lots of e2e tests * Add settings to select map version * Use maps/v2 as main path for MapLibre maps * Update routing * Update live mode * Update maplibre controller * Update changelog * Remove some console.log statements --------- Co-authored-by: Robin Tuszik * Remove esbuild scripts from package.json * Remove sideEffects field from package.json * Raw data archivation * Add tests * Fix tests * Fix tests * Update ExceptionReporter * Add schedule to run raw data archival job monthly * Change file structure for raw data archival feature * Update changelog and version for raw data archival feature --------- Co-authored-by: Robin Tuszik * Set raw_data to an empty hash instead of nil when archiving * Fix storage configuration and file extraction * Consider MIN_MINUTES_SPENT_IN_CITY during stats calculation (#2018) * Consider MIN_MINUTES_SPENT_IN_CITY during stats calculation * Remove raw data from visited cities api endpoint * Use user timezone to show dates on maps (#2020) * Fix/pre epoch time (#2019) * Use user timezone to show dates on maps * Limit timestamps to valid range to prevent database errors when users enter pre-epoch dates. * Limit timestamps to valid range to prevent database errors when users enter pre-epoch dates. * Fix tests failing due to new index on stats table * Fix failing specs * Update redis client configuration to support unix socket connection * Update changelog * Fix kml kmz import issues (#2023) * Fix kml kmz import issues * Refactor KML importer to improve readability and maintainability * Implement moving points in map v2 and fix route rendering logic to ma… (#2027) * Implement moving points in map v2 and fix route rendering logic to match map v1. * Fix route spec * fix(maplibre): update date format to ISO 8601 (#2029) * Add verification step to raw data archival process (#2028) * Add verification step to raw data archival process * Add actual verification of raw data archives after creation, and only clear raw_data for verified archives. * Fix failing specs * Eliminate zip-bomb risk * Fix potential memory leak in js * Return .keep files * Use Toast instead of alert for notifications * Add help section to navbar dropdown * Update changelog * Remove raw_data_archival_job * Ensure file is being closed properly after reading in Archivable concern * Add composite index to stats table if not exists * Update changelog * Update entrypoint to always sync static assets (not only new ones) * Add family layer to MapLibre maps (#2055) * Add family layer to MapLibre maps * Update migration * Don't show family toggle if feature is disabled * Update changelog * Return changelog * Update changelog * Update tailwind file * Bump sentry-rails from 6.0.0 to 6.1.0 (#1945) Bumps [sentry-rails](https://github.com/getsentry/sentry-ruby) from 6.0.0 to 6.1.0. - [Release notes](https://github.com/getsentry/sentry-ruby/releases) - [Changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-ruby/compare/6.0.0...6.1.0) --- updated-dependencies: - dependency-name: sentry-rails dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump turbo-rails from 2.0.17 to 2.0.20 (#1944) Bumps [turbo-rails](https://github.com/hotwired/turbo-rails) from 2.0.17 to 2.0.20. - [Release notes](https://github.com/hotwired/turbo-rails/releases) - [Commits](https://github.com/hotwired/turbo-rails/compare/v2.0.17...v2.0.20) --- updated-dependencies: - dependency-name: turbo-rails dependency-version: 2.0.20 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Evgenii Burmakin * Bump webmock from 3.25.1 to 3.26.1 (#1943) Bumps [webmock](https://github.com/bblimke/webmock) from 3.25.1 to 3.26.1. - [Release notes](https://github.com/bblimke/webmock/releases) - [Changelog](https://github.com/bblimke/webmock/blob/master/CHANGELOG.md) - [Commits](https://github.com/bblimke/webmock/compare/v3.25.1...v3.26.1) --- updated-dependencies: - dependency-name: webmock dependency-version: 3.26.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Evgenii Burmakin * Bump brakeman from 7.1.0 to 7.1.1 (#1942) Bumps [brakeman](https://github.com/presidentbeef/brakeman) from 7.1.0 to 7.1.1. - [Release notes](https://github.com/presidentbeef/brakeman/releases) - [Changelog](https://github.com/presidentbeef/brakeman/blob/main/CHANGES.md) - [Commits](https://github.com/presidentbeef/brakeman/compare/v7.1.0...v7.1.1) --- updated-dependencies: - dependency-name: brakeman dependency-version: 7.1.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump redis from 5.4.0 to 5.4.1 (#1941) Bumps [redis](https://github.com/redis/redis-rb) from 5.4.0 to 5.4.1. - [Changelog](https://github.com/redis/redis-rb/blob/master/CHANGELOG.md) - [Commits](https://github.com/redis/redis-rb/compare/v5.4.0...v5.4.1) --- updated-dependencies: - dependency-name: redis dependency-version: 5.4.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Put import deletion into background job (#2045) * Put import deletion into background job * Update changelog * fix null type error and update heatmap styling (#2037) * fix: use constant weight for maplibre heatmap layer * fix null type, update heatmap styling * improve heatmap styling * fix typo * Fix stats calculation to recursively reduce H3 resolution when too ma… (#2065) * Fix stats calculation to recursively reduce H3 resolution when too many hexagons are generated * Update CHANGELOG.md * Validate trip start and end dates (#2066) * Validate trip start and end dates * Update changelog * Update migration to clean up duplicate stats before adding unique index * Fix fog of war radius setting being ignored and applying settings causing errors (#2068) * Update changelog * Add Rack::Deflater middleware to config/application.rb to enable gzip compression for responses. * Add composite index to points on user_id and timestamp * Deduplicte points based on timestamp brought to unix time * Fix/stats cache invalidation (#2072) * Fix family layer toggle in Map v2 settings for non-selfhosted env * Invalidate cache * Remove comments * Remove comment * Add new indicies to improve performance and remove unused ones to opt… (#2078) * Add new indicies to improve performance and remove unused ones to optimize database. * Remove comments * Update map search suggestions panel styling * Add yearly digest (#2073) * Add yearly digest * Rename YearlyDigests to Users::Digests * Minor changes * Update yearly digest layout and styles * Add flags and chart to email * Update colors * Fix layout of stats in yearly digest view * Remove cron job for yearly digest scheduling * Update CHANGELOG.md * Update digest email setting handling * Allow sharing digest for 1 week or 1 month * Change Digests Distance to Bigint * Fix settings page * Update changelog * Add RailsPulse (#2079) * Add RailsPulse * Add RailsPulse monitoring tool with basic HTTP authentication * Bring points_count to integer * Update migration and version * Update rubocop issues * Fix migrations and data verification to remove safety_assured blocks and handle missing points gracefully. * Update version * Update calculation of time spent in a country for year-end digest email (#2110) * Update calculation of time spent in a country for year-end digest email * Add a filter to exclude raw data points when calculating yearly digests. * Bump trix from 2.1.15 to 2.1.16 in the npm_and_yarn group across 1 directory (#2098) * 0.37.1 (#2092) * fix: move foreman to global gems to fix startup crash (#1971) * Update exporting code to stream points data to file in batches to red… (#1980) * Update exporting code to stream points data to file in batches to reduce memory usage * Update changelog * Update changelog * Feature/maplibre frontend (#1953) * Add a plan to use MapLibre GL JS for the frontend map rendering, replacing Leaflet * Implement phase 1 * Phases 1-3 + part of 4 * Fix e2e tests * Phase 6 * Implement fog of war * Phase 7 * Next step: fix specs, phase 7 done * Use our own map tiles * Extract v2 map logic to separate manager classes * Update settings panel on v2 map * Update v2 e2e tests structure * Reimplement location search in maps v2 * Update speed routes * Implement visits and places creation in v2 * Fix last failing test * Implement visits merging * Fix a routes e2e test and simplify the routes layer styling. * Extract js to modules from maps_v2_controller.js * Implement area creation * Fix spec problem * Fix some e2e tests * Implement live mode in v2 map * Update icons and panel * Extract some styles * Remove unused file * Start adding dark theme to popups on MapLibre maps * Make popups respect dark theme * Move v2 maps to maplibre namespace * Update v2 references to maplibre * Put place, area and visit info into side panel * Update API to use safe settings config method * Fix specs * Fix method name to config in SafeSettings and update usages accordingly * Add missing public files * Add handling for real time points * Fix remembering enabled/disabled layers of the v2 map * Fix lots of e2e tests * Add settings to select map version * Use maps/v2 as main path for MapLibre maps * Update routing * Update live mode * Update maplibre controller * Update changelog * Remove some console.log statements * Pull only necessary data for map v2 points * Feature/raw data archive (#2009) * 0.36.2 (#2007) * fix: move foreman to global gems to fix startup crash (#1971) * Update exporting code to stream points data to file in batches to red… (#1980) * Update exporting code to stream points data to file in batches to reduce memory usage * Update changelog * Update changelog * Feature/maplibre frontend (#1953) * Add a plan to use MapLibre GL JS for the frontend map rendering, replacing Leaflet * Implement phase 1 * Phases 1-3 + part of 4 * Fix e2e tests * Phase 6 * Implement fog of war * Phase 7 * Next step: fix specs, phase 7 done * Use our own map tiles * Extract v2 map logic to separate manager classes * Update settings panel on v2 map * Update v2 e2e tests structure * Reimplement location search in maps v2 * Update speed routes * Implement visits and places creation in v2 * Fix last failing test * Implement visits merging * Fix a routes e2e test and simplify the routes layer styling. * Extract js to modules from maps_v2_controller.js * Implement area creation * Fix spec problem * Fix some e2e tests * Implement live mode in v2 map * Update icons and panel * Extract some styles * Remove unused file * Start adding dark theme to popups on MapLibre maps * Make popups respect dark theme * Move v2 maps to maplibre namespace * Update v2 references to maplibre * Put place, area and visit info into side panel * Update API to use safe settings config method * Fix specs * Fix method name to config in SafeSettings and update usages accordingly * Add missing public files * Add handling for real time points * Fix remembering enabled/disabled layers of the v2 map * Fix lots of e2e tests * Add settings to select map version * Use maps/v2 as main path for MapLibre maps * Update routing * Update live mode * Update maplibre controller * Update changelog * Remove some console.log statements --------- Co-authored-by: Robin Tuszik * Remove esbuild scripts from package.json * Remove sideEffects field from package.json * Raw data archivation * Add tests * Fix tests * Fix tests * Update ExceptionReporter * Add schedule to run raw data archival job monthly * Change file structure for raw data archival feature * Update changelog and version for raw data archival feature --------- Co-authored-by: Robin Tuszik * Set raw_data to an empty hash instead of nil when archiving * Fix storage configuration and file extraction * Consider MIN_MINUTES_SPENT_IN_CITY during stats calculation (#2018) * Consider MIN_MINUTES_SPENT_IN_CITY during stats calculation * Remove raw data from visited cities api endpoint * Use user timezone to show dates on maps (#2020) * Fix/pre epoch time (#2019) * Use user timezone to show dates on maps * Limit timestamps to valid range to prevent database errors when users enter pre-epoch dates. * Limit timestamps to valid range to prevent database errors when users enter pre-epoch dates. * Fix tests failing due to new index on stats table * Fix failing specs * Update redis client configuration to support unix socket connection * Update changelog * Fix kml kmz import issues (#2023) * Fix kml kmz import issues * Refactor KML importer to improve readability and maintainability * Implement moving points in map v2 and fix route rendering logic to ma… (#2027) * Implement moving points in map v2 and fix route rendering logic to match map v1. * Fix route spec * fix(maplibre): update date format to ISO 8601 (#2029) * Add verification step to raw data archival process (#2028) * Add verification step to raw data archival process * Add actual verification of raw data archives after creation, and only clear raw_data for verified archives. * Fix failing specs * Eliminate zip-bomb risk * Fix potential memory leak in js * Return .keep files * Use Toast instead of alert for notifications * Add help section to navbar dropdown * Update changelog * Remove raw_data_archival_job * Ensure file is being closed properly after reading in Archivable concern * Add composite index to stats table if not exists * Update changelog * Update entrypoint to always sync static assets (not only new ones) * Add family layer to MapLibre maps (#2055) * Add family layer to MapLibre maps * Update migration * Don't show family toggle if feature is disabled * Update changelog * Return changelog * Update changelog * Update tailwind file * Bump sentry-rails from 6.0.0 to 6.1.0 (#1945) Bumps [sentry-rails](https://github.com/getsentry/sentry-ruby) from 6.0.0 to 6.1.0. - [Release notes](https://github.com/getsentry/sentry-ruby/releases) - [Changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-ruby/compare/6.0.0...6.1.0) --- updated-dependencies: - dependency-name: sentry-rails dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump turbo-rails from 2.0.17 to 2.0.20 (#1944) Bumps [turbo-rails](https://github.com/hotwired/turbo-rails) from 2.0.17 to 2.0.20. - [Release notes](https://github.com/hotwired/turbo-rails/releases) - [Commits](https://github.com/hotwired/turbo-rails/compare/v2.0.17...v2.0.20) --- updated-dependencies: - dependency-name: turbo-rails dependency-version: 2.0.20 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Evgenii Burmakin * Bump webmock from 3.25.1 to 3.26.1 (#1943) Bumps [webmock](https://github.com/bblimke/webmock) from 3.25.1 to 3.26.1. - [Release notes](https://github.com/bblimke/webmock/releases) - [Changelog](https://github.com/bblimke/webmock/blob/master/CHANGELOG.md) - [Commits](https://github.com/bblimke/webmock/compare/v3.25.1...v3.26.1) --- updated-dependencies: - dependency-name: webmock dependency-version: 3.26.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Evgenii Burmakin * Bump brakeman from 7.1.0 to 7.1.1 (#1942) Bumps [brakeman](https://github.com/presidentbeef/brakeman) from 7.1.0 to 7.1.1. - [Release notes](https://github.com/presidentbeef/brakeman/releases) - [Changelog](https://github.com/presidentbeef/brakeman/blob/main/CHANGES.md) - [Commits](https://github.com/presidentbeef/brakeman/compare/v7.1.0...v7.1.1) --- updated-dependencies: - dependency-name: brakeman dependency-version: 7.1.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump redis from 5.4.0 to 5.4.1 (#1941) Bumps [redis](https://github.com/redis/redis-rb) from 5.4.0 to 5.4.1. - [Changelog](https://github.com/redis/redis-rb/blob/master/CHANGELOG.md) - [Commits](https://github.com/redis/redis-rb/compare/v5.4.0...v5.4.1) --- updated-dependencies: - dependency-name: redis dependency-version: 5.4.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Put import deletion into background job (#2045) * Put import deletion into background job * Update changelog * fix null type error and update heatmap styling (#2037) * fix: use constant weight for maplibre heatmap layer * fix null type, update heatmap styling * improve heatmap styling * fix typo * Fix stats calculation to recursively reduce H3 resolution when too ma… (#2065) * Fix stats calculation to recursively reduce H3 resolution when too many hexagons are generated * Update CHANGELOG.md * Validate trip start and end dates (#2066) * Validate trip start and end dates * Update changelog * Update migration to clean up duplicate stats before adding unique index * Fix fog of war radius setting being ignored and applying settings causing errors (#2068) * Update changelog * Add Rack::Deflater middleware to config/application.rb to enable gzip compression for responses. * Add composite index to points on user_id and timestamp * Deduplicte points based on timestamp brought to unix time * Fix/stats cache invalidation (#2072) * Fix family layer toggle in Map v2 settings for non-selfhosted env * Invalidate cache * Remove comments * Remove comment * Add new indicies to improve performance and remove unused ones to opt… (#2078) * Add new indicies to improve performance and remove unused ones to optimize database. * Remove comments * Update map search suggestions panel styling * Add yearly digest (#2073) * Add yearly digest * Rename YearlyDigests to Users::Digests * Minor changes * Update yearly digest layout and styles * Add flags and chart to email * Update colors * Fix layout of stats in yearly digest view * Remove cron job for yearly digest scheduling * Update CHANGELOG.md * Update digest email setting handling * Allow sharing digest for 1 week or 1 month * Change Digests Distance to Bigint * Fix settings page * Update changelog * Add RailsPulse (#2079) * Add RailsPulse * Add RailsPulse monitoring tool with basic HTTP authentication * Bring points_count to integer * Update migration and version * Update rubocop issues * Fix migrations and data verification to remove safety_assured blocks and handle missing points gracefully. * Update version --------- Signed-off-by: dependabot[bot] Co-authored-by: Robin Tuszik Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump trix in the npm_and_yarn group across 1 directory Bumps the npm_and_yarn group with 1 update in the / directory: [trix](https://github.com/basecamp/trix). Updates `trix` from 2.1.15 to 2.1.16 - [Release notes](https://github.com/basecamp/trix/releases) - [Commits](https://github.com/basecamp/trix/compare/v2.1.15...v2.1.16) --- updated-dependencies: - dependency-name: trix dependency-version: 2.1.16 dependency-type: direct:production dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: Evgenii Burmakin Co-authored-by: Robin Tuszik Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Map v2 will no longer block the UI when Immich/Photoprism integration has a bad URL or is unreachable (#2113) * Bump rubocop-rails from 2.33.4 to 2.34.2 (#2080) Bumps [rubocop-rails](https://github.com/rubocop/rubocop-rails) from 2.33.4 to 2.34.2. - [Release notes](https://github.com/rubocop/rubocop-rails/releases) - [Changelog](https://github.com/rubocop/rubocop-rails/blob/master/CHANGELOG.md) - [Commits](https://github.com/rubocop/rubocop-rails/compare/v2.33.4...v2.34.2) --- updated-dependencies: - dependency-name: rubocop-rails dependency-version: 2.34.2 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump chartkick from 5.2.0 to 5.2.1 (#2081) Bumps [chartkick](https://github.com/ankane/chartkick) from 5.2.0 to 5.2.1. - [Changelog](https://github.com/ankane/chartkick/blob/master/CHANGELOG.md) - [Commits](https://github.com/ankane/chartkick/compare/v5.2.0...v5.2.1) --- updated-dependencies: - dependency-name: chartkick dependency-version: 5.2.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump rubyzip from 3.2.0 to 3.2.2 (#2082) Bumps [rubyzip](https://github.com/rubyzip/rubyzip) from 3.2.0 to 3.2.2. - [Release notes](https://github.com/rubyzip/rubyzip/releases) - [Changelog](https://github.com/rubyzip/rubyzip/blob/main/Changelog.md) - [Commits](https://github.com/rubyzip/rubyzip/compare/v3.2.0...v3.2.2) --- updated-dependencies: - dependency-name: rubyzip dependency-version: 3.2.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump sentry-ruby from 6.0.0 to 6.2.0 (#2083) Bumps [sentry-ruby](https://github.com/getsentry/sentry-ruby) from 6.0.0 to 6.2.0. - [Release notes](https://github.com/getsentry/sentry-ruby/releases) - [Changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-ruby/compare/6.0.0...6.2.0) --- updated-dependencies: - dependency-name: sentry-ruby dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Evgenii Burmakin * Bump sidekiq from 8.0.8 to 8.1.0 (#2084) Bumps [sidekiq](https://github.com/sidekiq/sidekiq) from 8.0.8 to 8.1.0. - [Changelog](https://github.com/sidekiq/sidekiq/blob/main/Changes.md) - [Commits](https://github.com/sidekiq/sidekiq/compare/v8.0.8...v8.1.0) --- updated-dependencies: - dependency-name: sidekiq dependency-version: 8.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Evgenii Burmakin * Update digest calculation to use actual time spent in countries based… (#2115) * Update digest calculation to use actual time spent in countries based on consecutive points, avoiding double-counting days when crossing borders. * Move methods to private * Update Gemfile and Gemfile.lock to pin connection_pool and sidekiq versions * Rework country tracked days calculation * Adjust calculate_duration_in_minutes to only count continuous presence within cities, excluding long gaps. * Move helpers for digest city progress to a helper method * Implement globe projection option for Map v2 using MapLibre GL JS. * Update time spent calculation for country minutes in user digests * Stats are now calculated with more accuracy by storing total minutes spent per country. * Add globe_projection setting to safe settings * Remove console.logs from most of map v2 * Implement some performance improvements and caching for various featu… (#2133) * Implement some performance improvements and caching for various features. * Fix failing tests * Implement routes behaviour in map v2 to match map v1 * Fix route highlighting * Add fallbacks when retrieving full route features to handle cases where source data access methods vary. * Fix some e2e tests * Add immediate verification and count validation to raw data archiving (#2138) * Add immediate verification and count validation to raw data archiving * Remove verifying job * Add archive metrics reporting * Disable RailsPulse in Self-hosted Environments * Remove user_id and points_count parameters from Metrics::Archives::Operation and related calls. * Move points creation logic from background jobs to service objects (#2145) * Move points creation logic from background jobs to service objects * Remove unused point creation jobs * Update changelog * Add tracks to map v2 (#2142) * Add tracks to map v2 * Remove console log * Update tracks generation behavior to ignore distance threshold for frontend parity * Extract logic to services from TracksController#index and add tests * Move query logic for track listing into a service object. * Minor changes * Fix minor issues * Support properties->date field for timestamp in GeoJSON imports (#2159) * Support properties->date field for timestamp in GeoJSON imports * Fix GeoJSON date parsing * Address number of photos related issues (#2152) * Address number of photos related issues * Fix minor stuff * Update integrations page layout * Add tests for live mode (#2164) * Add release notification workflow * Update notification workflow for releases * Address a number of github issues (#2178) * Address a number of github issues * Update changelog * Fix redundant WebSocket subscriptions and ensure live mode functions correctly on maps. * Add content type to Immich and Photoprism API requests * Fix failing specs * Update release_notifications.yml to remove workflow_dispatch event and fix changelog parsing * Add DNS caching with 5-minute TTL * Update changelog * Feature/insights page (#2179) * Update stuff * Extract insights controller logic to services * Fix failing specs * Refactorings * Add indicies with if_not_exists to migrations * Recalculate tracks transportation modes upon settings change * Block transportation modes settings form while a recalculation is in progress. * Add top visited locations * Bit of a refactoring * Update navbar * Add activity heatmap feature and update insights header * Add activity streak to insights page and changelog * Add caching for insights page * Preheat cache for insights * Move digest cache preheating to a service * Use user defined transportation thresholds in transportation mode detection and classification. * Fix failing specs * Generate tracks faster (#2198) * Generate tracks faster * Remove supporter verification code * Recalculate track segments after merging tracks. * Add couple specs * Add general settings tab (#2200) * Add general settings tab * Update changelog * Supporter verification (#2199) * Update * Implement supporter verification feature * Cache supporter verification results to improve performance * Remove initializer * Update verify service * Add supporter badge * Move verification form * Move settings update logic to a service object and adjust routes and views accordingly. * Disable caching for supporter verification * Reintroduce caching for supporter verification results * Fix some issues * Update changelog * Update readme * Fix couple of bugs --------- Signed-off-by: dependabot[bot] Co-authored-by: Robin Tuszik Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- CHANGELOG.md | 1 + .../reverse_geocoding/places/fetch_data.rb | 2 +- config/schedule.yml | 2 +- e2e/v2/map/interactions.spec.js | 224 +++++++++--------- e2e/v2/map/layers/points.spec.js | 143 ++++++----- e2e/v2/realtime/live-mode-api.spec.js | 51 ++-- e2e/v2/realtime/live-mode.spec.js | 56 +++-- 7 files changed, 247 insertions(+), 232 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0fee4d3..40de65c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Anyway, enjoy the release and don't forget to report any bugs you may find! - Remove assets before precompilation to prevent stale assets from being served. #2187 - undefined method 'to_sym' for nil in sidekiq #2190 - `Tracks::BoundaryResolverJob` now uses deterministic exponential backoff instead of random delays, and stops retrying after 5 attempts to avoid infinite rescheduling. +- Hanging Sidekiq job #2134 ## Changed diff --git a/app/services/reverse_geocoding/places/fetch_data.rb b/app/services/reverse_geocoding/places/fetch_data.rb index f9b9d209..ac5e846d 100644 --- a/app/services/reverse_geocoding/places/fetch_data.rb +++ b/app/services/reverse_geocoding/places/fetch_data.rb @@ -139,7 +139,7 @@ class ReverseGeocoding::Places::FetchData return unless places_to_update.any? - update_attributes = places_to_update.map do |place| + update_attributes = places_to_update.uniq(&:id).map do |place| { id: place.id, name: place.name, diff --git a/config/schedule.yml b/config/schedule.yml index cc28dff3..adb6f53a 100644 --- a/config/schedule.yml +++ b/config/schedule.yml @@ -48,7 +48,7 @@ nightly_reverse_geocoding_job: nightly_family_invitations_cleanup_job: cron: "30 2 * * *" # every day at 02:30 class: "Family::Invitations::CleanupJob" - queue: family + queue: families rails_pulse_summary_job: cron: "5 * * * *" # every hour at 5 minutes past the hour diff --git a/e2e/v2/map/interactions.spec.js b/e2e/v2/map/interactions.spec.js index efcd0600..46e9eb06 100644 --- a/e2e/v2/map/interactions.spec.js +++ b/e2e/v2/map/interactions.spec.js @@ -271,50 +271,53 @@ test.describe('Map Interactions', () => { await page.waitForTimeout(1000) - // Click on a route - const routeCenter = await page.evaluate(() => { + // Center map on route midpoint for reliable clicking + await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller.map.getSource('routes-source') - if (!source._data?.features?.length) return null + if (!source._data?.features?.length) return const route = source._data.features[0] const coords = route.geometry.coordinates const midCoord = coords[Math.floor(coords.length / 2)] - const point = controller.map.project(midCoord) - return { x: point.x, y: point.y } + controller.map.jumpTo({ center: midCoord, zoom: 15 }) }) - if (routeCenter) { - const canvas = page.locator('.maplibregl-canvas') - await canvas.click({ - position: { x: routeCenter.x, y: routeCenter.y } - }) + await page.waitForTimeout(500) - await page.waitForTimeout(500) + // Click at the center of the canvas — the route midpoint is projected there + const canvas = page.locator('.maplibregl-canvas') + const box = await canvas.boundingBox() + if (!box) return - // Move mouse away from route - await canvas.hover({ position: { x: 100, y: 100 } }) - await page.waitForTimeout(500) + await canvas.click({ + position: { x: Math.floor(box.width / 2), y: Math.floor(box.height / 2) } + }) - // Check if route is still highlighted - const isStillHighlighted = await page.evaluate(() => { - const element = document.querySelector('[data-controller*="maps--maplibre"]') - const app = window.Stimulus || window.Application - const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') - const hoverSource = controller.map.getSource('routes-hover-source') - return hoverSource && hoverSource._data?.features?.length > 0 - }) + await page.waitForTimeout(500) - expect(isStillHighlighted).toBe(true) + // Move mouse away from route + await canvas.hover({ position: { x: 50, y: 50 } }) + await page.waitForTimeout(500) - // Check if info panel is still visible - const infoDisplay = page.locator('[data-maps--maplibre-target="infoDisplay"]') - await expect(infoDisplay).not.toHaveClass(/hidden/) - } + // Check if route is still highlighted + const isStillHighlighted = await page.evaluate(() => { + const element = document.querySelector('[data-controller*="maps--maplibre"]') + const app = window.Stimulus || window.Application + const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') + const hoverSource = controller.map.getSource('routes-hover-source') + return hoverSource && hoverSource._data?.features?.length > 0 + }) + + expect(isStillHighlighted).toBe(true) + + // Check if info panel is still visible + const infoDisplay = page.locator('[data-maps--maplibre-target="infoDisplay"]') + await expect(infoDisplay).not.toHaveClass(/hidden/) }) test('clicking elsewhere on map deselects route', async ({ page }) => { @@ -331,53 +334,56 @@ test.describe('Map Interactions', () => { await page.waitForTimeout(1000) - // Click on a route first - const routeCenter = await page.evaluate(() => { + // Center map on route midpoint for reliable clicking + await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller.map.getSource('routes-source') - if (!source._data?.features?.length) return null + if (!source._data?.features?.length) return const route = source._data.features[0] const coords = route.geometry.coordinates const midCoord = coords[Math.floor(coords.length / 2)] - const point = controller.map.project(midCoord) - return { x: point.x, y: point.y } + controller.map.jumpTo({ center: midCoord, zoom: 15 }) }) - if (routeCenter) { - const canvas = page.locator('.maplibregl-canvas') - await canvas.click({ - position: { x: routeCenter.x, y: routeCenter.y } - }) + await page.waitForTimeout(500) - await page.waitForTimeout(500) + // Click at the center of the canvas — the route midpoint is projected there + const canvas = page.locator('.maplibregl-canvas') + const box = await canvas.boundingBox() + if (!box) return - // Verify route is selected - const infoDisplay = page.locator('[data-maps--maplibre-target="infoDisplay"]') - await expect(infoDisplay).not.toHaveClass(/hidden/) + await canvas.click({ + position: { x: Math.floor(box.width / 2), y: Math.floor(box.height / 2) } + }) - // Click elsewhere on map (far from route) - await canvas.click({ position: { x: 100, y: 100 } }) - await page.waitForTimeout(500) + await page.waitForTimeout(500) - // Check if route is deselected (hover source cleared) - const isDeselected = await page.evaluate(() => { - const element = document.querySelector('[data-controller*="maps--maplibre"]') - const app = window.Stimulus || window.Application - const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') - const hoverSource = controller.map.getSource('routes-hover-source') - return hoverSource && hoverSource._data?.features?.length === 0 - }) + // Verify route is selected + const infoDisplay = page.locator('[data-maps--maplibre-target="infoDisplay"]') + await expect(infoDisplay).not.toHaveClass(/hidden/) - expect(isDeselected).toBe(true) + // Click elsewhere on map (top-left corner, far from route) + await canvas.click({ position: { x: 50, y: 50 } }) + await page.waitForTimeout(500) - // Check if info panel is hidden - await expect(infoDisplay).toHaveClass(/hidden/) - } + // Check if route is deselected (hover source cleared) + const isDeselected = await page.evaluate(() => { + const element = document.querySelector('[data-controller*="maps--maplibre"]') + const app = window.Stimulus || window.Application + const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') + const hoverSource = controller.map.getSource('routes-hover-source') + return hoverSource && hoverSource._data?.features?.length === 0 + }) + + expect(isDeselected).toBe(true) + + // Check if info panel is hidden + await expect(infoDisplay).toHaveClass(/hidden/) }) test('clicking close button on info panel deselects route', async ({ page }) => { @@ -394,54 +400,57 @@ test.describe('Map Interactions', () => { await page.waitForTimeout(1000) - // Click on a route - const routeCenter = await page.evaluate(() => { + // Center map on route midpoint for reliable clicking + await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller.map.getSource('routes-source') - if (!source._data?.features?.length) return null + if (!source._data?.features?.length) return const route = source._data.features[0] const coords = route.geometry.coordinates const midCoord = coords[Math.floor(coords.length / 2)] - const point = controller.map.project(midCoord) - return { x: point.x, y: point.y } + controller.map.jumpTo({ center: midCoord, zoom: 15 }) }) - if (routeCenter) { - const canvas = page.locator('.maplibregl-canvas') - await canvas.click({ - position: { x: routeCenter.x, y: routeCenter.y } - }) + await page.waitForTimeout(500) - await page.waitForTimeout(500) + // Click at the center of the canvas — the route midpoint is projected there + const canvas = page.locator('.maplibregl-canvas') + const box = await canvas.boundingBox() + if (!box) return - // Verify info panel is open - const infoDisplay = page.locator('[data-maps--maplibre-target="infoDisplay"]') - await expect(infoDisplay).not.toHaveClass(/hidden/) + await canvas.click({ + position: { x: Math.floor(box.width / 2), y: Math.floor(box.height / 2) } + }) - // Click the close button - const closeButton = page.locator('button[data-action="click->maps--maplibre#closeInfo"]') - await closeButton.click() - await page.waitForTimeout(500) + await page.waitForTimeout(500) - // Check if route is deselected - const isDeselected = await page.evaluate(() => { - const element = document.querySelector('[data-controller*="maps--maplibre"]') - const app = window.Stimulus || window.Application - const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') - const hoverSource = controller.map.getSource('routes-hover-source') - return hoverSource && hoverSource._data?.features?.length === 0 - }) + // Verify info panel is open + const infoDisplay = page.locator('[data-maps--maplibre-target="infoDisplay"]') + await expect(infoDisplay).not.toHaveClass(/hidden/) - expect(isDeselected).toBe(true) + // Click the close button + const closeButton = page.locator('button[data-action="click->maps--maplibre#closeInfo"]') + await closeButton.click() + await page.waitForTimeout(500) - // Check if info panel is hidden - await expect(infoDisplay).toHaveClass(/hidden/) - } + // Check if route is deselected + const isDeselected = await page.evaluate(() => { + const element = document.querySelector('[data-controller*="maps--maplibre"]') + const app = window.Stimulus || window.Application + const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') + const hoverSource = controller.map.getSource('routes-hover-source') + return hoverSource && hoverSource._data?.features?.length === 0 + }) + + expect(isDeselected).toBe(true) + + // Check if info panel is hidden + await expect(infoDisplay).toHaveClass(/hidden/) }) test('route cursor changes to pointer on hover', async ({ page }) => { @@ -458,41 +467,44 @@ test.describe('Map Interactions', () => { await page.waitForTimeout(1000) - // Hover over a route - const routeCenter = await page.evaluate(() => { + // Center map on route midpoint for reliable hovering + await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller.map.getSource('routes-source') - if (!source._data?.features?.length) return null + if (!source._data?.features?.length) return const route = source._data.features[0] const coords = route.geometry.coordinates const midCoord = coords[Math.floor(coords.length / 2)] - const point = controller.map.project(midCoord) - return { x: point.x, y: point.y } + controller.map.jumpTo({ center: midCoord, zoom: 15 }) }) - if (routeCenter) { - const canvas = page.locator('.maplibregl-canvas') - await canvas.hover({ - position: { x: routeCenter.x, y: routeCenter.y } - }) + await page.waitForTimeout(500) - await page.waitForTimeout(300) + // Hover at the center of the canvas — the route midpoint is projected there + const canvas = page.locator('.maplibregl-canvas') + const box = await canvas.boundingBox() + if (!box) return - // Check cursor style - const cursor = await page.evaluate(() => { - const element = document.querySelector('[data-controller*="maps--maplibre"]') - const app = window.Stimulus || window.Application - const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') - return controller.map.getCanvas().style.cursor - }) + await canvas.hover({ + position: { x: Math.floor(box.width / 2), y: Math.floor(box.height / 2) } + }) - expect(cursor).toBe('pointer') - } + await page.waitForTimeout(300) + + // Check cursor style + const cursor = await page.evaluate(() => { + const element = document.querySelector('[data-controller*="maps--maplibre"]') + const app = window.Stimulus || window.Application + const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') + return controller.map.getCanvas().style.cursor + }) + + expect(cursor).toBe('pointer') }) test('hovering over different route while one is selected shows both highlighted', async ({ page }) => { diff --git a/e2e/v2/map/layers/points.spec.js b/e2e/v2/map/layers/points.spec.js index 4c477a74..416ac991 100644 --- a/e2e/v2/map/layers/points.spec.js +++ b/e2e/v2/map/layers/points.spec.js @@ -231,24 +231,23 @@ test.describe('Points Layer', () => { routesSource?._data?.features?.length > 0 }, { timeout: 15000 }) - // Ensure points layer is visible via the settings panel UI - await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click() - await page.waitForTimeout(300) - await page.locator('button[data-tab="layers"]').click() - await page.waitForTimeout(300) + // Ensure points layer is visible before testing dragging (same pattern as test 74) + await page.evaluate(() => { + const element = document.querySelector('[data-controller*="maps--maplibre"]') + const app = window.Stimulus || window.Application + const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') + const pointsLayer = controller?.layerManager?.layers?.pointsLayer - const pointsCheckbox = page.locator('[data-maps--maplibre-target="pointsToggle"]') - const isChecked = await pointsCheckbox.isChecked() - if (!isChecked) { - await pointsCheckbox.click() - await page.waitForTimeout(500) - } - - // Close settings panel - const closeBtn = page.locator('.panel-header button[data-action="click->maps--maplibre#toggleSettings"]') - await closeBtn.click() - await page.waitForTimeout(300) + if (pointsLayer) { + const visibility = controller.map.getLayoutProperty('points', 'visibility') + const isVisible = visibility === 'visible' || visibility === undefined + if (!isVisible) { + pointsLayer.show() + } + } + }) + // Wait for layer to render await page.waitForTimeout(2000) // Get initial data @@ -261,24 +260,22 @@ test.describe('Points Layer', () => { const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') - // Get all rendered point features const features = controller.map.queryRenderedFeatures(undefined, { layers: ['points'] }) if (features.length === 0) { - return { found: false } + return { found: false, totalFeatures: 0 } } - // Pick the first rendered point const feature = features[0] const coords = feature.geometry.coordinates const point = controller.map.project(coords) - // Get the canvas position on the page const canvas = controller.map.getCanvas() const rect = canvas.getBoundingClientRect() return { found: true, + totalFeatures: features.length, pointId: feature.properties.id, coords: coords, x: point.x, @@ -292,12 +289,6 @@ test.describe('Points Layer', () => { const pointId = renderedPoint.pointId const initialCoords = renderedPoint.coords - const pointPixel = { - x: renderedPoint.x, - y: renderedPoint.y, - pageX: renderedPoint.pageX, - pageY: renderedPoint.pageY - } // Find routes that contain this point const connectedRoutes = initialRoutesData.features.filter(route => { @@ -307,23 +298,24 @@ test.describe('Points Layer', () => { ) }) - const dragOffset = { x: 100, y: 100 } - const startX = pointPixel.pageX - const startY = pointPixel.pageY + const startX = renderedPoint.pageX + const startY = renderedPoint.pageY const endX = startX + dragOffset.x const endY = startY + dragOffset.y - // Perform drag with slower movement + // Check cursor style on hover (matches test 74 pattern) await page.mouse.move(startX, startY) - await page.waitForTimeout(100) + await page.waitForTimeout(200) + + // Perform the drag operation with slower movement await page.mouse.down() await page.waitForTimeout(100) await page.mouse.move(endX, endY, { steps: 20 }) await page.waitForTimeout(100) await page.mouse.up() - // Wait for updates + // Wait for API call to complete await page.waitForTimeout(3000) // Get updated data @@ -331,8 +323,18 @@ test.describe('Points Layer', () => { const updatedRoutesData = await getRoutesSourceData(page) const updatedPoint = updatedPointsData.features.find(f => f.properties.id === pointId) + expect(updatedPoint).toBeDefined() const updatedCoords = updatedPoint.geometry.coordinates + // The point moved, so verify the coordinates actually changed + const updatedLng = parseFloat(updatedCoords[0]) + const updatedLat = parseFloat(updatedCoords[1]) + const initialLng = parseFloat(initialCoords[0]) + const initialLat = parseFloat(initialCoords[1]) + + expect(updatedLng).not.toBeCloseTo(initialLng, 5) + expect(updatedLat).not.toBeCloseTo(initialLat, 5) + // Verify routes have been updated const updatedConnectedRoutes = updatedRoutesData.features.filter(route => { return route.geometry.coordinates.some(coord => @@ -341,20 +343,10 @@ test.describe('Points Layer', () => { ) }) - // Routes that were originally connected should now be at the new position if (connectedRoutes.length > 0) { expect(updatedConnectedRoutes.length).toBeGreaterThan(0) } - - // The point moved, so verify the coordinates actually changed - const lngChanged = Math.abs(parseFloat(updatedCoords[0]) - initialCoords[0]) > 0.0001 - const latChanged = Math.abs(parseFloat(updatedCoords[1]) - initialCoords[1]) > 0.0001 - - expect(lngChanged || latChanged).toBe(true) - - // Since the route segments update is best-effort (depends on coordinate matching), - // we'll just verify that routes exist and the point moved }) test('persists point position after page reload', async ({ page }) => { @@ -367,14 +359,23 @@ test.describe('Points Layer', () => { return source?._data?.features?.length > 0 }, { timeout: 15000 }) - // Ensure points layer is visible by clicking the checkbox - const pointsCheckbox = page.locator('[data-maps--maplibre-target="pointsToggle"]') - const isChecked = await pointsCheckbox.isChecked() - if (!isChecked) { - await pointsCheckbox.click() - await page.waitForTimeout(500) - } + // Ensure points layer is visible (programmatic - same pattern as test 74) + await page.evaluate(() => { + const element = document.querySelector('[data-controller*="maps--maplibre"]') + const app = window.Stimulus || window.Application + const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') + const pointsLayer = controller?.layerManager?.layers?.pointsLayer + if (pointsLayer) { + const visibility = controller.map.getLayoutProperty('points', 'visibility') + const isVisible = visibility === 'visible' || visibility === undefined + if (!isVisible) { + pointsLayer.show() + } + } + }) + + // Wait for layer to render await page.waitForTimeout(2000) // Find a rendered point feature on the map @@ -383,24 +384,22 @@ test.describe('Points Layer', () => { const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') - // Get all rendered point features const features = controller.map.queryRenderedFeatures(undefined, { layers: ['points'] }) if (features.length === 0) { - return { found: false } + return { found: false, totalFeatures: 0 } } - // Pick the first rendered point const feature = features[0] const coords = feature.geometry.coordinates const point = controller.map.project(coords) - // Get the canvas position on the page const canvas = controller.map.getCanvas() const rect = canvas.getBoundingClientRect() return { found: true, + totalFeatures: features.length, pointId: feature.properties.id, coords: coords, x: point.x, @@ -414,30 +413,25 @@ test.describe('Points Layer', () => { const pointId = renderedPoint.pointId const initialCoords = renderedPoint.coords - const pointPixel = { - x: renderedPoint.x, - y: renderedPoint.y, - pageX: renderedPoint.pageX, - pageY: renderedPoint.pageY - } - const dragOffset = { x: 100, y: 100 } - const startX = pointPixel.pageX - const startY = pointPixel.pageY + const startX = renderedPoint.pageX + const startY = renderedPoint.pageY const endX = startX + dragOffset.x const endY = startY + dragOffset.y - // Perform drag with slower movement + // Check cursor style on hover (matches test 74 pattern) await page.mouse.move(startX, startY) - await page.waitForTimeout(100) + await page.waitForTimeout(200) + + // Perform the drag operation with slower movement await page.mouse.down() await page.waitForTimeout(100) await page.mouse.move(endX, endY, { steps: 20 }) await page.waitForTimeout(100) await page.mouse.up() - // Wait for API call + // Wait for API call to complete await page.waitForTimeout(3000) // Get the new position @@ -445,6 +439,14 @@ test.describe('Points Layer', () => { const afterDragPoint = afterDragData.features.find(f => f.properties.id === pointId) const afterDragCoords = afterDragPoint.geometry.coordinates + // Verify the drag succeeded before reloading + const dragLng = parseFloat(afterDragCoords[0]) + const dragLat = parseFloat(afterDragCoords[1]) + const initialLng = parseFloat(initialCoords[0]) + const initialLat = parseFloat(initialCoords[1]) + + expect(dragLng).not.toBeCloseTo(initialLng, 5) + expect(dragLat).not.toBeCloseTo(initialLat, 5) // Reload the page await page.reload() @@ -466,20 +468,15 @@ test.describe('Points Layer', () => { const afterReloadPoint = afterReloadData.features.find(f => f.properties.id === pointId) const afterReloadCoords = afterReloadPoint.geometry.coordinates - - // Verify the position persisted (parse coordinates as numbers) + // Verify the position persisted const reloadLng = parseFloat(afterReloadCoords[0]) const reloadLat = parseFloat(afterReloadCoords[1]) - const dragLng = parseFloat(afterDragCoords[0]) - const dragLat = parseFloat(afterDragCoords[1]) - const initialLng = parseFloat(initialCoords[0]) - const initialLat = parseFloat(initialCoords[1]) - // Position after reload should match position after drag (high precision) + // Position after reload should match position after drag expect(reloadLng).toBeCloseTo(dragLng, 5) expect(reloadLat).toBeCloseTo(dragLat, 5) - // And it should be different from the initial position (lower precision - just verify it moved) + // And it should be different from the initial position const lngDiff = Math.abs(reloadLng - initialLng) const latDiff = Math.abs(reloadLat - initialLat) const moved = lngDiff > 0.00001 || latDiff > 0.00001 diff --git a/e2e/v2/realtime/live-mode-api.spec.js b/e2e/v2/realtime/live-mode-api.spec.js index e05242e9..9439c491 100644 --- a/e2e/v2/realtime/live-mode-api.spec.js +++ b/e2e/v2/realtime/live-mode-api.spec.js @@ -6,7 +6,6 @@ import { sendOwnTracksPoint, waitForPointOnMap, waitForFamilyMemberOnMap, - waitForRecentPointVisible, enableLiveMode, waitForActionCableConnection, waitForPointsChannelConnected @@ -220,32 +219,40 @@ test.describe('Live Mode API Integration', () => { // Enable live mode await enableLiveMode(page); + await page.waitForTimeout(1000); - // Wait for connection - await waitForActionCableConnection(page); - await waitForPointsChannelConnected(page, 5000); - await page.waitForTimeout(2000); - - // Send point + // Simulate receiving a new point by calling handleNewPoint directly + // This bypasses ActionCable and tests the client-side handling + const testLat = TEST_LOCATIONS.BERLIN_NORTH.lat; + const testLon = TEST_LOCATIONS.BERLIN_NORTH.lon; const timestamp = Math.floor(Date.now() / 1000); - const response = await sendOwnTracksPoint( - request, - API_KEYS.DEMO_USER, - TEST_LOCATIONS.BERLIN_NORTH.lat, - TEST_LOCATIONS.BERLIN_NORTH.lon, - timestamp - ); - expect(response.status()).toBe(200); + const result = await page.evaluate(({ lat, lon, ts }) => { + const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]'); + const app = window.Stimulus || window.Application; + const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime'); - // Check if recent point layer becomes visible - const recentPointVisible = await waitForRecentPointVisible(page, 10000); + if (!controller) return { success: false, reason: 'controller not found' }; + if (typeof controller.handleNewPoint !== 'function') return { success: false, reason: 'handleNewPoint not found' }; - if (recentPointVisible) { - console.log('[Test] Recent point marker displayed successfully'); - } else { - console.log('[Test] Recent point marker not visible - may require ActionCable'); - } + // Enable live mode programmatically + controller.liveModeEnabled = true; + + // Call handleNewPoint with array format: [lat, lon, battery, altitude, timestamp, velocity, id, country_name] + controller.handleNewPoint([lat, lon, 85, 0, ts, 0, 999999, null]); + + // Check if recent point layer became visible + const mapsController = controller.mapsV2Controller; + const recentPointLayer = mapsController?.layerManager?.getLayer('recentPoint'); + + return { + success: true, + recentPointVisible: recentPointLayer?.visible === true + }; + }, { lat: testLat, lon: testLon, ts: timestamp }); + + expect(result.success).toBe(true); + expect(result.recentPointVisible).toBe(true); }); }); diff --git a/e2e/v2/realtime/live-mode.spec.js b/e2e/v2/realtime/live-mode.spec.js index 4904fef4..47a13b89 100644 --- a/e2e/v2/realtime/live-mode.spec.js +++ b/e2e/v2/realtime/live-mode.spec.js @@ -468,45 +468,43 @@ test.describe('Live Mode', () => { expect(hasMethod).toBe(true) }) - test('should display recent point when new point is broadcast in live mode', async ({ page, request }) => { + test('should display recent point when new point is broadcast in live mode', async ({ page }) => { // Enable live mode await enableLiveMode(page) - const channelConnected = await waitForPointsChannelConnected(page, 5000) await page.waitForTimeout(1000) - // Send a new point via API - this triggers ActionCable broadcast - const testLat = TEST_LOCATIONS.BERLIN_CENTER.lat + (Math.random() * 0.001) - const testLon = TEST_LOCATIONS.BERLIN_CENTER.lon + (Math.random() * 0.001) + // Simulate receiving a new point by calling handleNewPoint directly + // This bypasses ActionCable and tests the client-side handling + const testLat = TEST_LOCATIONS.BERLIN_CENTER.lat + const testLon = TEST_LOCATIONS.BERLIN_CENTER.lon const timestamp = Math.floor(Date.now() / 1000) - const response = await sendOwnTracksPoint( - request, - API_KEYS.DEMO_USER, - testLat, - testLon, - timestamp - ) + const result = await page.evaluate(({ lat, lon, ts }) => { + const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]') + const app = window.Stimulus || window.Application + const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime') - expect(response.status()).toBe(200) + if (!controller) return { success: false, reason: 'controller not found' } + if (typeof controller.handleNewPoint !== 'function') return { success: false, reason: 'handleNewPoint not found' } - // Wait for recent point layer to become visible - // Note: requires server-side live_map_enabled setting + working ActionCable - const recentPointVisible = await waitForRecentPointVisible(page, 10000) + // Enable live mode programmatically + controller.liveModeEnabled = true - if (channelConnected && recentPointVisible) { - // Verify recent point layer is showing - const hasRecentPoint = await page.evaluate(() => { - const element = document.querySelector('[data-controller*="maps--maplibre"]') - const app = window.Stimulus || window.Application - const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre') - const recentPointLayer = controller?.layerManager?.getLayer('recentPoint') - return recentPointLayer?.visible === true - }) + // Call handleNewPoint with array format: [lat, lon, battery, altitude, timestamp, velocity, id, country_name] + controller.handleNewPoint([lat, lon, 85, 0, ts, 0, 999998, null]) - expect(hasRecentPoint).toBe(true) - } else { - console.log('[Test] Recent point not visible - broadcast requires server-side live_map_enabled setting') - } + // Check if recent point layer became visible + const mapsController = controller.mapsV2Controller + const recentPointLayer = mapsController?.layerManager?.getLayer('recentPoint') + + return { + success: true, + recentPointVisible: recentPointLayer?.visible === true + } + }, { lat: testLat, lon: testLon, ts: timestamp }) + + expect(result.success).toBe(true) + expect(result.recentPointVisible).toBe(true) }) }) })