feat(vwb): add dashboard competence testing and health tools
This commit is contained in:
@@ -492,6 +492,9 @@ function renderCompetences(competences) {
|
||||
}
|
||||
|
||||
async function openCompetenceTest(competenceId) {
|
||||
if (!confirmRunDialogReplay(competenceId)) {
|
||||
return;
|
||||
}
|
||||
clearTestPolling();
|
||||
currentTest = { competenceId, replayId: null, lastState: null };
|
||||
document.getElementById('testModal').classList.add('visible');
|
||||
@@ -528,6 +531,24 @@ async function openCompetenceTest(competenceId) {
|
||||
}
|
||||
}
|
||||
|
||||
function confirmRunDialogReplay(competenceId) {
|
||||
if (!mayOpenRunDialog(competenceId)) return true;
|
||||
return window.confirm(
|
||||
"Ce test peut ouvrir Win+R / Exécuter. Si la fenêtre Exécuter est déjà ouverte, le replay peut produire un faux positif. Fermez-la ou vérifiez l'état du poste avant de continuer."
|
||||
);
|
||||
}
|
||||
|
||||
function mayOpenRunDialog(competenceId) {
|
||||
const normalized = String(competenceId || '')
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase();
|
||||
return normalized.includes('win_r')
|
||||
|| normalized.includes('windows_r')
|
||||
|| normalized.includes('executer')
|
||||
|| normalized.includes('run_dialog');
|
||||
}
|
||||
|
||||
function startTestPolling() {
|
||||
clearTestPolling();
|
||||
pollCompetenceTest();
|
||||
@@ -568,6 +589,8 @@ function renderTestState(state) {
|
||||
const remaining = state.actions_remaining ?? '';
|
||||
const failed = state.failed_action || {};
|
||||
const phase = state.pause_phase || failed.phase || inferPausePhase(failed.action_id || '');
|
||||
const stepResults = compactStepResults(state.step_results || state.results || []);
|
||||
const evidenceAvailable = hasReplayEvidence(state, stepResults);
|
||||
const progress = [
|
||||
`Replay: ${state.replay_id || currentTest?.replayId || '-'}`,
|
||||
`Machine: ${state.machine_id || '-'}`,
|
||||
@@ -585,15 +608,20 @@ function renderTestState(state) {
|
||||
pause_phase: phase,
|
||||
message: state.message || state.pause_message,
|
||||
failed_action: failed,
|
||||
results_count: Array.isArray(state.step_results || state.results) ? (state.step_results || state.results).length : 0,
|
||||
results_count: stepResults.length,
|
||||
evidence_available: evidenceAvailable,
|
||||
}, null, 2);
|
||||
|
||||
if (state.status === 'paused_need_help') {
|
||||
const msg = escapeHtml(state.message || state.pause_message || 'Validation humaine requise');
|
||||
if (phase === 'after' || state.verdict_required || failed.verdict_required) {
|
||||
document.getElementById('testStatus').innerHTML = `<div class="alert alert-info">${msg}</div>`;
|
||||
const evidenceWarning = evidenceAvailable
|
||||
? ''
|
||||
: '<div class="alert alert-warning">Verdict valide bloqué : aucune step_results ni evidence replay disponible. Choisir Incertain/Invalide ou relancer avec evidence.</div>';
|
||||
const validDisabled = evidenceAvailable ? '' : 'disabled title="Aucune step_results ni evidence replay disponible"';
|
||||
document.getElementById('testStatus').innerHTML = `<div class="alert alert-info">${msg}</div>${evidenceWarning}`;
|
||||
document.getElementById('testActions').innerHTML = `
|
||||
<button class="action-btn" onclick="submitCompetenceVerdict('valid')">Valide</button>
|
||||
<button class="action-btn" ${validDisabled} onclick="submitCompetenceVerdict('valid')">Valide</button>
|
||||
<button class="secondary-btn" onclick="submitCompetenceVerdict('inconclusive')">Incertain</button>
|
||||
<button class="secondary-btn" onclick="submitCompetenceVerdict('invalid')">Invalide</button>
|
||||
<button class="secondary-btn" onclick="closeTestModal()">Fermer</button>
|
||||
@@ -653,6 +681,10 @@ async function submitCompetenceVerdict(kind) {
|
||||
if (!currentTest?.competenceId || !currentTest?.replayId) return;
|
||||
const state = currentTest.lastState || {};
|
||||
const stepResults = compactStepResults(state.step_results || state.results || []);
|
||||
if (kind === 'valid' && !hasReplayEvidence(state, stepResults)) {
|
||||
document.getElementById('testStatus').innerHTML = '<div class="alert alert-warning">Verdict valide refusé : aucune step_results ni evidence replay disponible.</div>';
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
verdict_id: newPromotionId(),
|
||||
verdict_kind: kind,
|
||||
@@ -710,6 +742,28 @@ function compactStepResults(results) {
|
||||
}));
|
||||
}
|
||||
|
||||
function hasReplayEvidence(state, stepResults) {
|
||||
if (Array.isArray(stepResults) && stepResults.length > 0) return true;
|
||||
return [
|
||||
state.evidence,
|
||||
state.evidence_summary,
|
||||
state.artifacts,
|
||||
state.screenshots,
|
||||
state.last_screenshot,
|
||||
state.screenshot,
|
||||
state.trace_path,
|
||||
state.events,
|
||||
].some(hasMeaningfulEvidenceValue);
|
||||
}
|
||||
|
||||
function hasMeaningfulEvidenceValue(value) {
|
||||
if (value === null || value === undefined) return false;
|
||||
if (Array.isArray(value)) return value.length > 0;
|
||||
if (typeof value === 'object') return Object.keys(value).length > 0;
|
||||
if (typeof value === 'string') return value.trim() !== '';
|
||||
return Boolean(value);
|
||||
}
|
||||
|
||||
function closeTestModal() {
|
||||
clearTestPolling();
|
||||
document.getElementById('testModal').classList.remove('visible');
|
||||
|
||||
Reference in New Issue
Block a user