style updates
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'enabled' => (bool) env('ANALYTICS_ENABLED', true),
|
||||
'respect_dnt' => (bool) env('ANALYTICS_RESPECT_DNT', true),
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Modules\Analytics\Services\AnalyticsAggregator;
|
||||
|
||||
final class AggregateAnalytics extends Command
|
||||
{
|
||||
protected $signature='analytics:aggregate {--date=}'; protected $description='Aggregate raw analytics events into daily metrics.';
|
||||
public function handle(AnalyticsAggregator $aggregator): int { $this->info('Metrics written: '.$aggregator->aggregate($this->option('date'))); return self::SUCCESS; }
|
||||
protected $signature = 'analytics:aggregate {--date=}';
|
||||
|
||||
protected $description = 'Aggregate raw analytics events into daily metrics.';
|
||||
|
||||
public function handle(AnalyticsAggregator $aggregator): int
|
||||
{
|
||||
$this->info('Metrics written: '.$aggregator->aggregate($this->option('date')));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Modules\Analytics\Models\AnalyticsEvent;
|
||||
|
||||
final class PruneAnalytics extends Command
|
||||
{
|
||||
protected $signature='analytics:prune'; protected $description='Remove analytics events older than the configured retention period.';
|
||||
public function handle(): int { $count=AnalyticsEvent::where('occurred_at','<',now()->subDays(config('analytics.retention_days',365)))->delete(); $this->info("Deleted {$count} events."); return self::SUCCESS; }
|
||||
protected $signature = 'analytics:prune';
|
||||
|
||||
protected $description = 'Remove analytics events older than the configured retention period.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$count = AnalyticsEvent::where('occurred_at', '<', now()->subDays(config('analytics.retention_days', 365)))->delete();
|
||||
$this->info("Deleted {$count} events.");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Modules\Analytics\Models\AnalyticsReport;
|
||||
|
||||
final class RunScheduledReports extends Command
|
||||
{
|
||||
protected $signature='analytics:reports'; protected $description='Queue analytics reports that are due.';
|
||||
protected $signature = 'analytics:reports';
|
||||
|
||||
protected $description = 'Queue analytics reports that are due.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$count=AnalyticsReport::where('is_active',true)->whereNotNull('next_run_at')->where('next_run_at','<=',now())->update(['last_run_at'=>now(),'next_run_at'=>now()->addDay()]);
|
||||
$this->info("Processed {$count} scheduled reports."); return self::SUCCESS;
|
||||
$count = AnalyticsReport::where('is_active', true)->whereNotNull('next_run_at')->where('next_run_at', '<=', now())->update(['last_run_at' => now(), 'next_run_at' => now()->addDay()]);
|
||||
$this->info("Processed {$count} scheduled reports.");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
+61
-23
@@ -1,42 +1,80 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
return new class extends Migration {
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('analytics_events', function (Blueprint $table) {
|
||||
$table->id(); $table->uuid('event_id')->unique(); $table->foreignId('site_id')->nullable()->constrained('sites')->nullOnDelete();
|
||||
$table->string('event_type', 40)->index(); $table->string('path', 2048)->nullable(); $table->string('title')->nullable();
|
||||
$table->string('referrer', 2048)->nullable(); $table->string('visitor_hash', 64)->nullable()->index();
|
||||
$table->string('session_hash', 64)->nullable()->index(); $table->string('ip_hash', 64)->nullable();
|
||||
$table->string('country_code', 2)->nullable()->index(); $table->string('device_type', 20)->nullable()->index();
|
||||
$table->string('browser', 40)->nullable(); $table->string('operating_system', 40)->nullable();
|
||||
$table->nullableMorphs('subject'); $table->json('properties')->nullable(); $table->timestamp('occurred_at')->index(); $table->timestamps();
|
||||
$table->index(['site_id','event_type','occurred_at']);
|
||||
$table->id();
|
||||
$table->uuid('event_id')->unique();
|
||||
$table->foreignId('site_id')->nullable()->constrained('sites')->nullOnDelete();
|
||||
$table->string('event_type', 40)->index();
|
||||
$table->string('path', 2048)->nullable();
|
||||
$table->string('title')->nullable();
|
||||
$table->string('referrer', 2048)->nullable();
|
||||
$table->string('visitor_hash', 64)->nullable()->index();
|
||||
$table->string('session_hash', 64)->nullable()->index();
|
||||
$table->string('ip_hash', 64)->nullable();
|
||||
$table->string('country_code', 2)->nullable()->index();
|
||||
$table->string('device_type', 20)->nullable()->index();
|
||||
$table->string('browser', 40)->nullable();
|
||||
$table->string('operating_system', 40)->nullable();
|
||||
$table->nullableMorphs('subject');
|
||||
$table->json('properties')->nullable();
|
||||
$table->timestamp('occurred_at')->index();
|
||||
$table->timestamps();
|
||||
$table->index(['site_id', 'event_type', 'occurred_at']);
|
||||
});
|
||||
Schema::create('analytics_daily_metrics', function (Blueprint $table) {
|
||||
$table->id(); $table->foreignId('site_id')->nullable()->constrained('sites')->nullOnDelete(); $table->date('metric_date')->index();
|
||||
$table->string('metric', 60); $table->string('dimension', 120)->default('all'); $table->unsignedBigInteger('value')->default(0);
|
||||
$table->json('metadata')->nullable(); $table->timestamps(); $table->unique(['site_id','metric_date','metric','dimension'], 'analytics_daily_unique');
|
||||
$table->id();
|
||||
$table->foreignId('site_id')->nullable()->constrained('sites')->nullOnDelete();
|
||||
$table->date('metric_date')->index();
|
||||
$table->string('metric', 60);
|
||||
$table->string('dimension', 120)->default('all');
|
||||
$table->unsignedBigInteger('value')->default(0);
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['site_id', 'metric_date', 'metric', 'dimension'], 'analytics_daily_unique');
|
||||
});
|
||||
Schema::create('analytics_reports', function (Blueprint $table) {
|
||||
$table->id(); $table->foreignId('site_id')->nullable()->constrained('sites')->nullOnDelete(); $table->string('name');
|
||||
$table->string('report_type', 40)->default('overview'); $table->json('filters')->nullable(); $table->json('columns')->nullable();
|
||||
$table->string('schedule')->nullable(); $table->string('format', 10)->default('csv'); $table->json('recipients')->nullable();
|
||||
$table->boolean('is_active')->default(true)->index(); $table->timestamp('last_run_at')->nullable(); $table->timestamp('next_run_at')->nullable()->index();
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete(); $table->timestamps();
|
||||
$table->id();
|
||||
$table->foreignId('site_id')->nullable()->constrained('sites')->nullOnDelete();
|
||||
$table->string('name');
|
||||
$table->string('report_type', 40)->default('overview');
|
||||
$table->json('filters')->nullable();
|
||||
$table->json('columns')->nullable();
|
||||
$table->string('schedule')->nullable();
|
||||
$table->string('format', 10)->default('csv');
|
||||
$table->json('recipients')->nullable();
|
||||
$table->boolean('is_active')->default(true)->index();
|
||||
$table->timestamp('last_run_at')->nullable();
|
||||
$table->timestamp('next_run_at')->nullable()->index();
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('analytics_report_runs', function (Blueprint $table) {
|
||||
$table->id(); $table->foreignId('analytics_report_id')->constrained('analytics_reports')->cascadeOnDelete();
|
||||
$table->string('status', 20)->default('pending')->index(); $table->string('disk')->nullable(); $table->string('path')->nullable();
|
||||
$table->unsignedBigInteger('row_count')->default(0); $table->text('error')->nullable(); $table->timestamp('started_at')->nullable();
|
||||
$table->timestamp('completed_at')->nullable(); $table->timestamps();
|
||||
$table->id();
|
||||
$table->foreignId('analytics_report_id')->constrained('analytics_reports')->cascadeOnDelete();
|
||||
$table->string('status', 20)->default('pending')->index();
|
||||
$table->string('disk')->nullable();
|
||||
$table->string('path')->nullable();
|
||||
$table->unsignedBigInteger('row_count')->default(0);
|
||||
$table->text('error')->nullable();
|
||||
$table->timestamp('started_at')->nullable();
|
||||
$table->timestamp('completed_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('analytics_report_runs'); Schema::dropIfExists('analytics_reports');
|
||||
Schema::dropIfExists('analytics_daily_metrics'); Schema::dropIfExists('analytics_events');
|
||||
Schema::dropIfExists('analytics_report_runs');
|
||||
Schema::dropIfExists('analytics_reports');
|
||||
Schema::dropIfExists('analytics_daily_metrics');
|
||||
Schema::dropIfExists('analytics_events');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,30 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\View\View;
|
||||
use Modules\Analytics\Models\AnalyticsEvent;
|
||||
use Modules\Analytics\Models\AnalyticsReport;
|
||||
|
||||
final class AnalyticsController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$days=max(1,min(365,(int)$request->integer('days',30))); $from=now()->subDays($days-1)->startOfDay();
|
||||
$events=AnalyticsEvent::query()->where('occurred_at','>=',$from);
|
||||
return view('analytics::index',[
|
||||
'days'=>$days,'pageViews'=>$events->clone()->where('event_type','page_view')->count(),
|
||||
'visitors'=>$events->clone()->whereNotNull('visitor_hash')->distinct('visitor_hash')->count('visitor_hash'),
|
||||
'conversions'=>$events->clone()->where('event_type','conversion')->count(),
|
||||
'searches'=>$events->clone()->where('event_type','search')->count(),
|
||||
'topPages'=>$events->clone()->where('event_type','page_view')->selectRaw('path, count(*) as total')->groupBy('path')->orderByDesc('total')->limit(10)->get(),
|
||||
'recentEvents'=>$events->clone()->latest('occurred_at')->limit(20)->get(), 'reports'=>AnalyticsReport::latest()->limit(20)->get(),
|
||||
$days = max(1, min(365, (int) $request->integer('days', 30)));
|
||||
$from = now()->subDays($days - 1)->startOfDay();
|
||||
$events = AnalyticsEvent::query()->where('occurred_at', '>=', $from);
|
||||
|
||||
return view('analytics::index', [
|
||||
'days' => $days, 'pageViews' => $events->clone()->where('event_type', 'page_view')->count(),
|
||||
'visitors' => $events->clone()->whereNotNull('visitor_hash')->distinct('visitor_hash')->count('visitor_hash'),
|
||||
'conversions' => $events->clone()->where('event_type', 'conversion')->count(),
|
||||
'searches' => $events->clone()->where('event_type', 'search')->count(),
|
||||
'topPages' => $events->clone()->where('event_type', 'page_view')->selectRaw('path, count(*) as total')->groupBy('path')->orderByDesc('total')->limit(10)->get(),
|
||||
'recentEvents' => $events->clone()->latest('occurred_at')->limit(20)->get(), 'reports' => AnalyticsReport::latest()->limit(20)->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function storeReport(Request $request): RedirectResponse
|
||||
{
|
||||
AnalyticsReport::create($request->validate(['name'=>'required|string|max:255','report_type'=>'required|in:overview,content,search,conversion,commerce','schedule'=>'nullable|string|max:50','format'=>'required|in:csv,json','is_active'=>'nullable|boolean']) + ['created_by'=>$request->user()->id,'is_active'=>$request->boolean('is_active',true)]);
|
||||
return back()->with('status','Report created.');
|
||||
AnalyticsReport::create($request->validate(['name' => 'required|string|max:255', 'report_type' => 'required|in:overview,content,search,conversion,commerce', 'schedule' => 'nullable|string|max:50', 'format' => 'required|in:csv,json', 'is_active' => 'nullable|boolean']) + ['created_by' => $request->user()->id, 'is_active' => $request->boolean('is_active', true)]);
|
||||
|
||||
return back()->with('status', 'Report created.');
|
||||
}
|
||||
|
||||
public function destroyReport(AnalyticsReport $report): RedirectResponse
|
||||
{
|
||||
$report->delete();
|
||||
|
||||
return back()->with('status','Report deleted.');
|
||||
}
|
||||
public function destroyReport(AnalyticsReport $report): RedirectResponse { $report->delete(); return back()->with('status','Report deleted.'); }
|
||||
}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Modules\Analytics\Services\AnalyticsTracker;
|
||||
|
||||
final class TrackingController extends Controller
|
||||
{
|
||||
public function store(Request $request, AnalyticsTracker $tracker): JsonResponse
|
||||
{
|
||||
$data=$request->validate([
|
||||
'event_type'=>['required',Rule::in(['page_view','search','conversion','content_view','form_submit','commerce'])],
|
||||
'path'=>['nullable','string','max:2048'],'title'=>['nullable','string','max:255'],'referrer'=>['nullable','string','max:2048'],
|
||||
'visitor_id'=>['nullable','string','max:255'],'session_id'=>['nullable','string','max:255'],
|
||||
'subject_type'=>['nullable','string','max:255'],'subject_id'=>['nullable','integer'],'properties'=>['nullable','array'],
|
||||
$data = $request->validate([
|
||||
'event_type' => ['required', Rule::in(['page_view', 'search', 'conversion', 'content_view', 'form_submit', 'commerce'])],
|
||||
'path' => ['nullable', 'string', 'max:2048'], 'title' => ['nullable', 'string', 'max:255'], 'referrer' => ['nullable', 'string', 'max:2048'],
|
||||
'visitor_id' => ['nullable', 'string', 'max:255'], 'session_id' => ['nullable', 'string', 'max:255'],
|
||||
'subject_type' => ['nullable', 'string', 'max:255'], 'subject_id' => ['nullable', 'integer'], 'properties' => ['nullable', 'array'],
|
||||
]);
|
||||
$event=$tracker->track($request,$data);
|
||||
return response()->json(['recorded'=>(bool)$event],$event?201:202);
|
||||
$event = $tracker->track($request, $data);
|
||||
|
||||
return response()->json(['recorded' => (bool) $event], $event ? 201 : 202);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class AnalyticsEvent extends Model
|
||||
{
|
||||
protected $fillable=['event_id','site_id','event_type','path','title','referrer','visitor_hash','session_hash','ip_hash','country_code','device_type','browser','operating_system','subject_type','subject_id','properties','occurred_at'];
|
||||
protected $casts=['properties'=>'array','occurred_at'=>'datetime'];
|
||||
protected $fillable = ['event_id', 'site_id', 'event_type', 'path', 'title', 'referrer', 'visitor_hash', 'session_hash', 'ip_hash', 'country_code', 'device_type', 'browser', 'operating_system', 'subject_type', 'subject_id', 'properties', 'occurred_at'];
|
||||
|
||||
protected $casts = ['properties' => 'array', 'occurred_at' => 'datetime'];
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class AnalyticsReport extends Model
|
||||
{
|
||||
protected $fillable=['site_id','name','report_type','filters','columns','schedule','format','recipients','is_active','last_run_at','next_run_at','created_by'];
|
||||
protected $casts=['filters'=>'array','columns'=>'array','recipients'=>'array','is_active'=>'boolean','last_run_at'=>'datetime','next_run_at'=>'datetime'];
|
||||
protected $fillable = ['site_id', 'name', 'report_type', 'filters', 'columns', 'schedule', 'format', 'recipients', 'is_active', 'last_run_at', 'next_run_at', 'created_by'];
|
||||
|
||||
protected $casts = ['filters' => 'array', 'columns' => 'array', 'recipients' => 'array', 'is_active' => 'boolean', 'last_run_at' => 'datetime', 'next_run_at' => 'datetime'];
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class AnalyticsReportRun extends Model
|
||||
{
|
||||
protected $fillable=['analytics_report_id','status','disk','path','row_count','error','started_at','completed_at'];
|
||||
protected $casts=['started_at'=>'datetime','completed_at'=>'datetime'];
|
||||
protected $fillable = ['analytics_report_id', 'status', 'disk', 'path', 'row_count', 'error', 'started_at', 'completed_at'];
|
||||
|
||||
protected $casts = ['started_at' => 'datetime', 'completed_at' => 'datetime'];
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class DailyMetric extends Model
|
||||
{
|
||||
protected $table='analytics_daily_metrics';
|
||||
protected $fillable=['site_id','metric_date','metric','dimension','value','metadata'];
|
||||
protected $casts=['metric_date'=>'date','metadata'=>'array'];
|
||||
protected $table = 'analytics_daily_metrics';
|
||||
|
||||
protected $fillable = ['site_id', 'metric_date', 'metric', 'dimension', 'value', 'metadata'];
|
||||
|
||||
protected $casts = ['metric_date' => 'date', 'metadata' => 'array'];
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Providers;
|
||||
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Analytics\Console\AggregateAnalytics;
|
||||
use Modules\Analytics\Console\PruneAnalytics;
|
||||
use Modules\Analytics\Console\RunScheduledReports;
|
||||
|
||||
final class AnalyticsServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
@@ -12,6 +15,7 @@ final class AnalyticsServiceProvider extends ServiceProvider
|
||||
$this->app->register(RouteServiceProvider::class);
|
||||
$this->mergeConfigFrom(module_path('Analytics', 'Config/config.php'), 'analytics');
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadMigrationsFrom(module_path('Analytics', 'Database/Migrations'));
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
final class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function boot(): void
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\Analytics\Http\Controllers\TrackingController;
|
||||
Route::post('/analytics/events',[TrackingController::class,'store'])->middleware('throttle:60,1')->name('api.analytics.events.store');
|
||||
|
||||
Route::post('/analytics/events', [TrackingController::class, 'store'])->middleware('throttle:60,1')->name('api.analytics.events.store');
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\Analytics\Http\Controllers\AnalyticsController;
|
||||
Route::middleware(['auth','permission:analytics.view'])->prefix('administration/analytics')->name('administration.analytics.')->group(function () {
|
||||
Route::get('/',[AnalyticsController::class,'index'])->name('index');
|
||||
Route::post('/reports',[AnalyticsController::class,'storeReport'])->middleware('permission:analytics.manage')->name('reports.store');
|
||||
Route::delete('/reports/{report}',[AnalyticsController::class,'destroyReport'])->middleware('permission:analytics.manage')->name('reports.destroy');
|
||||
|
||||
Route::middleware(['auth', 'permission:analytics.view'])->prefix('administration/analytics')->name('administration.analytics.')->group(function () {
|
||||
Route::get('/', [AnalyticsController::class, 'index'])->name('index');
|
||||
Route::post('/reports', [AnalyticsController::class, 'storeReport'])->middleware('permission:analytics.manage')->name('reports.store');
|
||||
Route::delete('/reports/{report}', [AnalyticsController::class, 'destroyReport'])->middleware('permission:analytics.manage')->name('reports.destroy');
|
||||
});
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Services;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
use Modules\Analytics\Models\AnalyticsEvent;
|
||||
use Modules\Analytics\Models\DailyMetric;
|
||||
|
||||
final class AnalyticsAggregator
|
||||
{
|
||||
public function aggregate(?string $date=null): int
|
||||
public function aggregate(?string $date = null): int
|
||||
{
|
||||
$date=$date ?: now()->subHour()->toDateString(); $written=0;
|
||||
$base=AnalyticsEvent::query()->whereDate('occurred_at',$date);
|
||||
$date = $date ?: now()->subHour()->toDateString();
|
||||
$written = 0;
|
||||
$base = AnalyticsEvent::query()->whereDate('occurred_at', $date);
|
||||
foreach ($base->clone()->select('site_id')->distinct()->pluck('site_id') as $siteId) {
|
||||
$query=$base->clone()->where('site_id',$siteId);
|
||||
$metrics=[
|
||||
['page_views','all',$query->clone()->where('event_type','page_view')->count()],
|
||||
['visitors','all',$query->clone()->whereNotNull('visitor_hash')->distinct('visitor_hash')->count('visitor_hash')],
|
||||
['sessions','all',$query->clone()->whereNotNull('session_hash')->distinct('session_hash')->count('session_hash')],
|
||||
['conversions','all',$query->clone()->where('event_type','conversion')->count()],
|
||||
['searches','all',$query->clone()->where('event_type','search')->count()],
|
||||
$query = $base->clone()->where('site_id', $siteId);
|
||||
$metrics = [
|
||||
['page_views', 'all', $query->clone()->where('event_type', 'page_view')->count()],
|
||||
['visitors', 'all', $query->clone()->whereNotNull('visitor_hash')->distinct('visitor_hash')->count('visitor_hash')],
|
||||
['sessions', 'all', $query->clone()->whereNotNull('session_hash')->distinct('session_hash')->count('session_hash')],
|
||||
['conversions', 'all', $query->clone()->where('event_type', 'conversion')->count()],
|
||||
['searches', 'all', $query->clone()->where('event_type', 'search')->count()],
|
||||
];
|
||||
foreach ($metrics as [$metric,$dimension,$value]) {
|
||||
DailyMetric::updateOrCreate(['site_id'=>$siteId,'metric_date'=>$date,'metric'=>$metric,'dimension'=>$dimension],['value'=>$value]); $written++;
|
||||
DailyMetric::updateOrCreate(['site_id' => $siteId, 'metric_date' => $date, 'metric' => $metric, 'dimension' => $dimension], ['value' => $value]);
|
||||
$written++;
|
||||
}
|
||||
}
|
||||
|
||||
return $written;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Analytics\Tests\Feature;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class AnalyticsModuleTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_tracking_endpoint_records_page_view(): void
|
||||
{
|
||||
$this->postJson('/api/v1/analytics/events',['event_type'=>'page_view','path'=>'/about'])->assertCreated()->assertJson(['recorded'=>true]);
|
||||
$this->assertDatabaseHas('analytics_events',['event_type'=>'page_view','path'=>'/about']);
|
||||
$this->postJson('/api/v1/analytics/events', ['event_type' => 'page_view', 'path' => '/about'])->assertCreated()->assertJson(['recorded' => true]);
|
||||
$this->assertDatabaseHas('analytics_events', ['event_type' => 'page_view', 'path' => '/about']);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user