Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Jérome Perrin
gitlab-ce
Commits
ac0146a0
Commit
ac0146a0
authored
Apr 06, 2017
by
Sean McGivern
Committed by
Rémy Coutable
Apr 14, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use serializer for formatting cohorts data
parent
61eaf4fe
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
168 additions
and
67 deletions
+168
-67
app/controllers/admin/cohorts_controller.rb
app/controllers/admin/cohorts_controller.rb
+4
-2
app/serializers/cohort_activity_month_entity.rb
app/serializers/cohort_activity_month_entity.rb
+11
-0
app/serializers/cohort_entity.rb
app/serializers/cohort_entity.rb
+17
-0
app/serializers/cohorts_entity.rb
app/serializers/cohorts_entity.rb
+4
-0
app/serializers/cohorts_serializer.rb
app/serializers/cohorts_serializer.rb
+3
-0
app/services/cohorts_service.rb
app/services/cohorts_service.rb
+40
-27
app/views/admin/cohorts/_cohorts_table.html.haml
app/views/admin/cohorts/_cohorts_table.html.haml
+13
-23
spec/services/cohorts_service_spec.rb
spec/services/cohorts_service_spec.rb
+76
-15
No files found.
app/controllers/admin/cohorts_controller.rb
View file @
ac0146a0
class
Admin::CohortsController
<
Admin
::
ApplicationController
class
Admin::CohortsController
<
Admin
::
ApplicationController
def
index
def
index
if
ApplicationSetting
.
current
.
usage_ping_enabled
if
current_application_settings
.
usage_ping_enabled
@cohor
ts
=
Rails
.
cache
.
fetch
(
'cohorts'
,
expires_in:
1
.
day
)
do
cohorts_resul
ts
=
Rails
.
cache
.
fetch
(
'cohorts'
,
expires_in:
1
.
day
)
do
CohortsService
.
new
.
execute
CohortsService
.
new
.
execute
end
end
@cohorts
=
CohortsSerializer
.
new
.
represent
(
cohorts_results
)
end
end
end
end
end
end
app/serializers/cohort_activity_month_entity.rb
0 → 100644
View file @
ac0146a0
class
CohortActivityMonthEntity
<
Grape
::
Entity
include
ActionView
::
Helpers
::
NumberHelper
expose
:total
do
|
cohort_activity_month
|
number_with_delimiter
(
cohort_activity_month
[
:total
])
end
expose
:percentage
do
|
cohort_activity_month
|
number_to_percentage
(
cohort_activity_month
[
:percentage
],
precision:
0
)
end
end
app/serializers/cohort_entity.rb
0 → 100644
View file @
ac0146a0
class
CohortEntity
<
Grape
::
Entity
include
ActionView
::
Helpers
::
NumberHelper
expose
:registration_month
do
|
cohort
|
cohort
[
:registration_month
].
strftime
(
'%b %Y'
)
end
expose
:total
do
|
cohort
|
number_with_delimiter
(
cohort
[
:total
])
end
expose
:inactive
do
|
cohort
|
number_with_delimiter
(
cohort
[
:inactive
])
end
expose
:activity_months
,
using:
CohortActivityMonthEntity
end
app/serializers/cohorts_entity.rb
0 → 100644
View file @
ac0146a0
class
CohortsEntity
<
Grape
::
Entity
expose
:months_included
expose
:cohorts
,
using:
CohortEntity
end
app/serializers/cohorts_serializer.rb
0 → 100644
View file @
ac0146a0
class
CohortsSerializer
<
AnalyticsGenericSerializer
entity
CohortsEntity
end
app/services/cohorts_service.rb
View file @
ac0146a0
class
CohortsService
class
CohortsService
MONTHS_INCLUDED
=
12
MONTHS_INCLUDED
=
12
# Get a hash that looks like:
def
execute
{
months_included:
MONTHS_INCLUDED
,
cohorts:
cohorts
}
end
# Get an array of hashes that looks like:
#
#
# {
# [
# month => {
# {
# months: [3, 2, 1],
# registration_month: Date.new(2017, 3),
# activity_months: [3, 2, 1],
# total: 3
# total: 3
# inactive: 0
# inactive: 0
# },
# },
...
@@ -13,29 +21,26 @@ class CohortsService
...
@@ -13,29 +21,26 @@ class CohortsService
#
#
# The `months` array is always from oldest to newest, so it's always
# The `months` array is always from oldest to newest, so it's always
# non-strictly decreasing from left to right.
# non-strictly decreasing from left to right.
#
def
cohorts
def
execute
cohorts
=
{}
months
=
Array
.
new
(
MONTHS_INCLUDED
)
{
|
i
|
i
.
months
.
ago
.
beginning_of_month
.
to_date
}
months
=
Array
.
new
(
MONTHS_INCLUDED
)
{
|
i
|
i
.
months
.
ago
.
beginning_of_month
.
to_date
}
MONTHS_INCLUDED
.
times
do
Array
.
new
(
MONTHS_INCLUDED
)
do
created_at
_month
=
months
.
last
registration
_month
=
months
.
last
activity_months
=
running_totals
(
months
,
created_at
_month
)
activity_months
=
running_totals
(
months
,
registration
_month
)
# Even if no users registered in this month, we always want to have a
# Even if no users registered in this month, we always want to have a
# value to fill in the table.
# value to fill in the table.
inactive
=
counts_by_month
[[
created_at_month
,
nil
]].
to_i
inactive
=
counts_by_month
[[
registration_month
,
nil
]].
to_i
months
.
pop
cohorts
[
created_at_month
]
=
{
{
months:
activity_months
,
registration_month:
registration_month
,
total:
activity_months
.
first
,
activity_months:
activity_months
,
total:
activity_months
.
first
[
:total
],
inactive:
inactive
inactive:
inactive
}
}
months
.
pop
end
end
cohorts
end
end
private
private
...
@@ -44,11 +49,20 @@ class CohortsService
...
@@ -44,11 +49,20 @@ class CohortsService
# count as active in this month, too. Start with the most recent month first,
# count as active in this month, too. Start with the most recent month first,
# for calculating the running totals, and then reverse for displaying in the
# for calculating the running totals, and then reverse for displaying in the
# table.
# table.
def
running_totals
(
all_months
,
created_at_month
)
#
all_months
# Each month has a total, and a percentage of the overall total, as keys.
.
map
{
|
activity_month
|
counts_by_month
[[
created_at_month
,
activity_month
]]
}
def
running_totals
(
all_months
,
registration_month
)
.
reduce
([])
{
|
result
,
total
|
result
<<
result
.
last
.
to_i
+
total
.
to_i
}
month_totals
=
.
reverse
all_months
.
map
{
|
activity_month
|
counts_by_month
[[
registration_month
,
activity_month
]]
}
.
reduce
([])
{
|
result
,
total
|
result
<<
result
.
last
.
to_i
+
total
.
to_i
}
.
reverse
overall_total
=
month_totals
.
first
month_totals
.
map
do
|
total
|
{
total:
total
,
percentage:
total
.
zero?
?
0
:
100
*
total
/
overall_total
}
end
end
end
# Get a hash that looks like:
# Get a hash that looks like:
...
@@ -60,9 +74,8 @@ class CohortsService
...
@@ -60,9 +74,8 @@ class CohortsService
# }
# }
#
#
# created_at_month can never be nil, but current_sign_in_at_month can (when a
# created_at_month can never be nil, but current_sign_in_at_month can (when a
# user has never logged in, just been created). This covers the last twelve
# user has never logged in, just been created). This covers the last
# months.
# MONTHS_INCLUDED months.
#
def
counts_by_month
def
counts_by_month
@counts_by_month
||=
@counts_by_month
||=
begin
begin
...
@@ -80,7 +93,7 @@ class CohortsService
...
@@ -80,7 +93,7 @@ class CohortsService
def
column_to_date
(
column
)
def
column_to_date
(
column
)
if
Gitlab
::
Database
.
postgresql?
if
Gitlab
::
Database
.
postgresql?
"CAST(DATE_TRUNC('month',
#{
column
}
) AS date)"
"CAST(DATE_TRUNC('month',
#{
column
}
) AS date)"
els
if
Gitlab
::
Database
.
mysql?
els
e
"STR_TO_DATE(DATE_FORMAT(
#{
column
}
, '%Y-%m-01'), '%Y-%m-%d')"
"STR_TO_DATE(DATE_FORMAT(
#{
column
}
, '%Y-%m-01'), '%Y-%m-%d')"
end
end
end
end
...
...
app/views/admin/cohorts/_cohorts_table.html.haml
View file @
ac0146a0
.bs-callout.clearfix
.bs-callout.clearfix
%p
%p
User cohorts are shown for the last
twelve months. Only users with
User cohorts are shown for the last
#{
@cohorts
[
:months_included
]
}
activity are counted in the cohort total; inactive users are counted
months. Only users with activity are counted in the cohort total; inactive
separately.
users are counted
separately.
=
link_to
icon
(
'question-circle'
),
help_page_path
(
'administration/usage_ping_and_cohorts'
,
anchor:
'cohorts'
),
title:
'About this feature'
,
target:
'_blank'
=
link_to
icon
(
'question-circle'
),
help_page_path
(
'administration/usage_ping_and_cohorts'
,
anchor:
'cohorts'
),
title:
'About this feature'
,
target:
'_blank'
.table-holder
.table-holder
...
@@ -12,27 +12,17 @@
...
@@ -12,27 +12,17 @@
%th
Registration month
%th
Registration month
%th
Inactive users
%th
Inactive users
%th
Cohort total
%th
Cohort total
%th
Month 0
-
@cohorts
[
:months_included
].
times
do
|
i
|
%th
Month 1
%th
Month
#{
i
}
%th
Month 2
%th
Month 3
%th
Month 4
%th
Month 5
%th
Month 6
%th
Month 7
%th
Month 8
%th
Month 9
%th
Month 10
%th
Month 11
%tbody
%tbody
-
@cohorts
.
each
do
|
registration_month
,
cohort
|
-
@cohorts
[
:cohorts
].
each
do
|
cohort
|
%tr
%tr
%td
=
registration_month
.
strftime
(
'%b %Y'
)
%td
=
cohort
[
:registration_month
]
%td
=
number_with_delimiter
(
cohort
[
:inactive
])
%td
=
cohort
[
:inactive
]
%td
=
number_with_delimiter
(
cohort
[
:total
])
%td
=
cohort
[
:total
]
-
cohort
[
:
months
].
each
do
|
running_total
|
-
cohort
[
:
activity_months
].
each
do
|
activity_month
|
%td
%td
-
next
if
cohort
[
:total
]
.
zero?
-
next
if
cohort
[
:total
]
==
'0'
=
number_to_percentage
(
100
*
running_total
/
cohort
[
:total
],
precision:
0
)
=
activity_month
[
:percentage
]
%br
%br
(
#{
number_with_delimiter
(
running_total
)
}
)
=
activity_month
[
:total
]
spec/services/cohorts_service_spec.rb
View file @
ac0146a0
...
@@ -17,22 +17,83 @@ describe CohortsService do
...
@@ -17,22 +17,83 @@ describe CohortsService do
create
(
:user
)
# this user is inactive and belongs to the current month
create
(
:user
)
# this user is inactive and belongs to the current month
expected
=
{
expected_cohorts
=
[
month_start
(
11
)
=>
{
months:
[
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
],
total:
0
,
inactive:
0
},
{
month_start
(
10
)
=>
{
months:
[
2
,
1
,
1
,
1
,
1
,
1
,
1
,
1
,
1
,
1
,
1
],
total:
2
,
inactive:
0
},
registration_month:
month_start
(
11
),
month_start
(
9
)
=>
{
months:
[
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
],
total:
0
,
inactive:
0
},
activity_months:
Array
.
new
(
12
)
{
{
total:
0
,
percentage:
0
}
},
month_start
(
8
)
=>
{
months:
[
2
,
1
,
1
,
1
,
1
,
1
,
1
,
1
,
1
],
total:
2
,
inactive:
0
},
total:
0
,
month_start
(
7
)
=>
{
months:
[
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
],
total:
0
,
inactive:
0
},
inactive:
0
month_start
(
6
)
=>
{
months:
[
2
,
1
,
1
,
1
,
1
,
1
,
1
],
total:
2
,
inactive:
0
},
},
month_start
(
5
)
=>
{
months:
[
0
,
0
,
0
,
0
,
0
,
0
],
total:
0
,
inactive:
0
},
{
month_start
(
4
)
=>
{
months:
[
2
,
1
,
1
,
1
,
1
],
total:
2
,
inactive:
0
},
registration_month:
month_start
(
10
),
month_start
(
3
)
=>
{
months:
[
0
,
0
,
0
,
0
],
total:
0
,
inactive:
0
},
activity_months:
[{
total:
2
,
percentage:
100
}]
+
Array
.
new
(
10
)
{
{
total:
1
,
percentage:
50
}
},
month_start
(
2
)
=>
{
months:
[
2
,
1
,
1
],
total:
2
,
inactive:
0
},
total:
2
,
month_start
(
1
)
=>
{
months:
[
0
,
0
],
total:
0
,
inactive:
0
},
inactive:
0
month_start
(
0
)
=>
{
months:
[
2
],
total:
2
,
inactive:
1
}
},
}
{
registration_month:
month_start
(
9
),
activity_months:
Array
.
new
(
10
)
{
{
total:
0
,
percentage:
0
}
},
total:
0
,
inactive:
0
},
{
registration_month:
month_start
(
8
),
activity_months:
[{
total:
2
,
percentage:
100
}]
+
Array
.
new
(
8
)
{
{
total:
1
,
percentage:
50
}
},
total:
2
,
inactive:
0
},
{
registration_month:
month_start
(
7
),
activity_months:
Array
.
new
(
8
)
{
{
total:
0
,
percentage:
0
}
},
total:
0
,
inactive:
0
},
{
registration_month:
month_start
(
6
),
activity_months:
[{
total:
2
,
percentage:
100
}]
+
Array
.
new
(
6
)
{
{
total:
1
,
percentage:
50
}
},
total:
2
,
inactive:
0
},
{
registration_month:
month_start
(
5
),
activity_months:
Array
.
new
(
6
)
{
{
total:
0
,
percentage:
0
}
},
total:
0
,
inactive:
0
},
{
registration_month:
month_start
(
4
),
activity_months:
[{
total:
2
,
percentage:
100
}]
+
Array
.
new
(
4
)
{
{
total:
1
,
percentage:
50
}
},
total:
2
,
inactive:
0
},
{
registration_month:
month_start
(
3
),
activity_months:
Array
.
new
(
4
)
{
{
total:
0
,
percentage:
0
}
},
total:
0
,
inactive:
0
},
{
registration_month:
month_start
(
2
),
activity_months:
[{
total:
2
,
percentage:
100
}]
+
Array
.
new
(
2
)
{
{
total:
1
,
percentage:
50
}
},
total:
2
,
inactive:
0
},
{
registration_month:
month_start
(
1
),
activity_months:
Array
.
new
(
2
)
{
{
total:
0
,
percentage:
0
}
},
total:
0
,
inactive:
0
},
{
registration_month:
month_start
(
0
),
activity_months:
[{
total:
2
,
percentage:
100
}],
total:
2
,
inactive:
1
},
]
expect
(
described_class
.
new
.
execute
).
to
eq
(
expected
)
expect
(
described_class
.
new
.
execute
).
to
eq
(
months_included:
12
,
cohorts:
expected_cohorts
)
end
end
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment