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 | } |