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