mirror of
https://github.com/SrIzan10/hc-harbor.git
synced 2026-05-01 10:45:21 +00:00
feat: use TestWakatimeService
This commit is contained in:
@@ -61,25 +61,33 @@ class Api::V1::StatsController < ApplicationController
|
||||
service_params[:scope] = scope if scope.present?
|
||||
|
||||
if params[:total_seconds] == "true"
|
||||
query = @user.heartbeats
|
||||
.coding_only
|
||||
.with_valid_timestamps
|
||||
.where(time: start_date..end_date)
|
||||
# dis just test and we don't want to affect other services!
|
||||
if params[:test_param] == "true"
|
||||
service_params[:boundary_aware] = params[:boundary_aware] == "true"
|
||||
|
||||
if params[:filter_by_project].present?
|
||||
filter_by_project = params[:filter_by_project].split(",")
|
||||
query = query.where(project: filter_by_project)
|
||||
end
|
||||
|
||||
# do the boundary thingie if requested
|
||||
use_boundary_aware = params[:boundary_aware] == "true"
|
||||
total_seconds = if use_boundary_aware
|
||||
Heartbeat.duration_seconds_boundary_aware(query, start_date.to_f, end_date.to_f) || 0
|
||||
summary = TestWakatimeService.new(**service_params).generate_summary
|
||||
return render json: { total_seconds: summary[:total_seconds] }
|
||||
else
|
||||
query.duration_seconds || 0
|
||||
end
|
||||
query = @user.heartbeats
|
||||
.coding_only
|
||||
.with_valid_timestamps
|
||||
.where(time: start_date..end_date)
|
||||
|
||||
return render json: { total_seconds: total_seconds }
|
||||
if params[:filter_by_project].present?
|
||||
filter_by_project = params[:filter_by_project].split(",")
|
||||
query = query.where(project: filter_by_project)
|
||||
end
|
||||
|
||||
# do the boundary thingie if requested
|
||||
use_boundary_aware = params[:boundary_aware] == "true"
|
||||
total_seconds = if use_boundary_aware
|
||||
Heartbeat.duration_seconds_boundary_aware(query, start_date.to_f, end_date.to_f) || 0
|
||||
else
|
||||
query.duration_seconds || 0
|
||||
end
|
||||
|
||||
return render json: { total_seconds: total_seconds }
|
||||
end
|
||||
end
|
||||
|
||||
summary = WakatimeService.new(**service_params).generate_summary
|
||||
|
||||
164
lib/test_wakatime_service.rb
Normal file
164
lib/test_wakatime_service.rb
Normal file
@@ -0,0 +1,164 @@
|
||||
include ApplicationHelper
|
||||
|
||||
class TestWakatimeService
|
||||
def initialize(user: nil, specific_filters: [], allow_cache: true, limit: 10, start_date: nil, end_date: nil, scope: nil, boundary_aware: false)
|
||||
@scope = scope || Heartbeat.all
|
||||
@user = user
|
||||
@boundary_aware = boundary_aware
|
||||
|
||||
@start_date = convert_to_unix_timestamp(start_date)
|
||||
@end_date = convert_to_unix_timestamp(end_date)
|
||||
|
||||
# apply with_valid_timestamps filter if no custom scope provided-- this is copied from query in stats_controller
|
||||
if scope.nil?
|
||||
@scope = @scope.with_valid_timestamps
|
||||
end
|
||||
|
||||
# Default to 1 year ago if no start_date provided or if no data exists
|
||||
@start_date = @start_date || @scope.minimum(:time) || 1.year.ago.to_i
|
||||
@end_date = @end_date || @scope.maximum(:time) || Time.current.to_i
|
||||
|
||||
@scope = @scope.where("time >= ? AND time < ?", @start_date, @end_date)
|
||||
|
||||
@limit = limit
|
||||
@limit = nil if @limit&.zero?
|
||||
|
||||
@scope = @scope.where(user_id: @user.id) if @user.present?
|
||||
|
||||
@specific_filters = specific_filters
|
||||
@allow_cache = allow_cache
|
||||
end
|
||||
|
||||
def generate_summary
|
||||
summary = {}
|
||||
|
||||
summary[:username] = @user.username if @user.present?
|
||||
summary[:user_id] = @user.id.to_s if @user.present?
|
||||
summary[:is_coding_activity_visible] = true if @user.present?
|
||||
summary[:is_other_usage_visible] = true if @user.present?
|
||||
summary[:status] = "ok"
|
||||
|
||||
@start_time = @start_date
|
||||
@end_time = @end_date
|
||||
|
||||
summary[:start] = Time.at(@start_time).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
summary[:end] = Time.at(@end_time).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
summary[:range] = "all_time"
|
||||
summary[:human_readable_range] = "All Time"
|
||||
|
||||
@total_seconds = if @boundary_aware
|
||||
result = Heartbeat.duration_seconds_boundary_aware(@scope, @start_date, @end_date) || 0
|
||||
result
|
||||
else
|
||||
result = @scope.duration_seconds || 0
|
||||
result
|
||||
end
|
||||
|
||||
summary[:total_seconds] = @total_seconds
|
||||
|
||||
@total_days = (@end_time - @start_time) / 86400
|
||||
summary[:daily_average] = @total_days.zero? ? 0 : @total_seconds / @total_days
|
||||
|
||||
summary[:human_readable_total] = ApplicationController.helpers.short_time_detailed(@total_seconds)
|
||||
summary[:human_readable_daily_average] = ApplicationController.helpers.short_time_detailed(summary[:daily_average])
|
||||
|
||||
summary[:languages] = generate_summary_chunk(:language) if @specific_filters.include?(:languages)
|
||||
summary[:projects] = generate_summary_chunk(:project) if @specific_filters.include?(:projects)
|
||||
|
||||
summary
|
||||
end
|
||||
|
||||
def generate_summary_chunk(group_by)
|
||||
result = []
|
||||
@scope.group(group_by).duration_seconds.each do |key, value|
|
||||
result << {
|
||||
name: key.presence || "Other",
|
||||
total_seconds: value,
|
||||
text: ApplicationController.helpers.short_time_simple(value),
|
||||
hours: value / 3600,
|
||||
minutes: (value % 3600) / 60,
|
||||
percent: (100.0 * value / @total_seconds).round(2),
|
||||
digital: ApplicationController.helpers.digital_time(value)
|
||||
}
|
||||
end
|
||||
result = result.sort_by { |item| -item[:total_seconds] }
|
||||
result = result.first(@limit) if @limit.present?
|
||||
result
|
||||
end
|
||||
|
||||
def self.parse_user_agent(user_agent)
|
||||
# Based on https://github.com/muety/wakapi/blob/b3668085c01dc0724d8330f4d51efd5b5aecaeb2/utils/http.go#L89
|
||||
|
||||
# Regex pattern to match wakatime client user agents
|
||||
user_agent_pattern = /wakatime\/[^ ]+ \(([^)]+)\)(?: [^ ]+ ([^\/]+)(?:\/([^\/]+))?)?/
|
||||
|
||||
if matches = user_agent.match(user_agent_pattern)
|
||||
os = matches[1].split("-").first
|
||||
|
||||
editor = matches[2]
|
||||
editor ||= ""
|
||||
|
||||
{ os: os, editor: editor, err: nil }
|
||||
else
|
||||
# Try parsing as browser user agent as fallback
|
||||
if browser_ua = user_agent.match(/^([^\/]+)\/([^\/\s]+)/)
|
||||
# If "wakatime" is present, assume it's the browser extension
|
||||
if user_agent.include?("wakatime") then
|
||||
full_os = user_agent.split(" ")[1]
|
||||
if full_os.present?
|
||||
os = full_os.include?("_") ? full_os.split("_")[0] : full_os
|
||||
{ os: os, editor: browser_ua[1].downcase, err: nil }
|
||||
else
|
||||
{ os: "", editor: "", err: "failed to parse user agent string" }
|
||||
end
|
||||
else
|
||||
{ os: browser_ua[1], editor: browser_ua[2], err: nil }
|
||||
end
|
||||
else
|
||||
{ os: "", editor: "", err: "failed to parse user agent string" }
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error("Error parsing user agent string: #{e.message}")
|
||||
{ os: "", editor: "", err: "failed to parse user agent string" }
|
||||
end
|
||||
|
||||
def categorize_os(os)
|
||||
case os.downcase
|
||||
when "win" then "Windows"
|
||||
when "darwin" then "MacOS"
|
||||
when os.include?("windows") then "Windows"
|
||||
else os.capitalize
|
||||
end
|
||||
end
|
||||
|
||||
def categorize_editor(editor)
|
||||
case editor.downcase
|
||||
when "vscode" then "VSCode"
|
||||
when "KTextEditor" then "Kate"
|
||||
else editor.capitalize
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def convert_to_unix_timestamp(timestamp)
|
||||
# our lord and savior stack overflow for this bit of code
|
||||
return nil if timestamp.nil?
|
||||
|
||||
case timestamp
|
||||
when String
|
||||
Time.parse(timestamp).to_i
|
||||
when Time, DateTime, Date
|
||||
timestamp.to_i
|
||||
when Numeric
|
||||
timestamp.to_i
|
||||
else
|
||||
nil
|
||||
end
|
||||
rescue ArgumentError => e
|
||||
Rails.logger.error("Error converting timestamp: #{e.message}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user