Page MenuHomePhorge

D5274.1775261690.diff
No OneTemporary

Authored By
Unknown
Size
6 KB
Referenced Files
None
Subscribers
None

D5274.1775261690.diff

diff --git a/src/app/Http/Controllers/API/V4/Admin/StatsController.php b/src/app/Http/Controllers/API/V4/Admin/StatsController.php
--- a/src/app/Http/Controllers/API/V4/Admin/StatsController.php
+++ b/src/app/Http/Controllers/API/V4/Admin/StatsController.php
@@ -4,9 +4,11 @@
use App\Http\Controllers\Controller;
use App\Payment;
+use App\User;
use App\Utils;
use Carbon\Carbon;
use Illuminate\Database\Query\Builder;
+use Illuminate\Database\Query\JoinClause;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
@@ -30,6 +32,7 @@
'users',
'users-all',
'vouchers',
+ 'users-per-country',
];
/**
@@ -370,6 +373,55 @@
];
}
+ /**
+ * Get users-per-country chart
+ */
+ protected function chartUsersPerCountry(): array
+ {
+ $counts = DB::table('users')
+ ->selectRaw("user_settings.value as country, count(users.id) as cnt")
+ ->leftJoin('user_settings', function (JoinClause $join) {
+ $join->on('users.id', '=', 'user_settings.user_id')
+ ->where('key', 'country');
+ })
+ ->whereNull('users.deleted_at')
+ ->whereNot('users.status', '&', User::STATUS_DEGRADED)
+ ->whereNot('users.status', '&', User::STATUS_SUSPENDED)
+ ->groupByRaw('1');
+
+ $addTenantScope = static function ($builder, $tenantId) {
+ return $builder->where('users.tenant_id', $tenantId);
+ };
+
+ // We get 7 countries with biggest count, the rest is aggregated in 'Other' item
+ $result = [];
+ $other = 0;
+ $counts = $this->applyTenantScope($counts, $addTenantScope)
+ ->pluck('cnt', 'country')
+ ->each(function (int $count, string $country) use (&$result, &$other) {
+ if (empty($country) || count($result) >= 7) {
+ $other += $count;
+ } else {
+ $result[$country] = $count;
+ }
+ })
+ ->all();
+
+ if ($other) {
+ $result[self::trans('app.other')] = $other;
+ }
+
+ arsort($result, \SORT_NUMERIC);
+
+ $labels = self::countryLabels(array_keys($result));
+ $result = array_values($result);
+
+ // $labels = ['Other', 'Germany', 'Poland', 'Switzerland'];
+ // $result = [200, 120, 30, 50];
+
+ return $this->donutChart(self::trans('app.chart-users-per-country'), $labels, $result);
+ }
+
/**
* Get vouchers chart
*/
@@ -402,6 +454,19 @@
return $this->donutChart(self::trans('app.chart-vouchers'), $labels, $vouchers);
}
+ /**
+ * Convert country codes into country names
+ */
+ protected static function countryLabels(array $labels): array
+ {
+ $countries = include resource_path('countries.php');
+
+ return array_map(
+ fn (string $code) => $countries[$code][1] ?? $code,
+ $labels
+ );
+ }
+
protected static function donutChart($title, $labels, $data): array
{
// See https://frappe.io/charts/docs for format/options description
diff --git a/src/resources/lang/en/app.php b/src/resources/lang/en/app.php
--- a/src/resources/lang/en/app.php
+++ b/src/resources/lang/en/app.php
@@ -18,6 +18,7 @@
'chart-income' => 'Income in :currency - last 8 weeks',
'chart-payers' => 'Payers - last year',
'chart-users' => 'Users - last 8 weeks',
+ 'chart-users-per-country' => 'Users per country',
'companion-create-success' => 'Companion app has been created.',
'companion-delete-success' => 'Companion app has been removed.',
@@ -32,6 +33,7 @@
'mandate-description-suffix' => 'Auto-Payment Setup',
+ 'other' => 'Other',
'planbutton' => 'Choose :plan',
'process-async' => 'Setup process has been pushed. Please wait.',
diff --git a/src/resources/vue/Admin/Stats.vue b/src/resources/vue/Admin/Stats.vue
--- a/src/resources/vue/Admin/Stats.vue
+++ b/src/resources/vue/Admin/Stats.vue
@@ -9,7 +9,7 @@
data() {
return {
charts: {},
- chartTypes: ['users', 'users-all', 'income', 'payers', 'discounts', 'vouchers']
+ chartTypes: ['users', 'users-all', 'income', 'payers', 'discounts', 'vouchers', 'users-per-country']
}
},
mounted() {
diff --git a/src/tests/Browser/Admin/StatsTest.php b/src/tests/Browser/Admin/StatsTest.php
--- a/src/tests/Browser/Admin/StatsTest.php
+++ b/src/tests/Browser/Admin/StatsTest.php
@@ -29,13 +29,14 @@
->assertSeeIn('@links .link-stats', 'Stats')
->click('@links .link-stats')
->on(new Stats())
- ->assertElementsCount('@container > div', 6)
+ ->assertElementsCount('@container > div', 7)
->waitForTextIn('@container #chart-users svg .title', 'Users - last 8 weeks')
->waitForTextIn('@container #chart-users-all svg .title', 'All Users - last year')
->waitForTextIn('@container #chart-payers svg .title', 'Payers - last year')
->waitForTextIn('@container #chart-income svg .title', 'Income in CHF - last 8 weeks')
->waitForTextIn('@container #chart-discounts svg .title', 'Discounts')
- ->waitForTextIn('@container #chart-vouchers svg .title', 'Vouchers');
+ ->waitForTextIn('@container #chart-vouchers svg .title', 'Vouchers')
+ ->waitForTextIn('@container #chart-users-per-country svg .title', 'Users per country');
});
}
}
diff --git a/src/tests/Feature/Controller/Admin/StatsTest.php b/src/tests/Feature/Controller/Admin/StatsTest.php
--- a/src/tests/Feature/Controller/Admin/StatsTest.php
+++ b/src/tests/Feature/Controller/Admin/StatsTest.php
@@ -94,6 +94,19 @@
$this->assertCount(54, $json['data']['labels']);
$this->assertCount(1, $json['data']['datasets']);
+ // 'users-per-country' chart
+ $response = $this->actingAs($admin)->get("api/v4/stats/chart/users-per-country");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('Users per country', $json['title']);
+ $this->assertSame('donut', $json['type']);
+ $this->assertContains('Switzerland', $json['data']['labels']);
+ $this->assertContains('United States', $json['data']['labels']);
+ $this->assertContains('Other', $json['data']['labels']);
+ $this->assertCount(1, $json['data']['datasets']);
+
// 'vouchers' chart
$discount = Discount::withObjectTenantContext($user)->where('code', 'TEST')->first();
$wallet = $user->wallets->first();

File Metadata

Mime Type
text/plain
Expires
Sat, Apr 4, 12:14 AM (2 d, 2 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18827388
Default Alt Text
D5274.1775261690.diff (6 KB)

Event Timeline