mirror of
git://f0xx.org/android_cast
synced 2026-07-29 05:37:52 +03:00
BE sync, BLOCKER fix, confirmation needed
This commit is contained in:
@@ -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');
|
||||||
|
|||||||
@@ -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();
|
$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])) {
|
return self::assocToSeries($bucket);
|
||||||
$bucket[$day] = [];
|
|
||||||
}
|
|
||||||
$bucket[$day][$dev] = true;
|
|
||||||
}
|
|
||||||
$out = [];
|
|
||||||
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 = ?';
|
||||||
|
|||||||
Reference in New Issue
Block a user