Skip to content

Add heartbeat import #469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion app/controllers/my/heartbeats_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def export
end
}

filename = "heartbeats_#{current_user.slack_uid}_#{start_date.strftime('%Y%m%d')}_#{end_date.strftime('%Y%m%d')}.json"
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 {
Expand All @@ -76,6 +76,56 @@ def export
end
end

def import
unless Rails.env.development?
redirect_to my_settings_path, alert: "Hey you! This is noit a dev env, STOP DOING THIS!!!!!) Also, idk why this is happning, you should not be able to see this button hmm...."
return
end

unless params[:heartbeat_file].present?
redirect_to my_settings_path, alert: "pls select a file to import"
return
end

file = params[:heartbeat_file]

unless file.content_type == "application/json" || file.original_filename.ends_with?(".json")
redirect_to my_settings_path, alert: "pls upload only json (download from the button above it)"
return
end

begin
file_content = file.read.force_encoding("UTF-8")
rescue => e
redirect_to my_settings_path, alert: "error reading file: #{e.message}"
return
end

result = HeartbeatImportService.import_from_file(file_content, current_user)

if result[:success]
message = "Imported #{result[:imported_count]} out of #{result[:total_count]} heartbeats"
if result[:skipped_count] > 0
message += " (#{result[:skipped_count]} skipped cause they were duplicates)"
end
if result[:errors].any?
error_count = result[:errors].length
if error_count <= 3
message += ". Errors occurred: #{result[:errors].join("; ")}"
else
message += ". #{error_count} errors occurred. First few: #{result[:errors].first(2).join("; ")}..."
end
end
redirect_to root_path, notice: message
else
error_message = "Import failed: #{result[:error]}"
if result[:errors].any? && result[:errors].length > 1
error_message += "Errors: #{result[:errors][1..2].join("; ")}"
end
redirect_to my_settings_path, alert: error_message
end
end

private

def ensure_current_user
Expand Down
105 changes: 105 additions & 0 deletions app/services/heartbeat_import_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
class HeartbeatImportService
def self.import_from_file(file_content, user)
unless Rails.env.development?
raise StandardError, "Not dev env, not running"
end

begin
parsed_data = JSON.parse(file_content)
rescue JSON::ParserError => e
raise StandardError, "Not json: #{e.message}"
end

unless parsed_data.is_a?(Hash) && parsed_data["heartbeats"].is_a?(Array)
raise StandardError, "Not correct format, download from /my/settings on the offical hackatime then import here"
end

heartbeats_data = parsed_data["heartbeats"]
imported_count = 0
skipped_count = 0
errors = []
cc = 817263
heartbeats_data.each_slice(100) do |batch|
records_to_upsert = []

batch.each_with_index do |heartbeat_data, index|
begin
time_value = if heartbeat_data["time"].is_a?(String)
Time.parse(heartbeat_data["time"]).to_f
else
heartbeat_data["time"].to_f
end

attrs = {
user_id: user.id,
time: time_value,
entity: heartbeat_data["entity"],
type: heartbeat_data["type"],
category: heartbeat_data["category"] || "coding",
project: heartbeat_data["project"],
language: heartbeat_data["language"],
editor: heartbeat_data["editor"],
operating_system: heartbeat_data["operating_system"],
machine: heartbeat_data["machine"],
branch: heartbeat_data["branch"],
user_agent: heartbeat_data["user_agent"],
is_write: heartbeat_data["is_write"] || false,
line_additions: heartbeat_data["line_additions"],
line_deletions: heartbeat_data["line_deletions"],
lineno: heartbeat_data["lineno"],
lines: heartbeat_data["lines"],
cursorpos: heartbeat_data["cursorpos"],
dependencies: heartbeat_data["dependencies"] || [],
project_root_count: heartbeat_data["project_root_count"],
source_type: :wakapi_import,
raw_data: heartbeat_data.slice(*Heartbeat.indexed_attributes)
}

attrs[:fields_hash] = Heartbeat.generate_fields_hash(attrs)
print(attrs[:fields_hash])
print("\n")
records_to_upsert << attrs

rescue => e
errors << "Row #{index + 1}: #{e.message}"
next
end
end

if records_to_upsert.any?
print("importing!!!!!!!!!!!!!!!!!!!!!!")
print("\n")
begin
# Copied from migrate user from hackatime (app\jobs\migrate_user_from_hackatime_job.rb)
records_to_upsert = records_to_upsert.group_by { |r| r[:fields_hash] }.map do |_, records|
records.max_by { |r| r[:time] }
end
result = Heartbeat.upsert_all(records_to_upsert, unique_by: [ :fields_hash ])
imported_count += result.length
rescue => e
errors << "Import error: #{e.message}"
print(e.message)
print("\n")
end
end
end

{
success: true,
imported_count: imported_count,
total_count: heartbeats_data.length,
skipped_count: heartbeats_data.length - imported_count,
errors: errors
}

rescue => e
{
success: false,
error: e.message,
imported_count: 0,
total_count: 0,
skipped_count: 0,
errors: [ e.message ]
}
end
end
32 changes: 32 additions & 0 deletions app/views/users/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,38 @@
</div>
</div>

<% dev_tool do %>
<div class="p-6 bg-gray-800 border border-gray-600 rounded">
<div class="flex items-center gap-3 mb-3">
<div class="p-2 bg-green-600/10 rounded">
<svg class="w-4 h-4 text-green-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
</div>
<h4 class="text-white font-medium">Import Heartbeat Data</h4>
</div>
<p class="text-gray-300 text-sm mb-4">Import ur data from real hackatime to test stuff with.</p>
<p class="text-gray-300 text-sm mb-4">PS: your console will be spammed and might crash ur dev env so be carefull if the file is very big</p>
<%= form_with url: import_my_heartbeats_path, method: :post, multipart: true, local: true, class: "space-y-4" do |form| %>
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">Select JSON File</label>
<%= form.file_field :heartbeat_file,
accept: ".json,application/json",
class: "w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded text-white file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-primary file:text-white hover:file:bg-red transition-colors",
required: true %>
</div>

<div class="flex gap-3">
<%= form.submit "Import Heartbeats",
class: "bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded font-medium transition-colors inline-flex items-center gap-2",
data: { confirm: "Are you sure you want to import heartbeats? This will add new data to your account." } %>
</div>
<% end %>


</div>
<% end %>


</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def matches?(request)
resources :heartbeats, only: [] do
collection do
get :export
post :import
end
end
end
Expand Down
4 changes: 3 additions & 1 deletion db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
# Creating test user
test_user = User.find_or_create_by(slack_uid: 'TEST123456') do |user|
user.username = 'testuser'
user.is_admin = true

# Before you had user.is_admin = true, does not work, changed it to that, looks like it works but idk how to use the admin pages so pls check this, i just guess coded this, the cmd to seed the db works without errors
user.set_admin_level(:superadmin)
# Ensure timezone is set to avoid nil timezone issues
user.timezone = 'America/New_York'
end
Expand Down