Commit be1e6435 authored by Corinna Wiesner's avatar Corinna Wiesner

Implement the new users statistics view logic

With the clearer breakdown of the users statistics a new logic was
introduced to gather the users counts. The users statistics is also
available for CE and not just for EE now.
parent 87fbd139
......@@ -16,6 +16,10 @@ class Admin::DashboardController < Admin::ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
def stats
@users_statistics = UsersStatistics.latest
end
def show_license_breakdown?
false
end
......
# frozen_string_literal: true
class UsersStatistics < ApplicationRecord
STATISTICS_NAMES = [
:without_groups_and_projects,
:with_highest_role_guest,
:with_highest_role_reporter,
:with_highest_role_developer,
:with_highest_role_maintainer,
:with_highest_role_owner,
:bots,
:blocked
].freeze
scope :order_created_at_desc, -> { order(created_at: :desc) }
class << self
def latest
order_created_at_desc.first
end
end
def active
[
without_groups_and_projects,
with_highest_role_guest,
with_highest_role_reporter,
with_highest_role_developer,
with_highest_role_maintainer,
with_highest_role_owner,
bots
].sum
end
def total
active + blocked
end
class << self
def create_current_stats!
......
......@@ -30,7 +30,7 @@
%hr
.btn-group.d-flex{ role: 'group' }
= link_to 'New user', new_admin_user_path, class: "btn btn-success"
= render_if_exists 'admin/dashboard/users_statistics'
= link_to s_('AdminArea|Users statistics'), admin_dashboard_stats_path, class: 'btn btn-primary'
.col-sm-4
.info-well.dark-well
.well-segment.well-centered
......
- page_title s_('AdminArea|Users statistics')
%h3.my-4
= s_('AdminArea|Users statistics')
%table.table.gl-text-gray-700
%tr
%td.p-3
= s_('AdminArea|Users without a Group and Project')
= render_if_exists 'admin/dashboard/included_free_in_license_tooltip'
%td.p-3.text-right
= @users_statistics&.without_groups_and_projects.to_i
%tr
%td.p-3
= s_('AdminArea|Users with highest role')
%strong
= s_('AdminArea|Guest')
= render_if_exists 'admin/dashboard/included_free_in_license_tooltip'
%td.p-3.text-right
= @users_statistics&.with_highest_role_guest.to_i
%tr
%td.p-3
= s_('AdminArea|Users with highest role')
%strong
= s_('AdminArea|Reporter')
%td.p-3.text-right
= @users_statistics&.with_highest_role_reporter.to_i
%tr
%td.p-3
= s_('AdminArea|Users with highest role')
%strong
= s_('AdminArea|Developer')
%td.p-3.text-right
= @users_statistics&.with_highest_role_developer.to_i
%tr
%td.p-3
= s_('AdminArea|Users with highest role')
%strong
= s_('AdminArea|Maintainer')
%td.p-3.text-right
= @users_statistics&.with_highest_role_maintainer.to_i
%tr
%td.p-3
= s_('AdminArea|Users with highest role')
%strong
= s_('AdminArea|Owner')
%td.p-3.text-right
= @users_statistics&.with_highest_role_owner.to_i
%tr
%td.p-3
= s_('AdminArea|Bots')
%td.p-3.text-right
= @users_statistics&.bots.to_i
%tr.bg-gray-light.gl-text-gray-900
%td.p-3
%strong
= s_('AdminArea|Active users')
= render_if_exists 'admin/dashboard/billable_users_text'
%td.p-3.text-right
%strong
= @users_statistics&.active.to_i
%tr.bg-gray-light.gl-text-gray-900
%td.p-3
%strong
= s_('AdminArea|Blocked users')
%td.p-3.text-right
%strong
= @users_statistics&.blocked.to_i
%tr.bg-gray-light.gl-text-gray-900
%td.p-3
%strong
= s_('AdminArea|Total users')
%td.p-3.text-right
%strong
= @users_statistics&.total.to_i
---
title: Show user statistics in admin area also in CE, and use daily generated data for these statistics
merge_request: 27345
author:
type: changed
......@@ -161,5 +161,7 @@ namespace :admin do
concerns :clusterable
get '/dashboard/stats', to: 'dashboard#stats'
root to: 'dashboard#index'
end
......@@ -147,6 +147,9 @@ The **Total users** is calculated as: **Active users** + **Blocked users**.
GitLab billing is based on the number of active users. For details of active users, see
[Choosing the number of users](../../subscriptions/index.md#choosing-the-number-of-users).
**Please note** that during the initial stage, the information won't be 100% accurate given that
background processes are still recollecting data.
### Administering Groups
You can administer all groups in the GitLab instance from the Admin Area's Groups page.
......
......@@ -16,11 +16,6 @@ module EE
@license = License.current
end
def stats
@roles_count = ::ProjectAuthorization.roles_stats
@bot_count = ::User.bots.count
end
# The license section may time out if the number of users is
# high. To avoid 500 errors, just hide this section. This is a
# workaround for https://gitlab.com/gitlab-org/gitlab/issues/32287.
......
......@@ -465,6 +465,10 @@ class License < ApplicationRecord
settings.update license_trial_ends_on: license.expires_at
end
def paid?
[License::STARTER_PLAN, License::PREMIUM_PLAN, License::ULTIMATE_PLAN].include?(plan)
end
private
def restricted_attr(name, default = nil)
......
- if License.current&.paid?
= "(#{s_('AdminArea|Billable users')})"
- if License.current&.exclude_guests_from_active_count?
%span.has-tooltip{ data: { container: 'body' }, title: s_('AdminArea|Included Free in license') }
= sprite_icon('question', size: 12, css_class: 'gl-text-gray-900')
= link_to 'Users statistics', admin_dashboard_stats_path, class: "btn btn-primary"
- page_title s_('AdminArea|Users statistics')
%h3
= s_('AdminArea|Users statistics')
%table.table
%tr
%td
= s_('AdminArea|Users total')
%td
= User.count
- if @roles_count
- @roles_count.each do |row|
%tr
%td
= s_('AdminArea|Users with highest role')
%strong
= row['kind']
- if row['kind'] == 'guest' && License.current&.exclude_guests_from_active_count?
%span.has-tooltip{ data: { container: 'body' }, title: s_('AdminArea|Included Free in license') }
= sprite_icon('question', size: 16)
%td
= row['amount']
%tr
%td
= s_('AdminArea|Bots')
%td
= @bot_count
......@@ -60,6 +60,4 @@ namespace :admin do
namespace :elasticsearch do
post :enqueue_index
end
get '/dashboard/stats', to: 'dashboard#stats'
end
......@@ -4,75 +4,51 @@ require 'spec_helper'
describe 'Admin Dashboard' do
describe 'Users statistic' do
before do
project1 = create(:project_empty_repo)
project1.add_reporter(create(:user))
project2 = create(:project_empty_repo)
project2.add_developer(create(:user))
# Add same user as Reporter and Developer to different projects
# and expect it to be counted once for the stats
user = create(:user)
project1.add_reporter(user)
project2.add_developer(user)
create(:user, user_type: :support_bot)
create(:user, user_type: :alert_bot)
let_it_be(:users_statistics) { create(:users_statistics) }
before do
sign_in(create(:admin))
end
describe 'Roles stats' do
context 'for tooltip' do
let(:create_guest) { false }
before do
allow(License).to receive(:current).and_return(license)
if create_guest
project = create(:project_empty_repo)
guest_user = create(:user)
project.add_guest(guest_user)
end
visit admin_dashboard_stats_path
end
context 'when license is empty' do
let(:license) { nil }
context 'for tooltip' do
before do
allow(License).to receive(:current).and_return(license)
it { expect(page).not_to have_css('span.has-tooltip') }
end
context 'when license is on a plan Ultimate' do
let(:license) { create(:license, plan: License::ULTIMATE_PLAN) }
context 'when guests do not exist' do
it { expect(page).not_to have_css('span.has-tooltip') }
end
visit admin_dashboard_stats_path
end
context 'when guests exist' do
let(:create_guest) { true }
context 'when license is empty' do
let(:license) { nil }
it { expect(page).to have_css('span.has-tooltip') }
end
end
it { expect(page).not_to have_css('span.has-tooltip') }
end
context 'when license is on a plan other than Ultimate' do
let(:license) { create(:license, plan: License::PREMIUM_PLAN) }
context 'when license is on a plan Ultimate' do
let(:license) { create(:license, plan: License::ULTIMATE_PLAN) }
it { expect(page).not_to have_css('span.has-tooltip') }
end
it { expect(page).to have_css('span.has-tooltip') }
end
it 'shows correct amounts of users per role', :aggregate_failures do
visit admin_dashboard_stats_path
context 'when license is on a plan other than Ultimate' do
let(:license) { create(:license, plan: License::PREMIUM_PLAN) }
expect(page).to have_content('Users with highest role developer 2')
expect(page).to have_content('Users with highest role reporter 1')
expect(page).to have_content('Bots 2')
it { expect(page).not_to have_css('span.has-tooltip') }
end
end
it 'shows correct amounts of users', :aggregate_failures do
visit admin_dashboard_stats_path
expect(page).to have_content("Users without a Group and Project 23")
expect(page).to have_content("Users with highest role Guest 5")
expect(page).to have_content("Users with highest role Reporter 9")
expect(page).to have_content("Users with highest role Developer 21")
expect(page).to have_content("Users with highest role Maintainer 6")
expect(page).to have_content("Users with highest role Owner 5")
expect(page).to have_content("Bots 2")
expect(page).to have_content("Active users (Billable users) 71")
expect(page).to have_content("Blocked users 7")
expect(page).to have_content("Total users 78")
end
end
end
......@@ -760,4 +760,25 @@ describe License do
trueup_to: Date.today.to_s
}
end
describe '#paid?' do
using RSpec::Parameterized::TableSyntax
where(:plan, :paid_result) do
License::STARTER_PLAN | true
License::PREMIUM_PLAN | true
License::ULTIMATE_PLAN | true
nil | true
end
with_them do
let(:license) { build(:license, plan: plan) }
subject { license.paid? }
it do
is_expected.to eq(paid_result)
end
end
end
end
......@@ -1320,12 +1320,36 @@ msgstr ""
msgid "Admin notes"
msgstr ""
msgid "AdminArea|Active users"
msgstr ""
msgid "AdminArea|Billable users"
msgstr ""
msgid "AdminArea|Blocked users"
msgstr ""
msgid "AdminArea|Bots"
msgstr ""
msgid "AdminArea|Developer"
msgstr ""
msgid "AdminArea|Guest"
msgstr ""
msgid "AdminArea|Included Free in license"
msgstr ""
msgid "AdminArea|Maintainer"
msgstr ""
msgid "AdminArea|Owner"
msgstr ""
msgid "AdminArea|Reporter"
msgstr ""
msgid "AdminArea|Stop all jobs"
msgstr ""
......@@ -1338,15 +1362,18 @@ msgstr ""
msgid "AdminArea|Stopping jobs failed"
msgstr ""
msgid "AdminArea|Users statistics"
msgid "AdminArea|Total users"
msgstr ""
msgid "AdminArea|Users total"
msgid "AdminArea|Users statistics"
msgstr ""
msgid "AdminArea|Users with highest role"
msgstr ""
msgid "AdminArea|Users without a Group and Project"
msgstr ""
msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running."
msgstr ""
......
......@@ -2,5 +2,13 @@
FactoryBot.define do
factory :users_statistics do
without_groups_and_projects { 23 }
with_highest_role_guest { 5 }
with_highest_role_reporter { 9 }
with_highest_role_developer { 21 }
with_highest_role_maintainer { 6 }
with_highest_role_owner { 5 }
bots { 2 }
blocked { 7 }
end
end
......@@ -2,14 +2,14 @@
require 'spec_helper'
describe 'admin visits dashboard', :js do
describe 'admin visits dashboard' do
include ProjectForksHelper
before do
sign_in(create(:admin))
end
context 'counting forks' do
context 'counting forks', :js do
it 'correctly counts 2 forks of a project' do
project = create(:project)
project_fork = fork_project(project)
......@@ -25,4 +25,26 @@ describe 'admin visits dashboard', :js do
expect(page).to have_content('Forks 2')
end
end
describe 'Users statistic' do
let_it_be(:users_statistics) { create(:users_statistics) }
it 'shows correct amounts of users', :aggregate_failures do
expected_active_users_text = Gitlab.ee? ? 'Active users (Billable users) 71' : 'Active users 71'
sign_in(create(:admin))
visit admin_dashboard_stats_path
expect(page).to have_content('Users without a Group and Project 23')
expect(page).to have_content('Users with highest role Guest 5')
expect(page).to have_content('Users with highest role Reporter 9')
expect(page).to have_content('Users with highest role Developer 21')
expect(page).to have_content('Users with highest role Maintainer 6')
expect(page).to have_content('Users with highest role Owner 5')
expect(page).to have_content('Bots 2')
expect(page).to have_content(expected_active_users_text)
expect(page).to have_content('Blocked users 7')
expect(page).to have_content('Total users 78')
end
end
end
......@@ -2,7 +2,36 @@
require 'spec_helper'
RSpec.describe UsersStatistics do
describe UsersStatistics do
let(:users_statistics) { build(:users_statistics) }
describe 'scopes' do
describe '.order_created_at_desc' do
it 'returns the entries ordered by created at descending' do
users_statistics1 = create(:users_statistics, created_at: Time.current)
users_statistics2 = create(:users_statistics, created_at: Time.current - 2.days)
users_statistics3 = create(:users_statistics, created_at: Time.current - 5.hours)
expect(described_class.order_created_at_desc).to eq(
[
users_statistics1,
users_statistics3,
users_statistics2
]
)
end
end
end
describe '.latest' do
it 'returns the latest entry' do
create(:users_statistics, created_at: Time.current - 1.day)
users_statistics = create(:users_statistics, created_at: Time.current)
expect(described_class.latest).to eq(users_statistics)
end
end
describe '.create_current_stats!' do
before do
create_list(:user_highest_role, 4)
......@@ -40,4 +69,16 @@ RSpec.describe UsersStatistics do
end
end
end
describe '#active' do
it 'sums users statistics values without the value for blocked' do
expect(users_statistics.active).to eq(71)
end
end
describe '#total' do
it 'sums all users statistics values' do
expect(users_statistics.total).to eq(78)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment