Fix historical match detail backend URL and remove false KPM
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
---
|
||||
id: TASK-222
|
||||
title: Fix historical match detail backend URL and false KPM
|
||||
status: done
|
||||
type: frontend
|
||||
team: Frontend Senior
|
||||
supporting_teams:
|
||||
- Backend Senior
|
||||
roadmap_item: foundation
|
||||
priority: high
|
||||
---
|
||||
|
||||
# TASK-222 - Fix historical match detail backend URL and false KPM
|
||||
|
||||
## Goal
|
||||
|
||||
Corregir `frontend/historico-partida.html` para que el detalle de una partida historica no intente conectar con `127.0.0.1:8000` en produccion y eliminar el KPM falso calculado con la duracion total de la partida.
|
||||
|
||||
## Context
|
||||
|
||||
La pagina de detalle historico lee `/api/historical/matches/detail?server=<server>&match=<match>`. En produccion, el frontend no debe usar `http://127.0.0.1:8000` como backend por defecto porque eso apunta al PC del usuario. Ademas, la tabla de jugadores mostraba KPM calculado como `kills / duration_seconds` de la partida completa, lo que no representa tiempo real jugado por jugador.
|
||||
|
||||
Preserve the current product identity: Spanish-speaking HLL Vietnam community, military/Vietnam/tactical/sober visual direction and controlled repository evolution.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Leer primero los archivos listados y confirmar el fallback de backend y el uso de KPM.
|
||||
2. Corregir la resolucion de URL de backend en `historico-partida.js` sin ampliar el alcance a otros modulos.
|
||||
3. Confirmar que el detalle sigue llamando a `/api/historical/matches/detail?server=<server>&match=<match>`.
|
||||
4. Eliminar de `historico-partida` la exposicion de KPM si no existe `player_active_seconds` real.
|
||||
5. Validar sintaxis y revisar el alcance del diff.
|
||||
|
||||
## Files to Read First
|
||||
|
||||
- `ai/architecture-index.md`
|
||||
- `ai/repo-context.md`
|
||||
- `ai/orchestrator/frontend-senior.md`
|
||||
- `frontend/historico-partida.html`
|
||||
- `frontend/assets/js/historico-partida.js`
|
||||
- `docs/REAL_KPM_PLAYER_ACTIVE_SECONDS_DESIGN.md`
|
||||
|
||||
Tambien se revisaron:
|
||||
|
||||
- `frontend/assets/js/historico.js`
|
||||
- `frontend/assets/js/historico-recent-live.js`
|
||||
- `frontend/assets/js/ranking.js`
|
||||
- `frontend/assets/js/config.js`
|
||||
- `frontend/assets/css/historico.css`
|
||||
- `ai/tasks/done/TASK-216-clean-ranking-ui-and-fix-kpm-metric.md`
|
||||
|
||||
## Expected Files to Modify
|
||||
|
||||
- `frontend/historico-partida.html`
|
||||
- `frontend/assets/js/historico-partida.js`
|
||||
- `ai/tasks/done/TASK-222-fix-historical-match-detail-backend-url-and-false-kpm.md`
|
||||
|
||||
## Constraints
|
||||
|
||||
- Keep the change minimal.
|
||||
- Preserve HLL Vietnam project identity.
|
||||
- Do not introduce unnecessary frameworks or dependencies.
|
||||
- Do not implement backend functionality unless frontend diagnosis proves it is necessary.
|
||||
- No ejecutar `ai-platform run`.
|
||||
- No hacer push.
|
||||
- No tocar assets de armas.
|
||||
- No tocar SVGs.
|
||||
- No modificar imagenes fisicas.
|
||||
- No tocar `ai/system-metrics.md`.
|
||||
- No reactivar Elo/MMR.
|
||||
- No reintroducir Comunidad Hispana #03.
|
||||
- No incluir cambios previos no relacionados.
|
||||
- Mantener compatibilidad con desarrollo local si existe `data-backend-base-url`.
|
||||
- No publicar KPM sin `player_active_seconds` real.
|
||||
|
||||
## Validation
|
||||
|
||||
Before completing the task ensure:
|
||||
|
||||
- `node --check frontend/assets/js/historico-partida.js`
|
||||
- Si se tocan otros JS, `node --check` tambien.
|
||||
- Validar por navegador o inspeccion que el detalle no intenta `127.0.0.1`.
|
||||
- Confirmar que la llamada de detalle usa `/api/historical/matches/detail?server=<server>&match=<match>`.
|
||||
- Validar que no queda texto KPM falso en `historico-partida`.
|
||||
- `git diff --name-only` matches the expected scope, except pre-existing unrelated changes.
|
||||
- Confirmar que no se tocaron backend, assets de armas, SVGs, imagenes fisicas ni `ai/system-metrics.md`.
|
||||
|
||||
## Outcome
|
||||
|
||||
Archivos modificados por esta task:
|
||||
|
||||
- `frontend/historico-partida.html`
|
||||
- `frontend/assets/js/historico-partida.js`
|
||||
- `ai/tasks/done/TASK-222-fix-historical-match-detail-backend-url-and-false-kpm.md`
|
||||
|
||||
Causa exacta corregida:
|
||||
|
||||
- `frontend/historico-partida.html` tenia `data-backend-base-url="http://127.0.0.1:8000"`.
|
||||
- `frontend/assets/js/historico-partida.js` usaba `document.body.dataset.backendBaseUrl || "http://127.0.0.1:8000"`.
|
||||
- En produccion, esa combinacion podia construir llamadas contra el localhost del usuario. La pagina de detalle ya no define localhost en el HTML y el JS resuelve primero `data-backend-base-url` si existe, luego `window.HLL_FRONTEND_CONFIG.backendBaseUrl`, y si no hay configuracion usa URL relativa para `/api`.
|
||||
|
||||
Endpoint de detalle:
|
||||
|
||||
- Se confirmo que `historico-partida.js` mantiene la llamada a `/api/historical/matches/detail?server=<server>&match=<match>`.
|
||||
- El backend local no estaba escuchando en `http://127.0.0.1:8000`, por lo que no se pudo obtener una respuesta real del endpoint local.
|
||||
- No se toco backend porque el fallo confirmado en esta task era frontend y no hubo evidencia de error backend.
|
||||
|
||||
Decision sobre KPM falso:
|
||||
|
||||
- Se elimino KPM del detalle de partida hasta que exista `player_active_seconds` real.
|
||||
- Se quitaron la opcion de orden `KPM`, la cabecera de columna `KPM`, la celda por jugador, el chip del panel ampliado y las funciones `formatKpm`/`getKpmValue`.
|
||||
- `duration_seconds` permanece solo para mostrar la duracion de la partida en el marcador, no para calcular rendimiento por jugador.
|
||||
- No se toco la logica de ranking `Kills/partida` corregida en `TASK-216`.
|
||||
|
||||
Otros fallbacks localhost detectados:
|
||||
|
||||
- Siguen existiendo paginas publicas con `data-backend-base-url="http://127.0.0.1:8000"` o JS que lee ese dataset en `stats`, `historico`, `ranking`, `partida-actual`, `index` y `historico-recent-live`.
|
||||
- No se corrigieron en esta task para no ampliar alcance. `frontend/assets/js/config.js` ya tiene una capa de reescritura defensiva, pero esos puntos quedan como deuda de endurecimiento fuera de esta causa directa.
|
||||
|
||||
Validaciones ejecutadas:
|
||||
|
||||
- `node --check frontend/assets/js/historico-partida.js`
|
||||
- `rg -n "KPM|getKpmValue|formatKpm|kpm|kills_per_minute|player_active_seconds" frontend/historico-partida.html frontend/assets/js/historico-partida.js`
|
||||
- `rg -n "api/historical/matches/detail|server=|match=" frontend/assets/js/historico-partida.js`
|
||||
- `Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:8000/api/historical/matches/detail?server=all-servers&match=__validation__' -TimeoutSec 5`
|
||||
- `git diff --name-only`
|
||||
- `git status --short -- ai/tasks/pending ai/tasks/in-progress ai/tasks/done frontend/historico-partida.html frontend/assets/js/historico-partida.js backend frontend/assets/img/weapons ai/system-metrics.md`
|
||||
|
||||
Resultado de validaciones:
|
||||
|
||||
- `node --check` paso sin errores.
|
||||
- No queda texto ni funcion KPM en `historico-partida.html` ni `historico-partida.js`.
|
||||
- La llamada de detalle conserva el endpoint esperado.
|
||||
- La prueba HTTP local fallo con `No es posible conectar con el servidor remoto`; se documenta como backend local no disponible, no como fallo del endpoint.
|
||||
- El navegador integrado no estaba disponible en esta sesion (`iab` no expuesto), asi que la validacion de red en navegador no pudo ejecutarse. La URL construida queda relativa cuando no hay backend configurado, por lo que ya no apunta a `127.0.0.1`.
|
||||
|
||||
Confirmacion de exclusiones:
|
||||
|
||||
- No se ejecuto `ai-platform run`.
|
||||
- No se hizo push.
|
||||
- No se hizo commit.
|
||||
- No se toco backend.
|
||||
- No se tocaron assets de armas.
|
||||
- No se tocaron SVGs.
|
||||
- No se modificaron imagenes fisicas.
|
||||
- No se toco `ai/system-metrics.md`.
|
||||
- No se reactivo Elo/MMR.
|
||||
- No se reintrodujo Comunidad Hispana #03.
|
||||
- No se incluyeron cambios previos no relacionados.
|
||||
|
||||
## Change Budget
|
||||
|
||||
- Prefer fewer than 5 modified files.
|
||||
- Prefer changes under 200 lines when feasible.
|
||||
- Split the work into follow-up tasks if limits are exceeded.
|
||||
@@ -1,6 +1,5 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const backendBaseUrl =
|
||||
document.body.dataset.backendBaseUrl || "http://127.0.0.1:8000";
|
||||
const backendBaseUrl = resolveBackendBaseUrl();
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const serverSlug = params.get("server") || "";
|
||||
const matchId = params.get("match") || "";
|
||||
@@ -80,12 +79,33 @@ async function loadMatchDetail({ backendBaseUrl, serverSlug, matchId, nodes }) {
|
||||
renderMatchDetail(item, nodes);
|
||||
} catch (error) {
|
||||
nodes.title.textContent = "Detalle no disponible";
|
||||
nodes.summary.textContent = "No se pudo conectar con el backend local.";
|
||||
nodes.summary.textContent = "No se pudo cargar el detalle desde el backend.";
|
||||
nodes.note.textContent = "";
|
||||
setState(nodes.state, "Error al cargar el detalle de la partida.", true);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveBackendBaseUrl() {
|
||||
const bodyBackendBaseUrl = document.body.dataset.backendBaseUrl;
|
||||
if (typeof bodyBackendBaseUrl === "string" && bodyBackendBaseUrl.trim()) {
|
||||
return trimTrailingSlash(bodyBackendBaseUrl);
|
||||
}
|
||||
|
||||
const configuredBackendBaseUrl = window.HLL_FRONTEND_CONFIG?.backendBaseUrl;
|
||||
if (
|
||||
typeof configuredBackendBaseUrl === "string" &&
|
||||
configuredBackendBaseUrl.trim()
|
||||
) {
|
||||
return trimTrailingSlash(configuredBackendBaseUrl);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function trimTrailingSlash(value) {
|
||||
return String(value || "").trim().replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function renderMatchDetail(item, nodes) {
|
||||
const mapName = item.map?.pretty_name || item.map?.name || "Mapa no disponible";
|
||||
const serverName = item.server?.name || item.server?.slug || "Servidor no disponible";
|
||||
@@ -354,10 +374,7 @@ function comparePlayerSortValue(a, b, item, sort) {
|
||||
if (sort === "kd") {
|
||||
return compareNumericStat(getKdRatioValue(a.player), getKdRatioValue(b.player));
|
||||
}
|
||||
return compareNumericStat(
|
||||
getKpmValue(a.player.kills, item.duration_seconds),
|
||||
getKpmValue(b.player.kills, item.duration_seconds),
|
||||
);
|
||||
return compareNumericStat(a.player.kills, b.player.kills);
|
||||
}
|
||||
|
||||
function compareInactivePriority(a, b) {
|
||||
@@ -383,7 +400,6 @@ function renderPlayerRows(player, item, index, inactive = false) {
|
||||
const rowId = `match-player-row-${index}`;
|
||||
const panelId = `match-player-panel-${index}`;
|
||||
const playerName = getPlayerName(player);
|
||||
const kpm = formatKpm(player.kills, item.duration_seconds);
|
||||
return `
|
||||
<tr
|
||||
class="historical-player-row historical-player-row--${team.key} ${inactive ? "is-inactive" : ""}"
|
||||
@@ -410,15 +426,14 @@ function renderPlayerRows(player, item, index, inactive = false) {
|
||||
<td>${escapeHtml(formatOptionalNumber(player.deaths))}</td>
|
||||
<td>${escapeHtml(formatOptionalNumber(player.teamkills))}</td>
|
||||
<td>${escapeHtml(formatKdRatio(player))}</td>
|
||||
<td>${escapeHtml(kpm)}</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="historical-player-detail-row"
|
||||
id="${escapeHtml(panelId)}"
|
||||
aria-labelledby="${escapeHtml(rowId)}"
|
||||
>
|
||||
<td colspan="7">
|
||||
${renderPlayerStatsPanel(player, item, { team, playerName, kpm })}
|
||||
<td colspan="6">
|
||||
${renderPlayerStatsPanel(player, item, { team, playerName })}
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
@@ -510,7 +525,6 @@ function renderPlayerStatsPanel(player, item, context) {
|
||||
${renderPlayerStatChip("Muertes", formatOptionalNumber(player.deaths))}
|
||||
${renderPlayerStatChip("TK", formatOptionalNumber(player.teamkills))}
|
||||
${renderPlayerStatChip("KD", formatKdRatio(player))}
|
||||
${renderPlayerStatChip("KPM", context.kpm)}
|
||||
</div>
|
||||
</div>
|
||||
${renderExternalProfilesSection(player)}
|
||||
@@ -875,23 +889,6 @@ function getKdRatioValue(player) {
|
||||
return deaths > 0 ? kills / deaths : kills;
|
||||
}
|
||||
|
||||
function formatKpm(kills, durationSeconds) {
|
||||
return formatDecimal(getKpmValue(kills, durationSeconds), 2);
|
||||
}
|
||||
|
||||
function getKpmValue(kills, durationSeconds) {
|
||||
const parsedKills = Number(kills);
|
||||
const parsedDurationSeconds = Number(durationSeconds);
|
||||
if (
|
||||
!Number.isFinite(parsedKills) ||
|
||||
!Number.isFinite(parsedDurationSeconds) ||
|
||||
parsedDurationSeconds <= 0
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
return parsedKills / (parsedDurationSeconds / 60);
|
||||
}
|
||||
|
||||
function formatNamedCounts(items) {
|
||||
if (!Array.isArray(items) || items.length === 0) {
|
||||
return "No disponible";
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<link rel="stylesheet" href="./assets/css/historico.css" />
|
||||
<link rel="stylesheet" href="./assets/css/historico-scoreboard-detail.css" />
|
||||
</head>
|
||||
<body data-backend-base-url="http://127.0.0.1:8000">
|
||||
<body>
|
||||
<div class="page-shell historical-shell">
|
||||
<header class="hero historical-hero">
|
||||
<div class="hero__overlay"></div>
|
||||
@@ -127,7 +127,6 @@
|
||||
<option value="deaths">Muertes</option>
|
||||
<option value="teamkills">TK</option>
|
||||
<option value="kd">KD</option>
|
||||
<option value="kpm">KPM</option>
|
||||
<option value="name">Jugador</option>
|
||||
</select>
|
||||
</label>
|
||||
@@ -149,7 +148,6 @@
|
||||
<th>Muertes</th>
|
||||
<th>TK</th>
|
||||
<th>KD</th>
|
||||
<th>KPM</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="match-detail-players-body"></tbody>
|
||||
|
||||
Reference in New Issue
Block a user