1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 07:39:15 +03:00

BE sync, BLOCKER fix, confirmation needed

This commit is contained in:
Anton Afanasyeu
2026-06-05 12:57:00 +02:00
parent 617670ad96
commit 25e11d0c5b
4 changed files with 82 additions and 61 deletions

View File

@@ -1879,6 +1879,8 @@
}; };
xhr.send(JSON.stringify(body)); xhr.send(JSON.stringify(body));
}); });
}
function initTagFilterBar(listContext) { function initTagFilterBar(listContext) {
const chipsEl = document.getElementById('tag-filter-chips'); const chipsEl = document.getElementById('tag-filter-chips');
const modeWrap = document.getElementById('tag-mode-wrap'); const modeWrap = document.getElementById('tag-mode-wrap');

View File

@@ -0,0 +1,6 @@
-- SQLite graph_sessions indexes (table may already exist via GraphRepository::ensureSchema).
CREATE INDEX IF NOT EXISTS idx_graph_company_time ON graph_sessions (company_id, started_at_ms);
CREATE INDEX IF NOT EXISTS idx_graph_user_time ON graph_sessions (user_id, started_at_ms);
CREATE INDEX IF NOT EXISTS idx_graph_started ON graph_sessions (started_at_ms);
CREATE INDEX IF NOT EXISTS idx_graph_device ON graph_sessions (device_id);

View File

@@ -0,0 +1,5 @@
-- Extra graph_sessions indexes for graphs dashboard (user scope + platform-wide time range).
-- Idempotent on MariaDB 10.5+: duplicate index names are ignored manually when re-run.
CREATE INDEX IF NOT EXISTS idx_graph_user_time ON graph_sessions (user_id, started_at_ms);
CREATE INDEX IF NOT EXISTS idx_graph_started ON graph_sessions (started_at_ms);

View File

@@ -6,9 +6,11 @@ final class GraphRepository {
$pdo = Database::pdo(); $pdo = Database::pdo();
if (!Database::tableExists($pdo, 'graph_sessions')) { if (!Database::tableExists($pdo, 'graph_sessions')) {
self::createTable($pdo); self::createTable($pdo);
self::ensureIndexes($pdo);
return; return;
} }
self::ensureColumns($pdo); self::ensureColumns($pdo);
self::ensureIndexes($pdo);
} }
private static function createTable(PDO $pdo): void { private static function createTable(PDO $pdo): void {
@@ -71,6 +73,30 @@ final class GraphRepository {
); );
} }
private static function ensureIndexes(PDO $pdo): void {
$defs = Database::isMysql()
? [
'idx_graph_user_time' => 'CREATE INDEX idx_graph_user_time ON graph_sessions (user_id, started_at_ms)',
'idx_graph_started' => 'CREATE INDEX idx_graph_started ON graph_sessions (started_at_ms)',
]
: [
'idx_graph_company_time' => 'CREATE INDEX IF NOT EXISTS idx_graph_company_time ON graph_sessions (company_id, started_at_ms)',
'idx_graph_user_time' => 'CREATE INDEX IF NOT EXISTS idx_graph_user_time ON graph_sessions (user_id, started_at_ms)',
'idx_graph_started' => 'CREATE INDEX IF NOT EXISTS idx_graph_started ON graph_sessions (started_at_ms)',
'idx_graph_device' => 'CREATE INDEX IF NOT EXISTS idx_graph_device ON graph_sessions (device_id)',
];
foreach ($defs as $sql) {
try {
$pdo->exec($sql);
} catch (PDOException $e) {
$msg = strtolower($e->getMessage());
if (!str_contains($msg, 'duplicate') && !str_contains($msg, 'already exists')) {
throw $e;
}
}
}
}
private static function ensureColumns(PDO $pdo): void { private static function ensureColumns(PDO $pdo): void {
$cols = [ $cols = [
'device_id' => Database::isMysql() ? 'VARCHAR(128) NULL' : 'TEXT NULL', 'device_id' => Database::isMysql() ? 'VARCHAR(128) NULL' : 'TEXT NULL',
@@ -299,55 +325,27 @@ final class GraphRepository {
$params[] = $direction; $params[] = $direction;
} }
self::appendScope($where, $params, $companyId, $userId); self::appendScope($where, $params, $companyId, $userId);
$sql = 'SELECT started_at_ms FROM graph_sessions WHERE ' . implode(' AND ', $where); return self::countByDay('graph_sessions', 'started_at_ms', $where, $params);
return self::countByDay($sql, $params, 'started_at_ms');
} }
private static function uniqueDevicesPerDay(int $startMs, ?int $companyId, ?int $userId): array { private static function uniqueDevicesPerDay(int $startMs, ?int $companyId, ?int $userId): array {
$where = ['started_at_ms >= ?', "device_id IS NOT NULL", "device_id <> ''"]; $where = ['started_at_ms >= ?', "device_id IS NOT NULL", "device_id <> ''"];
$params = [$startMs]; $params = [$startMs];
self::appendScope($where, $params, $companyId, $userId); self::appendScope($where, $params, $companyId, $userId);
$sql = 'SELECT started_at_ms, device_id FROM graph_sessions WHERE ' . implode(' AND ', $where); $day = self::msDayExpr('started_at_ms');
$sql = 'SELECT ' . $day . ' AS d, COUNT(DISTINCT device_id) AS c FROM graph_sessions WHERE '
. implode(' AND ', $where) . ' GROUP BY d ORDER BY d';
$stmt = Database::pdo()->prepare($sql); $stmt = Database::pdo()->prepare($sql);
$stmt->execute($params); $stmt->execute($params);
$bucket = []; $bucket = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) { foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$day = gmdate('Y-m-d', intdiv((int) ($row['started_at_ms'] ?? 0), 1000)); $bucket[(string) ($row['d'] ?? '')] = (int) ($row['c'] ?? 0);
$dev = (string) ($row['device_id'] ?? '');
if ($dev === '') {
continue;
}
if (!isset($bucket[$day])) {
$bucket[$day] = [];
}
$bucket[$day][$dev] = true;
} }
$out = []; return self::assocToSeries($bucket);
foreach ($bucket as $day => $set) {
$out[$day] = count($set);
}
return self::assocToSeries($out);
} }
private static function avgDurationPerDay(int $startMs, ?int $companyId, ?int $userId): array { private static function avgDurationPerDay(int $startMs, ?int $companyId, ?int $userId): array {
$where = ['started_at_ms >= ?']; return self::avgNumericPerDay($startMs, $companyId, $userId, 'duration_s');
$params = [$startMs];
self::appendScope($where, $params, $companyId, $userId);
$sql = 'SELECT started_at_ms, duration_s FROM graph_sessions WHERE ' . implode(' AND ', $where);
$stmt = Database::pdo()->prepare($sql);
$stmt->execute($params);
$sum = [];
$cnt = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$day = gmdate('Y-m-d', intdiv((int) ($row['started_at_ms'] ?? 0), 1000));
$sum[$day] = ($sum[$day] ?? 0) + (int) ($row['duration_s'] ?? 0);
$cnt[$day] = ($cnt[$day] ?? 0) + 1;
}
$avg = [];
foreach ($sum as $day => $total) {
$avg[$day] = $cnt[$day] > 0 ? round($total / $cnt[$day], 1) : 0.0;
}
return self::assocToSeries($avg);
} }
private static function successRatio(int $startMs, ?int $companyId, ?int $userId): float { private static function successRatio(int $startMs, ?int $companyId, ?int $userId): float {
@@ -378,25 +376,7 @@ final class GraphRepository {
if (!in_array($column, $allowed, true)) { if (!in_array($column, $allowed, true)) {
return []; return [];
} }
$where = ['started_at_ms >= ?']; return self::avgNumericPerDay($startMs, $companyId, $userId, $column);
$params = [$startMs];
self::appendScope($where, $params, $companyId, $userId);
$sql = 'SELECT started_at_ms, ' . $column . ' AS v FROM graph_sessions WHERE '
. implode(' AND ', $where);
$stmt = Database::pdo()->prepare($sql);
$stmt->execute($params);
$sum = [];
$cnt = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$day = gmdate('Y-m-d', intdiv((int) ($row['started_at_ms'] ?? 0), 1000));
$sum[$day] = ($sum[$day] ?? 0.0) + (float) ($row['v'] ?? 0);
$cnt[$day] = ($cnt[$day] ?? 0) + 1;
}
$avg = [];
foreach ($sum as $day => $total) {
$avg[$day] = $cnt[$day] > 0 ? round($total / $cnt[$day], 1) : 0.0;
}
return self::assocToSeries($avg);
} }
private static function crashesPerDay(int $startMs, ?int $companyId): array { private static function crashesPerDay(int $startMs, ?int $companyId): array {
@@ -409,8 +389,7 @@ final class GraphRepository {
$where[] = 'company_id = ?'; $where[] = 'company_id = ?';
$params[] = $companyId; $params[] = $companyId;
} }
$sql = 'SELECT received_at_ms FROM reports WHERE ' . implode(' AND ', $where); return self::countByDay('reports', 'received_at_ms', $where, $params);
return self::countByDay($sql, $params, 'received_at_ms');
} }
private static function ticketsPerDay(int $startMs, ?int $companyId): array { private static function ticketsPerDay(int $startMs, ?int $companyId): array {
@@ -420,8 +399,7 @@ final class GraphRepository {
$where[] = 'company_id = ?'; $where[] = 'company_id = ?';
$params[] = $companyId; $params[] = $companyId;
} }
$sql = 'SELECT opened_at_ms FROM tickets WHERE ' . implode(' AND ', $where); return self::countByDay('tickets', 'opened_at_ms', $where, $params);
return self::countByDay($sql, $params, 'opened_at_ms');
} }
private static function topCrashFingerprints(int $startMs, int $limit): array { private static function topCrashFingerprints(int $startMs, int $limit): array {
@@ -502,17 +480,47 @@ final class GraphRepository {
return $out; return $out;
} }
private static function countByDay(string $sql, array $params, string $msColumn): array { private static function msDayExpr(string $msColumn): string {
if (Database::isMysql()) {
return 'DATE(FROM_UNIXTIME(FLOOR(' . $msColumn . ' / 1000)))';
}
return "strftime('%Y-%m-%d', " . $msColumn . " / 1000, 'unixepoch')";
}
private static function countByDay(string $table, string $msColumn, array $where, array $params): array {
$day = self::msDayExpr($msColumn);
$sql = 'SELECT ' . $day . ' AS d, COUNT(*) AS c FROM ' . $table . ' WHERE '
. implode(' AND ', $where) . ' GROUP BY d ORDER BY d';
$stmt = Database::pdo()->prepare($sql); $stmt = Database::pdo()->prepare($sql);
$stmt->execute($params); $stmt->execute($params);
$bucket = []; $bucket = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) { foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$day = gmdate('Y-m-d', intdiv((int) ($row[$msColumn] ?? 0), 1000)); $bucket[(string) ($row['d'] ?? '')] = (int) ($row['c'] ?? 0);
$bucket[$day] = ($bucket[$day] ?? 0) + 1;
} }
return self::assocToSeries($bucket); return self::assocToSeries($bucket);
} }
private static function avgNumericPerDay(
int $startMs,
?int $companyId,
?int $userId,
string $column
): array {
$where = ['started_at_ms >= ?'];
$params = [$startMs];
self::appendScope($where, $params, $companyId, $userId);
$day = self::msDayExpr('started_at_ms');
$sql = 'SELECT ' . $day . ' AS d, AVG(' . $column . ') AS v FROM graph_sessions WHERE '
. implode(' AND ', $where) . ' GROUP BY d ORDER BY d';
$stmt = Database::pdo()->prepare($sql);
$stmt->execute($params);
$avg = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$avg[(string) ($row['d'] ?? '')] = round((float) ($row['v'] ?? 0), 1);
}
return self::assocToSeries($avg);
}
private static function appendScope(array &$where, array &$params, ?int $companyId, ?int $userId): void { private static function appendScope(array &$where, array &$params, ?int $companyId, ?int $userId): void {
if ($companyId !== null && $companyId > 0) { if ($companyId !== null && $companyId > 0) {
$where[] = 'company_id = ?'; $where[] = 'company_id = ?';