Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 224 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
MgmtCenterReportingService | |
0.00% |
0 / 224 |
|
0.00% |
0 / 11 |
870 | |
0.00% |
0 / 1 |
getCoachLevelsSpotlightUsers | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getFlyCutCoachLevels | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
30 | |||
getTotalUsersSpotlightByType | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
20 | |||
getFlyCutUsageSpotlightByType | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
30 | |||
spotlight | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
6 | |||
findLicenseOverview | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
findUsersOverview | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
findAllInactiveUsers | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
buildLineChartDataForSpotlight | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
20 | |||
getTotalCharactersByUsersInPeriod | |
0.00% |
0 / 62 |
|
0.00% |
0 / 1 |
12 | |||
getTotalUsers | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace App\Http\Services\Admin\Reports; |
4 | |
5 | use App\Helpers\CoachLevelHelper; |
6 | use App\Http\Models\Auth\User; |
7 | use App\Http\Models\FlyCutUsage; |
8 | use App\Http\Models\FlyMsgUserDailyUsage; |
9 | use App\Http\Models\UserInfo; |
10 | use App\Traits\HubspotPropertiesTrait; |
11 | use Carbon\Carbon; |
12 | use Illuminate\Support\Facades\Cache; |
13 | use Illuminate\Support\Facades\Log; |
14 | use MongoDB\BSON\UTCDateTime; |
15 | use function PHPUnit\Framework\isEmpty; |
16 | |
17 | class MgmtCenterReportingService extends SharedReportingService |
18 | { |
19 | use HubspotPropertiesTrait; |
20 | |
21 | public function getCoachLevelsSpotlightUsers($start, $end, $resetCache = false) |
22 | { |
23 | ini_set('memory_limit', '3072M'); |
24 | // Increase memory limit for this request |
25 | // ini_set('memory_limit', '512M'); |
26 | // if ($resetCache) { |
27 | // Cache::forget("cmc-spotlight"); |
28 | // } |
29 | // $result = Cache::get("cmc-spotlight"); |
30 | // |
31 | // if ($result) { |
32 | // return $result; |
33 | // } |
34 | |
35 | $result = $this->getTotalCharactersByUsersInPeriod($start, $end); |
36 | |
37 | // Cache::put("cmc-spotlight", $result, 24 * 60 * 60); |
38 | |
39 | return $result; |
40 | } |
41 | |
42 | public function getFlyCutCoachLevels(?Carbon $from, ?Carbon $to) |
43 | { |
44 | $start = !empty($from) ? new UTCDateTime($from->getTimestamp() * 1000) : null; |
45 | $end = !empty($to) ? new UTCDateTime($to->getTimestamp() * 1000) : null; |
46 | |
47 | $characterUsages = $this->getCoachLevelsSpotlightUsers($start, $end, true); |
48 | |
49 | $usersCount = $this->getTotalUsers(); |
50 | |
51 | $coachLevels = CoachLevelHelper::categorizeCharacterUsage($characterUsages, $usersCount); |
52 | |
53 | $chart = [ |
54 | $coachLevels->beginner, |
55 | $coachLevels->intermediate, |
56 | $coachLevels->proficient, |
57 | $coachLevels->advanced, |
58 | $coachLevels->expert |
59 | ]; |
60 | |
61 | $percentage_expert_users = $coachLevels->expert > 0 ? ($coachLevels->expert / $usersCount) * 100 : 0; |
62 | $percentage_beginner_users = $coachLevels->beginner > 0 ? ($coachLevels->beginner / $usersCount) * 100 : 0; |
63 | |
64 | return [ |
65 | "chart" => $chart, |
66 | "expert_users" => round($percentage_expert_users, 2), |
67 | "beginner_users" => round($percentage_beginner_users, 2), |
68 | ]; |
69 | } |
70 | public function getTotalUsersSpotlightByType(string $type, ?Carbon $from, ?Carbon $to) |
71 | { |
72 | return FlyMsgUserDailyUsage::withoutGlobalScopes()->raw(function ($collection) use ($type, $from, $to) { |
73 | if ($type == "cost_saved") { |
74 | $type = "cost_savings"; |
75 | } |
76 | |
77 | $match = [ |
78 | $type => ['$gt' => 0], |
79 | ]; |
80 | |
81 | if (!empty($from)) { |
82 | $match['created_at']['$gte'] = new UTCDateTime($from->getTimestamp() * 1000); |
83 | } |
84 | if (!empty($to)) { |
85 | $match['created_at']['$lte'] = new UTCDateTime($to->getTimestamp() * 1000); |
86 | } |
87 | |
88 | $pipeline = [ |
89 | ['$match' => $match], |
90 | ['$group' => ['_id' => '$user_id']], |
91 | ['$group' => ['_id' => null, 'total_users' => ['$sum' => 1]]], |
92 | ['$project' => ['_id' => 0, 'total_users' => 1]] |
93 | ]; |
94 | |
95 | return $collection->aggregate($pipeline); |
96 | }); |
97 | } |
98 | public function getFlyCutUsageSpotlightByType(string $type, ?Carbon $from, ?Carbon $to) |
99 | { |
100 | return FlyMsgUserDailyUsage::withoutGlobalScopes()->raw(function ($collection) use ($type, $from, $to) { |
101 | if ($type == "cost_saved") { |
102 | $type = "cost_savings"; |
103 | } |
104 | |
105 | $match = []; |
106 | if (!empty($from)) { |
107 | $match['created_at']['$gte'] = new UTCDateTime($from->getTimestamp() * 1000); |
108 | } |
109 | if (!empty($to)) { |
110 | $match['created_at']['$lte'] = new UTCDateTime($to->getTimestamp() * 1000); |
111 | } |
112 | |
113 | $pipeline = []; |
114 | if (!empty($match)) { |
115 | $pipeline[] = ['$match' => $match]; |
116 | } |
117 | |
118 | $pipeline = array_merge($pipeline, [ |
119 | [ |
120 | '$addFields' => [ |
121 | 'yearMonth' => [ |
122 | '$dateToString' => ['format' => "%m-%Y", 'date' => '$created_at'] |
123 | ] |
124 | ] |
125 | ], |
126 | [ |
127 | '$group' => [ |
128 | '_id' => '$yearMonth', |
129 | 'count' => ['$sum' => '$' . $type] |
130 | ] |
131 | ], |
132 | [ |
133 | '$project' => [ |
134 | '_id' => 0, |
135 | 'id' => '$_id', |
136 | 'count' => 1 |
137 | ] |
138 | ], |
139 | [ |
140 | '$sort' => ['id' => 1] |
141 | ] |
142 | ]); |
143 | |
144 | return $collection->aggregate($pipeline); |
145 | }); |
146 | } |
147 | |
148 | public function spotlight(string $type, ?Carbon $from, ?Carbon $to, ?bool $useCount = false) |
149 | { |
150 | $items = $this->getFlyCutUsageSpotlightByType($type, $from, $to); |
151 | $itemTotais = $this->getTotalUsersSpotlightByType($type, $from, $to); |
152 | |
153 | $start = ($from ?? FlyMsgUserDailyUsage::min('created_at'))->startOfMonth(); |
154 | $end = ($to ?? FlyMsgUserDailyUsage::max('created_at') ?? Carbon::now()); |
155 | |
156 | $months = $end->diffInMonths($start); |
157 | |
158 | $chart = $this->buildLineChartDataForSpotlight($items, $months, $type, $useCount, $end); |
159 | |
160 | $total = $items->sum("count"); |
161 | |
162 | $total_users = $itemTotais->sum("total_users"); |
163 | $average = $total_users > 0 ? $total / $total_users : 0; |
164 | |
165 | return [ |
166 | "chart" => $chart, |
167 | "total_$type" => number_format($total, 2, ".", ","), |
168 | "average_$type" => number_format($average, 2, ".", ","), |
169 | ]; |
170 | } |
171 | |
172 | public function findLicenseOverview(FindUsersOverviewFilter $filter) |
173 | { |
174 | $totalLicensesUsed = $this->findActiveLicensesUsage(true, $filter); |
175 | $extensions_installed = $this->findExtensionInstalled([], null, null); |
176 | $extensions_uninstalled = $this->findExtensionsUninstalled([], null, null); |
177 | $inactive_users = $this->findAllInactiveUsers($filter); |
178 | |
179 | return [ |
180 | "inactive_users" => (int) $inactive_users, |
181 | "activated" => $totalLicensesUsed, |
182 | "extensions_installed" => $extensions_installed, |
183 | "extensions_uninstalled" => $extensions_uninstalled, |
184 | ]; |
185 | } |
186 | |
187 | public function findUsersOverview(FindUsersOverviewFilter $filter) |
188 | { |
189 | $fromDate = $filter->fromDate; |
190 | $toDate = $filter->toDate; |
191 | |
192 | $users = $this->findUsers($filter); |
193 | $totalLicensesUsed = $this->findActiveLicensesUsage(false, $filter); |
194 | $userIds = $users->pluck("id")->toArray(); |
195 | |
196 | $extensions_installed = $this->findExtensionInstalled($userIds, $fromDate, $toDate); |
197 | |
198 | $extensions_uninstalled = $this->findExtensionsUninstalled($userIds, $fromDate, $toDate); |
199 | |
200 | $inactive_users = $this->findInactiveUsers($filter); |
201 | |
202 | return [ |
203 | 'active_users' => $totalLicensesUsed, |
204 | 'inactive_users' => $inactive_users, |
205 | 'with_extension_installed' => $extensions_installed, |
206 | 'with_extension_uninstalled' => $extensions_uninstalled, |
207 | ]; |
208 | } |
209 | |
210 | public function findAllInactiveUsers(FindUsersOverviewFilter $filter) |
211 | { |
212 | $thirtyDaysAgo = $filter->toDate->subDays(30); |
213 | |
214 | $usersWitUsage = FlyMsgUserDailyUsage::where('created_at', '>=', new UTCDateTime($thirtyDaysAgo->getTimestamp() * 1000)) |
215 | ->distinct('user_id') |
216 | ->pluck('user_id') |
217 | ->toArray(); |
218 | |
219 | return User::whereNotIn('id', $usersWitUsage)->count(); |
220 | } |
221 | |
222 | private function buildLineChartDataForSpotlight($flycutUsages, int $months, string $type, bool $useCount, Carbon $to) |
223 | { |
224 | $line_chart_data = []; |
225 | $current_date = $to->copy(); |
226 | |
227 | for ($i = 0; $i <= $months; $i++) { |
228 | $month_year = $current_date->format('m-Y'); |
229 | |
230 | $month = collect($flycutUsages)->firstWhere('id', $month_year); |
231 | $monthYear = $current_date->format('M Y'); |
232 | |
233 | $prop = $useCount ? 'count' : $type; |
234 | if ($month) { |
235 | $line_chart_data[$monthYear] = [ |
236 | 'month_year' => $monthYear, |
237 | "$prop" => round($month['count'] ?? 0, 2), |
238 | ]; |
239 | } else { |
240 | $line_chart_data[$monthYear] = [ |
241 | 'month_year' => $monthYear, |
242 | "$prop" => round(0, 2), |
243 | ]; |
244 | } |
245 | |
246 | $current_date = $current_date->subMonth(); |
247 | } |
248 | |
249 | uksort($line_chart_data, function ($a, $b) { |
250 | return strtotime($a) - strtotime($b); |
251 | }); |
252 | |
253 | return $line_chart_data; |
254 | } |
255 | |
256 | private function getTotalCharactersByUsersInPeriod($startDate, $endDate) |
257 | { |
258 | $match = [ |
259 | ['$eq' => ['$user_id', '$$userId']] |
260 | ]; |
261 | |
262 | if (!empty($startDate)) { |
263 | $match[] = ['$gte' => ['$created_at', $startDate]]; |
264 | } |
265 | |
266 | if (!empty($endDate)) { |
267 | $match[] = ['$lte' => ['created_at', $endDate]]; |
268 | } |
269 | |
270 | $pipeline = [ |
271 | [ |
272 | '$match' => [ |
273 | 'deactivated_at' => ['$exists' => false], |
274 | 'deleted_at' => ['$exists' => false], |
275 | 'status' => ['$ne' => 'Inactive'] |
276 | ] |
277 | ], |
278 | [ |
279 | '$lookup' => [ |
280 | 'from' => 'fly_msg_user_daily_usage', |
281 | 'let' => ['userId' => ['$toString' => '$_id']], |
282 | 'pipeline' => [ |
283 | [ |
284 | '$match' => [ |
285 | '$expr' => [ |
286 | '$and' => $match |
287 | ] |
288 | ] |
289 | ], |
290 | [ |
291 | '$group' => [ |
292 | '_id' => null, |
293 | 'characters_typed' => ['$sum' => '$characters_typed'], |
294 | 'characters_saved' => ['$sum' => '$characters_saved'], |
295 | 'time_saved' => ['$sum' => '$time_saved'], |
296 | 'cost_saved' => ['$sum' => '$cost_savings'], |
297 | 'flycuts_used' => ['$sum' => '$flycut_count'] |
298 | ] |
299 | ] |
300 | ], |
301 | 'as' => 'usage_data' |
302 | ] |
303 | ], |
304 | [ |
305 | '$unwind' => [ |
306 | 'path' => '$usage_data', |
307 | 'preserveNullAndEmptyArrays' => true |
308 | ] |
309 | ], |
310 | [ |
311 | '$project' => [ |
312 | 'user_id' => 1, |
313 | 'name' => ['$concat' => ['$first_name', ' ', '$last_name']], |
314 | 'characters_typed' => ['$ifNull' => ['$usage_data.characters_typed', 0]], |
315 | 'characters_saved' => ['$ifNull' => ['$usage_data.characters_saved', 0]], |
316 | 'time_saved' => ['$ifNull' => ['$usage_data.time_saved', 0]], |
317 | 'cost_saved' => ['$ifNull' => ['$usage_data.cost_saved', 0]], |
318 | 'flycuts_used' => ['$ifNull' => ['$usage_data.flycuts_used', 0]] |
319 | ] |
320 | ] |
321 | ]; |
322 | |
323 | return User::raw(function ($collection) use ($pipeline) { |
324 | return $collection->aggregate($pipeline); |
325 | }); |
326 | } |
327 | |
328 | private function getTotalUsers() |
329 | { |
330 | $filters = [ |
331 | ["deleted_at" => ['$exists' => false]] |
332 | ]; |
333 | |
334 | $filters[] = [ |
335 | '$or' => [ |
336 | ['deactivated_at' => ['$exists' => false]], |
337 | ['deactivated_at' => ['$eq' => null]], |
338 | ] |
339 | ]; |
340 | |
341 | $pipeline = []; |
342 | |
343 | if (filled($filters)) { |
344 | $pipeline[] = [ |
345 | '$match' => [ |
346 | '$and' => $filters |
347 | ] |
348 | ]; |
349 | } |
350 | |
351 | return User::raw(function ($collection) use ($pipeline) { |
352 | return $collection->aggregate($pipeline); |
353 | })->count(); |
354 | } |
355 | } |