Improve release QA dashboard diagnostics
This commit is contained in:
@@ -82,7 +82,13 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep
|
|||||||
const qa = readJsonFromDist(qaReport);
|
const qa = readJsonFromDist(qaReport);
|
||||||
const repeat = qaRepeatReport ? readJsonFromDist(qaRepeatReport) : undefined;
|
const repeat = qaRepeatReport ? readJsonFromDist(qaRepeatReport) : undefined;
|
||||||
const missedObjectives = repeat?.missedObjectives ?? qa?.missedObjectives ?? [];
|
const missedObjectives = repeat?.missedObjectives ?? qa?.missedObjectives ?? [];
|
||||||
|
const battleOutcomes = repeat?.battleOutcomes ?? battleOutcomesFromQaReport(qa);
|
||||||
const runRows = repeat?.runs ?? [];
|
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 = `<!doctype html>
|
const html = `<!doctype html>
|
||||||
<html lang="ko">
|
<html lang="ko">
|
||||||
@@ -98,7 +104,7 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep
|
|||||||
h1 { font-size: 28px; }
|
h1 { font-size: 28px; }
|
||||||
h2 { font-size: 18px; margin-top: 28px; }
|
h2 { font-size: 18px; margin-top: 28px; }
|
||||||
.meta, .grid, table { margin-top: 16px; }
|
.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; }
|
.tile { border: 1px solid #665941; background: #221f18; border-radius: 8px; padding: 12px; }
|
||||||
.label { color: #b9ad91; font-size: 12px; }
|
.label { color: #b9ad91; font-size: 12px; }
|
||||||
.value { margin-top: 5px; font-size: 18px; font-weight: 700; word-break: break-word; }
|
.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; }
|
tr:last-child td { border-bottom: 0; }
|
||||||
.ok { color: #a9db8f; }
|
.ok { color: #a9db8f; }
|
||||||
.warn { color: #f3c96b; }
|
.warn { color: #f3c96b; }
|
||||||
|
.bad { color: #ff9b86; }
|
||||||
a { color: #9cc6ff; }
|
a { color: #9cc6ff; }
|
||||||
@media (max-width: 820px) { .meta { grid-template-columns: 1fr 1fr; } }
|
@media (max-width: 820px) { .meta { grid-template-columns: 1fr 1fr; } }
|
||||||
</style>
|
</style>
|
||||||
@@ -120,6 +127,9 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep
|
|||||||
${metricTile('Commit', shortCommit)}
|
${metricTile('Commit', shortCommit)}
|
||||||
${metricTile('Early QA', `${qa?.summary?.victories ?? 0}/${qa?.battleCount ?? 0} victories`)}
|
${metricTile('Early QA', `${qa?.summary?.victories ?? 0}/${qa?.battleCount ?? 0} victories`)}
|
||||||
${metricTile('Repeat QA', repeat ? `${repeat.summary.runs} runs` : 'not run')}
|
${metricTile('Repeat QA', repeat ? `${repeat.summary.runs} runs` : 'not run')}
|
||||||
|
${metricTile('Battle Failures', battleFailureCount)}
|
||||||
|
${metricTile('Process Failures', processFailureCount)}
|
||||||
|
${metricTile('Objective Misses', totalMissedObjectives)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<h2>Artifacts</h2>
|
<h2>Artifacts</h2>
|
||||||
@@ -136,6 +146,23 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<h2>Repeated Battle Outcomes</h2>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Battle</th><th>Id</th><th>Outcome</th><th>Count</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
${
|
||||||
|
battleOutcomes.length > 0
|
||||||
|
? battleOutcomes
|
||||||
|
.map((outcome) => {
|
||||||
|
const cssClass = outcome.outcome === 'victory' ? 'ok' : 'bad';
|
||||||
|
return `<tr><td>${escapeHtml(String(outcome.battleNo))}</td><td>${escapeHtml(outcome.battleId ?? '')}</td><td class="${cssClass}">${escapeHtml(outcome.outcome)}</td><td>${escapeHtml(String(outcome.count ?? 1))}</td></tr>`;
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
: '<tr><td colspan="4">No battle outcome data recorded.</td></tr>'
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<h2>Repeated Objective Misses</h2>
|
<h2>Repeated Objective Misses</h2>
|
||||||
<table>
|
<table>
|
||||||
<thead><tr><th>Battle</th><th>Objective</th><th>Misses</th><th>Rate</th><th>Latest Reason</th></tr></thead>
|
<thead><tr><th>Battle</th><th>Objective</th><th>Misses</th><th>Rate</th><th>Latest Reason</th></tr></thead>
|
||||||
@@ -155,17 +182,20 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep
|
|||||||
|
|
||||||
<h2>Repeat Runs</h2>
|
<h2>Repeat Runs</h2>
|
||||||
<table>
|
<table>
|
||||||
<thead><tr><th>Run</th><th>Victories</th><th>Missed Objectives</th><th>Battles With Misses</th></tr></thead>
|
<thead><tr><th>Run</th><th>Status</th><th>Exit</th><th>Victories</th><th>Battle Failures</th><th>Missed Objectives</th><th>Battles With Misses</th><th>Report</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
${
|
${
|
||||||
runRows.length > 0
|
runRows.length > 0
|
||||||
? runRows
|
? runRows
|
||||||
.map(
|
.map(
|
||||||
(run) =>
|
(run) => {
|
||||||
`<tr><td>${escapeHtml(String(run.run))}</td><td>${escapeHtml(String(run.victories))}</td><td>${escapeHtml(String(run.missedObjectiveCount))}</td><td>${escapeHtml((run.battlesWithMissedObjectives ?? []).join(', ') || '-')}</td></tr>`
|
const reportLink = distAssetPath(run.path);
|
||||||
|
const statusClass = run.exitCode === 0 && run.failures === 0 ? 'ok' : 'bad';
|
||||||
|
return `<tr><td>${escapeHtml(String(run.run))}</td><td class="${statusClass}">${escapeHtml(run.outcome ?? 'unknown')}</td><td>${escapeHtml(String(run.exitCode ?? 0))}</td><td>${escapeHtml(String(run.victories))}</td><td>${escapeHtml(String(run.failures ?? 0))}</td><td>${escapeHtml(String(run.missedObjectiveCount))}</td><td>${escapeHtml((run.battlesWithMissedObjectives ?? []).join(', ') || '-')}</td><td>${reportLink ? `<a href="${escapeAttribute(reportLink)}">${escapeHtml(reportLink)}</a>` : '-'}</td></tr>`;
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.join('')
|
.join('')
|
||||||
: '<tr><td colspan="4">Repeat QA was not run for this release.</td></tr>'
|
: '<tr><td colspan="8">Repeat QA was not run for this release.</td></tr>'
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -179,6 +209,15 @@ function writeReleaseDashboard({ qaVersion, commit, shortCommit, qaReport, qaRep
|
|||||||
console.log(`Wrote dist/${releaseDashboardFile}`);
|
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) {
|
function readJsonFromDist(fileName) {
|
||||||
return JSON.parse(readFileSync(join('dist', fileName), 'utf8'));
|
return JSON.parse(readFileSync(join('dist', fileName), 'utf8'));
|
||||||
}
|
}
|
||||||
@@ -191,6 +230,13 @@ function formatRate(value) {
|
|||||||
return typeof value === 'number' ? `${Math.round(value * 100)}%` : '-';
|
return typeof value === 'number' ? `${Math.round(value * 100)}%` : '-';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function distAssetPath(filePath) {
|
||||||
|
if (!filePath) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return String(filePath).replaceAll('\\', '/').replace(/^dist\//, '');
|
||||||
|
}
|
||||||
|
|
||||||
function escapeHtml(value) {
|
function escapeHtml(value) {
|
||||||
return String(value)
|
return String(value)
|
||||||
.replaceAll('&', '&')
|
.replaceAll('&', '&')
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ try {
|
|||||||
const dashboard = await readTextAsset(targetUrl, releaseManifest.releaseDashboard);
|
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('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(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}`);
|
console.log(`Verified release dashboard ${releaseManifest.releaseDashboard}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user