dawarich/BULK_DELETE_SUMMARY.md

7.9 KiB

Bulk Delete Points Feature - Summary

Overview

Added a bulk delete feature that allows users to select multiple points on the map by drawing a rectangle and delete them all at once, with confirmation and without page reload.

Changes Made

Backend (API)

  1. app/controllers/api/v1/points_controller.rb

    • Added bulk_destroy to authentication (before_action) on line 4
    • Added bulk_destroy action (lines 48-59) that:
      • Accepts point_ids array parameter
      • Validates that points exist
      • Deletes points belonging to current user
      • Returns JSON with success message and count
    • Added bulk_destroy_params private method (lines 71-73) to permit point_ids array
  2. config/routes.rb (lines 127-131)

    • Added DELETE /api/v1/points/bulk_destroy collection route

Frontend

  1. app/javascript/maps/visits.js

    • Import (line 3): Added createPolylinesLayer import from ./polylines
    • Constructor (line 8): Added mapsController parameter to receive maps controller reference
    • Selection UI (lines 389-427): Updated addSelectionCancelButton() to add:
      • "Cancel Selection" button (warning style)
      • "Delete Points" button (error/danger style) with:
        • Trash icon SVG
        • Point count badge showing number of selected points
        • Both buttons in flex container
    • Delete Logic (lines 432-529): Added deleteSelectedPoints() async method:
      • Extracts point IDs from this.selectedPoints array at index 6 (not 2!)
      • Shows confirmation dialog with warning message
      • Makes DELETE request to /api/v1/points/bulk_destroy with Bearer token auth
      • On success:
        • Removes markers from map via mapsController.removeMarker()
        • Updates polylines layer
        • Updates heatmap with remaining points
        • Updates fog layer if enabled
        • Clears selection and removes buttons
        • Shows success flash message
      • On error: Shows error flash message
    • Polylines Update (lines 534-577): Added updatePolylinesAfterDeletion() helper method:
      • Checks if polylines layer was visible before deletion
      • Removes old polylines layer
      • Creates new polylines layer with updated markers
      • Re-adds to map ONLY if it was visible before (preserves layer state)
      • Updates layer control with new polylines reference
  2. app/javascript/controllers/maps_controller.js (line 211)

    • Pass this (maps controller reference) when creating VisitsManager
    • Enables VisitsManager to call maps controller methods like removeMarker(), updateFog(), etc.

Technical Details

Point ID Extraction

The point array structure is:

[lat, lng, ?, ?, timestamp, ?, id, country, ?]
 0    1    2  3  4         5  6   7        8

So point ID is at index 6, not index 2!

API Request Format

DELETE /api/v1/points/bulk_destroy
Headers:
  Authorization: Bearer {apiKey}
  Content-Type: application/json
  X-CSRF-Token: {token}
Body:
  {
    "point_ids": ["123", "456", "789"]
  }

API Response Format

Success (200):

{
  "message": "Points were successfully destroyed",
  "count": 3
}

Error (422):

{
  "error": "No points selected"
}

Map Updates Without Page Reload

After deletion, the following map elements are updated:

  1. Markers: Removed via mapsController.removeMarker(id) for each deleted point
  2. Polylines/Routes: Recreated with remaining points, preserving visibility state
  3. Heatmap: Updated with setLatLngs() using remaining markers
  4. Fog of War: Recalculated if layer is enabled
  5. Layer Control: Rebuilt to reflect updated layers
  6. Selection: Cleared (rectangle removed, buttons hidden)

Layer State Preservation

The Routes layer visibility is preserved after deletion:

  • If Routes was enabled before deletion → stays enabled
  • If Routes was disabled before deletion → stays disabled

This is achieved by:

  1. Checking map.hasLayer(polylinesLayer) before deletion
  2. Storing state in wasPolyLayerVisible boolean
  3. Only calling polylinesLayer.addTo(map) if it was visible
  4. Explicitly calling map.removeLayer(polylinesLayer) if it was NOT visible

User Experience

Workflow

  1. User clicks area selection tool button (square with dashed border icon)
  2. Selection mode activates (map dragging disabled)
  3. User draws rectangle by clicking and dragging on map
  4. On mouse up:
    • Rectangle finalizes
    • Points within bounds are selected
    • Visits drawer shows selected visits
    • Two buttons appear at top of drawer:
      • "Cancel Selection" (yellow/warning)
      • "Delete Points" with count badge (red/error)
  5. User clicks "Delete Points" button
  6. Warning confirmation dialog appears:
    ⚠️ WARNING: This will permanently delete X points from your location history.
    
    This action cannot be undone!
    
    Are you sure you want to continue?
    
  7. If confirmed:
    • Points deleted via API
    • Map updates without reload
    • Success message: "Successfully deleted X points"
    • Selection cleared automatically
  8. If canceled:
    • No action taken
    • Dialog closes

UI Elements

  • Area Selection Button: Located in top-right corner of map, shows dashed square icon
  • Cancel Button: Yellow/warning styled, full width in drawer
  • Delete Button: Red/error styled, shows trash icon + count badge
  • Count Badge: Small badge showing number of selected points (e.g., "5")
  • Flash Messages: Success (green) or error (red) notifications

Testing

Playwright Tests (e2e/bulk-delete-points.spec.js)

Created 12 comprehensive tests covering:

  1. Area selection button visibility
  2. Selection mode activation
  3. Point selection and delete button appearance (needs debugging)
  4. Point count badge display (needs debugging)
  5. Cancel/Delete button pair (needs debugging)
  6. Cancel functionality (needs debugging)
  7. Confirmation dialog (needs debugging)
  8. Successful deletion with flash message (needs debugging)
  9. Routes layer state preservation when disabled (needs debugging)
  10. Routes layer state preservation when enabled (needs debugging)
  11. Heatmap update after deletion (needs debugging)
  12. Selection cleanup after deletion (needs debugging)

Note: Tests 1-3 pass, but tests involving the delete button are timing out. This may be due to:

  • Points not being selected properly in test environment
  • Drawer not opening
  • Different date range needed
  • Need to wait for visits API call to complete

Manual Testing Verified

  • Area selection tool activation
  • Rectangle drawing
  • Point selection
  • Delete button with count badge
  • Confirmation dialog
  • Successful deletion
  • Map updates without reload
  • Routes layer visibility preservation
  • Heatmap updates
  • Success flash messages

Security Considerations

  • API endpoint requires authentication (authenticate_active_api_user!)
  • Points are scoped to current_api_user.points (can't delete other users' points)
  • Strong parameters used to permit only point_ids array
  • CSRF token included in request headers
  • Confirmation dialog prevents accidental deletion
  • Warning message clearly states action is irreversible

Performance Considerations

  • Bulk deletion is more efficient than individual deletes (single API call)
  • Map updates are batched (all markers removed, then layers updated once)
  • No page reload means faster UX
  • Potential improvement: Add loading indicator for large deletions

Future Enhancements

  • Add loading indicator during deletion
  • Add "Undo" functionality (would require soft deletes)
  • Allow selection of individual points within rectangle (checkbox per point)
  • Add keyboard shortcuts (Delete key to delete selected points)
  • Add selection stats in drawer header (e.g., "15 points selected, 2.3 km total distance")
  • Support polygon selection (not just rectangle)
  • Add "Select All Points" button for current date range