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 ...@@ -16,6 +16,10 @@ class Admin::DashboardController < Admin::ApplicationController
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def stats
@users_statistics = UsersStatistics.latest
end
def show_license_breakdown? def show_license_breakdown?
false false
end end
......
# frozen_string_literal: true # frozen_string_literal: true
class UsersStatistics < ApplicationRecord class UsersStatistics < ApplicationRecord
STATISTICS_NAMES = [ scope :order_created_at_desc, -> { order(created_at: :desc) }
:without_groups_and_projects,
:with_highest_role_guest, class << self
:with_highest_role_reporter, def latest
:with_highest_role_developer, order_created_at_desc.first
:with_highest_role_maintainer, end
:with_highest_role_owner, end
:bots,
:blocked def active
].freeze [
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 class << self
def create_current_stats! def create_current_stats!
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
%hr %hr
.btn-group.d-flex{ role: 'group' } .btn-group.d-flex{ role: 'group' }
= link_to 'New user', new_admin_user_path, class: "btn btn-success" = 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 .col-sm-4
.info-well.dark-well .info-well.dark-well
.well-segment.well-centered .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 ...@@ -161,5 +161,7 @@ namespace :admin do
concerns :clusterable concerns :clusterable
get '/dashboard/stats', to: 'dashboard#stats'
root to: 'dashboard#index' root to: 'dashboard#index'
end end
...@@ -147,6 +147,9 @@ The **Total users** is calculated as: **Active users** + **Blocked users**. ...@@ -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 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). [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 ### Administering Groups
You can administer all groups in the GitLab instance from the Admin Area's Groups page. You can administer all groups in the GitLab instance from the Admin Area's Groups page.
......
...@@ -16,11 +16,6 @@ module EE ...@@ -16,11 +16,6 @@ module EE
@license = License.current @license = License.current
end 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 # The license section may time out if the number of users is
# high. To avoid 500 errors, just hide this section. This is a # high. To avoid 500 errors, just hide this section. This is a
# workaround for https://gitlab.com/gitlab-org/gitlab/issues/32287. # workaround for https://gitlab.com/gitlab-org/gitlab/issues/32287.
......
...@@ -465,6 +465,10 @@ class License < ApplicationRecord ...@@ -465,6 +465,10 @@ class License < ApplicationRecord
settings.update license_trial_ends_on: license.expires_at settings.update license_trial_ends_on: license.expires_at
end end
def paid?
[License::STARTER_PLAN, License::PREMIUM_PLAN, License::ULTIMATE_PLAN].include?(plan)
end
private private
def restricted_attr(name, default = nil) 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 ...@@ -60,6 +60,4 @@ namespace :admin do
namespace :elasticsearch do namespace :elasticsearch do
post :enqueue_index post :enqueue_index
end end
get '/dashboard/stats', to: 'dashboard#stats'
end end
...@@ -4,75 +4,51 @@ require 'spec_helper' ...@@ -4,75 +4,51 @@ require 'spec_helper'
describe 'Admin Dashboard' do describe 'Admin Dashboard' do
describe 'Users statistic' do describe 'Users statistic' do
before do let_it_be(:users_statistics) { create(:users_statistics) }
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)
before do
sign_in(create(:admin)) sign_in(create(:admin))
end end
describe 'Roles stats' do context 'for tooltip' do
context 'for tooltip' do before do
let(:create_guest) { false } allow(License).to receive(:current).and_return(license)
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 }
it { expect(page).not_to have_css('span.has-tooltip') } visit admin_dashboard_stats_path
end 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
context 'when guests exist' do context 'when license is empty' do
let(:create_guest) { true } let(:license) { nil }
it { expect(page).to have_css('span.has-tooltip') } it { expect(page).not_to have_css('span.has-tooltip') }
end end
end
context 'when license is on a plan other than Ultimate' do context 'when license is on a plan Ultimate' do
let(:license) { create(:license, plan: License::PREMIUM_PLAN) } let(:license) { create(:license, plan: License::ULTIMATE_PLAN) }
it { expect(page).not_to have_css('span.has-tooltip') } it { expect(page).to have_css('span.has-tooltip') }
end
end end
it 'shows correct amounts of users per role', :aggregate_failures do context 'when license is on a plan other than Ultimate' do
visit admin_dashboard_stats_path let(:license) { create(:license, plan: License::PREMIUM_PLAN) }
expect(page).to have_content('Users with highest role developer 2') it { expect(page).not_to have_css('span.has-tooltip') }
expect(page).to have_content('Users with highest role reporter 1')
expect(page).to have_content('Bots 2')
end end
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
end end
...@@ -760,4 +760,25 @@ describe License do ...@@ -760,4 +760,25 @@ describe License do
trueup_to: Date.today.to_s trueup_to: Date.today.to_s
} }
end 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 end
...@@ -1320,12 +1320,36 @@ msgstr "" ...@@ -1320,12 +1320,36 @@ msgstr ""
msgid "Admin notes" msgid "Admin notes"
msgstr "" msgstr ""
msgid "AdminArea|Active users"
msgstr ""
msgid "AdminArea|Billable users"
msgstr ""
msgid "AdminArea|Blocked users"
msgstr ""
msgid "AdminArea|Bots" msgid "AdminArea|Bots"
msgstr "" msgstr ""
msgid "AdminArea|Developer"
msgstr ""
msgid "AdminArea|Guest"
msgstr ""
msgid "AdminArea|Included Free in license" msgid "AdminArea|Included Free in license"
msgstr "" msgstr ""
msgid "AdminArea|Maintainer"
msgstr ""
msgid "AdminArea|Owner"
msgstr ""
msgid "AdminArea|Reporter"
msgstr ""
msgid "AdminArea|Stop all jobs" msgid "AdminArea|Stop all jobs"
msgstr "" msgstr ""
...@@ -1338,15 +1362,18 @@ msgstr "" ...@@ -1338,15 +1362,18 @@ msgstr ""
msgid "AdminArea|Stopping jobs failed" msgid "AdminArea|Stopping jobs failed"
msgstr "" msgstr ""
msgid "AdminArea|Users statistics" msgid "AdminArea|Total users"
msgstr "" msgstr ""
msgid "AdminArea|Users total" msgid "AdminArea|Users statistics"
msgstr "" msgstr ""
msgid "AdminArea|Users with highest role" msgid "AdminArea|Users with highest role"
msgstr "" 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." msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running."
msgstr "" msgstr ""
......
...@@ -2,5 +2,13 @@ ...@@ -2,5 +2,13 @@
FactoryBot.define do FactoryBot.define do
factory :users_statistics 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
end end
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
require 'spec_helper' require 'spec_helper'
describe 'admin visits dashboard', :js do describe 'admin visits dashboard' do
include ProjectForksHelper include ProjectForksHelper
before do before do
sign_in(create(:admin)) sign_in(create(:admin))
end end
context 'counting forks' do context 'counting forks', :js do
it 'correctly counts 2 forks of a project' do it 'correctly counts 2 forks of a project' do
project = create(:project) project = create(:project)
project_fork = fork_project(project) project_fork = fork_project(project)
...@@ -25,4 +25,26 @@ describe 'admin visits dashboard', :js do ...@@ -25,4 +25,26 @@ describe 'admin visits dashboard', :js do
expect(page).to have_content('Forks 2') expect(page).to have_content('Forks 2')
end end
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 end
...@@ -2,7 +2,36 @@ ...@@ -2,7 +2,36 @@
require 'spec_helper' 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 describe '.create_current_stats!' do
before do before do
create_list(:user_highest_role, 4) create_list(:user_highest_role, 4)
...@@ -40,4 +69,16 @@ RSpec.describe UsersStatistics do ...@@ -40,4 +69,16 @@ RSpec.describe UsersStatistics do
end end
end 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 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