mirror of
https://github.com/SrIzan10/hc-harbor.git
synced 2026-05-01 10:45:21 +00:00
Add regional leaderboard beta opt-in (#305)
This commit is contained in:
@@ -1,61 +1,137 @@
|
||||
class LeaderboardsController < ApplicationController
|
||||
def index
|
||||
@period_type = (params[:period_type] || "daily").to_sym
|
||||
@period_type = :daily unless Leaderboard.period_types
|
||||
.keys
|
||||
.map(&:to_sym)
|
||||
.include?(@period_type)
|
||||
set_params
|
||||
validate_timezone_requirements
|
||||
|
||||
start_date = case @period_type
|
||||
when :weekly
|
||||
Date.current.beginning_of_week
|
||||
when :last_7_days
|
||||
Date.current
|
||||
else
|
||||
Date.current
|
||||
end
|
||||
|
||||
cache_key = "leaderboard_#{@period_type}_#{start_date}"
|
||||
@leaderboard = Rails.cache.fetch(cache_key, expires_in: 1.minute) do
|
||||
Leaderboard.where.not(finished_generating_at: nil)
|
||||
.find_by(
|
||||
start_date: start_date,
|
||||
period_type: @period_type,
|
||||
deleted_at: nil
|
||||
)
|
||||
end
|
||||
Rails.cache.delete(cache_key) if @leaderboard.nil?
|
||||
@leaderboard = find_or_generate_leaderboard
|
||||
|
||||
if @leaderboard.nil?
|
||||
LeaderboardUpdateJob.perform_later @period_type
|
||||
flash.now[:notice] = "Leaderboard is being updated..."
|
||||
else
|
||||
# Load entries with users and their project repo mappings in a single query
|
||||
@entries = @leaderboard.entries
|
||||
.includes(:user)
|
||||
.order(total_seconds: :desc)
|
||||
|
||||
tracked_user_ids = @leaderboard.entries.distinct.pluck(:user_id)
|
||||
|
||||
@user_on_leaderboard = current_user && tracked_user_ids.include?(current_user.id)
|
||||
unless @user_on_leaderboard
|
||||
time_range = case @period_type
|
||||
when :weekly
|
||||
(start_date.beginning_of_day...(start_date + 7.days).beginning_of_day)
|
||||
when :last_7_days
|
||||
((start_date - 6.days).beginning_of_day...start_date.end_of_day)
|
||||
else
|
||||
start_date.all_day
|
||||
end
|
||||
|
||||
@untracked_entries = Hackatime::Heartbeat
|
||||
.where(time: time_range)
|
||||
.distinct
|
||||
.pluck(:user_id)
|
||||
.count { |user_id| !tracked_user_ids.include?(user_id) }
|
||||
end
|
||||
|
||||
@active_projects = Cache::ActiveProjectsJob.perform_now
|
||||
load_entries_and_metadata
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_params
|
||||
@use_timezone_leaderboard = current_user && Flipper.enabled?(:timezone_leaderboard, current_user)
|
||||
@period_type = validated_period_type
|
||||
@scope = params[:scope] || (@use_timezone_leaderboard ? "regional" : "global")
|
||||
@scope_description = scope_description
|
||||
end
|
||||
|
||||
def validated_period_type
|
||||
period = (params[:period_type] || "daily").to_sym
|
||||
valid_periods = [ :daily, :weekly, :last_7_days ]
|
||||
valid_periods.include?(period) ? period : :daily
|
||||
end
|
||||
|
||||
def scope_description
|
||||
case @scope
|
||||
when "regional" then current_user&.timezone_offset_name
|
||||
when "timezone" then current_user&.timezone
|
||||
end
|
||||
end
|
||||
|
||||
def validate_timezone_requirements
|
||||
return unless regional_or_timezone_scope?
|
||||
|
||||
unless current_user&.timezone
|
||||
flash[:error] = "Please set your timezone in settings to view regional leaderboards"
|
||||
redirect_to my_settings_path
|
||||
return
|
||||
end
|
||||
|
||||
if @scope == "regional" && current_user.timezone_utc_offset.nil?
|
||||
flash[:error] = "Unable to determine UTC offset for your timezone: #{current_user.timezone}"
|
||||
redirect_to leaderboards_path
|
||||
end
|
||||
end
|
||||
|
||||
def regional_or_timezone_scope?
|
||||
%w[regional timezone].include?(@scope)
|
||||
end
|
||||
|
||||
def find_or_generate_leaderboard
|
||||
case @scope
|
||||
when "regional" then generate_regional_leaderboard
|
||||
when "timezone" then generate_timezone_leaderboard
|
||||
else find_or_generate_global_leaderboard
|
||||
end
|
||||
end
|
||||
|
||||
def generate_regional_leaderboard
|
||||
LeaderboardGenerator.generate_timezone_offset_leaderboard(
|
||||
start_date, current_user.timezone_utc_offset, @period_type
|
||||
)
|
||||
end
|
||||
|
||||
def generate_timezone_leaderboard
|
||||
LeaderboardGenerator.generate_timezone_leaderboard(
|
||||
start_date, current_user.timezone, @period_type
|
||||
)
|
||||
end
|
||||
|
||||
def find_or_generate_global_leaderboard
|
||||
cache_key = "leaderboard_#{@period_type}_#{start_date}"
|
||||
|
||||
leaderboard = Rails.cache.fetch(cache_key, expires_in: 1.minute) do
|
||||
Leaderboard.where.not(finished_generating_at: nil)
|
||||
.find_by(start_date: start_date, period_type: @period_type, deleted_at: nil)
|
||||
end
|
||||
|
||||
Rails.cache.delete(cache_key) if leaderboard.nil?
|
||||
|
||||
if leaderboard.nil?
|
||||
LeaderboardUpdateJob.perform_later(@period_type)
|
||||
nil
|
||||
else
|
||||
leaderboard
|
||||
end
|
||||
end
|
||||
|
||||
def start_date
|
||||
@start_date ||= case @period_type
|
||||
when :weekly then Date.current.beginning_of_week
|
||||
when :last_7_days then Date.current - 6.days
|
||||
else Date.current
|
||||
end
|
||||
end
|
||||
|
||||
def load_entries_and_metadata
|
||||
@entries = @leaderboard.entries
|
||||
|
||||
if @leaderboard.persisted?
|
||||
@entries = @entries.includes(:user).order(total_seconds: :desc)
|
||||
load_user_tracking_data
|
||||
end
|
||||
|
||||
@active_projects = Cache::ActiveProjectsJob.perform_now
|
||||
end
|
||||
|
||||
def load_user_tracking_data
|
||||
tracked_user_ids = @leaderboard.entries.distinct.pluck(:user_id)
|
||||
@user_on_leaderboard = current_user && tracked_user_ids.include?(current_user.id)
|
||||
|
||||
unless @user_on_leaderboard || regional_or_timezone_scope?
|
||||
@untracked_entries = calculate_untracked_entries(tracked_user_ids)
|
||||
end
|
||||
end
|
||||
|
||||
def calculate_untracked_entries(tracked_user_ids)
|
||||
time_range = case @period_type
|
||||
when :weekly
|
||||
(start_date.beginning_of_day...(start_date + 7.days).beginning_of_day)
|
||||
when :last_7_days
|
||||
((start_date - 6.days).beginning_of_day...start_date.end_of_day)
|
||||
else
|
||||
start_date.all_day
|
||||
end
|
||||
|
||||
Hackatime::Heartbeat.where(time: time_range)
|
||||
.distinct
|
||||
.pluck(:user_id)
|
||||
.count { |user_id| !tracked_user_ids.include?(user_id) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,15 +20,47 @@ class UsersController < ApplicationController
|
||||
end
|
||||
|
||||
def update
|
||||
if @user.update(user_params)
|
||||
if @user.uses_slack_status?
|
||||
@user.update_slack_status
|
||||
# Handle timezone leaderboard toggle
|
||||
if params[:toggle_timezone_leaderboard] == "1"
|
||||
if Flipper.enabled?(:timezone_leaderboard, @user)
|
||||
Flipper.disable(:timezone_leaderboard, @user)
|
||||
message = "Regional & Timezone Leaderboards disabled"
|
||||
else
|
||||
Flipper.enable(:timezone_leaderboard, @user)
|
||||
message = "Regional & Timezone Leaderboards enabled"
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.turbo_stream do
|
||||
render turbo_stream: turbo_stream.replace(
|
||||
"timezone_leaderboard_toggle",
|
||||
partial: "timezone_leaderboard_toggle",
|
||||
locals: { user: @user }
|
||||
)
|
||||
end
|
||||
format.html do
|
||||
redirect_to is_own_settings? ? my_settings_path : settings_user_path(@user),
|
||||
notice: message
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# Handle regular user settings updates
|
||||
if params[:user].present?
|
||||
if @user.update(user_params)
|
||||
if @user.uses_slack_status?
|
||||
@user.update_slack_status
|
||||
end
|
||||
redirect_to is_own_settings? ? my_settings_path : settings_user_path(@user),
|
||||
notice: "Settings updated successfully"
|
||||
else
|
||||
flash[:error] = "Failed to update settings"
|
||||
render :settings, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
redirect_to is_own_settings? ? my_settings_path : settings_user_path(@user),
|
||||
notice: "Settings updated successfully"
|
||||
else
|
||||
flash[:error] = "Failed to update settings"
|
||||
render :settings, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
80
app/jobs/timezone_leaderboard_update_job.rb
Normal file
80
app/jobs/timezone_leaderboard_update_job.rb
Normal file
@@ -0,0 +1,80 @@
|
||||
class TimezoneLeaderboardUpdateJob < ApplicationJob
|
||||
queue_as :latency_10s
|
||||
|
||||
include GoodJob::ActiveJobExtensions::Concurrency
|
||||
|
||||
# Limits concurrency to 1 job per date
|
||||
good_job_control_concurrency_with(
|
||||
key: -> { "timezone_daily_#{arguments[0] || Date.current.to_s}" },
|
||||
total: 1,
|
||||
drop: true
|
||||
)
|
||||
|
||||
def perform(date = Date.current)
|
||||
parsed_date = date.is_a?(Date) ? date : Date.parse(date.to_s)
|
||||
|
||||
leaderboard = Leaderboard.create!(
|
||||
start_date: parsed_date,
|
||||
period_type: :daily_timezone_normalized
|
||||
)
|
||||
|
||||
Rails.logger.info "Starting timezone-normalized leaderboard generation for #{parsed_date}"
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
# Get all unique timezones
|
||||
timezones = User.where.not(timezone: nil).distinct.pluck(:timezone)
|
||||
entries_data = []
|
||||
|
||||
timezones.each do |timezone|
|
||||
# Calculate the date range for this timezone
|
||||
timezone_date_range = Time.use_zone(timezone) do
|
||||
parsed_date.in_time_zone(timezone).all_day
|
||||
end
|
||||
|
||||
# Get all heartbeats for users in this timezone during their local day
|
||||
timezone_heartbeats = Heartbeat.joins(:user)
|
||||
.where(users: { timezone: timezone })
|
||||
.where(time: timezone_date_range)
|
||||
.coding_only
|
||||
.with_valid_timestamps
|
||||
.where.not(users: { github_uid: nil })
|
||||
|
||||
# Group by user and calculate totals
|
||||
user_totals = timezone_heartbeats.group(:user_id).duration_seconds
|
||||
user_totals = user_totals.filter { |_, total_seconds| total_seconds > 60 }
|
||||
|
||||
# Get streaks for all users at once
|
||||
user_ids = user_totals.keys
|
||||
streaks = Heartbeat.daily_streaks_for_users(user_ids) if user_ids.any?
|
||||
|
||||
# Build entries data
|
||||
user_totals.each do |user_id, total_seconds|
|
||||
entries_data << {
|
||||
leaderboard_id: leaderboard.id,
|
||||
user_id: user_id,
|
||||
total_seconds: total_seconds,
|
||||
streak_count: streaks[user_id] || 0
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
LeaderboardEntry.insert_all!(entries_data) if entries_data.any?
|
||||
end
|
||||
|
||||
leaderboard.finished_generating_at = Time.current
|
||||
leaderboard.save!
|
||||
|
||||
# Clean up old timezone-normalized leaderboards for this date
|
||||
Leaderboard.where.not(id: leaderboard.id)
|
||||
.where(start_date: parsed_date, period_type: :daily_timezone_normalized)
|
||||
.where(deleted_at: nil)
|
||||
.update_all(deleted_at: Time.current)
|
||||
|
||||
leaderboard
|
||||
rescue => e
|
||||
Rails.logger.error "Failed to update timezone-normalized leaderboard: #{e.message}"
|
||||
raise
|
||||
rescue Date::Error
|
||||
raise ArgumentError, "Invalid date format provided"
|
||||
end
|
||||
end
|
||||
71
app/models/concerns/timezone_regions.rb
Normal file
71
app/models/concerns/timezone_regions.rb
Normal file
@@ -0,0 +1,71 @@
|
||||
module TimezoneRegions
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def timezone_to_utc_offset(timezone)
|
||||
return nil if timezone.blank?
|
||||
|
||||
begin
|
||||
tz = Time.find_zone(timezone)
|
||||
return nil unless tz
|
||||
tz.now.utc_offset / 3600 # Convert seconds to hours
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def users_in_timezone_offset(utc_offset)
|
||||
# Get all users whose timezone has the same UTC offset
|
||||
user_timezones = User.where.not(timezone: nil).distinct.pluck(:timezone)
|
||||
matching_timezones = user_timezones.select do |tz|
|
||||
timezone_to_utc_offset(tz) == utc_offset
|
||||
end
|
||||
User.where(timezone: matching_timezones)
|
||||
end
|
||||
|
||||
def users_in_timezone(timezone)
|
||||
User.where(timezone: timezone)
|
||||
end
|
||||
|
||||
def available_timezone_offsets
|
||||
# Get all unique UTC offsets that have users
|
||||
user_timezones = User.where.not(timezone: nil).distinct.pluck(:timezone)
|
||||
offsets = user_timezones.map { |tz| timezone_to_utc_offset(tz) }.compact.uniq.sort
|
||||
offsets
|
||||
end
|
||||
|
||||
def available_timezones
|
||||
# Only return timezones that have users
|
||||
User.where.not(timezone: nil).distinct.pluck(:timezone).sort
|
||||
end
|
||||
|
||||
def offset_to_name(utc_offset)
|
||||
case utc_offset
|
||||
when -8 then "PST (UTC-8)"
|
||||
when -7 then "MST (UTC-7)"
|
||||
when -6 then "CST (UTC-6)"
|
||||
when -5 then "EST (UTC-5)"
|
||||
when -4 then "AST (UTC-4)"
|
||||
when 0 then "GMT (UTC+0)"
|
||||
when 1 then "CET (UTC+1)"
|
||||
when 2 then "EET (UTC+2)"
|
||||
when 8 then "CST Asia (UTC+8)"
|
||||
when 9 then "JST (UTC+9)"
|
||||
when 10 then "AEST (UTC+10)"
|
||||
else "UTC#{utc_offset >= 0 ? '+' : ''}#{utc_offset}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
included do
|
||||
def timezone_utc_offset
|
||||
self.class.timezone_to_utc_offset(timezone)
|
||||
end
|
||||
|
||||
def timezone_offset_name
|
||||
offset = timezone_utc_offset
|
||||
return "Unknown" unless offset
|
||||
self.class.offset_to_name(offset)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,6 @@
|
||||
class User < ApplicationRecord
|
||||
include TimezoneRegions
|
||||
|
||||
has_paper_trail
|
||||
encrypts :slack_access_token, :github_access_token
|
||||
|
||||
|
||||
85
app/services/leaderboard_generator.rb
Normal file
85
app/services/leaderboard_generator.rb
Normal file
@@ -0,0 +1,85 @@
|
||||
class LeaderboardGenerator
|
||||
include TimezoneRegions
|
||||
|
||||
def self.generate_timezone_offset_leaderboard(date, utc_offset, period_type = :daily)
|
||||
new.generate_timezone_offset_leaderboard(date, utc_offset, period_type)
|
||||
end
|
||||
|
||||
def self.generate_timezone_leaderboard(date, timezone, period_type = :daily)
|
||||
new.generate_timezone_leaderboard(date, timezone, period_type)
|
||||
end
|
||||
|
||||
def generate_timezone_offset_leaderboard(date, utc_offset, period_type = :daily)
|
||||
date = Date.current if date.blank?
|
||||
users = User.users_in_timezone_offset(utc_offset)
|
||||
generate_leaderboard_for_users(users, date, "UTC#{utc_offset >= 0 ? '+' : ''}#{utc_offset}", period_type)
|
||||
end
|
||||
|
||||
def generate_timezone_leaderboard(date, timezone, period_type = :daily)
|
||||
date = Date.current if date.blank?
|
||||
users = User.users_in_timezone(timezone)
|
||||
generate_leaderboard_for_users(users, date, timezone, period_type)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_leaderboard_for_users(users, date, scope_name, period_type = :daily)
|
||||
# Ensure date is valid
|
||||
date = Date.current if date.blank?
|
||||
|
||||
# Create a virtual leaderboard object (not saved to DB)
|
||||
leaderboard = Leaderboard.new(
|
||||
start_date: date,
|
||||
period_type: period_type,
|
||||
finished_generating_at: Time.current
|
||||
)
|
||||
|
||||
# Get user IDs
|
||||
user_ids = users.pluck(:id)
|
||||
return leaderboard if user_ids.empty?
|
||||
|
||||
# Calculate heartbeats for the date range in UTC
|
||||
date_range = case period_type
|
||||
when :weekly
|
||||
date.beginning_of_week.beginning_of_day..date.end_of_week.end_of_day
|
||||
when :last_7_days
|
||||
(date - 6.days).beginning_of_day..Date.current.end_of_day
|
||||
else
|
||||
date.all_day
|
||||
end
|
||||
|
||||
# Get heartbeats for these users
|
||||
heartbeats = Heartbeat.where(user_id: user_ids, time: date_range)
|
||||
.coding_only
|
||||
.with_valid_timestamps
|
||||
.joins(:user)
|
||||
.where.not(users: { github_uid: nil })
|
||||
|
||||
# Group by user and calculate totals
|
||||
user_totals = heartbeats.group(:user_id).duration_seconds
|
||||
user_totals = user_totals.filter { |_, total_seconds| total_seconds > 60 }
|
||||
|
||||
# Get streaks for all users at once
|
||||
streaks = Heartbeat.daily_streaks_for_users(user_totals.keys) if user_totals.any?
|
||||
|
||||
# Create virtual leaderboard entries
|
||||
entries = user_totals.map do |user_id, total_seconds|
|
||||
entry = LeaderboardEntry.new(
|
||||
leaderboard: leaderboard,
|
||||
user_id: user_id,
|
||||
total_seconds: total_seconds,
|
||||
streak_count: streaks[user_id] || 0
|
||||
)
|
||||
|
||||
# Manually set the user association to avoid N+1 queries
|
||||
entry.user = users.find { |u| u.id == user_id }
|
||||
entry
|
||||
end.sort_by(&:total_seconds).reverse
|
||||
|
||||
# Attach entries to leaderboard
|
||||
leaderboard.define_singleton_method(:entries) { entries }
|
||||
leaderboard.define_singleton_method(:scope_name) { scope_name }
|
||||
|
||||
leaderboard
|
||||
end
|
||||
end
|
||||
@@ -2,17 +2,38 @@
|
||||
<div class="header">
|
||||
<h1>Leaderboard</h1>
|
||||
|
||||
<em>This leaderboard runs in UTC time!</em>
|
||||
<% if @scope == 'regional' %>
|
||||
<em>🧪 <strong>Regional Leaderboard:</strong> Showing users in <%= @scope_description %></em>
|
||||
<% elsif @scope == 'timezone' %>
|
||||
<em>🧪 <strong>Timezone Leaderboard:</strong> Showing users in <%= @scope_description %></em>
|
||||
<% elsif @scope == 'global' %>
|
||||
<em>This leaderboard runs in UTC time!</em>
|
||||
<% else %>
|
||||
<em>🧪 <strong>Regional Leaderboard:</strong> Showing users in <%= @scope_description %></em>
|
||||
<% end %>
|
||||
|
||||
<div class="period-toggle">
|
||||
<%= link_to "Daily", leaderboards_path(period_type: 'daily'),
|
||||
class: "period-toggle-btn #{@period_type == :daily ? 'active' : ''}" %>
|
||||
<%= link_to "Weekly", leaderboards_path(period_type: 'weekly'),
|
||||
<%= link_to "Daily", leaderboards_path(period_type: 'daily', scope: @scope),
|
||||
class: "period-toggle-btn #{(@period_type == :daily || @period_type == :daily_timezone_normalized) ? 'active' : ''}" %>
|
||||
<%= link_to "Weekly", leaderboards_path(period_type: 'weekly', scope: @scope),
|
||||
class: "period-toggle-btn #{@period_type == :weekly ? 'active' : ''}" %>
|
||||
<%= link_to "Last 7 Days", leaderboards_path(period_type: 'last_7_days'),
|
||||
<%= link_to "Last 7 Days", leaderboards_path(period_type: 'last_7_days', scope: @scope),
|
||||
class: "period-toggle-btn #{@period_type == :last_7_days ? 'active' : ''}" %>
|
||||
</div>
|
||||
|
||||
<% if current_user && Flipper.enabled?(:timezone_leaderboard, current_user) %>
|
||||
<div class="period-toggle">
|
||||
<%= link_to "Timezone", leaderboards_path(period_type: @period_type, scope: 'timezone'),
|
||||
class: "period-toggle-btn #{@scope == 'timezone' ? 'active' : ''}" %>
|
||||
<%= link_to "Regional", leaderboards_path(period_type: @period_type, scope: 'regional'),
|
||||
class: "period-toggle-btn #{@scope == 'regional' ? 'active' : ''}" %>
|
||||
<%= link_to "Global", leaderboards_path(period_type: @period_type, scope: 'global'),
|
||||
class: "period-toggle-btn #{@scope == 'global' ? 'active' : ''}" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
||||
<% if current_user && current_user.github_uid.blank? %>
|
||||
<p>
|
||||
<%= link_to "Connect your GitHub", "/auth/github", class: "button" %> to qualify for the leaderboard.
|
||||
@@ -23,7 +44,7 @@
|
||||
<% if @leaderboard %>
|
||||
<%= @leaderboard.date_range_text %>
|
||||
|
||||
<% if @leaderboard.finished_generating? %>
|
||||
<% if @leaderboard.finished_generating? && @leaderboard.persisted? %>
|
||||
<span class="super">
|
||||
Updated <%= time_ago_in_words(@leaderboard.updated_at) %> ago.
|
||||
</span>
|
||||
@@ -103,7 +124,7 @@
|
||||
<%= link_to "updated their wakatime config", my_settings_path, target: "_blank" %>.
|
||||
</p>
|
||||
<% end %>
|
||||
<% if @leaderboard.finished_generating? %>
|
||||
<% if @leaderboard.finished_generating? && @leaderboard.persisted? %>
|
||||
<span class="super">
|
||||
Generated in <%= @leaderboard.finished_generating_at - @leaderboard.created_at %> seconds
|
||||
</span>
|
||||
|
||||
12
app/views/users/_timezone_leaderboard_toggle.html.erb
Normal file
12
app/views/users/_timezone_leaderboard_toggle.html.erb
Normal file
@@ -0,0 +1,12 @@
|
||||
<div id="timezone_leaderboard_toggle">
|
||||
<p>
|
||||
<strong>Regional & Timezone Leaderboards</strong><br>
|
||||
<small>Access regional leaderboards that show users in your timezone region or specific timezone. Choose between timezone-specific, regional (UTC offset), or global competition modes.</small>
|
||||
</p>
|
||||
<%= form_with url: (@is_own_settings ? my_settings_path : settings_user_path(user)), method: :patch, local: false do |f| %>
|
||||
<%= hidden_field_tag :toggle_timezone_leaderboard, "1" %>
|
||||
<%= f.submit Flipper.enabled?(:timezone_leaderboard, user) ? "Disable Feature" : "Enable Feature",
|
||||
role: "button",
|
||||
class: Flipper.enabled?(:timezone_leaderboard, user) ? "secondary" : "" %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -268,6 +268,15 @@
|
||||
<% end %>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<header>
|
||||
<h2 id="user_beta_features">🧪 Beta Features</h2>
|
||||
<p>Enable experimental features and help us test new functionality.</p>
|
||||
</header>
|
||||
|
||||
<%= render "timezone_leaderboard_toggle", user: @user %>
|
||||
</article>
|
||||
|
||||
<% admin_tool do %>
|
||||
<article>
|
||||
<header>
|
||||
|
||||
5
db/migrate/20250611070646_add_scope_to_leaderboards.rb
Normal file
5
db/migrate/20250611070646_add_scope_to_leaderboards.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class AddScopeToLeaderboards < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_column :leaderboards, :scope, :string
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class RemoveScopeFromLeaderboards < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
remove_column :leaderboards, :scope, :string
|
||||
end
|
||||
end
|
||||
2
db/schema.rb
generated
2
db/schema.rb
generated
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_06_11_062456) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_06_11_071124) do
|
||||
create_schema "pganalyze"
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
|
||||
Reference in New Issue
Block a user