mirror of
https://github.com/System-End/hackatime.git
synced 2026-04-19 19:55:16 +00:00
feat: add last seven days route (#266)
This commit is contained in:
parent
f7de55f10f
commit
e37cc2b65a
3 changed files with 212 additions and 9 deletions
|
|
@ -50,8 +50,159 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def stats_last_7_days
|
||||
Time.use_zone(@user.timezone) do
|
||||
# Calculate time range within the user's timezone
|
||||
start_time = (Time.current - 7.days).beginning_of_day
|
||||
end_time = Time.current.end_of_day
|
||||
|
||||
# Convert to Unix timestamps
|
||||
start_timestamp = start_time.to_i
|
||||
end_timestamp = end_time.to_i
|
||||
|
||||
# Get heartbeats in the time range
|
||||
heartbeats = @user.heartbeats.where(time: start_timestamp..end_timestamp)
|
||||
|
||||
# Calculate total seconds
|
||||
total_seconds = heartbeats.duration_seconds.to_i
|
||||
|
||||
# Get unique days
|
||||
days = []
|
||||
heartbeats.pluck(:time).each do |timestamp|
|
||||
day = Time.at(timestamp).in_time_zone(@user.timezone).to_date
|
||||
days << day unless days.include?(day)
|
||||
end
|
||||
days_covered = days.length
|
||||
|
||||
# Calculate daily average
|
||||
daily_average = days_covered > 0 ? (total_seconds.to_f / days_covered).round(1) : 0
|
||||
|
||||
# Format human readable strings
|
||||
hours = total_seconds / 3600
|
||||
minutes = (total_seconds % 3600) / 60
|
||||
human_readable_total = "#{hours} hrs #{minutes} mins"
|
||||
|
||||
avg_hours = daily_average.to_i / 3600
|
||||
avg_minutes = (daily_average.to_i % 3600) / 60
|
||||
human_readable_daily_average = "#{avg_hours} hrs #{avg_minutes} mins"
|
||||
|
||||
# Calculate statistics for different categories
|
||||
editors_data = calculate_category_stats(heartbeats, "editor")
|
||||
languages_data = calculate_category_stats(heartbeats, "language")
|
||||
projects_data = calculate_category_stats(heartbeats, "project")
|
||||
machines_data = calculate_category_stats(heartbeats, "machine")
|
||||
os_data = calculate_category_stats(heartbeats, "operating_system")
|
||||
|
||||
# Categories data
|
||||
hours = total_seconds / 3600
|
||||
minutes = (total_seconds % 3600) / 60
|
||||
seconds = total_seconds % 60
|
||||
|
||||
categories = [
|
||||
{
|
||||
name: "coding",
|
||||
total_seconds: total_seconds,
|
||||
percent: 100.0,
|
||||
digital: format("%d:%02d:%02d", hours, minutes, seconds),
|
||||
text: human_readable_total,
|
||||
hours: hours,
|
||||
minutes: minutes,
|
||||
seconds: seconds
|
||||
}
|
||||
]
|
||||
|
||||
result = {
|
||||
data: {
|
||||
username: @user.slack_uid,
|
||||
user_id: @user.slack_uid,
|
||||
start: start_time.iso8601,
|
||||
end: end_time.iso8601,
|
||||
status: "ok",
|
||||
total_seconds: total_seconds,
|
||||
daily_average: daily_average,
|
||||
days_including_holidays: days_covered,
|
||||
range: "last_7_days",
|
||||
human_readable_range: "Last 7 Days",
|
||||
human_readable_total: human_readable_total,
|
||||
human_readable_daily_average: human_readable_daily_average,
|
||||
is_coding_activity_visible: true,
|
||||
is_other_usage_visible: true,
|
||||
editors: editors_data,
|
||||
languages: languages_data,
|
||||
machines: machines_data,
|
||||
projects: projects_data,
|
||||
operating_systems: os_data,
|
||||
categories: categories
|
||||
}
|
||||
}
|
||||
|
||||
render json: result
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def calculate_category_stats(heartbeats, category)
|
||||
return [] if heartbeats.empty?
|
||||
|
||||
# Manual calculation approach to avoid SQL issues
|
||||
category_durations = {}
|
||||
|
||||
# First, group heartbeats by category
|
||||
grouped_heartbeats = {}
|
||||
heartbeats.each do |hb|
|
||||
category_value = hb.send(category) || "unknown"
|
||||
grouped_heartbeats[category_value] ||= []
|
||||
grouped_heartbeats[category_value] << hb
|
||||
end
|
||||
|
||||
# Calculate duration for each category
|
||||
grouped_heartbeats.each do |name, hbs|
|
||||
duration = 0
|
||||
hbs = hbs.sort_by(&:time)
|
||||
|
||||
prev_time = nil
|
||||
hbs.each do |hb|
|
||||
current_time = hb.time
|
||||
if prev_time && (current_time - prev_time) <= 120 # 2-minute timeout
|
||||
duration += (current_time - prev_time)
|
||||
end
|
||||
prev_time = current_time
|
||||
end
|
||||
|
||||
# Add a final 2 minutes for the last heartbeat if we have any
|
||||
duration += 120 if hbs.any?
|
||||
|
||||
category_durations[name] = duration
|
||||
end
|
||||
|
||||
# Calculate total duration for percentage calculations
|
||||
total_duration = category_durations.values.sum.to_f
|
||||
return [] if total_duration == 0
|
||||
|
||||
# Format the data for each category
|
||||
category_durations.map do |name, duration|
|
||||
name = name.presence || "unknown"
|
||||
percent = ((duration / total_duration) * 100).round(2)
|
||||
hours = duration.to_i / 3600
|
||||
minutes = (duration.to_i % 3600) / 60
|
||||
seconds = duration.to_i % 60
|
||||
digital = format("%d:%02d:%02d", hours, minutes, seconds)
|
||||
text = "#{hours} hrs #{minutes} mins"
|
||||
|
||||
{
|
||||
name: name,
|
||||
total_seconds: duration.to_i,
|
||||
percent: percent,
|
||||
digital: digital,
|
||||
text: text,
|
||||
hours: hours,
|
||||
minutes: minutes,
|
||||
seconds: seconds
|
||||
}
|
||||
end.sort_by { |item| -item[:total_seconds] }
|
||||
end
|
||||
|
||||
def set_raw_heartbeat_upload
|
||||
@raw_heartbeat_upload = RawHeartbeatUpload.create!(
|
||||
request_headers: headers_to_json,
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ Rails.application.routes.draw do
|
|||
get "/", to: "hackatime#index" # many clients seem to link this as the user's dashboard
|
||||
get "/users/:id/statusbar/today", to: "hackatime#status_bar_today"
|
||||
post "/users/:id/heartbeats", to: "hackatime#push_heartbeats"
|
||||
get "/users/current/stats/last_7_days", to: "hackatime#stats_last_7_days"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
69
db/seeds.rb
69
db/seeds.rb
|
|
@ -8,10 +8,13 @@ if Rails.env.development?
|
|||
test_user = User.find_or_create_by(slack_uid: 'TEST123456') do |user|
|
||||
user.username = 'testuser'
|
||||
user.is_admin = true
|
||||
# Ensure timezone is set to avoid nil timezone issues
|
||||
user.timezone = 'America/New_York'
|
||||
end
|
||||
|
||||
# Add email address
|
||||
# Add email address with slack as the source
|
||||
email = test_user.email_addresses.find_or_create_by(email: 'test@example.com')
|
||||
email.update(source: :slack) if email.source.nil?
|
||||
|
||||
# Create API key
|
||||
api_key = test_user.api_keys.find_or_create_by(name: 'Development API Key') do |key|
|
||||
|
|
@ -30,18 +33,66 @@ if Rails.env.development?
|
|||
puts " API Key: #{api_key.token}"
|
||||
puts " Sign-in Token: #{token.token}"
|
||||
|
||||
# Create sample heartbeats
|
||||
if test_user.heartbeats.count == 0
|
||||
5.times do |i|
|
||||
# Create sample heartbeats for last 7 days with variety of data
|
||||
if test_user.heartbeats.count < 50
|
||||
# Ensure timezone is set
|
||||
test_user.update!(timezone: 'America/New_York') unless test_user.timezone.present?
|
||||
|
||||
# Create diverse test data over the last 7 days
|
||||
editors = [ 'Zed', 'Neovim', 'VSCode', 'Emacs' ]
|
||||
languages = [ 'Ruby', 'JavaScript', 'TypeScript', 'Python', 'Go', 'HTML', 'CSS', 'Markdown' ]
|
||||
projects = [ 'panorama', 'harbor', 'zera', 'tern', 'smokie' ]
|
||||
operating_systems = [ 'Linux', 'macOS', 'Windows' ]
|
||||
machines = [ 'dev-machine', 'laptop', 'desktop' ]
|
||||
|
||||
# Clear existing heartbeats to ensure consistent test data
|
||||
test_user.heartbeats.destroy_all
|
||||
|
||||
# Create heartbeats for the last 7 days
|
||||
7.downto(0) do |day|
|
||||
# Add 5-20 heartbeats per day
|
||||
heartbeat_count = rand(5..20)
|
||||
heartbeat_count.times do |i|
|
||||
# Distribute throughout the day
|
||||
hour = rand(9..20) # Between 9 AM and 8 PM
|
||||
minute = rand(0..59)
|
||||
second = rand(0..59)
|
||||
|
||||
# Create timestamp for this heartbeat
|
||||
timestamp = (Time.current - day.days).beginning_of_day + hour.hours + minute.minutes + second.seconds
|
||||
|
||||
# Create the heartbeat with varied data
|
||||
test_user.heartbeats.create!(
|
||||
time: timestamp.to_i,
|
||||
entity: "test/file_#{rand(1..30)}.#{[ 'rb', 'js', 'ts', 'py', 'go' ].sample}",
|
||||
project: projects.sample,
|
||||
language: languages.sample,
|
||||
editor: editors.sample,
|
||||
operating_system: operating_systems.sample,
|
||||
machine: machines.sample,
|
||||
category: "coding",
|
||||
source_type: :direct_entry
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Create a few sequential heartbeats to properly test duration calculation
|
||||
base_time = Time.current - 2.days
|
||||
10.times do |i|
|
||||
test_user.heartbeats.create!(
|
||||
time: (Time.current - i.hours).to_f,
|
||||
entity: "test/file_#{i}.rb",
|
||||
project: "test-project",
|
||||
language: "ruby",
|
||||
time: (base_time + i.minutes).to_i,
|
||||
entity: "test/sequential_file.rb",
|
||||
project: "harbor",
|
||||
language: "Ruby",
|
||||
editor: "Zed",
|
||||
operating_system: "Linux",
|
||||
machine: "dev-machine",
|
||||
category: "coding",
|
||||
source_type: :direct_entry
|
||||
)
|
||||
end
|
||||
puts "Created 5 sample heartbeats for the test user"
|
||||
|
||||
puts "Created comprehensive heartbeat data over the last 7 days for the test user"
|
||||
else
|
||||
puts "Sample heartbeats already exist for the test user"
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue