From 5143639f3295cc15ca78d9b9d1102003b885e732 Mon Sep 17 00:00:00 2001 From: "Tom (Deployor)" <129990841+deployor@users.noreply.github.com> Date: Fri, 8 Aug 2025 04:38:15 +0200 Subject: [PATCH] Stream Export (#460) Co-authored-by: Jeffrey Wang <66625372+JeffreyWangDev@users.noreply.github.com> --- app/controllers/my/heartbeats_controller.rb | 90 ++++++++++++++------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/app/controllers/my/heartbeats_controller.rb b/app/controllers/my/heartbeats_controller.rb index 61811f6..f16154c 100644 --- a/app/controllers/my/heartbeats_controller.rb +++ b/app/controllers/my/heartbeats_controller.rb @@ -1,8 +1,8 @@ module My class HeartbeatsController < ApplicationController + include ActionController::Live before_action :ensure_current_user - def export all_data = params[:all_data] == "true" if all_data @@ -15,29 +15,59 @@ module My end_date = Date.current end else - start_date = params[:start_date].present? ? Date.parse(params[:start_date]) : 30.days.ago.to_date - end_date = params[:end_date].present? ? Date.parse(params[:end_date]) : Date.current + start_date = + params[:start_date].present? ? + Date.parse(params[:start_date]) : + 30.days.ago.to_date + end_date = + params[:end_date].present? ? + Date.parse(params[:end_date]) : + Date.current start_time = start_date.beginning_of_day.to_f end_time = end_date.end_of_day.to_f - heartbeats = current_user.heartbeats - .where("time >= ? AND time <= ?", start_time, end_time) - .order(time: :asc) + heartbeats = + current_user.heartbeats.where( + "time >= ? AND time <= ?", + start_time, + end_time + ).order(time: :asc) end + total_heartbeats = heartbeats.count + total_duration_seconds = heartbeats.duration_seconds - export_data = { - export_info: { - exported_at: Time.current.iso8601, - date_range: { - start_date: start_date.iso8601, - end_date: end_date.iso8601 - }, - total_heartbeats: heartbeats.count, - total_duration_seconds: heartbeats.duration_seconds - }, - heartbeats: heartbeats.map do |heartbeat| - { + filename = + "heartbeats_#{current_user.slack_uid}_#{start_date.strftime('%Y%m%d')}_#{end_date.strftime('%Y%m%d')}.json" + + response.headers["Content-Type"] = "application/json" + response.headers["Content-Disposition"] = + "attachment; filename=\"#{filename}\"" + response.headers["X-Accel-Buffering"] = "no" + + response.stream.write "{" + response.stream.write "\"export_info\": {" + response.stream.write "\"exported_at\": \"" + Time.current.iso8601 + "\"," + response.stream.write "\"date_range\": {" + response.stream.write "\"start_date\": \"" + start_date.iso8601 + "\"," + response.stream.write "\"end_date\": \"" + end_date.iso8601 + "\"" + response.stream.write "}," + response.stream.write "\"total_heartbeats\": " + total_heartbeats.to_s + "," + response.stream.write( + "\"total_duration_seconds\": " + total_duration_seconds.to_s + ) + response.stream.write "}," + response.stream.write "\"heartbeats\": [" + + first = true + heartbeats.find_in_batches(batch_size: 1000) do |batch| + batch.each do |heartbeat| + if first + first = false + else + response.stream.write "," + end + hb_json = { id: heartbeat.id, time: Time.at(heartbeat.time).iso8601, entity: heartbeat.entity, @@ -60,26 +90,24 @@ module My source_type: heartbeat.source_type, created_at: heartbeat.created_at.iso8601, updated_at: heartbeat.updated_at.iso8601 - } + }.to_json + response.stream.write hb_json end - } - - filename = "heartbeats_#{current_user.slack_uid}_#{start_date.strftime('%Y%m%d')}_#{end_date.strftime('%Y%m%d')}.json" - - respond_to do |format| - format.json { - send_data export_data.to_json, - filename: filename, - type: "application/json", - disposition: "attachment" - } end + + response.stream.write "]" + response.stream.write "}" + ensure + response.stream.close end private def ensure_current_user - redirect_to root_path, alert: "You must be logged in to view this page!!" unless current_user + unless current_user + redirect_to root_path, + alert: "You must be logged in to view this page!!" + end end end end