Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions app/assets/stylesheets/components/profile.css
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,61 @@
justify-content: center;
}

.team-stats {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
margin-bottom: var(--spacing-4);
}

.team-stats-group-title {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
margin-bottom: var(--spacing-2);
}

.team-stats-group-note {
color: var(--color-text-muted);
font-weight: var(--font-weight-normal);
margin-left: var(--spacing-1);
}

.team-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: var(--spacing-3);
}

.team-stat-tile {
padding: var(--spacing-3);
background: var(--color-bg-card);
border: var(--border-width) solid var(--color-border);
border-radius: var(--border-radius-lg);
}

.team-stat-value {
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}

.team-stat-label {
color: var(--color-text-muted);
font-size: var(--font-size-sm);
}

.team-stat-sub {
color: var(--color-text-muted);
font-size: var(--font-size-xs);
}

@media (max-width: 720px) {
.team-stats-grid {
grid-template-columns: repeat(2, 1fr);
}
}

.activity-types {
display: flex;
flex-wrap: wrap;
Expand Down
3 changes: 2 additions & 1 deletion app/assets/stylesheets/components/settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
.settings-page input[type="text"],
.settings-page input[type="email"],
.settings-page input[type="password"],
.settings-page input[type="number"] {
.settings-page input[type="number"],
.settings-page select {
width: 100%;
padding: var(--spacing-4) var(--spacing-6);
border-radius: 999px;
Expand Down
35 changes: 23 additions & 12 deletions app/controllers/concerns/profile_activity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ def person_ids_for_query
ids.is_a?(Set) ? ids.to_a : ids
end

def load_activity_data(scope: nil, year: nil)
def load_activity_data(scope: nil, year: nil, window_start: nil, window_end: nil)
@week_start_day = parse_week_start_day
@activity_filters = parse_activity_filters
effective_scope = scope || default_recent_scope
@activity_window_start = window_start || default_window_start
@activity_window_end = window_end || Time.current
@activity_entries = build_activity_entries(scope: effective_scope, filters: @activity_filters)
@activity_summary = build_activity_summary(scope: effective_scope, filters: @activity_filters)
@activity_period ||= { type: :recent } if scope.nil?
Expand All @@ -33,10 +35,13 @@ def load_activity_data(scope: nil, year: nil)
@weekday_labels = WeekCalculation.weekday_labels(@week_start_day)
end

def default_window_start
1.month.ago.beginning_of_day
end

def default_recent_scope
ids = person_ids_for_query
start_date = 1.month.ago.beginning_of_day
Message.where(sender_person_id: ids, created_at: start_date..)
Message.where(sender_person_id: ids, created_at: default_window_start..)
end

def build_activity_entries(scope: nil, filters: nil)
Expand Down Expand Up @@ -99,17 +104,11 @@ def build_activity_summary(scope: nil, filters: nil)

filter_symbols = filters&.map(&:to_sym)&.to_set

summary = {
total: 0,
started_thread: 0,
replied_own_thread: 0,
replied_other_thread: 0,
replied_other_topics: 0,
sent_first_patch: 0,
sent_followup_patch: 0
}
summary = empty_activity_summary

replied_other_topic_ids = Set.new
active_thread_ids = Set.new
contributor_ids = Set.new

messages.each do |message|
topic = message.topic
Expand All @@ -129,10 +128,18 @@ def build_activity_summary(scope: nil, filters: nil)
if activity_types.include?(:replied_other_thread)
replied_other_topic_ids << topic.id
end
if activity_types.include?(:started_thread)
key = message.is_patch_submission? ? :new_patch_series_count : :discussion_started_count
summary[key] += 1
end
active_thread_ids << topic.id
contributor_ids << message.sender_person_id
end
end

summary[:replied_other_topics] = replied_other_topic_ids.size
summary[:active_thread_count] = active_thread_ids.size
summary[:unique_contributor_count] = contributor_ids.size
summary
end

Expand All @@ -143,6 +150,10 @@ def empty_activity_summary
replied_own_thread: 0,
replied_other_thread: 0,
replied_other_topics: 0,
active_thread_count: 0,
unique_contributor_count: 0,
discussion_started_count: 0,
new_patch_series_count: 0,
sent_first_patch: 0,
sent_followup_patch: 0
}
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/settings/teams_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def team_params
end

def team_update_params
params.require(:team).permit(:visibility)
params.require(:team).permit(:visibility, :week_start_day)
end

def require_team_admin!
Expand Down
35 changes: 31 additions & 4 deletions app/controllers/teams_profile_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ class TeamsProfileController < ApplicationController
def show
@members = @team.team_members.includes(user: { person: :default_alias }).order(:role, :created_at)
load_activity_data
load_team_stats
end

def contributions
load_activity_data
load_team_stats
render :activity
end

def daily_activity
date = parse_activity_date
@activity_period = { type: :day, date: date }
load_activity_data(scope: messages_scope_for_date(date), year: date.year)
load_activity_data(
scope: messages_scope_for_date(date), year: date.year,
window_start: date.beginning_of_day, window_end: date.end_of_day
)
load_team_stats
render :activity
end

Expand All @@ -27,18 +33,26 @@ def monthly_activity
start_date = Date.new(year, month, 1)
end_date = start_date.end_of_month
@activity_period = { type: :month, year: year, month: month }
load_activity_data(scope: messages_scope_for_range(start_date, end_date), year: year)
load_activity_data(
scope: messages_scope_for_range(start_date, end_date), year: year,
window_start: start_date.beginning_of_day, window_end: end_date.end_of_day
)
load_team_stats
render :activity
end

def weekly_activity
year = params[:year].to_i
week = params[:week].to_i
wday_start = WeekCalculation.parse_week_start(params[:week_start])
wday_start = parse_week_start_day
start_date = WeekCalculation.week_start_date(year, week, wday_start)
end_date = start_date + 6
@activity_period = { type: :week, year: year, week: week, start_date: start_date, end_date: end_date }
load_activity_data(scope: messages_scope_for_range(start_date, end_date), year: year)
load_activity_data(
scope: messages_scope_for_range(start_date, end_date), year: year,
window_start: start_date.beginning_of_day, window_end: end_date.end_of_day
)
load_team_stats
render :activity
end

Expand All @@ -48,6 +62,19 @@ def activity_person_ids
@member_person_ids
end

def parse_week_start_day
return WeekCalculation.parse_week_start(params[:week_start]) if params[:week_start].present?
@team.week_start_day
end

def load_team_stats
@team_stats = TeamActivityStats.new(
team_person_ids: @member_person_ids,
window_start: @activity_window_start,
window_end: @activity_window_end
).call
end

def load_team
@team = Team.find_by!(name: params[:name])
@member_person_ids = @team.users.joins(:person).pluck("people.id").to_set
Expand Down
6 changes: 6 additions & 0 deletions app/helpers/team_stats_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module TeamStatsHelper
def format_duration_hours(hours)
return "–" if hours.nil?
hours < 24 ? "#{hours.round(1)}h" : "#{(hours / 24.0).round(1)}d"
end
end
1 change: 1 addition & 0 deletions app/models/team.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class Team < ApplicationRecord

validates :name, presence: true
validates :name, format: { with: /\A[a-zA-Z0-9_\-\.]+\z/ }
validates :week_start_day, inclusion: { in: 0..6 }
validate :name_available_in_reservations

after_create :reserve_name
Expand Down
Loading
Loading