Analytics - v1.0.0-beta1
Multi-Tenancy
ArtisanPack UI Analytics provides comprehensive multi-tenant support for SaaS and multi-site applications.
Enabling Multi-Tenancy
// .env
ANALYTICS_MULTI_TENANT=true
// config/artisanpack/analytics.php
'multi_tenant' => [
'enabled' => env('ANALYTICS_MULTI_TENANT', false),
],
Site Resolution
Sites are resolved from incoming requests using configurable resolvers.
Available Resolvers
| Resolver | Priority | Method |
|---|---|---|
ApiKeyResolver |
10 | API key in header/query |
HeaderResolver |
50 | X-Site-ID header |
SubdomainResolver |
90 | Subdomain extraction |
DomainResolver |
100 | Full domain match |
Configure Resolvers
// config/artisanpack/analytics.php
'multi_tenant' => [
'resolvers' => [
\ArtisanPackUI\Analytics\Resolvers\ApiKeyResolver::class,
\ArtisanPackUI\Analytics\Resolvers\HeaderResolver::class,
\ArtisanPackUI\Analytics\Resolvers\SubdomainResolver::class,
\ArtisanPackUI\Analytics\Resolvers\DomainResolver::class,
],
],
Resolution Strategies
API Key Resolution
Best for embedded tracking scripts:
// In customer's website
<script src="https://yourapp.com/analytics.js" data-api-key="site_abc123"></script>
The API key can be sent via:
X-API-Keyheaderapi_keyquery parameter (if enabled)
'multi_tenant' => [
'allow_query_api_key' => env('ANALYTICS_ALLOW_QUERY_API_KEY', false),
],
Header Resolution
For API integrations:
// HTTP Request
GET /api/analytics/track HTTP/1.1
X-Site-ID: 123
Configure the header name:
'multi_tenant' => [
'site_header' => env('ANALYTICS_SITE_HEADER', 'X-Site-ID'),
],
Subdomain Resolution
For subdomain-based multi-tenancy:
// .env
ANALYTICS_BASE_DOMAIN=myapp.com
// Resolves:
// tenant1.myapp.com → Site with domain "tenant1"
// tenant2.myapp.com → Site with domain "tenant2"
Domain Resolution
For custom domain support:
// Sites table
| id | domain |
|----|------------------|
| 1 | client1.com |
| 2 | client2.com |
// Request to client1.com → Site ID 1
Creating Sites
Via Code
use ArtisanPackUI\Analytics\Models\Site;
$site = Site::create([
'name' => 'Client Website',
'domain' => 'client.example.com',
'api_key' => Str::random(32),
'is_active' => true,
'tenant_id' => $tenant->id, // Optional
'settings' => [
'tracking' => [
'enabled' => true,
'anonymize_ip' => true,
],
],
]);
Via Artisan
php artisan analytics:create-site "Client Website" --domain=client.example.com
Site Settings
Each site can have custom settings that override global configuration:
$site->settings = [
'tracking' => [
'enabled' => true,
'respect_dnt' => true,
'anonymize_ip' => true,
'track_hash_changes' => false,
],
'dashboard' => [
'public' => false,
'default_date_range' => 30,
'realtime_enabled' => true,
],
'privacy' => [
'consent_required' => true,
'excluded_paths' => ['/admin/*'],
],
'features' => [
'events' => true,
'goals' => true,
'conversions' => true,
],
];
$site->save();
Using Site Context
In Controllers
use ArtisanPackUI\Analytics\Services\TenantManager;
class DashboardController extends Controller
{
public function index(TenantManager $tenantManager)
{
$site = $tenantManager->currentSite();
if (!$site) {
abort(404, 'Site not found');
}
return view('dashboard', compact('site'));
}
}
In Livewire Components
<livewire:artisanpack-analytics::analytics-dashboard
:site-id="$site->id"
/>
In Queries
use ArtisanPackUI\Analytics\Models\PageView;
// Automatic site scoping
$pageViews = PageView::forSite($siteId)->get();
// Or using tenant ID
$pageViews = PageView::forTenant($tenantId)->get();
Multi-Tenant Dashboard
Use the dedicated multi-tenant dashboard:
<livewire:artisanpack-analytics::multi-tenant-dashboard />
Or the site selector:
<livewire:artisanpack-analytics::site-selector />
Platform Dashboard
For platform administrators to view all sites:
<livewire:artisanpack-analytics::platform-dashboard />
Cross-Tenant Reporting
Use the CrossTenantReporting service for aggregate stats:
use ArtisanPackUI\Analytics\Services\CrossTenantReporting;
$reporting = app(CrossTenantReporting::class);
// Get stats for all sites
$allStats = $reporting->getAllSitesStats($dateRange);
// Get stats for specific tenant
$tenantStats = $reporting->getTenantStats($tenantId, $dateRange);
Custom Site Resolver
Create a custom resolver for your specific needs:
use ArtisanPackUI\Analytics\Contracts\SiteResolverInterface;
use ArtisanPackUI\Analytics\Models\Site;
use Illuminate\Http\Request;
class TeamBasedResolver implements SiteResolverInterface
{
public function resolve(Request $request): ?Site
{
$user = $request->user();
if (!$user || !$user->currentTeam) {
return null;
}
return Site::where('team_id', $user->currentTeam->id)->first();
}
public function getPriority(): int
{
return 25;
}
}
Register in config:
'multi_tenant' => [
'resolvers' => [
\App\Analytics\TeamBasedResolver::class,
// ... other resolvers
],
],
Middleware
Apply site resolution middleware to routes:
// routes/web.php
Route::middleware(['analytics.site'])->group(function () {
Route::get('/analytics', AnalyticsController::class);
});
Default Site
Configure a fallback site:
'multi_tenant' => [
'default_site_id' => env('ANALYTICS_DEFAULT_SITE_ID'),
],
Database Considerations
Indexing
Ensure proper indexes for multi-tenant queries:
// In a migration
$table->index(['site_id', 'created_at']);
$table->index(['tenant_id', 'created_at']);
Separate Databases
For large-scale deployments, consider separate databases:
'local' => [
'connection' => env('ANALYTICS_DB_CONNECTION', 'analytics'),
],
Testing Multi-Tenancy
use ArtisanPackUI\Analytics\Models\Site;
test('tracks to correct site', function () {
$site = Site::factory()->create();
$this->withHeader('X-API-Key', $site->api_key)
->post('/api/analytics/pageview', [
'path' => '/test',
])
->assertOk();
expect($site->pageViews()->count())->toBe(1);
});