feat(qlik): add step-by-step logging to sync extraction

Logs each stage (PG codes -> NTLM session -> chromium -> csrf token
-> in-page ws -> OpenDoc -> SelectValues -> cube size -> rows) so a
hanging sync reveals where it stalls. Forwards in-page browser console
to the node logger.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 4.8 committed 2026-06-21 08:14:12 +02:00
1 parent 6cdb845a1f
commit d54c3ae520
2 files changed
+16 -1

No files matched your search

+4
View File
@@ -33,6 +33,7 @@ export async function POST(req: NextRequest) {
.filter(Boolean),
),
];
console.log(`[api/qlik/sync] fournisseur=${fournisseur} — ${articles.length} articles, ${codes.length} codes centraux uniques. Échantillon: ${JSON.stringify(codes.slice(0, 5))}`);
if (codes.length === 0) {
return NextResponse.json({
success: true,
@@ -44,8 +45,11 @@ export async function POST(req: NextRequest) {
});
}
console.log(`[api/qlik/sync] → lancement extraction Qlik pour ${codes.length} codes…`);
const metrics = await fetchNetworkMetricsPlaywright(codes);
console.log(`[api/qlik/sync] ← Qlik a renvoyé ${metrics.size} produits réseau`);
const count = await upsertNetworkMetrics([...metrics.values()]);
console.log(`[api/qlik/sync] ${count} lignes upsert dans le cache`);
return NextResponse.json({
success: true,
fournisseur,
+12 -1
View File
@@ -39,11 +39,14 @@ export async function fetchNetworkMetricsPlaywright(
if (!cfg.appNetwork) throw new Error("[qlik-pw] QLIK_APP_NETWORK manquant");
if (!cfg.user || !cfg.password) throw new Error("[qlik-pw] identifiants Qlik manquants");
console.log(`[qlik-pw] host=${cfg.host} app=${cfg.appNetwork} — ${(codeCentraux ?? []).length} codes à sélectionner`);
const sess = await qlikNtlmSession(cfg);
console.log(`[qlik-pw] session NTLM OK, cookie=${sess.cookie.split("=")[0]}=…`);
const [cookieName, ...rest] = sess.cookie.split("=");
const cookieValue = rest.join("=");
const browser = await getBrowser();
console.log(`[qlik-pw] chromium lancé`);
const ctx = await browser.newContext({
httpCredentials: { username: cfg.user, password: cfg.password, origin: `https://${cfg.host}` },
ignoreHTTPSErrors: true,
@@ -52,6 +55,8 @@ export async function fetchNetworkMetricsPlaywright(
try {
const page = await ctx.newPage();
page.on("console", (msg) => console.log(`[qlik-pw][page] ${msg.text()}`));
page.on("pageerror", (e) => console.log(`[qlik-pw][pageerror] ${e.message}`));
// Capture le qlik-csrf-token depuis le websocket que le client Qlik ouvre
let csrfToken: string | null = null;
@@ -64,8 +69,10 @@ export async function fetchNetworkMetricsPlaywright(
await page.goto(`https://${cfg.host}/sense/app/${cfg.appNetwork}`, { waitUntil: "domcontentloaded", timeout: cfg.timeoutMs })
.catch(() => { /* le client ouvre son ws ensuite */ });
console.log(`[qlik-pw] page chargée, attente du qlik-csrf-token…`);
await Promise.race([tokenReady, page.waitForTimeout(15000)]);
if (!csrfToken) throw new Error("[qlik-pw] qlik-csrf-token introuvable (client Qlik non chargé ?)");
console.log(`[qlik-pw] csrf-token capturé, ouverture du websocket Engine in-page…`);
const result = (await page.evaluate(
({ app, dim, mca, mqte, mnb, codes, token }: { app: string; dim: string; mca: string; mqte: string; mnb: string; codes: string[]; token: string }) =>
@@ -92,13 +99,16 @@ export async function fetchNetworkMetricsPlaywright(
(async () => {
try {
await ready;
console.log("ws connecté (OnConnected)");
const open = await rpc("OpenDoc", { qDocName: app });
const doc = (open.qReturn as { qHandle: number }).qHandle;
console.log("OpenDoc OK handle=" + doc);
// Sélection des codes du fournisseur (sinon ~1,2M lignes)
if (codes.length) {
const gf = await rpc("GetField", { qFieldName: "Article Code" }, doc);
const fh = (gf.qReturn as { qHandle: number }).qHandle;
await rpc("SelectValues", { qFieldValues: codes.map((c) => ({ qText: c })), qToggleMode: false, qSoftLock: true }, fh);
const sel = await rpc("SelectValues", { qFieldValues: codes.map((c) => ({ qText: c })), qToggleMode: false, qSoftLock: true }, fh);
console.log("SelectValues " + codes.length + " codes → " + JSON.stringify(sel.qReturn));
}
const obj = await rpc("CreateSessionObject", { qProp: { qInfo: { qType: "cf-net" }, qHyperCubeDef: {
qDimensions: [{ qLibraryId: dim }],
@@ -108,6 +118,7 @@ export async function fetchNetworkMetricsPlaywright(
const oh = (obj.qReturn as { qHandle: number }).qHandle;
const layout = await rpc("GetLayout", {}, oh);
const size = ((layout.qLayout as { qHyperCube: { qSize: { qcy: number } } }).qHyperCube).qSize.qcy;
console.log("cube qSize.qcy=" + size + " lignes");
const out: Array<Array<string | number>> = [];
const PAGE = 2500;
for (let top = 0; top < size; top += PAGE) {