diff --git a/scripts/ship-release.mjs b/scripts/ship-release.mjs index 3e911c4..70fb4e2 100644 --- a/scripts/ship-release.mjs +++ b/scripts/ship-release.mjs @@ -82,7 +82,13 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep const qa = readJsonFromDist(qaReport); const repeat = qaRepeatReport ? readJsonFromDist(qaRepeatReport) : undefined; const missedObjectives = repeat?.missedObjectives ?? qa?.missedObjectives ?? []; + const battleOutcomes = repeat?.battleOutcomes ?? battleOutcomesFromQaReport(qa); const runRows = repeat?.runs ?? []; + const processFailureCount = repeat?.summary?.runsWithProcessFailures ?? 0; + const battleFailureCount = battleOutcomes + .filter((outcome) => outcome.outcome !== 'victory') + .reduce((total, outcome) => total + (outcome.count ?? 1), 0); + const totalMissedObjectives = repeat?.summary?.totalMissedObjectives ?? qa?.summary?.missedObjectiveCount ?? missedObjectives.length; const html = ` @@ -98,7 +104,7 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep h1 { font-size: 28px; } h2 { font-size: 18px; margin-top: 28px; } .meta, .grid, table { margin-top: 16px; } - .meta { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; } + .meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); gap: 10px; } .tile { border: 1px solid #665941; background: #221f18; border-radius: 8px; padding: 12px; } .label { color: #b9ad91; font-size: 12px; } .value { margin-top: 5px; font-size: 18px; font-weight: 700; word-break: break-word; } @@ -108,6 +114,7 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep tr:last-child td { border-bottom: 0; } .ok { color: #a9db8f; } .warn { color: #f3c96b; } + .bad { color: #ff9b86; } a { color: #9cc6ff; } @media (max-width: 820px) { .meta { grid-template-columns: 1fr 1fr; } } @@ -120,6 +127,9 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep ${metricTile('Commit', shortCommit)} ${metricTile('Early QA', `${qa?.summary?.victories ?? 0}/${qa?.battleCount ?? 0} victories`)} ${metricTile('Repeat QA', repeat ? `${repeat.summary.runs} runs` : 'not run')} + ${metricTile('Battle Failures', battleFailureCount)} + ${metricTile('Process Failures', processFailureCount)} + ${metricTile('Objective Misses', totalMissedObjectives)}

Artifacts

@@ -136,6 +146,23 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep +

Repeated Battle Outcomes

+ + + + ${ + battleOutcomes.length > 0 + ? battleOutcomes + .map((outcome) => { + const cssClass = outcome.outcome === 'victory' ? 'ok' : 'bad'; + return ``; + }) + .join('') + : '' + } + +
BattleIdOutcomeCount
${escapeHtml(String(outcome.battleNo))}${escapeHtml(outcome.battleId ?? '')}${escapeHtml(outcome.outcome)}${escapeHtml(String(outcome.count ?? 1))}
No battle outcome data recorded.
+

Repeated Objective Misses

@@ -155,17 +182,20 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep

Repeat Runs

BattleObjectiveMissesRateLatest Reason
- + ${ runRows.length > 0 ? runRows .map( - (run) => - `` + (run) => { + const reportLink = distAssetPath(run.path); + const statusClass = run.exitCode === 0 && run.failures === 0 ? 'ok' : 'bad'; + return ``; + } ) .join('') - : '' + : '' }
RunVictoriesMissed ObjectivesBattles With Misses
RunStatusExitVictoriesBattle FailuresMissed ObjectivesBattles With MissesReport
${escapeHtml(String(run.run))}${escapeHtml(String(run.victories))}${escapeHtml(String(run.missedObjectiveCount))}${escapeHtml((run.battlesWithMissedObjectives ?? []).join(', ') || '-')}
${escapeHtml(String(run.run))}${escapeHtml(run.outcome ?? 'unknown')}${escapeHtml(String(run.exitCode ?? 0))}${escapeHtml(String(run.victories))}${escapeHtml(String(run.failures ?? 0))}${escapeHtml(String(run.missedObjectiveCount))}${escapeHtml((run.battlesWithMissedObjectives ?? []).join(', ') || '-')}${reportLink ? `${escapeHtml(reportLink)}` : '-'}
Repeat QA was not run for this release.
Repeat QA was not run for this release.
@@ -179,6 +209,15 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep console.log(`Wrote dist/${releaseDashboardFile}`); } +function battleOutcomesFromQaReport(report) { + return (report?.results ?? []).map((result) => ({ + battleNo: result.no, + battleId: result.id, + outcome: result.outcome, + count: 1 + })); +} + function readJsonFromDist(fileName) { return JSON.parse(readFileSync(join('dist', fileName), 'utf8')); } @@ -191,6 +230,13 @@ function formatRate(value) { return typeof value === 'number' ? `${Math.round(value * 100)}%` : '-'; } +function distAssetPath(filePath) { + if (!filePath) { + return ''; + } + return String(filePath).replaceAll('\\', '/').replace(/^dist\//, ''); +} + function escapeHtml(value) { return String(value) .replaceAll('&', '&') diff --git a/scripts/verify-public-deploy.mjs b/scripts/verify-public-deploy.mjs index caaab76..77a9765 100644 --- a/scripts/verify-public-deploy.mjs +++ b/scripts/verify-public-deploy.mjs @@ -138,6 +138,9 @@ try { const dashboard = await readTextAsset(targetUrl, releaseManifest.releaseDashboard); assert(dashboard.includes('Heros Web Release QA'), `Expected deployed release dashboard title: ${dashboard.slice(0, 200)}`); assert(dashboard.includes(releaseManifest.shortCommit ?? releaseManifest.commit), `Expected deployed release dashboard commit: ${dashboard.slice(0, 400)}`); + assert(dashboard.includes('Repeated Battle Outcomes'), `Expected deployed release dashboard battle outcomes section: ${dashboard.slice(0, 800)}`); + assert(dashboard.includes('Process Failures'), `Expected deployed release dashboard process failure metric: ${dashboard.slice(0, 800)}`); + assert(dashboard.includes('Repeat Runs'), `Expected deployed release dashboard repeat runs section: ${dashboard.slice(0, 800)}`); console.log(`Verified release dashboard ${releaseManifest.releaseDashboard}`); }