summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean McGivern <sean@gitlab.com>2017-04-03 14:36:57 +0100
committerRémy Coutable <remy@rymai.me>2017-04-14 15:20:55 +0200
commitcc677c8f8ce038610018e43fd41d238a3bf73ff3 (patch)
tree4d28c98e9861eee1c327d24b9a232f7264121661
parent44f3fc499a333c1ba43f845f3cbbe87f8f75fbca (diff)
downloadgitlab-ce-cc677c8f8ce038610018e43fd41d238a3bf73ff3.tar.gz
Make UserCohortsService more understandable
1. Extract out into several methods. 2. Add more comments describing the data and the shape of the data.
-rw-r--r--app/services/user_cohorts_service.rb106
-rw-r--r--spec/services/user_cohorts_service_spec.rb6
2 files changed, 73 insertions, 39 deletions
diff --git a/app/services/user_cohorts_service.rb b/app/services/user_cohorts_service.rb
index 7f84b6a0634..6545ceffec6 100644
--- a/app/services/user_cohorts_service.rb
+++ b/app/services/user_cohorts_service.rb
@@ -1,41 +1,32 @@
class UserCohortsService
- def initialize
- end
+ MONTHS_INCLUDED = 12
- def execute(months_included)
- if Gitlab::Database.postgresql?
- created_at_month = "CAST(DATE_TRUNC('month', created_at) AS date)"
- current_sign_in_at_month = "CAST(DATE_TRUNC('month', current_sign_in_at) AS date)"
- elsif Gitlab::Database.mysql?
- created_at_month = "STR_TO_DATE(DATE_FORMAT(created_at, '%Y-%m-01'), '%Y-%m-%d')"
- current_sign_in_at_month = "STR_TO_DATE(DATE_FORMAT(current_sign_in_at, '%Y-%m-01'), '%Y-%m-%d')"
- end
+ # Get a hash that looks like:
+ #
+ # {
+ # month => {
+ # months: [3, 2, 1],
+ # total: 3
+ # inactive: 0
+ # },
+ # etc.
+ #
+ # The `months` array is always from oldest to newest, so it's always
+ # non-strictly decreasing from left to right.
+ #
+ def execute
+ cohorts = {}
+ months = Array.new(MONTHS_INCLUDED) { |i| i.months.ago.beginning_of_month.to_date }
- counts_by_month =
- User
- .where('created_at > ?', months_included.months.ago.end_of_month)
- .group(created_at_month, current_sign_in_at_month)
- .reorder("#{created_at_month} ASC", "#{current_sign_in_at_month} DESC")
- .count
+ MONTHS_INCLUDED.times do
+ created_at_month = months.last
+ activity_months = running_totals(months, created_at_month)
- cohorts = {}
- months = Array.new(months_included) { |i| i.months.ago.beginning_of_month.to_date }
-
- months_included.times do
- month = months.last
- inactive = counts_by_month[[month, nil]] || 0
-
- # Calculate a running sum of active users, so users active in later months
- # 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.
- activity_months =
- months
- .map { |activity_month| counts_by_month[[month, activity_month]] }
- .reduce([]) { |result, total| result << result.last.to_i + total.to_i }
- .reverse
-
- cohorts[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
+
+ cohorts[created_at_month] = {
months: activity_months,
total: activity_months.first,
inactive: inactive
@@ -46,4 +37,51 @@ class UserCohortsService
cohorts
end
+
+ private
+
+ # Calculate a running sum of active users, so users active in later months
+ # 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)
+ all_months
+ .map { |activity_month| counts_by_month[[created_at_month, activity_month]] }
+ .reduce([]) { |result, total| result << result.last.to_i + total.to_i }
+ .reverse
+ end
+
+ # Get a hash that looks like:
+ #
+ # {
+ # [created_at_month, current_sign_in_at_month] => count,
+ # [created_at_month, current_sign_in_at_month_2] => count_2,
+ # # etc.
+ # }
+ #
+ # 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.
+ #
+ def counts_by_month
+ @counts_by_month ||=
+ begin
+ created_at_month = column_to_date('created_at')
+ current_sign_in_at_month = column_to_date('current_sign_in_at_month')
+
+ User
+ .where('created_at > ?', MONTHS_INCLUDED.months.ago.end_of_month)
+ .group(created_at_month, current_sign_in_at_month)
+ .reorder("#{created_at_month} ASC", "#{current_sign_in_at_month} ASC")
+ .count
+ end
+ end
+
+ def column_to_date(column)
+ if Gitlab::Database.postgresql?
+ "CAST(DATE_TRUNC('month', #{column}) AS date)"
+ elsif Gitlab::Database.mysql?
+ "STR_TO_DATE(DATE_FORMAT(#{column}, '%Y-%m-01'), '%Y-%m-%d')"
+ end
+ end
end
diff --git a/spec/services/user_cohorts_service_spec.rb b/spec/services/user_cohorts_service_spec.rb
index 8d8d0de31cd..4db8d577d79 100644
--- a/spec/services/user_cohorts_service_spec.rb
+++ b/spec/services/user_cohorts_service_spec.rb
@@ -32,11 +32,7 @@ describe UserCohortsService do
month_start(0) => { months: [2], total: 2, inactive: 1 }
}
- result = described_class.new.execute(12)
-
- expect(result.length).to eq(12)
- expect(result.keys).to all(be_a(Date))
- expect(result).to eq(expected)
+ expect(described_class.new.execute).to eq(expected)
end
end
end