mirror of
https://github.com/System-End/hackatime.git
synced 2026-04-19 23:32:53 +00:00
Add heartbeat import (#469)
This commit is contained in:
parent
fac7758391
commit
9d8cc0d75d
5 changed files with 192 additions and 2 deletions
|
|
@ -64,7 +64,7 @@ module My
|
|||
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 {
|
||||
|
|
@ -76,6 +76,56 @@ module My
|
|||
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
|
||||
|
|
|
|||
105
app/services/heartbeat_import_service.rb
Normal file
105
app/services/heartbeat_import_service.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ Rails.application.routes.draw do
|
|||
resources :heartbeats, only: [] do
|
||||
collection do
|
||||
get :export
|
||||
post :import
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ if Rails.env.development?
|
|||
# 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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue