Viel neues

This commit is contained in:
Sven Steinert
2026-04-30 12:06:00 +02:00
parent 118809bfae
commit fce31ebcd7
1274 changed files with 181255 additions and 0 deletions

314
qa-tool/htdocs/js/flow.js Normal file
View File

@@ -0,0 +1,314 @@
// auto-split module
async function tryLoad(urls) {
for (const u of urls) {
try {
await loadScript(u);
} catch (_) {}
if (window.jspdf && window.jspdf.jsPDF) return true;
}
return !!(window.jspdf && window.jspdf.jsPDF);
}
function go(){
try{ window.focus(); }catch(e){}
setTimeout(function(){ window.print(); }, 120);
}
function applyRun(run) {
template = {
name: run.name || '',
steps: (run.steps || []).map(s => {
const k = s.kind || s.type || 'step';
if (k === 'group') return {
kind: 'group',
title: s.title || ''
};
return {
kind: 'step',
id: s.id,
title: s.title,
expected: s.expected,
required: !!s.required,
status: s.status || '',
comment: s.comment || '',
evidence: s.evidence || ''
};
})
};
if (els.tplName) els.tplName.textContent = run.name || '—';
if (els.module) els.module.value = run.module || '';
if (els.moduleVersion) els.moduleVersion.value = run.module_version || '';
if (els.pbxVersion) els.pbxVersion.value = run.pbx_version || '';
if (els.tester) els.tester.value = run.tester || '';
if (els.docbeeUrl) els.docbeeUrl.value = run.docbee_url || '';
renderSteps(template.steps);
recomputeGroupStyles();
if (els.statusTag) els.statusTag.textContent = 'Lauf geladen';
updateAutosave();
}
function applyTemplateOnly(tplObj) {
template = {
name: tplObj.name || '',
steps: (tplObj.steps || []).map(s => {
const k = s.kind || s.type || 'step';
if (k === 'group') return {
kind: 'group',
title: s.title || ''
};
return {
kind: 'step',
id: s.id || '',
title: s.title || '',
expected: s.expected || '',
required: !!s.required,
status: '',
comment: '',
evidence: ''
};
})
};
if (els.tplName) els.tplName.textContent = template.name || '—';
if (els.module) els.module.value = tplObj.module || '';
if (els.moduleVersion) els.moduleVersion.value = tplObj.module_version || '';
if (els.pbxVersion) els.pbxVersion.value = tplObj.pbx_version || '';
renderSteps(template.steps);
recomputeGroupStyles();
if (els.statusTag) els.statusTag.textContent = 'Template geladen';
updateAutosave();
}
function collectTemplateFromDOM() {
if (!template) return {
name: '',
module: '',
module_version: '',
pbx_version: '',
olm_nummer: '',
steps: []
};
captureEditsIntoTemplate();
renumberSteps();
return {
name: template?.name || els.tplName?.textContent || '',
module: els.module.value,
module_version: els.moduleVersion.value,
pbx_version: els.pbxVersion.value,
olm_nummer: els.olmNummer ? els.olmNummer.value : '',
steps: template.steps.map(s => {
if ((s.kind || 'step') === 'group') return {
type: 'group',
title: s.title
};
return {
type: 'step',
id: s.id,
title: s.title,
expected: s.expected,
required: !!s.required
};
})
};
}
function collectRun() {
if (!template) return null;
captureEditsIntoTemplate();
return {
name: template?.name || '',
module: els.module.value,
module_version: els.moduleVersion.value,
pbx_version: els.pbxVersion.value,
olm_nummer: els.olmNummer ? els.olmNummer.value : '',
tester: els.tester.value,
docbee_url: els.docbeeUrl.value,
ts: new Date().toISOString(),
steps: [...template.steps]
};
}
function appendResultLink(createdJSON) {
let link = null;
try {
const j = JSON.parse(createdJSON || "{}");
link = j?.link || null;
} catch {}
// Ticket-ID aus dem Eingabefeld (falls vorhanden) extrahieren
const ticketId = extractTicketId(els.docbeeUrl?.value || '');
// Ziel-URL bestimmen:
// 1) Falls API bereits einen Pfad liefert (z. B. "ticket/show/123"), normieren.
// 2) Sonst UI-Link über bekannte Struktur /ticket/show/%ID% bauen.
let url = null;
if (link) {
if (/^https?:\/\//i.test(link)) {
url = link; // bereits absolute URL
} else {
url = DOCBEE_UI_BASE.replace(/\/+$/, '') + '/' + String(link).replace(/^\/+/, '');
}
} else if (ticketId) {
url = `${DOCBEE_UI_BASE.replace(/\/+$/,'')}/ticket/show/${ticketId}`;
}
if (!url) return;
const hint = document.createElement('div');
hint.className = 'docbee-hint';
hint.innerHTML = `✅ Angelegt: <a href="${url}" target="_blank" rel="noopener">${url}</a>`;
document.querySelector('.actions')?.appendChild(hint);
}
function formatDocBeeMessage(run) {
const pad = (s, n) => (s || '').length > n ? (s || '').slice(0, n - 1) + '…' : (s || '').padEnd(n, ' ');
const fmtDate = new Date(run.ts).toLocaleString('de-DE');
// Kurz-Summary (nur echte Steps zählen)
const counts = {
pass: 0,
fail: 0,
skip: 0,
blocked: 0
};
(run.steps || []).forEach(s => {
const k = s.kind || s.type || 'step';
if (k !== 'step') return;
if (counts[s.status] !== undefined) counts[s.status]++;
});
const summary = `${counts.pass} | ❌ ${counts.fail} | ⏭️ ${counts.skip} | ⛔ ${counts.blocked}`;
// Kopf + Metadaten (out VOR jeglicher Nutzung initialisieren)
let out = '';
out += `QA REPORT\n`;
out += `===========\n`;
out += `Modul: ${run.module || ''}\n`;
out += `Modul-Version:${run.module_version || ''}\n`;
out += `PBX-Version: ${run.pbx_version || ''}\n`;
out += `Tester: ${run.tester || ''}\n`;
if (run.docbee_url) out += `Ticket: ${run.docbee_url}\n`;
out += `Datum: ${fmtDate}\n\n`;
out += `Übersicht: ${summary}\n\n`;
// Tabelle (monospace-geeignet)
const SEP = '────────────────────────────────────────────────────────────────────────';
out += `${SEP}\n`;
out += `${pad('Schritt', 12)} ${pad('Status', 7)} ${pad('Titel', 52)}\n`;
out += `${SEP}\n`;
(run.steps || []).forEach(s => {
const k = s.kind || s.type || 'step';
if (k === 'group') {
out += `\n## ${s.title || ''}\n\n`;
return;
}
const st = (s.status || '').toUpperCase(); // PASS/FAIL/SKIP/BLOCKED/…
const stShort = st === 'BLOCKED' ? 'BLOCK' : st;
const SMAP = {
pass: '✅',
fail: '❌',
skip: '⏭️',
blocked: '⛔'
};
const stLabel = (SMAP[(s.status || '').toLowerCase()] ?
SMAP[(s.status || '').toLowerCase()] + ' ' :
'') + stShort;
const req = s.required ? '📌 ' : '';
out += `${pad(s.id || '', 12)} ${pad(stLabel, 9)} ${pad(req + (s.title || ''), 50)}\n`;
if (s.comment) out += ` • Kommentar: ${s.comment}\n`;
if (s.evidence) out += ` • Evidenz: ${s.evidence}\n`;
});
out += `${SEP}\n`;
out += `Legende: PASS=✅, FAIL=❌, SKIP=⏭️, BLOCK=⛔\n`;
return out;
}
async function postToDocBee() {
const run = collectRun();
if (!run) return;
if (!checkRequired(run)) return;
const ticketId = extractTicketId(els.docbeeUrl?.value || '');
if (!ticketId) {
alert("Keine Ticket-ID in der DocBee-URL gefunden.");
return;
}
if (!hasToken()) {
alert("Kein API-Token konfiguriert.");
return;
}
// Kommentarinhalt (Markdown)
const content = formatDocBeeMessage(run);
// alten Vorgangs-Status holen (Vorgangs-Status = ticketStatus)
const tGet = await getJSON(`${DOCBEE_BASEURL}/restApi/v1/ticket/${ticketId}?fields=ticketStatus`);
const prevStatusId = tGet?.json?.ticketStatus ?? null;
// Busy-State
els.btnPushDocBee?.setAttribute('disabled', '');
els.btnPushDocBee?.classList.add('is-busy');
try {
// *** Message posten (muss Message sein) ***
const url = `${DOCBEE_BASEURL}/restApi/v1/ticket/${ticketId}/message`;
const rMsg = await postJSON(url, {
content: content,
subject: `QA Report ${ticketId}`,
internal: true,
hidden: false
});
if (rMsg.ok) {
// kurze Wartezeit, dann Status ggf. zurücksetzen (gegen Auto-"Antwort erhalten")
await sleep(800);
if (prevStatusId != null) {
await restoreTicketStatus(ticketId, prevStatusId);
}
appendResultLink(rMsg.text);
alert("Nachricht im Ticket angelegt (Status beibehalten).");
return;
}
if (ENABLE_FALLBACK_NOTE) {
const rNote = await postJSON(`${DOCBEE_BASEURL}/restApi/v1/note`, {
note: {
ticket: {
id: Number(ticketId)
},
subject: `QA Report ${ticketId}`,
text: content,
internal: false
}
});
if (rNote.ok) {
appendResultLink(rNote.text);
alert("Notiz angelegt (Fallback).");
return;
}
alert(`Fehler: MSG ${rMsg.status}, NOTE ${rNote.status}`);
} else {
alert(`Nachricht fehlgeschlagen (Status ${rMsg.status}). Details siehe Konsole.`);
}
} catch (e) {
alert("DocBee-Request fehlgeschlagen: " + String(e));
} finally {
els.btnPushDocBee?.removeAttribute('disabled');
els.btnPushDocBee?.classList.remove('is-busy');
}
}