mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Put import deletion into background job
This commit is contained in:
parent
87baf8bb11
commit
649e784457
8 changed files with 113 additions and 16 deletions
|
|
@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
- Fixed a bug preventing the app to start if a composite index on stats table already exists. #2034
|
- Fixed a bug preventing the app to start if a composite index on stats table already exists. #2034
|
||||||
- New compiled assets will override old ones on app start to prevent serving stale assets.
|
- New compiled assets will override old ones on app start to prevent serving stale assets.
|
||||||
|
- Deleting an import will no longer result in negative points count for the user.
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- Deleting an import will now be processed in the background to prevent request timeouts for large imports.
|
||||||
|
|
||||||
# [0.36.3] - 2025-12-14
|
# [0.36.3] - 2025-12-14
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,9 +78,13 @@ class ImportsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
Imports::Destroy.new(current_user, @import).call
|
@import.deleting!
|
||||||
|
Imports::DestroyJob.perform_later(@import.id)
|
||||||
|
|
||||||
redirect_to imports_url, notice: 'Import was successfully destroyed.', status: :see_other
|
respond_to do |format|
|
||||||
|
format.html { redirect_to imports_url, notice: 'Import is being deleted.', status: :see_other }
|
||||||
|
format.turbo_stream
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
||||||
|
|
@ -26,16 +26,23 @@ export default class extends BaseController {
|
||||||
received: (data) => {
|
received: (data) => {
|
||||||
const row = this.element.querySelector(`tr[data-import-id="${data.import.id}"]`);
|
const row = this.element.querySelector(`tr[data-import-id="${data.import.id}"]`);
|
||||||
|
|
||||||
if (row) {
|
if (!row) return;
|
||||||
const pointsCell = row.querySelector('[data-points-count]');
|
|
||||||
if (pointsCell) {
|
|
||||||
pointsCell.textContent = new Intl.NumberFormat().format(data.import.points_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusCell = row.querySelector('[data-status-display]');
|
// Handle deletion complete - remove the row
|
||||||
if (statusCell && data.import.status) {
|
if (data.action === 'delete') {
|
||||||
statusCell.textContent = data.import.status;
|
row.remove();
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle status and points updates
|
||||||
|
const pointsCell = row.querySelector('[data-points-count]');
|
||||||
|
if (pointsCell && data.import.points_count !== undefined) {
|
||||||
|
pointsCell.textContent = new Intl.NumberFormat().format(data.import.points_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusCell = row.querySelector('[data-status-display]');
|
||||||
|
if (statusCell && data.import.status) {
|
||||||
|
statusCell.textContent = data.import.status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
app/jobs/imports/destroy_job.rb
Normal file
46
app/jobs/imports/destroy_job.rb
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Imports::DestroyJob < ApplicationJob
|
||||||
|
queue_as :default
|
||||||
|
|
||||||
|
def perform(import_id)
|
||||||
|
import = Import.find_by(id: import_id)
|
||||||
|
return unless import
|
||||||
|
|
||||||
|
import.deleting!
|
||||||
|
broadcast_status_update(import)
|
||||||
|
|
||||||
|
Imports::Destroy.new(import.user, import).call
|
||||||
|
|
||||||
|
broadcast_deletion_complete(import)
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
Rails.logger.warn "Import #{import_id} not found, may have already been deleted"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def broadcast_status_update(import)
|
||||||
|
ImportsChannel.broadcast_to(
|
||||||
|
import.user,
|
||||||
|
{
|
||||||
|
action: 'status_update',
|
||||||
|
import: {
|
||||||
|
id: import.id,
|
||||||
|
status: import.status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def broadcast_deletion_complete(import)
|
||||||
|
ImportsChannel.broadcast_to(
|
||||||
|
import.user,
|
||||||
|
{
|
||||||
|
action: 'delete',
|
||||||
|
import: {
|
||||||
|
id: import.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -17,7 +17,7 @@ class Import < ApplicationRecord
|
||||||
validate :file_size_within_limit, if: -> { user.trial? }
|
validate :file_size_within_limit, if: -> { user.trial? }
|
||||||
validate :import_count_within_limit, if: -> { user.trial? }
|
validate :import_count_within_limit, if: -> { user.trial? }
|
||||||
|
|
||||||
enum :status, { created: 0, processing: 1, completed: 2, failed: 3 }
|
enum :status, { created: 0, processing: 1, completed: 2, failed: 3, deleting: 4 }
|
||||||
|
|
||||||
enum :source, {
|
enum :source, {
|
||||||
google_semantic_history: 0, owntracks: 1, google_records: 2,
|
google_semantic_history: 0, owntracks: 1, google_records: 2,
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,17 @@ class Imports::Destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
def call
|
def call
|
||||||
|
points_count = @import.points_count
|
||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
@import.points.delete_all
|
# Use destroy_all instead of delete_all to trigger counter_cache callbacks
|
||||||
|
# This ensures users.points_count is properly decremented
|
||||||
|
@import.points.destroy_all
|
||||||
@import.destroy!
|
@import.destroy!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Rails.logger.info "Import #{@import.id} deleted with #{points_count} points"
|
||||||
|
|
||||||
Stats::BulkCalculator.new(@user.id).call
|
Stats::BulkCalculator.new(@user.id).call
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
24
app/views/imports/destroy.turbo_stream.erb
Normal file
24
app/views/imports/destroy.turbo_stream.erb
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<%= turbo_stream.replace "import-#{@import.id}" do %>
|
||||||
|
<tr data-import-id="<%= @import.id %>"
|
||||||
|
id="import-<%= @import.id %>"
|
||||||
|
data-points-total="<%= @import.processed %>"
|
||||||
|
class="hover">
|
||||||
|
<td>
|
||||||
|
<%= @import.name %> (<%= @import.source %>)
|
||||||
|
|
||||||
|
<%= link_to '🗺️', map_path(import_id: @import.id) %>
|
||||||
|
|
||||||
|
<%= link_to '📋', points_path(import_id: @import.id) %>
|
||||||
|
</td>
|
||||||
|
<td><%= number_to_human_size(@import.file&.byte_size) || 'N/A' %></td>
|
||||||
|
<td data-points-count>
|
||||||
|
<%= number_with_delimiter @import.processed %>
|
||||||
|
</td>
|
||||||
|
<td data-status-display>deleting</td>
|
||||||
|
<td><%= human_datetime(@import.created_at) %></td>
|
||||||
|
<td class="whitespace-nowrap">
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
<span class="text-sm text-gray-500">Deleting...</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
|
@ -72,10 +72,15 @@
|
||||||
<td data-status-display><%= import.status %></td>
|
<td data-status-display><%= import.status %></td>
|
||||||
<td><%= human_datetime(import.created_at) %></td>
|
<td><%= human_datetime(import.created_at) %></td>
|
||||||
<td class="whitespace-nowrap">
|
<td class="whitespace-nowrap">
|
||||||
<% if import.file.present? %>
|
<% if import.deleting? %>
|
||||||
<%= link_to 'Download', rails_blob_path(import.file, disposition: 'attachment'), class: "btn btn-outline btn-sm btn-info", download: import.name %>
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
<span class="text-sm text-gray-500">Deleting...</span>
|
||||||
|
<% else %>
|
||||||
|
<% if import.file.present? %>
|
||||||
|
<%= link_to 'Download', rails_blob_path(import.file, disposition: 'attachment'), class: "btn btn-outline btn-sm btn-info", download: import.name %>
|
||||||
|
<% end %>
|
||||||
|
<%= link_to 'Delete', import, data: { turbo_confirm: "Are you sure?", turbo_method: :delete }, method: :delete, class: "btn btn-outline btn-sm btn-error" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to 'Delete', import, data: { turbo_confirm: "Are you sure?", turbo_method: :delete }, method: :delete, class: "btn btn-outline btn-sm btn-error" %>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue