Module: ExportTracking

Extended by:
ActiveSupport::Concern
Included in:
Partner::BookingExportWorker
Defined in:
app/workers/concerns/export_tracking.rb

Overview

Export tracking concern for background workers. Provides integration with PartnerService::Reservations::Exports::TrackingService while maintaining backward compatibility with existing Sidekiq::Status usage.

Note: TrackingService uses manual Redis operations instead of native Sidekiq::Status methods for compatibility. See TrackingService#store_sidekiq_status for details.

Examples:

Usage in worker

class MyExportWorker < ApplicationWorker
  include ExportTracking

  def perform(args, attachment_id = nil)
    setup_enhanced_tracking(attachment_id)

    with_export_tracking do
      # Data collection
      track_data_collection do
        collect_data_with_progress
      end

      # File generation
      track_file_generation do
        generate_file_with_progress
      end

      # Email sending
      track_email_sending do
        send_emails_with_progress
      end
    end
  end
end

Instance Method Summary collapse

Instance Method Details

#complete_export_tracking(download_url) ⇒ Object

Complete export with download URL

Parameters:

  • download_url (String)

    URL for downloading the export



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'app/workers/concerns/export_tracking.rb', line 281

def complete_export_tracking(download_url)
  if @enhanced_tracker
    @enhanced_tracker.complete_export(download_url)
  else
    # Legacy tracking - ensure both Sidekiq status and attachment are updated
    legacy_at(100)
    legacy_store(status: Attachment::DONE_STATUS)

    # Update attachment finished_at timestamp, status, and download_link for legacy tracking
    if @attachment.present?
      current_time = Time.current
      update_data = {
        status: :done,
        finished_at: current_time,
      }

      # Only update download_link if it's not already set and we have a valid download_url
      if download_url.present? && @attachment.download_link.blank?
        # Format download_link similar to TrackingService.format_download_link
        formatted_link = if download_url.is_a?(Hash)
                           download_url
                         else
                           { 'file_1' => download_url }
                         end
        update_data[:download_link] = formatted_link
      end

      begin
        @attachment.update!(update_data)
      rescue StandardError => e
        HH_LOGGER.error("Failed to update attachment #{@attachment.id} in legacy mode: #{e.message}")
        # Try to update without download_link in case that's causing issues
        @attachment.update!(status: :done, finished_at: current_time)
      end
    end
  end
end

#complete_export_tracking_fallback(download_url) ⇒ Object

Fallback completion method when enhanced tracking fails

Parameters:

  • download_url (String)

    URL for downloading the export



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'app/workers/concerns/export_tracking.rb', line 246

def complete_export_tracking_fallback(download_url)
  if @attachment.present?
    current_time = Time.current
    update_data = {
      status: :done,
      finished_at: current_time,
    }

    # Only update download_link if it's not already set and we have a valid download_url
    if download_url.present? && @attachment.download_link.blank?
      formatted_link = if download_url.is_a?(Hash)
                         download_url
                       else
                         { 'file_1' => download_url }
                       end
      update_data[:download_link] = formatted_link
    end

    begin
      @attachment.update!(update_data)
    rescue StandardError => e
      HH_LOGGER.error("Failed to update attachment #{@attachment.id} in fallback mode: #{e.message}")
      # Try to update without download_link in case that's causing issues
      @attachment.update!(status: :done, finished_at: current_time)
    end
  end

  # Update Sidekiq status as well
  legacy_at(100)
  legacy_store(status: Attachment::DONE_STATUS)
end

#create_progress_tracker(attachment, job_id = nil) ⇒ PartnerService::Reservations::Exports::TrackingService

Create and initialize export tracker

Parameters:

  • attachment (Attachment)

    Attachment to track

  • job_id (String) (defaults to: nil)

    Sidekiq job ID

Returns:



346
347
348
# File 'app/workers/concerns/export_tracking.rb', line 346

def create_progress_tracker(attachment, job_id = nil)
  @enhanced_tracker = PartnerService::Reservations::Exports::TrackingService.new(attachment, job_id)
end

#current_export_statusHash

Get current export status

Returns:

  • (Hash)

    Current export status information



322
323
324
325
326
327
328
329
330
331
332
# File 'app/workers/concerns/export_tracking.rb', line 322

def current_export_status
  if @enhanced_tracker
    @enhanced_tracker.current_status
  else
    {
      percentage: legacy_pct_complete,
      message: legacy_message,
      status: legacy_status,
    }
  end
end

#enhanced_tracking_enabled?Boolean

Check if enhanced tracking is enabled

Returns:

  • (Boolean)

    true if enhanced tracking is available



337
338
339
# File 'app/workers/concerns/export_tracking.rb', line 337

def enhanced_tracking_enabled?
  @enhanced_tracker.present?
end

#handle_export_error(category, message, _exception = nil, _retry_count = 0) ⇒ Object

Handle errors with enhanced tracking (no notifications sent)

Parameters:

  • category (Symbol)

    Error category

  • message (String)

    Error message

  • exception (Exception)

    The exception

  • retry_count (Integer)

    Retry attempt number



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'app/workers/concerns/export_tracking.rb', line 226

def handle_export_error(category, message, _exception = nil, _retry_count = 0)
  error_message = "#{category}: #{message}"

  if @enhanced_tracker
    # Store error in message instead of APM reporting
    @enhanced_tracker.attachment&.update!(status: :failed)
    # Use the minimum percentage of the current phase to avoid range validation errors
    current_phase = @enhanced_tracker.current_phase
    min_percentage = ExportConstants::PROGRESS_PHASES[current_phase]&.first || 0
    @enhanced_tracker.update_progress(current_phase, min_percentage, error_message)
  else
    # Legacy error handling - store error in message
    legacy_store(status: Attachment::FAILED_STATUS)
    legacy_store(message: error_message)
  end
end

#progress_trackerPartnerService::Reservations::Exports::TrackingService

Get current export tracker

Returns:



353
354
355
# File 'app/workers/concerns/export_tracking.rb', line 353

def progress_tracker
  @enhanced_tracker
end

#setup_enhanced_tracking(attachment_id, enable_legacy = true) ⇒ Object

Setup enhanced tracking for the export process

Parameters:

  • attachment_id (Integer)

    ID of attachment to track

  • enable_legacy (Boolean) (defaults to: true)

    Whether to maintain legacy Sidekiq::Status calls



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'app/workers/concerns/export_tracking.rb', line 51

def setup_enhanced_tracking(attachment_id, enable_legacy = true)
  @legacy_tracking_enabled = enable_legacy

  if attachment_id.present?
    attachment = Attachment.find_by(id: attachment_id)
    if attachment
      @attachment = attachment # Set the attachment for the worker to use
      @enhanced_tracker = create_progress_tracker(attachment, jid)
      return true
    end
  end

  # Fallback to legacy tracking only
  @enhanced_tracker = nil
  @attachment = nil
  false
end

#track_data_collection(_total_items = nil) { ... } ⇒ Object

Track data collection phase with progress updates

Parameters:

  • total_items (Integer)

    Total number of items to process

Yields:

  • Block containing data collection logic



105
106
107
108
109
110
111
112
113
114
# File 'app/workers/concerns/export_tracking.rb', line 105

def track_data_collection(_total_items = nil, &block)
  if @enhanced_tracker
    with_progress_tracking(:collecting_data, 'Collecting reservation data...', :data_collection, &block)
  else
    legacy_at(5)
    result = yield
    legacy_at(30)
    result
  end
end

#track_email_sending(_total_emails = nil) { ... } ⇒ Object

Track email sending phase with progress updates

Parameters:

  • total_emails (Integer)

    Total number of emails to send

Yields:

  • Block containing email sending logic



149
150
151
152
153
154
155
156
157
158
# File 'app/workers/concerns/export_tracking.rb', line 149

def track_email_sending(_total_emails = nil, &block)
  if @enhanced_tracker
    with_progress_tracking(:sending_email, 'Sending notification emails...', :email_sending, &block)
  else
    legacy_at(90)
    result = yield
    legacy_at(95)
    result
  end
end

#track_file_generation(_total_rows = nil) { ... } ⇒ Object

Track file generation phase with progress updates

Parameters:

  • total_rows (Integer)

    Total number of rows to generate

Yields:

  • Block containing file generation logic



120
121
122
123
124
125
126
127
128
129
# File 'app/workers/concerns/export_tracking.rb', line 120

def track_file_generation(_total_rows = nil, &block)
  if @enhanced_tracker
    with_progress_tracking(:generating_file, 'Generating export file...', :file_generation, &block)
  else
    legacy_at(30)
    result = yield
    legacy_at(80)
    result
  end
end

#track_file_upload { ... } ⇒ Object

Track file upload phase with progress updates

Yields:

  • Block containing file upload logic



134
135
136
137
138
139
140
141
142
143
# File 'app/workers/concerns/export_tracking.rb', line 134

def track_file_upload(&block)
  if @enhanced_tracker
    with_progress_tracking(:uploading_file, 'Uploading file to storage...', :file_upload, &block)
  else
    legacy_at(80)
    result = yield
    legacy_at(90)
    result
  end
end

#update_data_collection_progress(current_batch, total_batches, message = nil) ⇒ Object

Update progress during data collection

Parameters:

  • current_batch (Integer)

    Current batch number

  • total_batches (Integer)

    Total number of batches

  • message (String) (defaults to: nil)

    Optional progress message



165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'app/workers/concerns/export_tracking.rb', line 165

def update_data_collection_progress(current_batch, total_batches, message = nil)
  if @enhanced_tracker
    # Calculate percentage directly for data collection phase (5-30% range)
    batch_percentage = total_batches > 0 ? (current_batch.to_f / total_batches) : 0
    percentage = 5 + (batch_percentage * 25).round
    default_message = "Processing batch #{current_batch + 1}/#{total_batches}"
    @enhanced_tracker.update_progress(:collecting_data, percentage, message || default_message)
  else
    # Legacy progress calculation
    progress = 5 + ((current_batch.to_f / total_batches) * 25).round
    legacy_at(progress)
  end
end

#update_email_sending_progress(emails_sent, total_emails, message = nil) ⇒ Object

Update progress during email sending

Parameters:

  • emails_sent (Integer)

    Number of emails sent

  • total_emails (Integer)

    Total number of emails

  • message (String) (defaults to: nil)

    Optional progress message



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'app/workers/concerns/export_tracking.rb', line 203

def update_email_sending_progress(emails_sent, total_emails, message = nil)
  if @enhanced_tracker
    # Calculate percentage for email sending phase (90-95% range)
    email_percentage = total_emails > 0 ? (emails_sent.to_f / total_emails) : 0
    # Ensure percentage stays within the valid range for sending_email phase (90-95%)
    percentage = 90 + (email_percentage * 5).round
    # Cap at 95% to stay within the valid range
    percentage = [percentage, 95].min
    default_message = "Sent #{emails_sent}/#{total_emails} emails"
    @enhanced_tracker.update_progress(:sending_email, percentage, message || default_message)
  else
    # Legacy progress calculation
    progress = 90 + ((emails_sent.to_f / total_emails) * 5).round
    legacy_at(progress)
  end
end

#update_file_generation_progress(current_row, total_rows, message = nil) ⇒ Object

Update progress during file generation

Parameters:

  • current_row (Integer)

    Current row number

  • total_rows (Integer)

    Total number of rows

  • message (String) (defaults to: nil)

    Optional progress message



184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'app/workers/concerns/export_tracking.rb', line 184

def update_file_generation_progress(current_row, total_rows, message = nil)
  if @enhanced_tracker
    # Calculate percentage directly for file generation phase (30-80% range)
    row_percentage = total_rows > 0 ? (current_row.to_f / total_rows) : 0
    percentage = 30 + (row_percentage * 50).round
    default_message = "Generated #{current_row}/#{total_rows} rows"
    @enhanced_tracker.update_progress(:generating_file, percentage, message || default_message)
  else
    # Legacy progress calculation
    progress = 30 + ((current_row.to_f / total_rows) * 50).round
    legacy_at(progress)
  end
end

#with_export_tracking { ... } ⇒ Object

Execute export with enhanced tracking

Yields:

  • Block containing the export logic



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'app/workers/concerns/export_tracking.rb', line 72

def with_export_tracking
  if @enhanced_tracker
    @enhanced_tracker.start_export

    begin
      yield

      # Note: Export completion is now handled by the worker after files are ready
      # This ensures finished_at is set only when download files are actually available
    rescue StandardError => e
      @enhanced_tracker.handle_error(:system_error, e.message, e)
      raise e
    end
  else
    # Fallback to legacy tracking
    legacy_total(100)
    legacy_at(0)
    legacy_store(status: Attachment::ON_PROGRESS_STATUS)

    begin
      yield
      # Note: Completion is now handled by the worker after files are ready
    rescue StandardError => e
      legacy_store(status: Attachment::FAILED_STATUS)
      raise e
    end
  end
end