From 6c5e28b05edb629450cd5e1261d0a2586ad47def Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 09:47:31 +0000 Subject: [PATCH] =?UTF-8?q?perf(qlik):=20chemin=20rapide=20select-all=20(s?= =?UTF-8?q?=C3=A9lection=20Article=20Code=20unique)=20derri=C3=A8re=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La sync Qlik itérait mois × lots de 150 articles, en re-sélectionnant les mêmes Article Code à chaque lot. La SelectValues répétée (~22 s × ~120) représentait ~72% du temps total (~60 min pour 12 mois). Ajoute un chemin rapide (flag env QLIK_SELECT_ALL_CODES=1) qui sélectionne TOUS les Article Code une seule fois, puis n'itère que la sélection Date par mois avec un seul cube paginé (Article Code en dimension). Passe de ~120 SelectValues Article Code à 1, et de ~120 cubes à 12 → gain estimé ~60 min → ~5-8 min. Repli automatique et sûr : en cas d'erreur moteur code 15, les résultats partiels et les sélections sont réinitialisés et l'ancien chemin par lots reprend le relais. Flag désactivé par défaut : comportement inchangé tant qu'on n'active pas la variable. À valider sur le Qlik réel. --- src/lib/qlik-playwright.ts | 78 +++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/src/lib/qlik-playwright.ts b/src/lib/qlik-playwright.ts index d779fed..3c254a9 100644 --- a/src/lib/qlik-playwright.ts +++ b/src/lib/qlik-playwright.ts @@ -96,7 +96,11 @@ export async function fetchNetworkMetricsPlaywright( const qlikArticleBatchSize = envNumber("QLIK_ARTICLE_BATCH_SIZE", 150, 1, 500); const csvFallbacks = envNumberCsv("QLIK_ARTICLE_BATCH_FALLBACKS", 1, 500); const qlikArticleBatchFallbacks = uniqueOrdered([qlikArticleBatchSize, ...(csvFallbacks.length ? csvFallbacks : [75, 50]), 75, 50]); - console.log(`[qlik-pw] tuning settle=${qlikSettleMs}ms code15Attempts=${qlikCode15MaxAttempts} batchFallbacks=${qlikArticleBatchFallbacks.join(">")}`); + // Chemin rapide : sélectionner TOUS les Article Code une seule fois, puis ne + // changer que la Date par mois (au lieu de re-sélectionner par lots de 150 chaque + // mois). Repli automatique sur le chemin par lots en cas de code 15. + const qlikSelectAllCodes = ["1", "true", "yes", "on"].includes((process.env.QLIK_SELECT_ALL_CODES ?? "").trim().toLowerCase()); + console.log(`[qlik-pw] tuning settle=${qlikSettleMs}ms code15Attempts=${qlikCode15MaxAttempts} batchFallbacks=${qlikArticleBatchFallbacks.join(">")} selectAllCodes=${qlikSelectAllCodes}`); const browser = await getBrowser(); console.log(`[qlik-pw] chromium lancé`); @@ -137,7 +141,7 @@ export async function fetchNetworkMetricsPlaywright( })); const result = (await page.evaluate( - ({ app, dim, mca, mqte, mnb, mcamag, mmarge, codes, token, monthlyPayload, noDateMode, qlikSettleMs, qlikCode15MaxAttempts, qlikArticleBatchFallbacks }: { + ({ app, dim, mca, mqte, mnb, mcamag, mmarge, codes, token, monthlyPayload, noDateMode, qlikSettleMs, qlikCode15MaxAttempts, qlikArticleBatchFallbacks, selectAllCodes }: { app: string; dim: string; mca: string; mqte: string; mnb: string; mcamag: string; mmarge: string; codes: string[]; token: string; monthlyPayload: Array<{ label: string; dateDebut: string; dateFin: string; serials: number[] }>; @@ -145,6 +149,7 @@ export async function fetchNetworkMetricsPlaywright( qlikSettleMs: number; qlikCode15MaxAttempts: number; qlikArticleBatchFallbacks: number[]; + selectAllCodes: boolean; }) => new Promise((resolve) => { const loc = (window as unknown as { location: Location }).location; @@ -320,6 +325,71 @@ export async function fetchNetworkMetricsPlaywright( } }; + // Chemin rapide (flag QLIK_SELECT_ALL_CODES) : sélectionne TOUS les + // Article Code une seule fois, puis n'itère que la sélection Date par + // mois avec UN cube paginé (au lieu de re-sélectionner les codes par lots + // de 150 à chaque mois — la SelectValues répétée coûte ~22 s × 120). + // Renvoie true si le mois complet a réussi ; false si repli code 15 (les + // résultats partiels et sélections sont réinitialisés avant le chemin par lots). + const trySelectAllCodesPath = async (): Promise => { + const savedLen = out.length; + try { + if (codes.length && fh !== -1) { + await timed("clearCodeAll", "Clear", () => rpc("Clear", {}, fh), {}); + await sleep(qlikSettleMs); + const { value: sel } = await timed("selectCodeAll", "SelectValues", () => rpc("SelectValues", { + qFieldValues: codes.map((c) => ({ qText: c })), + qToggleMode: false, + qSoftLock: true, + }, fh), {}); + console.log("[qlik-pw] select-all — SelectValues Article Code (" + codes.length + " codes) → " + JSON.stringify(sel.qReturn)); + await sleep(qlikSettleMs); + } + const total = monthlyPayload.length; + console.log("[qlik-pw] chemin rapide select-all — " + total + " mois, 1 sélection Article Code globale"); + for (let mi = 0; mi < total; mi++) { + const m = monthlyPayload[mi]; + const prefix = "[qlik-pw] (rapide) mois " + (mi + 1) + "/" + total + " " + m.label + " (" + m.dateDebut + "→" + m.dateFin + ", " + m.serials.length + "j)"; + const monthStart = nowMs(); + const monthTimings: Record = {}; + aggMonths = mi + 1; + let code15Retries = 0; + // Sélection Date du mois uniquement (la sélection Article Code reste active). + const { value: dsel } = await timed("selectDate", "SelectValues", () => rpc("SelectValues", { + qFieldValues: m.serials.map((s) => ({ qNum: s, qText: String(s) })), + qToggleMode: false, + qSoftLock: true, + }, dfh), monthTimings); + console.log(prefix + " — SelectValues Date " + m.serials.length + " serials → " + JSON.stringify(dsel.qReturn)); + await sleep(qlikSettleMs); + // Un seul cube paginé pour tous les articles du mois. + const batchRows: Array> = []; + const { value: got, ms } = await timed("cube", "batch", () => fetchCubeForSelection(batchRows, monthTimings, () => { code15Retries++; }), monthTimings); + out.push(...batchRows); + aggBatches++; + aggCode15Retries += code15Retries; + console.log("[qlik-pw][timing] " + prefix + " — rows=" + got + " cumul=" + out.length + " ms=" + ms + " retries15=" + code15Retries + " timings=" + timingSummary(monthTimings)); + // Clear Date uniquement — on garde la sélection Article Code pour le mois suivant. + if (mi < total - 1) { + try { await timed("clearDate", "Clear", () => rpc("Clear", {}, dfh), monthTimings); } + catch (e) { console.log(prefix + " — Clear Date ignoré: " + String((e as Error)?.message || e)); } + } + console.log("[qlik-pw][timing] " + prefix + " — done ms=" + Math.round(nowMs() - monthStart) + " timings=" + timingSummary(monthTimings)); + } + return true; + } catch (e) { + if (isEngineCode15(e)) { + console.log("[qlik-pw] chemin rapide échoué (code 15) → repli sur le chemin par lots"); + out.length = savedLen; + aggMonths = 0; aggBatches = 0; aggCode15Retries = 0; + try { await rpc("Clear", {}, dfh); if (fh !== -1) await rpc("Clear", {}, fh); } catch { /* noop */ } + await sleep(qlikSettleMs); + return false; + } + throw e; + } + }; + if (noDateMode) { // Aucun filtre Date : extraction en un seul appel (tous les codes d'un coup). // On sélectionne uniquement Article Code ; Date reste libre. @@ -337,6 +407,9 @@ export async function fetchNetworkMetricsPlaywright( aggBatches = 1; aggCode15Retries = code15Retries; console.log("[qlik-pw][timing] noDate rows=" + got + " cumul=" + out.length + " ms=" + ms + " retries15=" + code15Retries + " timings=" + timingSummary(timings)); + } else if (selectAllCodes && (await trySelectAllCodesPath())) { + // Chemin rapide terminé avec succès (voir trySelectAllCodesPath). + // Si false (repli code 15), on tombe dans le chemin par lots ci-dessous. } else { // Itération par mois × lots de codes : pour chaque mois, SelectValues Date // (~30 serials), puis SelectValues Article Code par petits lots. Le cube @@ -454,6 +527,7 @@ export async function fetchNetworkMetricsPlaywright( qlikSettleMs, qlikCode15MaxAttempts, qlikArticleBatchFallbacks, + selectAllCodes: qlikSelectAllCodes, }, )) as InPageResult;