Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F15755202
D4965.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
6 KB
Referenced Files
None
Subscribers
None
D4965.diff
View Options
diff --git a/src/app/Http/Controllers/API/V4/MetricsController.php b/src/app/Http/Controllers/API/V4/MetricsController.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/Controllers/API/V4/MetricsController.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace App\Http\Controllers\API\V4;
+
+use App\Http\Controllers\Controller;
+use App\Payment;
+use App\User;
+use App\Wallet;
+use Illuminate\Support\Facades\DB;
+
+class MetricsController extends Controller
+{
+ /**
+ * Collect current payers count
+ */
+ protected function collectPayersCount(): int
+ {
+ $tenant_id = \config('app.tenant_id');
+
+ // A subquery to get the all wallets with a successful payment
+ $payments = DB::table('payments')
+ ->selectRaw('distinct wallet_id')
+ ->where('status', Payment::STATUS_PAID);
+
+ // A subquery to get users' wallets (by entitlement) - one record per user
+ $wallets = DB::table('entitlements')
+ ->selectRaw("min(wallet_id) as id, entitleable_id as user_id")
+ ->where('entitleable_type', User::class)
+ ->groupBy('entitleable_id');
+
+ // Count all non-degraded and non-deleted users with any successful payment
+ $count = DB::table('users')
+ ->joinSub($wallets, 'wallets', function ($join) {
+ $join->on('users.id', '=', 'wallets.user_id');
+ })
+ ->joinSub($payments, 'payments', function ($join) {
+ $join->on('wallets.id', '=', 'payments.wallet_id');
+ })
+ ->whereNull('users.deleted_at')
+ ->whereNot('users.status', '&', User::STATUS_DEGRADED)
+ ->whereNot('users.status', '&', User::STATUS_SUSPENDED);
+
+ if ($tenant_id) {
+ $count->where('users.tenant_id', $tenant_id);
+ } else {
+ $count->whereNull('users.tenant_id');
+ }
+
+ return $count->count();
+ }
+
+ /**
+ * Collect number of wallets that require topup
+ */
+ protected function numberOfWalletsWithBalanceBelowManadate(): int
+ {
+ return Wallet::select('wallets.id')
+ ->join('users', 'users.id', '=', 'wallets.user_id')
+ ->join('wallet_settings', function (\Illuminate\Database\Query\JoinClause $join) {
+ $join->on('wallet_settings.wallet_id', '=', 'wallets.id')
+ ->where('wallet_settings.key', '=', 'mandate_balance');
+ })
+ ->whereNull('users.deleted_at')
+ ->whereRaw('wallets.balance < (wallet_settings.value * 100)')
+ ->whereNot('users.status', '&', User::STATUS_DEGRADED | User::STATUS_SUSPENDED | User::STATUS_DELETED)
+ ->count();
+ }
+
+ /**
+ * Expose kolab metrics
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public function metrics()
+ {
+ $appDomain = \config('app.domain');
+ $tenantId = \config('app.tenant_id');
+ //TODO just get this from the stats table instead?
+ $numberOfPayingUsers = $this->collectPayersCount();
+
+ $numberOfUsers = User::count();
+ $numberOfDeletedUsers = User::withTrashed()->whereNotNull('deleted_at')->count();
+ $numberOfSuspendedUsers = User::where('status', '&', User::STATUS_SUSPENDED)->count();
+ $numberOfRestrictedUsers = User::where('status', '&', User::STATUS_RESTRICTED)->count();
+ $numberOfWalletsWithBalanceBelowManadate = $this->numberOfWalletsWithBalanceBelowManadate();
+
+ $text = <<<EOF
+ # HELP kolab_users_count Total number of users
+ # TYPE kolab_users_count gauge
+ kolab_users_count{instance="$appDomain", tenant="$tenantId"} $numberOfUsers
+ # HELP kolab_users_deleted_count Number of deleted users
+ # TYPE kolab_users_deleted_count gauge
+ kolab_users_deleted_count{instance="$appDomain", tenant="$tenantId"} $numberOfDeletedUsers
+ # HELP kolab_users_suspended_count Number of suspended users
+ # TYPE kolab_users_suspended_count gauge
+ kolab_users_suspended_count{instance="$appDomain", tenant="$tenantId"} $numberOfSuspendedUsers
+ # HELP kolab_users_restricted_count Number of restricted users
+ # TYPE kolab_users_restricted_count gauge
+ kolab_users_restricted_count{instance="$appDomain", tenant="$tenantId"} $numberOfRestrictedUsers
+ # HELP kolab_users_paying_count Number of paying users
+ # TYPE kolab_users_paying_count gauge
+ kolab_users_paying_count{instance="$appDomain", tenant="$tenantId"} $numberOfPayingUsers
+ # HELP kolab_wallets_balance_below_mandate_amount_count Number of wallets requiring topup
+ # TYPE kolab_wallets_balance_below_mandate_amount_count gauge
+ kolab_wallets_balance_below_mandate_amount{instance="$appDomain", tenant="$tenantId"} $numberOfWalletsWithBalanceBelowManadate
+ \n
+ EOF;
+
+ return response(
+ $text,
+ 200,
+ [
+ 'Content-Type' => "text/plain",
+ ]
+ );
+ }
+}
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -227,6 +227,7 @@
Route::post('policy/ratelimit', [API\V4\PolicyController::class, 'ratelimit']);
Route::post('policy/spf', [API\V4\PolicyController::class, 'senderPolicyFramework']);
Route::get('health/status', [API\V4\HealthController::class, 'status']);
+ Route::get('metrics', [API\V4\MetricsController::class, 'metrics']);
}
);
}
diff --git a/src/tests/Feature/Controller/MetricsTest.php b/src/tests/Feature/Controller/MetricsTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Controller/MetricsTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Tests\Feature\Controller;
+
+use Tests\TestCase;
+
+/**
+ * @group files
+ */
+class MetricsTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ $this->useServicesUrl();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ parent::tearDown();
+ }
+
+ /**
+ * Test webhook
+ */
+ public function testStatus(): void
+ {
+ $response = $this->get("api/webhooks/metrics");
+ $response->assertStatus(200);
+
+ $body = $response->content();
+ $this->assertTrue(str_contains($body, 'kolab'));
+ }
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Oct 3, 1:15 PM (21 h, 54 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
9640815
Default Alt Text
D4965.diff (6 KB)
Attached To
Mode
D4965: Metrics controller
Attached
Detach File
Event Timeline
Log In to Comment