hackatime/app/controllers/concerns/dashboard_data.rb
Mahad Kalam 922e7384c0
Fix email from name/dates, login flash, wakatime_setup redirect, Sunday 6:30pm GMT (#1067)
* Smol fixes

* Map <<LAST_LANGUAGE>

* whoops

* Fix

* Move emails from Friday to Sunday
2026-03-13 10:53:57 +00:00

148 lines
5.8 KiB
Ruby

module DashboardData
extend ActiveSupport::Concern
private
def filterable_dashboard_data
filters = %i[project language operating_system editor category]
interval = params[:interval]
key = [ current_user ] + filters.map { |f| params[f] } + [ interval.to_s, params[:from], params[:to] ]
hb = current_user.heartbeats
h = ApplicationController.helpers
Rails.cache.fetch(key, expires_in: 5.minutes) do
archived = current_user.project_repo_mappings.archived.pluck(:project_name)
result = {}
Time.use_zone(current_user.timezone) do
filters.each do |f|
options = current_user.heartbeats.distinct.pluck(f).compact_blank
options = options.reject { |n| archived.include?(n) } if f == :project
result[f] = options.map { |k|
if f == :language then k.categorize_language
elsif f == :editor then h.display_editor_name(k)
elsif f == :operating_system then h.display_os_name(k)
else k
end
}.uniq
next unless params[f].present?
arr = params[f].split(",")
hb = if %i[operating_system editor].include?(f)
hb.where(f => arr.flat_map { |v| [ v.downcase, v.capitalize ] }.uniq)
elsif f == :language
raw = current_user.heartbeats.distinct.pluck(f).compact_blank.select { |l| arr.include?(l.categorize_language) }
raw.any? ? hb.where(f => raw) : hb
else
hb.where(f => arr)
end
result["singular_#{f}"] = arr.length == 1
end
hb = hb.filter_by_time_range(interval, params[:from], params[:to])
result[:total_time] = hb.duration_seconds
result[:total_heartbeats] = hb.count
filters.each do |f|
stats = hb.group(f).duration_seconds
stats = stats.reject { |n, _| archived.include?(n) } if f == :project
result["top_#{f}"] = stats.max_by { |_, v| v }&.first
end
result["top_editor"] &&= h.display_editor_name(result["top_editor"])
result["top_operating_system"] &&= h.display_os_name(result["top_operating_system"])
result["top_language"] &&= h.display_language_name(result["top_language"])
unless result["singular_project"]
result[:project_durations] = hb.group(:project).duration_seconds
.reject { |p, _| archived.include?(p) }.sort_by { |_, d| -d }.first(10).to_h
end
%i[language editor operating_system category].each do |f|
next if result["singular_#{f}"]
stats = hb.group(f).duration_seconds.each_with_object({}) do |(raw, dur), agg|
k = raw.to_s.presence || "Unknown"
k = f == :language ? (k == "Unknown" ? k : k.categorize_language) : (%i[editor operating_system].include?(f) ? k.downcase : k)
agg[k] = (agg[k] || 0) + dur
end
result["#{f}_stats"] = stats.sort_by { |_, d| -d }.first(10).map { |k, v|
label = case f
when :editor then h.display_editor_name(k)
when :operating_system then h.display_os_name(k)
when :language then h.display_language_name(k)
else k
end
[ label, v ]
}.to_h
end
if result["language_stats"].present?
result[:language_colors] = LanguageUtils.colors_for(result["language_stats"].keys)
end
result[:weekly_project_stats] = (0..11).to_h do |w|
ws = w.weeks.ago.beginning_of_week
[ ws.to_date.iso8601, hb.where(time: ws.to_f..w.weeks.ago.end_of_week.to_f)
.group(:project).duration_seconds.reject { |p, _| archived.include?(p) } ]
end
end
result[:selected_interval] = interval.to_s
result[:selected_from] = params[:from].to_s
result[:selected_to] = params[:to].to_s
filters.each { |f| result["selected_#{f}"] = params[f]&.split(",") || [] }
result
end
end
def activity_graph_data
tz = current_user.timezone
key = "user_#{current_user.id}_daily_durations_#{tz}"
durations = Rails.cache.fetch(key, expires_in: 1.minute) do
Time.use_zone(tz) { current_user.heartbeats.daily_durations(user_timezone: tz).to_h }
end
{
start_date: 365.days.ago.to_date.iso8601,
end_date: Time.current.to_date.iso8601,
duration_by_date: durations.transform_keys { |d| d.to_date.iso8601 }.transform_values(&:to_i),
busiest_day_seconds: 8.hours.to_i,
timezone_label: ActiveSupport::TimeZone[tz].to_s,
timezone_settings_path: "/my/settings#user_timezone"
}
end
def today_stats_data
h = ApplicationController.helpers
Time.use_zone(current_user.timezone) do
rows = current_user.heartbeats.today
.select(:language, :editor,
"COUNT(*) OVER (PARTITION BY language) as language_count",
"COUNT(*) OVER (PARTITION BY editor) as editor_count")
.distinct.to_a
lang_counts = rows
.map { |r| [ r.language&.categorize_language, r.language_count ] }
.reject { |l, _| l.blank? }
.group_by(&:first).transform_values { |p| p.sum(&:last) }
.sort_by { |_, c| -c }
ed_counts = rows
.map { |r| [ r.editor, r.editor_count ] }
.reject { |e, _| e.blank? }.uniq
.sort_by { |_, c| -c }
todays_languages = lang_counts.map { |l, _| h.display_language_name(l) }
todays_editors = ed_counts.map { |e, _| h.display_editor_name(e) }
todays_duration = current_user.heartbeats.today.duration_seconds
show_logged_time_sentence = todays_duration > 1.minute && (todays_languages.any? || todays_editors.any?)
{
show_logged_time_sentence: show_logged_time_sentence,
todays_duration_display: h.short_time_detailed(todays_duration.to_i),
todays_languages: todays_languages,
todays_editors: todays_editors
}
end
end
end