admin: Reports ▸ Wachstum mit den Abbruchbedingungen, Umami nur lesend angebunden
Roadmap 6.1/6.16: Block 1 bis 3 mit Ist, zeitanteiligem Soll, Schwelle und Status, Trichter seit 17.09., Transparenzseiten aus Umami. Neue Befehle growth:report und umami:report. Zugang über UMAMI_API_* (View-only-Benutzer). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e8cf684f02
commit
b4870f98bb
12 changed files with 1098 additions and 0 deletions
103
app/Console/Commands/ReportUmami.php
Normal file
103
app/Console/Commands/ReportUmami.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\Analytics\UmamiClient;
|
||||
use App\Services\Analytics\UmamiUnavailable;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* Nur lesende Abfrage der Umami-Webanalyse (Roadmap 6.16): Kennzahlen je
|
||||
* Website, dazu eine Rangliste (Seiten, Herkunft, Kanäle …), optional auf
|
||||
* Pfade gefiltert. Zugangsdaten aus `UMAMI_API_*`, werden nie ausgegeben.
|
||||
*/
|
||||
class ReportUmami extends Command
|
||||
{
|
||||
protected $signature = 'umami:report
|
||||
{--from= : Startdatum (Standard: vor 30 Tagen)}
|
||||
{--to= : Enddatum (Standard: jetzt)}
|
||||
{--site=* : pressekonto, presseecho, businessportal24 (Standard: alle)}
|
||||
{--path= : nur Pfade, die diesen Text enthalten, z. B. gute-pressemitteilung}
|
||||
{--metric=channel : Rangliste nach path, referrer, channel, country, device, browser, event}
|
||||
{--limit=10 : Zeilen der Rangliste}
|
||||
{--json : Ausgabe als JSON statt als Tabelle}';
|
||||
|
||||
protected $description = 'Umami-Kennzahlen je Website (nur lesend).';
|
||||
|
||||
private const array METRICS = ['path', 'referrer', 'channel', 'country', 'device', 'browser', 'os', 'event', 'title', 'entry', 'exit'];
|
||||
|
||||
public function handle(UmamiClient $umami): int
|
||||
{
|
||||
if (! $umami->isConfigured()) {
|
||||
$this->error('Umami-API nicht konfiguriert: UMAMI_API_URL, UMAMI_API_USERNAME und UMAMI_API_PASSWORD setzen.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$metric = (string) $this->option('metric');
|
||||
|
||||
if (! in_array($metric, self::METRICS, true)) {
|
||||
$this->error('Unbekannte Rangliste. Möglich: '.implode(', ', self::METRICS).'.');
|
||||
|
||||
return self::INVALID;
|
||||
}
|
||||
|
||||
$to = $this->option('to') !== null ? Carbon::parse((string) $this->option('to'))->endOfDay() : Carbon::now();
|
||||
$from = $this->option('from') !== null ? Carbon::parse((string) $this->option('from'))->startOfDay() : $to->copy()->subDays(30);
|
||||
$sites = (array) $this->option('site') ?: UmamiClient::BRANDS;
|
||||
$filters = filled($this->option('path')) ? ['path' => 'c.'.$this->option('path')] : [];
|
||||
$limit = max(1, min(500, (int) $this->option('limit')));
|
||||
|
||||
$result = [];
|
||||
|
||||
try {
|
||||
foreach ($sites as $site) {
|
||||
$websiteId = $umami->websiteId((string) $site);
|
||||
|
||||
if ($websiteId === null) {
|
||||
$this->warn("Keine Umami-Website für „{$site}“ in config/domains.php.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$site] = [
|
||||
'stats' => $umami->stats($websiteId, $from, $to, $filters),
|
||||
$metric => $umami->metrics($websiteId, $metric, $from, $to, $filters, $limit),
|
||||
];
|
||||
}
|
||||
} catch (UmamiUnavailable $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if ($this->option('json')) {
|
||||
$this->line((string) json_encode([
|
||||
'from' => $from->toIso8601String(),
|
||||
'to' => $to->toIso8601String(),
|
||||
'filters' => $filters,
|
||||
'sites' => $result,
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info(sprintf('Umami — %s bis %s%s', $from->format('d.m.Y'), $to->format('d.m.Y'), $filters === [] ? '' : ', Pfad enthält „'.$this->option('path').'“'));
|
||||
|
||||
foreach ($result as $site => $data) {
|
||||
$this->newLine();
|
||||
$this->line("<comment>{$site}</comment>");
|
||||
$this->table(
|
||||
['Aufrufe', 'Besucher', 'Besuche', 'Absprünge'],
|
||||
[[$data['stats']['pageviews'], $data['stats']['visitors'], $data['stats']['visits'], $data['stats']['bounces']]],
|
||||
);
|
||||
$this->table(
|
||||
[ucfirst($metric), 'Anzahl'],
|
||||
array_map(static fn (array $row): array => [$row['x'], $row['y']], $data[$metric]),
|
||||
);
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue