mirror of
git://f0xx.org/android_cast
synced 2026-07-29 04:38:53 +03:00
BE sync, BLOCKER fix, confirmation needed
This commit is contained in:
@@ -1879,6 +1879,8 @@
|
||||
};
|
||||
xhr.send(JSON.stringify(body));
|
||||
});
|
||||
}
|
||||
|
||||
function initTagFilterBar(listContext) {
|
||||
const chipsEl = document.getElementById('tag-filter-chips');
|
||||
const modeWrap = document.getElementById('tag-mode-wrap');
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -6,9 +6,11 @@ final class GraphRepository {
|
||||
$pdo = Database::pdo();
|
||||
if (!Database::tableExists($pdo, 'graph_sessions')) {
|
||||
self::createTable($pdo);
|
||||
self::ensureIndexes($pdo);
|
||||
return;
|
||||
}
|
||||
self::ensureColumns($pdo);
|
||||
self::ensureIndexes($pdo);
|
||||
}
|
||||
|
||||
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 {
|
||||
$cols = [
|
||||
'device_id' => Database::isMysql() ? 'VARCHAR(128) NULL' : 'TEXT NULL',
|
||||
@@ -299,55 +325,27 @@ final class GraphRepository {
|
||||
$params[] = $direction;
|
||||
}
|
||||
self::appendScope($where, $params, $companyId, $userId);
|
||||
$sql = 'SELECT started_at_ms FROM graph_sessions WHERE ' . implode(' AND ', $where);
|
||||
return self::countByDay($sql, $params, 'started_at_ms');
|
||||
return self::countByDay('graph_sessions', 'started_at_ms', $where, $params);
|
||||
}
|
||||
|
||||
private static function uniqueDevicesPerDay(int $startMs, ?int $companyId, ?int $userId): array {
|
||||
$where = ['started_at_ms >= ?', "device_id IS NOT NULL", "device_id <> ''"];
|
||||
$params = [$startMs];
|
||||
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->execute($params);
|
||||
$bucket = [];
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$day = gmdate('Y-m-d', intdiv((int) ($row['started_at_ms'] ?? 0), 1000));
|
||||
$dev = (string) ($row['device_id'] ?? '');
|
||||
if ($dev === '') {
|
||||
continue;
|
||||
}
|
||||
if (!isset($bucket[$day])) {
|
||||
$bucket[$day] = [];
|
||||
}
|
||||
$bucket[$day][$dev] = true;
|
||||
$bucket[(string) ($row['d'] ?? '')] = (int) ($row['c'] ?? 0);
|
||||
}
|
||||
$out = [];
|
||||
foreach ($bucket as $day => $set) {
|
||||
$out[$day] = count($set);
|
||||
}
|
||||
return self::assocToSeries($out);
|
||||
return self::assocToSeries($bucket);
|
||||
}
|
||||
|
||||
private static function avgDurationPerDay(int $startMs, ?int $companyId, ?int $userId): array {
|
||||
$where = ['started_at_ms >= ?'];
|
||||
$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);
|
||||
return self::avgNumericPerDay($startMs, $companyId, $userId, 'duration_s');
|
||||
}
|
||||
|
||||
private static function successRatio(int $startMs, ?int $companyId, ?int $userId): float {
|
||||
@@ -378,25 +376,7 @@ final class GraphRepository {
|
||||
if (!in_array($column, $allowed, true)) {
|
||||
return [];
|
||||
}
|
||||
$where = ['started_at_ms >= ?'];
|
||||
$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);
|
||||
return self::avgNumericPerDay($startMs, $companyId, $userId, $column);
|
||||
}
|
||||
|
||||
private static function crashesPerDay(int $startMs, ?int $companyId): array {
|
||||
@@ -409,8 +389,7 @@ final class GraphRepository {
|
||||
$where[] = 'company_id = ?';
|
||||
$params[] = $companyId;
|
||||
}
|
||||
$sql = 'SELECT received_at_ms FROM reports WHERE ' . implode(' AND ', $where);
|
||||
return self::countByDay($sql, $params, 'received_at_ms');
|
||||
return self::countByDay('reports', 'received_at_ms', $where, $params);
|
||||
}
|
||||
|
||||
private static function ticketsPerDay(int $startMs, ?int $companyId): array {
|
||||
@@ -420,8 +399,7 @@ final class GraphRepository {
|
||||
$where[] = 'company_id = ?';
|
||||
$params[] = $companyId;
|
||||
}
|
||||
$sql = 'SELECT opened_at_ms FROM tickets WHERE ' . implode(' AND ', $where);
|
||||
return self::countByDay($sql, $params, 'opened_at_ms');
|
||||
return self::countByDay('tickets', 'opened_at_ms', $where, $params);
|
||||
}
|
||||
|
||||
private static function topCrashFingerprints(int $startMs, int $limit): array {
|
||||
@@ -502,17 +480,47 @@ final class GraphRepository {
|
||||
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->execute($params);
|
||||
$bucket = [];
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$day = gmdate('Y-m-d', intdiv((int) ($row[$msColumn] ?? 0), 1000));
|
||||
$bucket[$day] = ($bucket[$day] ?? 0) + 1;
|
||||
$bucket[(string) ($row['d'] ?? '')] = (int) ($row['c'] ?? 0);
|
||||
}
|
||||
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 {
|
||||
if ($companyId !== null && $companyId > 0) {
|
||||
$where[] = 'company_id = ?';
|
||||
|
||||
Reference in New Issue
Block a user