Viel neues
This commit is contained in:
314
qa-tool/htdocs/js/flow.js
Normal file
314
qa-tool/htdocs/js/flow.js
Normal 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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user