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
Léo-Paul Géneau
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
Show 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
def
index
if
ApplicationSetting
.
current
.
usage_ping_enabled
@cohor
ts
=
Rails
.
cache
.
fetch
(
'cohorts'
,
expires_in:
1
.
day
)
do
if
current_application_settings
.
usage_ping_enabled
cohorts_resul
ts
=
Rails
.
cache
.
fetch
(
'cohorts'
,
expires_in:
1
.
day
)
do
CohortsService
.
new
.
execute
end
@cohorts
=
CohortsSerializer
.
new
.
represent
(
cohorts_results
)
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
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
# inactive: 0
# },
...
...
@@ -13,29 +21,26 @@ class CohortsService
#
# The `months` array is always from oldest to newest, so it's always
# non-strictly decreasing from left to right.
#
def
execute
cohorts
=
{}
def
cohorts
months
=
Array
.
new
(
MONTHS_INCLUDED
)
{
|
i
|
i
.
months
.
ago
.
beginning_of_month
.
to_date
}
MONTHS_INCLUDED
.
times
do
created_at
_month
=
months
.
last
activity_months
=
running_totals
(
months
,
created_at
_month
)
Array
.
new
(
MONTHS_INCLUDED
)
do
registration
_month
=
months
.
last
activity_months
=
running_totals
(
months
,
registration
_month
)
# Even if no users registered in this month, we always want to have a
# 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
,
total:
activity_months
.
first
,
{
registration_month:
registration_month
,
activity_months:
activity_months
,
total:
activity_months
.
first
[
:total
],
inactive:
inactive
}
months
.
pop
end
cohorts
end
private
...
...
@@ -44,11 +49,20 @@ class CohortsService
# 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
# table.
def
running_totals
(
all_months
,
created_at_month
)
#
# Each month has a total, and a percentage of the overall total, as keys.
def
running_totals
(
all_months
,
registration_month
)
month_totals
=
all_months
.
map
{
|
activity_month
|
counts_by_month
[[
created_at
_month
,
activity_month
]]
}
.
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
# Get a hash that looks like:
...
...
@@ -60,9 +74,8 @@ class CohortsService
# }
#
# 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
# months.
#
# user has never logged in, just been created). This covers the last
# MONTHS_INCLUDED months.
def
counts_by_month
@counts_by_month
||=
begin
...
...
@@ -80,7 +93,7 @@ class CohortsService
def
column_to_date
(
column
)
if
Gitlab
::
Database
.
postgresql?
"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')"
end
end
...
...
app/views/admin/cohorts/_cohorts_table.html.haml
View file @
ac0146a0
.bs-callout.clearfix
%p
User cohorts are shown for the last
twelve months. Only users with
activity are counted in the cohort total; inactive users are counted
separately.
User cohorts are shown for the last
#{
@cohorts
[
:months_included
]
}
months. Only users with activity are counted in the cohort total; inactive
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'
.table-holder
...
...
@@ -12,27 +12,17 @@
%th
Registration month
%th
Inactive users
%th
Cohort total
%th
Month 0
%th
Month 1
%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
-
@cohorts
[
:months_included
].
times
do
|
i
|
%th
Month
#{
i
}
%tbody
-
@cohorts
.
each
do
|
registration_month
,
cohort
|
-
@cohorts
[
:cohorts
].
each
do
|
cohort
|
%tr
%td
=
registration_month
.
strftime
(
'%b %Y'
)
%td
=
number_with_delimiter
(
cohort
[
:inactive
])
%td
=
number_with_delimiter
(
cohort
[
:total
])
-
cohort
[
:
months
].
each
do
|
running_total
|
%td
=
cohort
[
:registration_month
]
%td
=
cohort
[
:inactive
]
%td
=
cohort
[
:total
]
-
cohort
[
:
activity_months
].
each
do
|
activity_month
|
%td
-
next
if
cohort
[
:total
]
.
zero?
=
number_to_percentage
(
100
*
running_total
/
cohort
[
:total
],
precision:
0
)
-
next
if
cohort
[
:total
]
==
'0'
=
activity_month
[
:percentage
]
%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
create
(
:user
)
# this user is inactive and belongs to the current month
expected
=
{
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
},
month_start
(
9
)
=>
{
months:
[
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
],
total:
0
,
inactive:
0
},
month_start
(
8
)
=>
{
months:
[
2
,
1
,
1
,
1
,
1
,
1
,
1
,
1
,
1
],
total:
2
,
inactive:
0
},
month_start
(
7
)
=>
{
months:
[
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
],
total:
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
},
month_start
(
3
)
=>
{
months:
[
0
,
0
,
0
,
0
],
total:
0
,
inactive:
0
},
month_start
(
2
)
=>
{
months:
[
2
,
1
,
1
],
total:
2
,
inactive:
0
},
month_start
(
1
)
=>
{
months:
[
0
,
0
],
total:
0
,
inactive:
0
},
month_start
(
0
)
=>
{
months:
[
2
],
total:
2
,
inactive:
1
}
}
expected_cohorts
=
[
{
registration_month:
month_start
(
11
),
activity_months:
Array
.
new
(
12
)
{
{
total:
0
,
percentage:
0
}
},
total:
0
,
inactive:
0
},
{
registration_month:
month_start
(
10
),
activity_months:
[{
total:
2
,
percentage:
100
}]
+
Array
.
new
(
10
)
{
{
total:
1
,
percentage:
50
}
},
total:
2
,
inactive:
0
},
{
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
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