Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Classification user groups individual stats #25

Merged
merged 7 commits into from
Sep 6, 2023
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)
zwolf marked this conversation as resolved.
Show resolved Hide resolved
{
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) }
yuenmichelle1 marked this conversation as resolved.
Show resolved Hide resolved
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" '
yuenmichelle1 marked this conversation as resolved.
Show resolved Hide resolved
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