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\FlyCutUsage;
8use App\Http\Models\FlyMsgUserDailyUsage;
9use App\Http\Models\UserInfo;
10use App\Traits\HubspotPropertiesTrait;
11use Carbon\Carbon;
12use Illuminate\Support\Facades\Cache;
13use Illuminate\Support\Facades\Log;
14use MongoDB\BSON\UTCDateTime;
15use function PHPUnit\Framework\isEmpty;
16
17class 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}