Skip to content

Commit d237663

Browse files
authored
feat: Added support to retry cancelled or error uploads (#512)
1 parent 04fdfc1 commit d237663

File tree

14 files changed

+431
-160
lines changed

14 files changed

+431
-160
lines changed

application/app/controllers/download_files_controller.rb

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,65 +3,63 @@ class DownloadFilesController < ApplicationController
33
include LoggingCommon
44
include EventLogger
55

6-
def cancel
7-
project_id = params[:project_id]
8-
file_id = params[:id]
9-
file = DownloadFile.find(project_id, file_id)
6+
before_action :find_file
107

11-
if file.nil?
12-
return redirect_back fallback_location: root_path, alert: t('download_files.file_not_found', file_id: file_id, project_id: project_id)
13-
end
14-
15-
previous_status = file.status.to_s
16-
if file.status.downloading?
8+
def cancel
9+
previous_status = @file.status.to_s
10+
if @file.status.downloading?
1711
command_client = Command::CommandClient.new(socket_path: ::Configuration.command_server_socket_file)
18-
request = Command::Request.new(command: 'download.cancel', body: {project_id: project_id, file_id: file_id})
12+
request = Command::Request.new(command: 'download.cancel', body: {project_id: @file.project_id, file_id: @file.id})
1913
response = command_client.request(request)
20-
return redirect_back fallback_location: root_path, alert: t('download_files.file_cancellation_error', filename: file.filename) if response.status != 200
14+
return redirect_back fallback_location: root_path, alert: t('download_files.file_cancellation_error', filename: @file.filename) if response.status != 200
2115
end
2216

23-
if file.update(status: FileStatus::CANCELLED)
24-
log_download_file_event(file, message: 'events.download_file.cancel_completed', metadata: { filename: file.filename, previous_status: previous_status })
25-
redirect_back fallback_location: root_path, notice: t('download_files.file_cancellation_success', filename: file.filename)
17+
if @file.update(status: FileStatus::CANCELLED)
18+
log_download_file_event(@file, message: 'events.download_file.cancel_completed', metadata: { previous_status: previous_status })
19+
log_info('Download file cancelled', {id: @file.id, filename: @file.filename})
20+
redirect_back fallback_location: root_path, notice: t('download_files.file_cancellation_success', filename: @file.filename)
2621
else
27-
redirect_back fallback_location: root_path, alert: t('download_files.file_cancellation_update_error', filename: file.filename)
22+
redirect_back fallback_location: root_path, alert: t('download_files.file_cancellation_update_error', filename: @file.filename)
2823
end
2924
end
3025

31-
def update
32-
project_id = params[:project_id]
33-
file_id = params[:id]
34-
state = params[:state]
35-
file = DownloadFile.find(project_id, file_id)
36-
37-
if file.nil?
38-
redirect_back fallback_location: root_path, alert: t('download_files.file_not_found', file_id: file_id, project_id: project_id)
26+
def destroy
27+
if @file.status.downloading?
28+
redirect_back fallback_location: root_path, alert: t('download_files.file_in_progress', filename: @file.filename)
3929
return
4030
end
4131

42-
if file.update(status: FileStatus.get(state))
43-
redirect_back fallback_location: root_path, notice: t('download_files.file_update_success', filename: file.filename)
32+
@file.destroy
33+
log_download_file_event(@file, message: 'events.download_file.deleted')
34+
log_info('Download file deleted', {id: @file.id, filename: @file.filename})
35+
redirect_back fallback_location: root_path, notice: t('download_files.file_deletion_success', filename: @file.filename)
36+
end
37+
38+
def retry
39+
status = @file.status
40+
unless FileStatus.retryable_statuses.include?(status)
41+
return redirect_back fallback_location: root_path, alert: t('download_files.file_retry_invalid', status: status)
42+
end
43+
44+
if @file.update(status: FileStatus::PENDING)
45+
log_download_file_event(@file, message: 'events.download_file.retry_request', metadata: { previous_status: status })
46+
log_info('Download file retry requested', {id: @file.id, filename: @file.filename})
47+
redirect_back fallback_location: root_path, notice: t('download_files.file_retry_success', filename: @file.filename)
4448
else
45-
redirect_back fallback_location: root_path, alert: t('download_files.file_update_error', filename: file.filename)
49+
redirect_back fallback_location: root_path, alert: t('download_files.file_retry_error', filename: @file.filename)
4650
end
4751
end
4852

49-
def destroy
53+
private
54+
55+
def find_file
5056
project_id = params[:project_id]
5157
file_id = params[:id]
52-
file = DownloadFile.find(project_id, file_id)
53-
if file.nil?
54-
redirect_back fallback_location: root_path, alert: t('download_files.file_not_found', file_id: file_id, project_id: project_id)
55-
return
56-
end
58+
@file = DownloadFile.find(project_id, file_id)
5759

58-
if file.status.downloading?
59-
redirect_back fallback_location: root_path, alert: t('download_files.file_in_progress', filename: file.filename)
60-
return
60+
if @file.nil?
61+
redirect_back fallback_location: root_path, alert: t('download_files.file_not_found', file_id: file_id, project_id: project_id)
6162
end
62-
63-
file.destroy
64-
redirect_back fallback_location: root_path, notice: t('download_files.file_deletion_success', filename: file.filename)
6563
end
6664

6765
end

application/app/controllers/upload_files_controller.rb

Lines changed: 64 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,22 @@ class UploadFilesController < ApplicationController
55
include DateTimeCommon
66
include EventLogger
77

8+
before_action :find_upload_file, only: [:destroy, :cancel, :retry]
9+
before_action :find_bundle, only: [:index, :create]
10+
811
def index
9-
project_id = params[:project_id]
10-
upload_bundle_id = params[:upload_bundle_id]
11-
upload_bundle = UploadBundle.find(project_id, upload_bundle_id)
12-
render partial: '/projects/show/upload_files', layout: false, locals: { bundle: upload_bundle }
12+
render partial: '/projects/show/upload_files', layout: false, locals: { bundle: @upload_bundle }
1313
end
1414

1515
# JSON based create method to add a local filepath to an upload bundle
1616
def create
17-
project_id = params[:project_id]
18-
upload_bundle_id = params[:upload_bundle_id]
19-
upload_bundle = UploadBundle.find(project_id, upload_bundle_id)
20-
if upload_bundle.nil?
21-
head :not_found
22-
return
23-
end
24-
2517
path = params[:path]
2618
files = list_files(path)
2719
upload_files = files.map do |file|
2820
UploadFile.new.tap do |f|
2921
f.id = UploadFile.generate_id
30-
f.project_id = project_id
31-
f.upload_bundle_id = upload_bundle_id
22+
f.project_id = @upload_bundle.project_id
23+
f.upload_bundle_id = @upload_bundle.id
3224
f.creation_date = now
3325
f.file_location = file.fullpath
3426
f.filename = file.filename
@@ -40,69 +32,94 @@ def create
4032
upload_files.each do |file|
4133
unless file.valid?
4234
errors = file.errors.full_messages.join(", ")
43-
log_error('UploadFile validation error', {error: errors, project_id: project_id, upload_bundle_id: upload_bundle_id, file: file.to_s})
35+
log_error('UploadFile validation error', {error: errors, project_id: @upload_bundle.project_id, upload_bundle_id: @upload_bundle.id, file: file.to_s})
4436
render json: { message: t(".invalid_file", filename: file.filename, errors: errors) }, status: :bad_request
4537
return
4638
end
4739
end
4840

4941
upload_files.each do |file|
5042
file.save
51-
log_info('Added file to upload bundle', {project_id: project_id, upload_bundle_id: upload_bundle_id, file: file.filename})
43+
log_info('Added file to upload bundle', {project_id: @upload_bundle.project_id, upload_bundle_id: @upload_bundle.id, file: file.filename})
5244
end
5345

5446
message = upload_files.size > 1 ? t(".files_added", count: upload_files.size, path_folder: path) : t(".file_added", filename: upload_files.first.filename)
5547
render json: { message: message }, status: :ok
5648
end
5749

5850
def destroy
59-
project_id = params[:project_id]
60-
upload_bundle_id = params[:upload_bundle_id]
61-
file_id = params[:id]
62-
upload_file = UploadFile.find(project_id, upload_bundle_id, file_id)
51+
if @upload_file.status.uploading?
52+
return redirect_back fallback_location: root_path, alert: t(".file_in_progress", filename: @upload_file.filename)
53+
end
6354

64-
if upload_file.nil?
65-
redirect_back fallback_location: root_path, alert: t(".file_not_found", file_id: file_id, project_id: project_id)
66-
return
55+
@upload_file.destroy
56+
log_upload_file_event(@upload_file, message: 'events.upload_file.deleted')
57+
log_info('Upload file deleted', {id: @upload_file.id, filename: @upload_file.filename})
58+
redirect_back fallback_location: root_path, notice: t(".upload_file_removed", filename: @upload_file.filename)
59+
end
60+
61+
def cancel
62+
previous_status = @upload_file.status.to_s
63+
if @upload_file.status.uploading?
64+
command_client = Command::CommandClient.new(socket_path: ::Configuration.command_server_socket_file)
65+
request = Command::Request.new(command: 'upload.cancel', body: { project_id: @upload_file.project_id, upload_bundle_id: @upload_file.upload_bundle_id, file_id: @upload_file.id})
66+
response = command_client.request(request)
67+
return redirect_back fallback_location: root_path, alert: t('.file_cancellation_error', filename: @upload_file.filename) if response.status != 200
6768
end
6869

69-
if upload_file.status.uploading?
70-
redirect_back fallback_location: root_path, alert: t(".file_in_progress", filename: upload_file.filename)
71-
return
70+
if @upload_file.update(status: FileStatus::CANCELLED)
71+
log_upload_file_event(@upload_file, message: 'events.upload_file.cancel_completed', metadata: { previous_status: previous_status })
72+
log_info('Upload file cancelled', {id: @upload_file.id, filename: @upload_file.filename})
73+
redirect_back fallback_location: root_path, notice: t('.file_cancellation_success', filename: @upload_file.filename)
74+
else
75+
redirect_back fallback_location: root_path, notice: t('.file_cancellation_update_error', filename: @upload_file.filename)
7276
end
77+
end
7378

74-
upload_file.destroy
75-
redirect_back fallback_location: root_path, notice: t(".upload_file_removed", filename: upload_file.filename)
79+
def retry
80+
status = @upload_file.status
81+
unless FileStatus.retryable_statuses.include?(status)
82+
return redirect_back fallback_location: root_path, alert: t('.file_retry_invalid', status: status)
83+
end
84+
if @upload_file.update(status: FileStatus::PENDING)
85+
log_upload_file_event(@upload_file, message: 'events.upload_file.retry_request', metadata: { previous_status: status })
86+
log_info('Upload file retry requested', {id: @upload_file.id, filename: @upload_file.filename})
87+
redirect_back fallback_location: root_path, notice: t('.file_retry_success', filename: @upload_file.filename)
88+
else
89+
redirect_back fallback_location: root_path, alert: t('.file_retry_error', filename: @upload_file.filename)
90+
end
7691
end
7792

78-
def cancel
93+
private
94+
95+
def find_upload_file
7996
project_id = params[:project_id]
8097
upload_bundle_id = params[:upload_bundle_id]
8198
file_id = params[:id]
82-
file = UploadFile.find(project_id, upload_bundle_id, file_id)
99+
@upload_file = UploadFile.find(project_id, upload_bundle_id, file_id)
83100

84-
if file.nil?
85-
return redirect_back fallback_location: root_path, alert: t(".file_not_found", project_id: project_id, upload_bundle_id: upload_bundle_id, file_id: file_id)
86-
end
87-
88-
previous_status = file.status.to_s
89-
if file.status.uploading?
90-
command_client = Command::CommandClient.new(socket_path: ::Configuration.command_server_socket_file)
91-
request = Command::Request.new(command: 'upload.cancel', body: { project_id: project_id, upload_bundle_id: upload_bundle_id, file_id: file_id})
92-
response = command_client.request(request)
93-
return redirect_back fallback_location: root_path, alert: t('.file_cancellation_error', filename: file.filename) if response.status != 200
101+
if @upload_file.nil?
102+
redirect_back fallback_location: root_path, alert: t("upload_files.file_not_found", project_id: project_id, upload_bundle_id: upload_bundle_id, file_id: file_id)
103+
return
94104
end
105+
end
95106

96-
if file.update(status: FileStatus::CANCELLED)
97-
log_upload_file_event(file, message: 'events.upload_file.cancel_completed', metadata: { filename: file.filename, previous_status: previous_status })
98-
redirect_back fallback_location: root_path, notice: t('.file_cancellation_success', filename: file.filename)
99-
else
100-
redirect_back fallback_location: root_path, notice: t('.file_cancellation_update_error', filename: file.filename)
107+
def find_bundle
108+
project_id = params[:project_id]
109+
upload_bundle_id = params[:upload_bundle_id]
110+
@upload_bundle = UploadBundle.find(project_id, upload_bundle_id)
111+
112+
if @upload_bundle.nil?
113+
message = t("upload_files.bundle_not_found", project_id: project_id, upload_bundle_id: upload_bundle_id)
114+
if ajax_request?
115+
render json: { message: message }, status: :not_found
116+
else
117+
redirect_back fallback_location: root_path, alert: message
118+
end
119+
return
101120
end
102121
end
103122

104-
private
105-
106123
def list_files(path, limit: 100)
107124
return [] unless File.exist?(path)
108125

application/app/models/event.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def initialize(project_id:, entity_type:, entity_id: nil, message:, metadata: {}
1414
@entity_id = entity_id
1515
@message = message
1616
@creation_date = creation_date || DateTimeCommon.now
17-
@metadata = metadata || {}
17+
@metadata = stringify_metadata(metadata)
1818
end
1919

2020
def to_h
@@ -25,11 +25,20 @@ def to_h
2525

2626
def self.from_hash(data)
2727
new(project_id: data['project_id'],
28-
entity_type: data['entity_type'].to_s.downcase,
28+
entity_type: data['entity_type'],
2929
entity_id: data['entity_id'],
3030
message: data['message'],
3131
metadata: data['metadata'],
3232
id: data['id'],
3333
creation_date: data['creation_date'])
3434
end
35+
36+
private
37+
38+
# Convert all keys to strings, all values to strings
39+
def stringify_metadata(input)
40+
return {} if input.nil?
41+
42+
input.transform_values(&:to_s)
43+
end
3544
end

application/app/views/download_status/_files.html.erb

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
</div>
2929
</div>
3030

31-
<div class="download-actions col-md-2 text-end d-flex justify-content-end align-items-center" role="cell">
31+
<div class="download-actions col-md-2 text-end d-flex justify-content-end align-items-center gap-1" role="cell">
3232
<% if data.file.status.downloading? %>
3333
<%= render partial: '/shared/progress_bar', locals: { progress: data.file.connector_status.download_progress, file: data.file } %>
3434
<% else %>
@@ -46,18 +46,16 @@
4646
<span class="visually-hidden"><%= t('.button_view_events_label') %></span>
4747
<% end %>
4848
<% end %>
49-
&nbsp;
49+
5050
<% if retry_button_visible?(data.file) %>
51-
<%= render layout: '/shared/button_to',
51+
<%= render partial: '/shared/button_to',
5252
locals: {
53-
url: project_download_file_path(project_id: data.file.project_id, id: data.file.id),
54-
method: :patch,
53+
url: retry_project_download_file_path(project_id: data.file.project_id, id: data.file.id),
54+
method: :post,
5555
icon: 'bi bi-arrow-clockwise',
5656
class: 'btn-icon-sm btn-outline-dark icon-hover-success',
5757
title: t('.button_retry_download_title')
58-
} do %>
59-
<%= hidden_field_tag :state, FileStatus::PENDING.to_s %>
60-
<% end %>
58+
}%>
6159
<% else %>
6260
<%= render partial: '/shared/button_to',
6361
locals: {

application/app/views/projects/show/_download_files.html.erb

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,15 @@
5252
<%= render partial: '/shared/file_row_date', locals: { date: file.start_date, title: t('.field_download_start_date_title'), classes: 'mx-2'} if file.status.downloading? %>
5353

5454
<% if retry_button_visible?(file) %>
55-
<%= render layout: '/shared/button_to',
55+
<%= render partial: '/shared/button_to',
5656
locals: {
57-
url: project_download_file_path(project_id: file.project_id, id: file.id),
58-
method: :patch,
57+
url: retry_project_download_file_path(project_id: file.project_id, id: file.id),
58+
method: :post,
5959
icon: 'bi bi-arrow-clockwise',
6060
class: 'btn-icon-sm btn-outline-secondary icon-hover-success',
6161
title: t('.button_retry_download_title'),
6262
button_id: "retry-download-file-#{file.id}-btn"
63-
} do %>
64-
<%= hidden_field_tag :state, FileStatus::PENDING.to_s %>
65-
<% end %>
63+
} %>
6664
<% end %>
6765

6866
<%= render layout: 'shared/button_to', locals: {

application/app/views/projects/show/_upload_files.html.erb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,19 @@
4444
<%= render partial: '/shared/file_row_date', locals: { date: file.end_date, title: t('.field_completion_date_title'), classes: 'mx-2'} if file.end_date %>
4545
<%= render partial: '/shared/file_row_date', locals: { date: file.start_date, title: t('.field_download_start_date_title'), classes: 'mx-2'} if file.status.downloading? %>
4646

47+
<% if retry_button_visible?(file) %>
48+
<%= render layout: '/shared/button_to',
49+
locals: {
50+
url: retry_project_upload_bundle_upload_file_path(project_id: file.project_id, upload_bundle_id: file.upload_bundle_id, id: file.id),
51+
method: :post,
52+
icon: 'bi bi-arrow-clockwise',
53+
class: 'btn-icon-sm btn-outline-secondary icon-hover-success',
54+
title: t('.button_retry_upload_title')
55+
} do %>
56+
<%= hidden_field_tag :anchor, tab_anchor_for(bundle) %>
57+
<% end %>
58+
<% end %>
59+
4760
<%= render layout: "shared/button_to", locals: {
4861
url: project_upload_bundle_upload_file_path(project_id: bundle.project_id, upload_bundle_id: bundle.id, id: file.id),
4962
method: 'DELETE',

0 commit comments

Comments
 (0)