From 45cc88172e6a5245cd067dbc1797e14dc8c332dd Mon Sep 17 00:00:00 2001 From: Niklas Rosenqvist Date: Wed, 25 Dec 2024 23:10:51 +0100 Subject: [PATCH 1/2] Add tenant configuration --- .../DatabaseAnalyticsRepository.php | 40 ++++++++++---- src/PanConfiguration.php | 54 ++++++++++++++++++- src/ValueObjects/Analytic.php | 4 +- tests/Feature/Laravel/Http/EventsTest.php | 10 ++-- tests/Unit/Actions/CreateEventTest.php | 2 +- tests/Unit/PanConfigurationTest.php | 40 ++++++++++++++ .../Unit/Presentors/AnalyticPresentorTest.php | 12 ++--- 7 files changed, 139 insertions(+), 23 deletions(-) diff --git a/src/Adapters/Laravel/Repositories/DatabaseAnalyticsRepository.php b/src/Adapters/Laravel/Repositories/DatabaseAnalyticsRepository.php index 7e3d1da..9367d8b 100644 --- a/src/Adapters/Laravel/Repositories/DatabaseAnalyticsRepository.php +++ b/src/Adapters/Laravel/Repositories/DatabaseAnalyticsRepository.php @@ -30,13 +30,18 @@ public function __construct(private PanConfiguration $config) */ public function all(): array { + [ + 'tenant_field' => $tenantField, + ] = $this->config->toArray(); + /** @var array $all */ $all = DB::table('pan_analytics')->get()->map(fn (mixed $analytic): Analytic => new Analytic( - id: (int) $analytic->id, // @phpstan-ignore-line - name: $analytic->name, // @phpstan-ignore-line - impressions: (int) $analytic->impressions, // @phpstan-ignore-line - hovers: (int) $analytic->hovers, // @phpstan-ignore-line - clicks: (int) $analytic->clicks, // @phpstan-ignore-line + id: (int) $analytic->id, + tenant: ($tenantField) ? $analytic->{$tenantField} : null, + name: $analytic->name, + impressions: (int) $analytic->impressions, + hovers: (int) $analytic->hovers, + clicks: (int) $analytic->clicks, ))->toArray(); return $all; @@ -50,21 +55,38 @@ public function increment(string $name, EventType $event): void [ 'allowed_analytics' => $allowedAnalytics, 'max_analytics' => $maxAnalytics, + 'tenant_field' => $tenantField, + 'tenant_id' => $tenantId, ] = $this->config->toArray(); if (count($allowedAnalytics) > 0 && ! in_array($name, $allowedAnalytics, true)) { return; } - if (DB::table('pan_analytics')->where('name', $name)->count() === 0) { - if (DB::table('pan_analytics')->count() < $maxAnalytics) { - DB::table('pan_analytics')->insert(['name' => $name, $event->column() => 1]); + // Restrict query to tenant if tenant field and id are set + $baseQuery = DB::table('pan_analytics'); + + if ($tenantField !== null && $tenantId !== null) { + $baseQuery->where($tenantField, $tenantId); + } + + $fieldQuery = clone $baseQuery; + $fieldQuery = $fieldQuery->where('name', $name); + + if ($fieldQuery->count() === 0) { + if ($baseQuery->count() < $maxAnalytics) { + $baseQuery->insert(array_filter([ + 'name' => $name, + $event->column() => 1, + 'tenant_field' => $tenantField, + 'tenant_id' => $tenantId, + ])); } return; } - DB::table('pan_analytics')->where('name', $name)->increment($event->column()); + $fieldQuery->increment($event->column()); } /** diff --git a/src/PanConfiguration.php b/src/PanConfiguration.php index b70b4a1..8cd8eea 100644 --- a/src/PanConfiguration.php +++ b/src/PanConfiguration.php @@ -20,6 +20,8 @@ private function __construct( private int $maxAnalytics = 50, private array $allowedAnalytics = [], private string $routePrefix = 'pan', + private ?string $tenantField = null, + private string|int|null $tenantId = null, ) { // } @@ -66,6 +68,26 @@ public function setRoutePrefix(string $prefix): void $this->routePrefix = $prefix; } + /** + * Sets the tenant field to be used. + * + * @internal + */ + public function setTenantField(?string $field): void + { + $this->tenantField = $field; + } + + /** + * Sets the tenant ID to be used. + * + * @internal + */ + public function setTenantId(string|int|null $id): void + { + $this->tenantId = $id; + } + /** * Sets the maximum number of analytics to store. */ @@ -102,6 +124,26 @@ public static function routePrefix(string $prefix): void self::instance()->setRoutePrefix($prefix); } + /** + * Sets the tenant field to be used. + * + * @internal + */ + public static function tenantField(?string $field): void + { + self::instance()->setTenantField($field); + } + + /** + * Sets the tenant ID to be used. + * + * @internal + */ + public static function tenantId(string|int|null $id): void + { + self::instance()->setTenantId($id); + } + /** * Resets the configuration to its default values. * @@ -112,12 +154,20 @@ public static function reset(): void self::maxAnalytics(50); self::allowedAnalytics([]); self::routePrefix('pan'); + self::tenantField(null); + self::tenantId(null); } /** * Converts the Pan configuration to an array. * - * @return array{max_analytics: int, allowed_analytics: array, route_prefix: string} + * @return array{ + * max_analytics: int, + * allowed_analytics: array, + * route_prefix: string, + * tenant_field: string|null, + * tenant_id: string|int|null, + * } * * @internal */ @@ -127,6 +177,8 @@ public function toArray(): array 'max_analytics' => $this->maxAnalytics, 'allowed_analytics' => $this->allowedAnalytics, 'route_prefix' => $this->routePrefix, + 'tenant_field' => $this->tenantField, + 'tenant_id' => $this->tenantId, ]; } } diff --git a/src/ValueObjects/Analytic.php b/src/ValueObjects/Analytic.php index 69fdcb9..1eef232 100644 --- a/src/ValueObjects/Analytic.php +++ b/src/ValueObjects/Analytic.php @@ -16,6 +16,7 @@ */ public function __construct( public int $id, + public string|int|null $tenant, public string $name, public int $impressions, public int $hovers, @@ -27,12 +28,13 @@ public function __construct( /** * Returns the analytic as an array. * - * @return array{id: int, name: string, impressions: int, hovers: int, clicks: int} + * @return array{id: int, tenant: string|int|null, name: string, impressions: int, hovers: int, clicks: int} */ public function toArray(): array { return [ 'id' => $this->id, + 'tenant' => $this->tenant, 'name' => $this->name, 'impressions' => $this->impressions, 'hovers' => $this->hovers, diff --git a/tests/Feature/Laravel/Http/EventsTest.php b/tests/Feature/Laravel/Http/EventsTest.php index 0408e80..57451f0 100644 --- a/tests/Feature/Laravel/Http/EventsTest.php +++ b/tests/Feature/Laravel/Http/EventsTest.php @@ -18,7 +18,7 @@ $analytics = array_map(fn (Analytic $analytic): array => $analytic->toArray(), app(AnalyticsRepository::class)->all()); expect($analytics)->toBe([ - ['id' => 1, 'name' => 'help-modal', 'impressions' => 0, 'hovers' => 0, 'clicks' => 1], + ['id' => 1, 'tenant' => null, 'name' => 'help-modal', 'impressions' => 0, 'hovers' => 0, 'clicks' => 1], ]); }); @@ -35,7 +35,7 @@ $analytics = array_map(fn (Analytic $analytic): array => $analytic->toArray(), app(AnalyticsRepository::class)->all()); expect($analytics)->toBe([ - ['id' => 1, 'name' => 'help-modal', 'impressions' => 0, 'hovers' => 1, 'clicks' => 0], + ['id' => 1, 'tenant' => null, 'name' => 'help-modal', 'impressions' => 0, 'hovers' => 1, 'clicks' => 0], ]); }); @@ -52,7 +52,7 @@ $analytics = array_map(fn (Analytic $analytic): array => $analytic->toArray(), app(AnalyticsRepository::class)->all()); expect($analytics)->toBe([ - ['id' => 1, 'name' => 'help-modal', 'impressions' => 1, 'hovers' => 0, 'clicks' => 0], + ['id' => 1, 'tenant' => null, 'name' => 'help-modal', 'impressions' => 1, 'hovers' => 0, 'clicks' => 0], ]); }); @@ -75,7 +75,7 @@ $analytics = array_map(fn (Analytic $analytic): array => $analytic->toArray(), app(AnalyticsRepository::class)->all()); expect($analytics)->toBe([ - ['id' => 1, 'name' => 'help-modal', 'impressions' => 1, 'hovers' => 0, 'clicks' => 1], + ['id' => 1, 'tenant' => null, 'name' => 'help-modal', 'impressions' => 1, 'hovers' => 0, 'clicks' => 1], ]); }); @@ -135,7 +135,7 @@ $analytics = array_map(fn (Analytic $analytic): array => $analytic->toArray(), app(AnalyticsRepository::class)->all()); expect($analytics)->toBe([ - ['id' => 1, 'name' => 'help-modal', 'impressions' => 1, 'hovers' => 0, 'clicks' => 0], + ['id' => 1, 'tenant' => null, 'name' => 'help-modal', 'impressions' => 1, 'hovers' => 0, 'clicks' => 0], ]); })->after(function (): void { PanConfiguration::routePrefix('pan'); diff --git a/tests/Unit/Actions/CreateEventTest.php b/tests/Unit/Actions/CreateEventTest.php index f0aa086..e1352eb 100644 --- a/tests/Unit/Actions/CreateEventTest.php +++ b/tests/Unit/Actions/CreateEventTest.php @@ -15,6 +15,6 @@ $analytics = array_map(fn (Analytic $analytic): array => $analytic->toArray(), app(AnalyticsRepository::class)->all()); expect($analytics)->toBe([ - ['id' => 1, 'name' => 'help-modal', 'impressions' => 0, 'hovers' => 1, 'clicks' => 2], + ['id' => 1, 'tenant' => null, 'name' => 'help-modal', 'impressions' => 0, 'hovers' => 1, 'clicks' => 2], ]); }); diff --git a/tests/Unit/PanConfigurationTest.php b/tests/Unit/PanConfigurationTest.php index daf4bda..c4fa51a 100644 --- a/tests/Unit/PanConfigurationTest.php +++ b/tests/Unit/PanConfigurationTest.php @@ -7,6 +7,8 @@ 'max_analytics' => 50, 'allowed_analytics' => [], 'route_prefix' => 'pan', + 'tenant_field' => null, + 'tenant_id' => null, ]); }); @@ -17,6 +19,8 @@ 'max_analytics' => 100, 'allowed_analytics' => [], 'route_prefix' => 'pan', + 'tenant_field' => null, + 'tenant_id' => null, ]); }); @@ -27,6 +31,8 @@ 'max_analytics' => PHP_INT_MAX, 'allowed_analytics' => [], 'route_prefix' => 'pan', + 'tenant_field' => null, + 'tenant_id' => null, ]); }); @@ -37,6 +43,8 @@ 'max_analytics' => 50, 'allowed_analytics' => ['help-modal', 'contact-modal'], 'route_prefix' => 'pan', + 'tenant_field' => null, + 'tenant_id' => null, ]); }); @@ -45,6 +53,8 @@ 'max_analytics' => 50, 'allowed_analytics' => [], 'route_prefix' => 'pan', + 'tenant_field' => null, + 'tenant_id' => null, ]); }); @@ -55,6 +65,32 @@ 'max_analytics' => 50, 'allowed_analytics' => [], 'route_prefix' => 'new-pan', + 'tenant_field' => null, + 'tenant_id' => null, + ]); +}); + +it('can set the tenant field', function (): void { + PanConfiguration::tenantField('team_id'); + + expect(PanConfiguration::instance()->toArray())->toBe([ + 'max_analytics' => 50, + 'allowed_analytics' => [], + 'route_prefix' => 'pan', + 'tenant_field' => 'team_id', + 'tenant_id' => null, + ]); +}); + +it('can set the tenant id', function (): void { + PanConfiguration::tenantId(1); + + expect(PanConfiguration::instance()->toArray())->toBe([ + 'max_analytics' => 50, + 'allowed_analytics' => [], + 'route_prefix' => 'pan', + 'tenant_field' => null, + 'tenant_id' => 1, ]); }); @@ -67,6 +103,8 @@ 'max_analytics' => 99, 'allowed_analytics' => ['help-modal', 'contact-modal'], 'route_prefix' => 'new-pan', + 'tenant_field' => null, + 'tenant_id' => null, ]); PanConfiguration::reset(); @@ -75,5 +113,7 @@ 'max_analytics' => 50, 'allowed_analytics' => [], 'route_prefix' => 'pan', + 'tenant_field' => null, + 'tenant_id' => null, ]); }); diff --git a/tests/Unit/Presentors/AnalyticPresentorTest.php b/tests/Unit/Presentors/AnalyticPresentorTest.php index e735654..f16ff0c 100644 --- a/tests/Unit/Presentors/AnalyticPresentorTest.php +++ b/tests/Unit/Presentors/AnalyticPresentorTest.php @@ -4,7 +4,7 @@ use Pan\ValueObjects\Analytic; it('present an analytic', function (): void { - $analytic = new Analytic(1, 'help-modal', 1, 1, 1); + $analytic = new Analytic(1, null, 'help-modal', 1, 1, 1); $presentor = new AnalyticPresentor; @@ -20,7 +20,7 @@ }); it('present an analytic with 0 impressions', function (): void { - $analytic = new Analytic(1, 'help-modal', 0, 1, 1); + $analytic = new Analytic(1, null, 'help-modal', 0, 1, 1); $presentor = new AnalyticPresentor; @@ -36,7 +36,7 @@ }); it('present an analytic with 0 hovers', function (): void { - $analytic = new Analytic(1, 'help-modal', 1, 0, 1); + $analytic = new Analytic(1, null, 'help-modal', 1, 0, 1); $presentor = new AnalyticPresentor; @@ -52,7 +52,7 @@ }); it('present an analytic with 0 clicks', function (): void { - $analytic = new Analytic(1, 'help-modal', 1, 1, 0); + $analytic = new Analytic(1, null, 'help-modal', 1, 1, 0); $presentor = new AnalyticPresentor; @@ -68,7 +68,7 @@ }); it('presents huge numbers', function (): void { - $analytic = new Analytic(1, 'help-modal', 1000000, 1000000, 1000000); + $analytic = new Analytic(1, null, 'help-modal', 1000000, 1000000, 1000000); $presentor = new AnalyticPresentor; @@ -84,7 +84,7 @@ }); it('presents huge numbers with 0 impressions', function (): void { - $analytic = new Analytic(1, 'help-modal', 0, 1000000, 1000000); + $analytic = new Analytic(1, null, 'help-modal', 0, 1000000, 1000000); $presentor = new AnalyticPresentor; From e284ec48899ecafa6e713942f5ff2f4c8c130228 Mon Sep 17 00:00:00 2001 From: Niklas Rosenqvist Date: Thu, 26 Dec 2024 10:22:52 +0100 Subject: [PATCH 2/2] Add filtering for artisan command --- src/Adapters/Laravel/Console/Commands/PanCommand.php | 7 ++++++- tests/Feature/Laravel/Console/PanTest.php | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Adapters/Laravel/Console/Commands/PanCommand.php b/src/Adapters/Laravel/Console/Commands/PanCommand.php index c6bfead..6ed2702 100644 --- a/src/Adapters/Laravel/Console/Commands/PanCommand.php +++ b/src/Adapters/Laravel/Console/Commands/PanCommand.php @@ -20,7 +20,7 @@ final class PanCommand extends Command * * @var string */ - protected $signature = 'pan {--filter= : Filter the analytics by name}'; + protected $signature = 'pan {--filter= : Filter the analytics by name} {--tenant= : Show only analytics for specific tenant}'; /** * The console command description. @@ -40,6 +40,11 @@ public function handle(AnalyticsRepository $analytics, AnalyticPresentor $presen $analytics = array_filter($analytics, fn (Analytic $analytic): bool => str_contains($analytic->name, $filter)); } + if (! empty($tenant = $this->option('tenant'))) { + $tenant = ctype_digit($tenant) ? (int) $tenant : $tenant; + $analytics = array_filter($analytics, fn (Analytic $analytic): bool => $analytic->tenant === $filter); + } + if ($analytics === []) { $this->components->info('No analytics have been recorded yet. Get started collecting analytics by adding the [data-pan="my-button"] attribute to your HTML elements.'); diff --git a/tests/Feature/Laravel/Console/PanTest.php b/tests/Feature/Laravel/Console/PanTest.php index 7d114a1..5a56732 100644 --- a/tests/Feature/Laravel/Console/PanTest.php +++ b/tests/Feature/Laravel/Console/PanTest.php @@ -3,6 +3,7 @@ use Illuminate\Support\Facades\Artisan; use Pan\Contracts\AnalyticsRepository; use Pan\Enums\EventType; +use Pan\PanConfiguration; it('displays analytics even if they are empty', function (): void { $response = $this->artisan('pan'); @@ -48,3 +49,12 @@ expect($exitCode)->toBe(0); }); + +it('displays tenant specific analytics', function (): void { + PanConfiguration::tenantField('team_id'); + PanConfiguration::tenantId(1); + + $exitCode = Artisan::call('pan --tenant=1'); + + expect($exitCode)->toBe(0); +});