Skip to content

Commit

Permalink
Classification user groups individual stats (#25)
Browse files Browse the repository at this point in the history
* individual stats breakdown per group. add session time to user group projects breakdown.

* update sort of group member stats breakdown

* Update user_group_member_stats_breakdown_serializer.rb

* add specs user group classification count controller for individual stats breakdown stats

* add specs for group member breakdown query stats

* add spec for serializer

* update session time to be included with top contributors
  • Loading branch information
yuenmichelle1 authored Sep 6, 2023
1 parent 364f28d commit d0ec241
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 25 deletions.
5 changes: 3 additions & 2 deletions app/controllers/user_group_classification_count_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ def query
# authorize :queried_user_group_context, :show?
skip_authorization
if params[:individual_stats_breakdown]
# TODO: in a separate PR
# Plan is to query from DailyGroupClassificationCountAndTimePerUserPerProject
group_member_classification_counts = CountGroupMemberBreakdown.new.call(group_classification_count_params)

render json: UserGroupMemberStatsBreakdownSerializer.new(group_member_classification_counts)
else
group_classification_counts = CountGroupClassifications.new(group_classification_count_params).call(group_classification_count_params)
group_active_user_classification_counts = CountGroupActiveUserClassifications.new(group_classification_count_params).call(group_classification_count_params)
Expand Down
2 changes: 1 addition & 1 deletion app/queries/count_group_active_user_classifications.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def initial_scope(relation)
end

def select_clause
'user_id, SUM(classification_count)::integer AS count'
'user_id, SUM(classification_count)::integer AS count, SUM(total_session_time)::float AS session_time'
end

def relation(params)
Expand Down
30 changes: 30 additions & 0 deletions app/queries/count_group_member_breakdown.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

class CountGroupMemberBreakdown
include Filterable
attr_reader :counts

def initialize
@counts = initial_scope(relation)
end

def call(params={})
scoped = @counts
scoped = filter_by_user_group_id(scoped, params[:id])
filter_by_date_range(scoped, params[:start_date], params[:end_date])
end

private

def initial_scope(relation)
relation.select(select_clause).group('user_id, project_id')
end

def select_clause
'user_id, project_id, SUM(classification_count)::integer AS count, SUM(total_session_time)::float AS session_time'
end

def relation
UserGroupClassificationCounts::DailyGroupUserProjectClassificationCount
end
end
2 changes: 1 addition & 1 deletion app/queries/count_group_project_contributions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def initial_scope(relation)
end

def select_clause
'project_id, SUM(classification_count)::integer AS count'
'project_id, SUM(classification_count)::integer AS count, SUM(total_session_time)::float AS session_time'
end

def relation
Expand Down
31 changes: 31 additions & 0 deletions app/serializers/user_group_member_stats_breakdown_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

class UserGroupMemberStatsBreakdownSerializer
attr_reader :group_member_classification_counts

def initialize(counts_scope)
@group_member_classification_counts = counts_scope
end

def as_json(_options)
{
group_member_stats_breakdown: counts_grouped_by_user.sort_by { |member_stat| member_stat[:count] }.reverse
}
end

private

def counts_grouped_by_user
counts_grouped_by_member = group_member_classification_counts.group_by { |member_proj_contribution| member_proj_contribution[:user_id] }.transform_values do |member_counts_per_project|
total_per_member = { count: member_counts_per_project.sum(&:count) }
total_per_member[:session_time] = member_counts_per_project.sum(&:session_time)
total_per_member[:project_contributions] = individual_member_project_contributions(member_counts_per_project)
total_per_member
end
counts_grouped_by_member.map { |user_id, totals| { user_id: }.merge(totals) }
end

def individual_member_project_contributions(member_counts_per_project)
member_counts_per_project.sort_by(&:count).reverse.map { |member_count| member_count.as_json.except('user_id') }
end
end
49 changes: 30 additions & 19 deletions spec/controllers/user_group_classification_count_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,39 @@
describe 'GET query' do
let!(:classification_user_group) { create(:classification_user_group) }

it 'returns total_count, time_spent, active_users, and project_contributions of user group' do
get :query, params: { id: classification_user_group.user_group_id.to_s }
expect(response.status).to eq(200)
response_body = JSON.parse(response.body)
expect(response_body['total_count']).to eq(1)
expect(response_body['time_spent']).to eq(classification_user_group.session_time)
expect(response_body['active_users']).to eq(1)
expect(response_body['project_contributions'].length).to eq(1)
end
context 'individual_stats_breakdown is false/not a param' do
it 'returns total_count, time_spent, active_users, and project_contributions of user group' do
get :query, params: { id: classification_user_group.user_group_id.to_s }
expect(response.status).to eq(200)
response_body = JSON.parse(response.body)
expect(response_body['total_count']).to eq(1)
expect(response_body['time_spent']).to eq(classification_user_group.session_time)
expect(response_body['active_users']).to eq(1)
expect(response_body['project_contributions'].length).to eq(1)
end

it 'does not compute project_contributions when params[:project_id] given' do
get :query, params: { id: classification_user_group.user_group_id.to_s, project_id: 2 }
expect(response.status).to eq(200)
response_body = JSON.parse(response.body)
expect(response_body).not_to have_key('project_contributions')
end

it 'does not compute project_contributions when params[:project_id] given' do
get :query, params: { id: classification_user_group.user_group_id.to_s, project_id: 2 }
expect(response.status).to eq(200)
response_body = JSON.parse(response.body)
expect(response_body).not_to have_key(:project_contributions)
it 'does not compute project_contributions when params[:workflow_id] given' do
get :query, params: { id: classification_user_group.user_group_id.to_s, workflow_id: 2 }
expect(response.status).to eq(200)
response_body = JSON.parse(response.body)
expect(response_body).not_to have_key('project_contributions')
end
end

it 'does not compute project_contributions when params[:workflow_id] given' do
get :query, params: { id: classification_user_group.user_group_id.to_s, workflow_id: 2 }
expect(response.status).to eq(200)
response_body = JSON.parse(response.body)
expect(response_body).not_to have_key(:project_contributions)
context 'individual_stats_breakdown is true' do
it 'returns group_member_stats_breakdown' do
get :query, params: { id: classification_user_group.user_group_id.to_s, individual_stats_breakdown: true }
expect(response.status).to eq(200)
response_body = JSON.parse(response.body)
expect(response_body).to have_key('group_member_stats_breakdown')
end
end

context 'param validations' do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
describe 'select_clause' do
it 'selects user_id and orders users by count' do
counts = group_active_users_query.call(params)
expected_select_query = 'SELECT user_id, SUM(classification_count)::integer AS count FROM "daily_group_classification_count_and_time_per_user" '
expected_select_query = 'SELECT user_id, SUM(classification_count)::integer AS count, SUM(total_session_time)::float AS session_time FROM "daily_group_classification_count_and_time_per_user" '
expected_select_query += 'GROUP BY "daily_group_classification_count_and_time_per_user"."user_id" ORDER BY count DESC'
expect(counts.to_sql).to eq(expected_select_query)
end
Expand Down
73 changes: 73 additions & 0 deletions spec/queries/count_group_member_breakdown_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe CountGroupMemberBreakdown do
let(:params) { {} }
let(:group_member_breakdown_query) { described_class.new }
describe 'relation' do
it 'returns DailyGroupUserProjectClassificationCount' do
expect(group_member_breakdown_query.counts.model).to be UserGroupClassificationCounts::DailyGroupUserProjectClassificationCount
end
end

describe 'select_clause' do
it 'selects user_id, project_id, sum of counts and time' do
counts = group_member_breakdown_query.call(params)
expected_select_query = 'SELECT user_id, project_id, SUM(classification_count)::integer AS count, SUM(total_session_time)::float AS session_time '
expected_select_query += 'FROM "daily_group_classification_count_and_time_per_user_per_project" '
expected_select_query += 'GROUP BY user_id, project_id'
expect(counts.to_sql).to eq(expected_select_query)
end
end

describe '#call' do
let!(:classification_user_group) { create(:classification_user_group) }
let!(:diff_workflow_event) { create(:cug_with_diff_workflow) }
let!(:diff_project_event) { create(:cug_with_diff_project) }
let!(:diff_time_event) { create(:cug_created_yesterday) }
let!(:diff_user_classification) { create(:cug_with_diff_user) }
let!(:diff_user_group_classification) { create(:cug_with_diff_user_group) }

before(:each) do
params[:id] = classification_user_group.user_group_id.to_s
end

it_behaves_like 'is filterable by date range' do
let(:counts_query) { described_class.new }
end

it 'filters by given user_group_id' do
counts = group_member_breakdown_query.call(params)
expect(counts.to_sql).to include(".\"user_group_id\" = #{classification_user_group.user_id}")
end

it 'returns classification counts of given user group grouped by user and project' do
counts = group_member_breakdown_query.call(params)
# because default is grouped by project_id and user_id, we expect results to look something like:
# [
# <UserGroupClassificationCounts::DailyGroupUserProjectClassificationCount user_id: 1, project_id: 1, count: 3, session_time: 10>,
# <UserGroupClassificationCounts::DailyGroupUserProjectClassificationCount user_id: 1, project_id: 2, count: 1, session_time: 10>,
# <UserGroupClassificationCounts::DailyGroupUserProjectClassificationCount user_id: 2, project_id: 1, count: 3, session_time: 10>
# ]
expect(counts.length).to eq(3)
# the 3 for user_id: 1, project_id:1 being
# [classification_user_group, diff_workflow_event, diff_time_event]
expect(counts[0].count).to eq(3)
expect(counts[1].count).to eq(1)
expect(counts[1].count).to eq(1)
end

it 'returns counts of events within given date range' do
last_week = Date.today - 7
yesterday = Date.today - 1
params[:start_date] = last_week.to_s
params[:end_date] = yesterday.to_s
counts = group_member_breakdown_query.call(params)
expect(counts.length).to eq(1)
expect(counts[0].count).to eq(1)
expect(counts[0].project_id).to eq(diff_time_event.project_id)
expect(counts[0].user_id).to eq(diff_time_event.user_id)
end
end
end
2 changes: 1 addition & 1 deletion spec/queries/count_group_project_contributions_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
describe 'select_clause' do
it 'selects project_id and orders by count' do
counts = group_classifications_query.call(params)
expected_select_query = 'SELECT project_id, SUM(classification_count)::integer AS count FROM "daily_group_classification_count_and_time_per_project" '
expected_select_query = 'SELECT project_id, SUM(classification_count)::integer AS count, SUM(total_session_time)::float AS session_time FROM "daily_group_classification_count_and_time_per_project" '
expected_select_query += 'GROUP BY "daily_group_classification_count_and_time_per_project"."project_id" ORDER BY count DESC'
expect(counts.to_sql).to eq(expected_select_query)
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe UserGroupMemberStatsBreakdownSerializer do
let(:user_project_classification_count) { build(:daily_user_project_classification_count) }

let(:count_serializer) { described_class.new([user_project_classification_count]) }

it 'returns group_member_stats as array' do
serialized = count_serializer.as_json({})
expect(serialized).to have_key(:group_member_stats_breakdown)
expect(serialized[:group_member_stats_breakdown].length).to eq(1)
expect(serialized[:group_member_stats_breakdown][0]).to have_key(:user_id)
expect(serialized[:group_member_stats_breakdown][0]).to have_key(:count)
expect(serialized[:group_member_stats_breakdown][0]).to have_key(:session_time)
expect(serialized[:group_member_stats_breakdown][0]).to have_key(:project_contributions)
end

it 'sums up total_count per user correctly' do
count2 = build(:user_diff_proj_classification_count)
classification_counts = [user_project_classification_count, count2]
serializer = described_class.new(classification_counts)
serialized = serializer.as_json({})
expect(serialized[:group_member_stats_breakdown][0][:count]).to eq(classification_counts.sum(&:count))
end

it 'sums up time_spent per user correctly' do
count2 = build(:user_diff_proj_classification_count)
classification_counts = [user_project_classification_count, count2]
serializer = described_class.new(classification_counts)
serialized = serializer.as_json({})
expect(serialized[:group_member_stats_breakdown][0][:session_time]).to eq(classification_counts.sum(&:session_time))
end

it 'shows project contributions per user correctly' do
count2 = build(:user_diff_proj_classification_count)
classification_counts = [user_project_classification_count, count2]
serializer = described_class.new(classification_counts)
serialized = serializer.as_json({})
member_project_contributions = serialized[:group_member_stats_breakdown][0][:project_contributions]
expect(member_project_contributions.length).to eq(2)
expect(member_project_contributions[0]).not_to have_key('user_id')
expect(member_project_contributions[0]).to have_key('project_id')
expect(member_project_contributions[0]).to have_key('count')
expect(member_project_contributions[0]).to have_key('session_time')
end

it 'shows user project contributions in order by count desc' do
count2 = build(:user_diff_proj_classification_count)
count2.count = user_project_classification_count.count + 100
classification_counts = [user_project_classification_count, count2]
serializer = described_class.new(classification_counts)
serialized = serializer.as_json({})
member_project_contributions = serialized[:group_member_stats_breakdown][0][:project_contributions]
expect(member_project_contributions[0]['project_id']).to eq(count2.project_id)
expect(member_project_contributions[0]['count']).to eq(count2.count)
expect(member_project_contributions[0]['session_time']).to eq(count2.session_time)
expect(member_project_contributions[1]['project_id']).to eq(user_project_classification_count.project_id)
expect(member_project_contributions[1]['count']).to eq(user_project_classification_count.count)
expect(member_project_contributions[1]['session_time']).to eq(user_project_classification_count.session_time)
end

it 'shows group_memer_stats_breakdown in order by top contributors' do
diff_group_member_stats = build(:daily_user_project_classification_count)
diff_group_member_stats.user_id = 2
diff_group_member_stats.count = user_project_classification_count.count + 100
classification_counts = [user_project_classification_count, diff_group_member_stats]
serializer = described_class.new(classification_counts)
serialized = serializer.as_json({})
expect(serialized[:group_member_stats_breakdown].length).to eq(2)
expect(serialized[:group_member_stats_breakdown][0][:user_id]).to eq(diff_group_member_stats.user_id)
expect(serialized[:group_member_stats_breakdown][1][:user_id]).to eq(user_project_classification_count.user_id)
end
end

0 comments on commit d0ec241

Please sign in to comment.