Files
archived-hc-harbor/app/controllers/static_pages_controller.rb
ByteAtATime 83c2987ab3 feat: add recent signup users with avatars (#89)
* feat: add recent signup users with avatars

- Show avatars of recent Hackatime setup users
- Add a hoverable user list showing all setup users with names and photos
- Add tooltips for first 5 users (as preview)

* Add flag to force the 'setup waka' notice

---------

Co-authored-by: Max Wofford <max@maxwofford.com>
2025-03-20 23:54:28 -04:00

213 lines
7.6 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class StaticPagesController < ApplicationController
def index
@leaderboard = Leaderboard.where.associated(:entries)
.where(start_date: Date.current)
.where(deleted_at: nil)
.where(period_type: :daily)
.distinct
.first
if current_user
flavor_texts = FlavorText.motto + FlavorText.conditional_mottos(current_user)
flavor_texts += FlavorText.rare_motto if Random.rand(10) < 1
@flavor_text = flavor_texts.sample
unless params[:date].blank?
# implement this later for now just redirect to a random video
allowed_hosts = FlavorText.random_time_video.map { |v| URI.parse(v).host }
redirect_to FlavorText.random_time_video.sample, allow_other_host: allowed_hosts
end
@show_wakatime_setup_notice = current_user.heartbeats.empty? || params[:show_wakatime_setup_notice]
@setup_social_proof = get_setup_social_proof if @show_wakatime_setup_notice
# Get languages and editors in a single query using window functions
results = 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
# Process results to get sorted languages and editors
language_counts = results
.map { |r| [ r.language, r.language_count ] }
.reject { |lang, _| lang.nil? || lang.empty? }
.uniq
.sort_by { |_, count| -count }
editor_counts = results
.map { |r| [ r.editor, r.editor_count ] }
.reject { |ed, _| ed.nil? || ed.empty? }
.uniq
.sort_by { |_, count| -count }
@todays_languages = language_counts.map(&:first)
@todays_editors = editor_counts.map(&:first)
@show_logged_time_sentence = @todays_languages.any? || @todays_editors.any?
else
@social_proof ||= begin
# Only run queries as needed, starting with the smallest time range
if (in_past_hour = Heartbeat.where("time > ?", 1.hour.ago.to_f).distinct.count(:user_id)) > 5
"In the past hour, #{in_past_hour} Hack Clubbers have coded with Hackatime."
elsif (in_past_day = Heartbeat.where("time > ?", 1.day.ago.to_f).distinct.count(:user_id)) > 5
"In the past day, #{in_past_day} Hack Clubbers have coded with Hackatime."
elsif (in_past_week = Heartbeat.where("time > ?", 1.week.ago.to_f).distinct.count(:user_id)) > 5
"In the past week, #{in_past_week} Hack Clubbers have coded with Hackatime."
end
end
@home_stats = Rails.cache.read("home_stats")
CacheHomeStatsJob.perform_later if @home_stats.nil?
end
# Get active projects for the mini leaderboard
if @leaderboard
user_ids = @leaderboard.entries.pluck(:user_id)
users = User.where(id: user_ids).includes(:project_repo_mappings)
@active_projects = {}
users.each do |user|
@active_projects[user.id] = user.project_repo_mappings.find { |p| p.project_name == user.active_project }
end
end
end
def project_durations
return unless current_user
@project_repo_mappings = current_user.project_repo_mappings
project_durations = Rails.cache.fetch("user_#{current_user.id}_project_durations", expires_in: 1.minute) do
project_times = current_user.heartbeats.group(:project).duration_seconds
project_labels = current_user.project_labels
project_times.map do |project, duration|
{
project: project_labels.find { |p| p.project_key == project }&.label || project || "Unknown",
repo_url: @project_repo_mappings.find { |p| p.project_name == project }&.repo_url,
duration: duration
}
end.filter { |p| p[:duration].positive? }.sort_by { |p| p[:duration] }.reverse
end
render partial: "project_durations", locals: { project_durations: project_durations }
end
def activity_graph
return unless current_user
daily_durations = Rails.cache.fetch("user_#{current_user.id}_daily_durations", expires_in: 1.minute) do
# Set the timezone for the duration of this request
Time.use_zone(current_user.timezone) do
current_user.heartbeats.daily_durations.to_h
end
end
# Consider 8 hours as a "full" day of coding
length_of_busiest_day = 8.hours.to_i # 28800 seconds
render partial: "activity_graph", locals: {
daily_durations: daily_durations,
length_of_busiest_day: length_of_busiest_day
}
end
def currently_hacking
# Get all users who have heartbeats in the last 15 minutes
users = Rails.cache.fetch("currently_hacking", expires_in: 1.minute) do
user_ids = Heartbeat.where("time > ?", 5.minutes.ago.to_f)
.distinct
.pluck(:user_id)
User.where(id: user_ids).includes(:project_repo_mappings)
end
active_projects = {}
users.each do |user|
active_projects[user.id] = user.project_repo_mappings.find { |p| p.project_name == user.active_project }
end
render partial: "currently_hacking", locals: { users: users, active_projects: active_projects }
end
def 🃏
redirect_to root_path unless current_user && current_user.slack_uid.present?
record = HTTP.auth("Bearer #{ENV.fetch("WILDCARD_AIRTABLE_KEY")}").patch("https://api.airtable.com/v0/appt3yVn2nbiUaijm/tblRCAMjfQ4MIsMPp",
json: {
records: [
{
fields: {
slack_id: current_user.slack_uid
}
}
],
performUpsert: {
fieldsToMergeOn: [ "slack_id" ]
}
}
)
record_data = JSON.parse(record.body)
record_id = record_data.dig("records", 0, "id")
redirect_to root_path unless record_id&.present?
# if record is created, set a new auth_key:
auth_key = SecureRandom.hex(16)
HTTP.auth("Bearer #{ENV.fetch("WILDCARD_AIRTABLE_KEY")}").patch("https://api.airtable.com/v0/appt3yVn2nbiUaijm/tblRCAMjfQ4MIsMPp",
json: {
records: [
{ id: record_id, fields: { auth_key: auth_key } }
]
}
)
wildcard_host = ENV.fetch("WILDCARD_HOST")
redirect_to "#{wildcard_host}?auth_key=#{auth_key}", allow_other_host: wildcard_host
end
private
def get_setup_social_proof
# Count users who set up in different time periods
result = social_proof_for_time_period(5.minutes.ago, 1, "in the last 5 minutes") ||
social_proof_for_time_period(1.hour.ago, 3, "in the last hour") ||
social_proof_for_time_period(1.day.ago, 5, "today") ||
social_proof_for_time_period(1.week.ago, 5, "in the past week") ||
social_proof_for_time_period(1.month.ago, 5, "in the past month") ||
social_proof_for_time_period(Time.current.beginning_of_year, 5, "this year")
result
end
def social_proof_for_time_period(time_period, threshold, humanized_time_period)
user_ids = Heartbeat.where("time > ?", time_period.to_f)
.where(source_type: :test_entry)
.distinct
.pluck(:user_id)
count_unique = user_ids.count
return nil if count_unique < threshold
all_setup_users = User.where(id: user_ids).flat_map do |user|
{
id: user.id,
avatar_url: user.avatar_url,
display_name: user.display_name || "Hack Clubber"
}
end
@all_setup_users = all_setup_users
@recent_setup_users = all_setup_users.take(5)
"#{count_unique.to_s + ' Hack Clubber'.pluralize(count_unique)} set up Hackatime #{humanized_time_period}"
end
end