Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee065978b2 | ||
|
|
71c7113a8c | ||
|
|
c0bf0e8523 | ||
|
|
9ae054d1ba | ||
|
|
984e92b593 | ||
|
|
8ba38a69a4 | ||
|
|
ee477bf2d1 | ||
|
|
ba5f8c8eab | ||
|
|
d4fef9c1bf |
@@ -1,3 +1,7 @@
|
||||
## v7.84.4 (2026-10-28) — Alignement recherche console : onglet Console Spark sur /api/v1/recherche
|
||||
|
||||
- **Recherche (#68)** : l'onglet Console Spark de `/recherche` appelle désormais `GET /api/v1/recherche` (au lieu de `/api/search`) et reconstruit les liens profonds console (`?section=…&objet=…`, `disc=`, `activite=`) — les résultats console réels s'affichent enfin dans la page hôte.
|
||||
|
||||
## v7.84.3 (2026-10-28) — Corrections recherche/alertes : CORS prod+dev, recherche en onglets, alertes en onglets
|
||||
|
||||
- **CORS recherche/alertes (#68/#69)** : allowlist d'origines soeurs prod+dev explicites + credentials, jamais `*` — chaque serveur n'autorisait que l'environnement resolu, d'ou les echecs inter-apps constates.
|
||||
|
||||
@@ -63,7 +63,12 @@ def recherche_page(request: Request, q: str = Query(default="")):
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
bases = draco_urls.toutes()
|
||||
template = ENV.get_template("recherche.html")
|
||||
# Defaut 6 : la base agent est exposee explicitement (data-base-console
|
||||
# dans recherche.html), comme les 3 autres hotes — recherche.js ne doit
|
||||
# plus la deviner (derivation -dev / href d'onglet de barre) pour
|
||||
# l'onglet Console Spark (GET /api/v1/recherche, alignement #36).
|
||||
return HTMLResponse(template.render(
|
||||
request=request, q=q.strip(), user=user,
|
||||
bases={k: bases[k] for k in ("obsigate", "flowdeck", "tiktak", "kanbante")},
|
||||
bases={k: bases[k] for k in ("obsigate", "flowdeck", "tiktak",
|
||||
"kanbante", "agent")},
|
||||
))
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title_prefix %}Rechercher partout{% endblock %}
|
||||
{% block content %}
|
||||
<link rel="stylesheet" href="/static/css/recherche.css">
|
||||
{# Console : pas de data-base-console ici — la route Flowdeck de /recherche
|
||||
n'expose pas la base agent dans son contexte `bases` ; recherche.js
|
||||
dérive donc la base console (hôte -dev -> agent-dev, sinon prod) ou
|
||||
reprend le href d'un onglet de barre console/spark quand il existe.
|
||||
Les 3 autres hôtes rendent data-base-console explicitement. #}
|
||||
{# Versionnes (?v=asset_version) comme les autres feuilles/scripts de
|
||||
base.html : sans version, le service worker (static/sw.js, cache-first
|
||||
des .js/.css) resservait indefiniment les copies pre-#33 de ces deux
|
||||
fichiers aux navigateurs les ayant deja visites — la page paraissait
|
||||
donc « non corrigee » malgre les PR #33/#36. #}
|
||||
<link rel="stylesheet" href="/static/css/recherche.css?v={{ asset_version }}">
|
||||
{# Console : data-base-console rendu explicitement, comme les 3 autres
|
||||
hotes — la route Flowdeck de /recherche expose la base agent dans
|
||||
son contexte `bases` (defaut 6). recherche.js garde ses replis
|
||||
(derivation -dev, href d'onglet de barre) si l'attribut est vide. #}
|
||||
<main class="rp-page" id="recherche" data-host="flowdeck"
|
||||
data-base-obsigate="{{ bases.obsigate }}"
|
||||
data-base-flowdeck="{{ bases.flowdeck }}"
|
||||
data-base-tiktak="{{ bases.tiktak }}"
|
||||
data-base-kanbante="{{ bases.kanbante }}">
|
||||
data-base-kanbante="{{ bases.kanbante }}"
|
||||
data-base-console="{{ bases.agent }}">
|
||||
<h1>Rechercher partout</h1>
|
||||
<p class="rp-hint">Notes ObsiGate, pages Flowdeck, tâches TikTak, cartes Kanbante — dans l'environnement courant (dev reste dev). « / » ou Ctrl+K pour rechercher, ↑/↓ puis Entrée pour ouvrir un résultat. « Tout voir » bascule vers l'onglet de l'application, sans quitter la page.</p>
|
||||
<div class="rp-form">
|
||||
@@ -49,5 +54,5 @@
|
||||
</main>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="/static/js/recherche.js"></script>
|
||||
<script src="/static/js/recherche.js?v={{ asset_version }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
+128
-31
@@ -46,23 +46,35 @@
|
||||
* -> { groups: [{ full_name, rows: [{ issue, column }] }] }
|
||||
* Lien profond : {base}board/<full_name> (la carte n'a pas d'URL propre
|
||||
* adressable — même limite que la page /search native de Kanbante).
|
||||
* - Console Spark : AUCUN endpoint de recherche mergé au moment de
|
||||
* cette livraison (agent-commun : seule la PR #12 est ouverte, autre
|
||||
* programme — ce dépôt n'est pas touché ici). Le code tente quand
|
||||
* même l'appel conventionnel GET {base-console}/api/search?q= (même
|
||||
* convention que les autres hôtes ; chemin À CONFIRMER avec le
|
||||
* travailleur console dès que son endpoint atterrit) et, en cas
|
||||
* d'échec, de HTTP non-OK ou de format non reconnu, affiche
|
||||
* honnêtement « Console Spark : recherche indisponible pour
|
||||
* l'instant » — jamais de faux résultats, jamais de faux 0. La base
|
||||
* console vient, dans l'ordre, de data-base-console rendu par l'hôte,
|
||||
* de window.DRACO_URLS.agent (ObsiGate) / window.DRACO_AGENT_URL
|
||||
* (TikTak), de la dérivation -dev si l'hôte courant est -dev, du
|
||||
* href d'un onglet de barre data-app="console"/"spark" s'il existe,
|
||||
* sinon du défaut prod agent.dracodev.net. L'onglet console n'est
|
||||
* interrogé que lorsqu'il est affiché (ou quand une recherche a lieu
|
||||
* pendant qu'il est affiché) : « Tout » ne le contient pas, pour ne
|
||||
* pas polluer la vue groupée d'un message d'indisponibilité.
|
||||
* - Console Spark : GET {base-console}/api/v1/recherche?q=&limite=
|
||||
* (agent-commun, authentifié par le cookie SSO draco_session) ->
|
||||
* { q, domain, limite_par_type, resultats[], par_type,
|
||||
* non_cherchables[] } ; chaque résultat porte type, id, titre,
|
||||
* extrait, statut, domaine, section, objet et lien (relatif à
|
||||
* l'origine console). q < 2 caractères -> 422. Types cherchables :
|
||||
* discussion, mission, approbation, livrable, activite,
|
||||
* planification, objectif, identite (aucun secret). Liens profonds
|
||||
* (préfixés par la base console) : discussion
|
||||
* /?section=conversation&disc=<id>, mission
|
||||
* /?section=missions&objet=<id>, approbation
|
||||
* /?section=approbations&objet=<id>, objectif
|
||||
* /?section=objectifs&objet=<id>, activite /?activite=<id> ;
|
||||
* livrable /?section=livrables, planification /?section=planifie
|
||||
* et identite /?section=identites ouvrent la vue sans sélection
|
||||
* d'objet. L'onglet appelle avec limite=20 (plafond par type) car
|
||||
* il affiche la liste complète, comme les onglets d'applications.
|
||||
* En cas d'échec, de HTTP non-OK ou de format non reconnu, la
|
||||
* section affiche honnêtement « Console Spark : recherche
|
||||
* indisponible pour l'instant » — jamais de faux résultats,
|
||||
* jamais de faux 0. La base console vient, dans l'ordre, de
|
||||
* data-base-console rendu par l'hôte, de window.DRACO_URLS.agent
|
||||
* (ObsiGate) / window.DRACO_AGENT_URL (TikTak), de la dérivation
|
||||
* -dev si l'hôte courant est -dev, du href d'un onglet de barre
|
||||
* data-app="console"/"spark"/"agent" s'il existe, sinon du défaut
|
||||
* prod agent.dracodev.net. L'onglet console n'est interrogé que
|
||||
* lorsqu'il est affiché (ou quand une recherche a lieu pendant
|
||||
* qu'il est affiché) : « Tout » ne le contient pas, pour ne pas
|
||||
* polluer la vue groupée d'un message d'indisponibilité.
|
||||
*
|
||||
* Résolution des bases sœurs (même ordre que la barre v0.1.6) :
|
||||
* 1. data-base-* rendus par l'hôte (son propre résolveur : explicite >
|
||||
@@ -76,12 +88,14 @@
|
||||
* credentials:"include". Le navigateur envoie alors les cookies de
|
||||
* chaque origine, dont le cookie SSO partagé draco_session
|
||||
* (Domain=.dracodev.net), accepté par les 4 API de recherche
|
||||
* (ObsiGate et TikTak nativement, Flowdeck et Kanbante via #68).
|
||||
* Chaque sœur n'autorise en CORS que les origines sœurs
|
||||
* (GET sur l'endpoint de recherche uniquement, pas de "*").
|
||||
* (ObsiGate et TikTak nativement, Flowdeck et Kanbante via #68)
|
||||
* et par GET /api/v1/recherche de la console (identity() accepte
|
||||
* draco_session). Chaque sœur n'autorise en CORS que les origines
|
||||
* sœurs (GET sur l'endpoint de recherche uniquement, pas de "*") ;
|
||||
* côté console, un middleware borné à /api/v1/recherche autorise
|
||||
* de même les 8 origines hôtes prod ET dev, avec credentials.
|
||||
* Une sœur injoignable / non autorisée ne casse pas les autres :
|
||||
* sa section affiche l'erreur + un lien vers sa recherche native.
|
||||
* Le correctif CORS lui-même est chez un autre travailleur.
|
||||
*/
|
||||
(function (global) {
|
||||
"use strict";
|
||||
@@ -104,7 +118,8 @@
|
||||
};
|
||||
var PROD_AGENT = "https://agent.dracodev.net/";
|
||||
var DEV_AGENT = "https://agent-dev.dracodev.net/";
|
||||
var CONSOLE_SEARCH_PATH = "api/search";
|
||||
var CONSOLE_SEARCH_PATH = "api/v1/recherche";
|
||||
var CONSOLE_SEARCH_LIMIT = 20;
|
||||
var APPS = [
|
||||
{ key: "obsigate", label: "ObsiGate" },
|
||||
{ key: "flowdeck", label: "Flowdeck" },
|
||||
@@ -112,6 +127,24 @@
|
||||
{ key: "kanbante", label: "Kanbante" },
|
||||
];
|
||||
var CONSOLE_APP = { key: "console", label: "Console Spark" };
|
||||
var CONSOLE_TYPE_LABELS = {
|
||||
discussion: "Discussion",
|
||||
mission: "Mission",
|
||||
approbation: "Approbation",
|
||||
livrable: "Livrable",
|
||||
activite: "Activité",
|
||||
planification: "Planification",
|
||||
objectif: "Objectif",
|
||||
identite: "Identité",
|
||||
};
|
||||
/* Lien profond console (relatif à l'origine console) quand le
|
||||
* résultat n'en porte pas : même règle que _lien_recherche
|
||||
* d'agent-commun (section + objet, activite en section=null). */
|
||||
var CONSOLE_VIEW_LINKS = {
|
||||
livrable: "/?section=livrables",
|
||||
planification: "/?section=planifie",
|
||||
identite: "/?section=identites",
|
||||
};
|
||||
var TABS = ["all", "obsigate", "flowdeck", "tiktak", "kanbante", "console"];
|
||||
var COLUMN_LABELS = {
|
||||
backlog: "Backlog",
|
||||
@@ -182,7 +215,8 @@
|
||||
if (!u && isDevHost(opts.host)) u = DEV_AGENT;
|
||||
if (!u)
|
||||
u = cleanUrl(
|
||||
opts.tabHrefs && (opts.tabHrefs.console || opts.tabHrefs.spark)
|
||||
opts.tabHrefs &&
|
||||
(opts.tabHrefs.console || opts.tabHrefs.spark || opts.tabHrefs.agent)
|
||||
);
|
||||
if (!u) u = PROD_AGENT;
|
||||
return withSlash(u);
|
||||
@@ -336,19 +370,71 @@
|
||||
return [];
|
||||
}
|
||||
|
||||
/* Normalisation console, défensive : accepte results/discussions/
|
||||
* items. Un élément sans URL exploitable (http(s), /relatif, #ancre)
|
||||
* garde url=null et sera rendu en <span>, pas en faux lien profond.
|
||||
* Retourne { recognized, items } : recognized=false -> l'appelant
|
||||
* affiche « indisponible », jamais un vide mensonger. */
|
||||
/* Normalisation console : format réel GET /api/v1/recherche,
|
||||
* { resultats: [{ type, id, titre, extrait, statut, domaine,
|
||||
* section, objet, lien }] }. Le lien relatif est préfixé par la
|
||||
* base console ; sans lien, il est reconstruit depuis type/id
|
||||
* (même règle que la console) ; sans lien reconstructible,
|
||||
* url=null et le résultat est rendu en <span>, pas en faux lien.
|
||||
* Les anciennes formes tolérées (results/discussions/items, avec
|
||||
* url/href/link) restent acceptées en repli, pour ne pas casser
|
||||
* un déploiement console antérieur. Retourne { recognized,
|
||||
* items } : recognized=false -> l'appelant affiche
|
||||
* « indisponible », jamais un vide mensonger. */
|
||||
function consoleLinkFor(r) {
|
||||
var lien = r.lien || "";
|
||||
if (lien) return lien;
|
||||
var id = r.id != null ? String(r.id) : "";
|
||||
if (r.type === "discussion")
|
||||
return "/?section=conversation&disc=" + encodeURIComponent(id);
|
||||
if (r.type === "mission")
|
||||
return "/?section=missions&objet=" + encodeURIComponent(id);
|
||||
if (r.type === "approbation")
|
||||
return "/?section=approbations&objet=" + encodeURIComponent(id);
|
||||
if (r.type === "objectif")
|
||||
return "/?section=objectifs&objet=" + encodeURIComponent(id);
|
||||
if (r.type === "activite") return "/?activite=" + encodeURIComponent(id);
|
||||
if (CONSOLE_VIEW_LINKS[r.type]) return CONSOLE_VIEW_LINKS[r.type];
|
||||
if (r.section)
|
||||
return (
|
||||
"/?section=" +
|
||||
encodeURIComponent(r.section) +
|
||||
(r.objet != null
|
||||
? "&objet=" + encodeURIComponent(String(r.objet))
|
||||
: "")
|
||||
);
|
||||
return "";
|
||||
}
|
||||
|
||||
function normalizeConsole(data, base) {
|
||||
data = data || {};
|
||||
if (Array.isArray(data.resultats)) {
|
||||
var items = data.resultats.map(function (r) {
|
||||
r = r || {};
|
||||
var rel = consoleLinkFor(r);
|
||||
var url = null;
|
||||
if (/^https?:\/\//.test(rel)) url = rel;
|
||||
else if (rel.charAt(0) === "/" || rel.charAt(0) === "#")
|
||||
url = base + String(rel).replace(/^\//, "");
|
||||
var label = CONSOLE_TYPE_LABELS[r.type] || r.type || "Console";
|
||||
var context = label;
|
||||
if (r.statut) context += " · " + r.statut;
|
||||
if (r.domaine) context += " · " + r.domaine;
|
||||
return {
|
||||
title: r.titre || r.title || "Sans titre",
|
||||
snippet: snippetOf(r.extrait || r.snippet || ""),
|
||||
context: context,
|
||||
url: url,
|
||||
};
|
||||
});
|
||||
return { recognized: true, items: items };
|
||||
}
|
||||
var list = null;
|
||||
if (Array.isArray(data.results)) list = data.results;
|
||||
else if (Array.isArray(data.discussions)) list = data.discussions;
|
||||
else if (Array.isArray(data.items)) list = data.items;
|
||||
if (!list) return { recognized: false, items: [] };
|
||||
var items = list.map(function (r) {
|
||||
var legacy = list.map(function (r) {
|
||||
r = r || {};
|
||||
var raw = r.url || r.href || r.link || "";
|
||||
var url = null;
|
||||
@@ -361,7 +447,7 @@
|
||||
url: url,
|
||||
};
|
||||
});
|
||||
return { recognized: true, items: items };
|
||||
return { recognized: true, items: legacy };
|
||||
}
|
||||
|
||||
/* Recherche native de l'app (repli quand la section est en erreur). */
|
||||
@@ -408,6 +494,9 @@
|
||||
PROD_AGENT: PROD_AGENT,
|
||||
DEV_AGENT: DEV_AGENT,
|
||||
CONSOLE_SEARCH_PATH: CONSOLE_SEARCH_PATH,
|
||||
CONSOLE_SEARCH_LIMIT: CONSOLE_SEARCH_LIMIT,
|
||||
CONSOLE_TYPE_LABELS: CONSOLE_TYPE_LABELS,
|
||||
consoleLinkFor: consoleLinkFor,
|
||||
PER_APP_LIMIT: PER_APP_LIMIT,
|
||||
FULL_LIMIT: FULL_LIMIT,
|
||||
};
|
||||
@@ -671,12 +760,20 @@
|
||||
};
|
||||
renderSection(sec, states.console, true);
|
||||
renderTabs();
|
||||
var url = consoleBase + CONSOLE_SEARCH_PATH + "?q=" + encodeURIComponent(q);
|
||||
var url =
|
||||
consoleBase +
|
||||
CONSOLE_SEARCH_PATH +
|
||||
"?q=" +
|
||||
encodeURIComponent(q) +
|
||||
"&limite=" +
|
||||
CONSOLE_SEARCH_LIMIT;
|
||||
fetchSection("console", url, function (err, data) {
|
||||
if (myRun !== runId) return;
|
||||
if (err) {
|
||||
var msg;
|
||||
if (err.name === "AbortError") msg = "délai dépassé (8 s)";
|
||||
else if (err.status === 422)
|
||||
msg = "au moins 2 caractères requis";
|
||||
else if (err.status) msg = "HTTP " + err.status;
|
||||
else msg = "injoignable (CORS ou réseau)";
|
||||
states.console = {
|
||||
|
||||
+8
-1
@@ -8,7 +8,14 @@
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
'use strict';
|
||||
|
||||
const CACHE_NAME = 'flowdeck-v8'; // v8 : Alpine CSP build (A20 fini)
|
||||
const CACHE_NAME = 'flowdeck-v9'; // v9 : purge des caches pre-#33/#36 —
|
||||
// recherche.js/recherche.css etaient servis cache-first SANS version
|
||||
// (?v= absent dans recherche.html) : les navigateurs ayant visite
|
||||
// Flowdeck avant les onglets gardaient les copies pre-#33 (JS sans
|
||||
// onglets/compteurs/console, CSS sans .rp-tabs) malgre les deplois
|
||||
// 7.84.3/7.84.4. Le bump purge ces caches a l'activation, et les
|
||||
// liens versionnes (?v=asset_version) font des futurs changements
|
||||
// de nouvelles cles de cache.
|
||||
const DATA_CACHE = 'flowdeck-data-v7';
|
||||
|
||||
// App shell (assets versionnés comme référencés dans les templates).
|
||||
|
||||
@@ -147,4 +147,38 @@ ok(rp.resolveConsoleBase({ host: "tiktak-dev.dracodev.net" }) === "https://agent
|
||||
ok(rp.resolveConsoleBase({ host: "x.example", tabHrefs: { console: "https://console-perso.example/" } }) === "https://console-perso.example/", "console : href onglet barre");
|
||||
ok(rp.resolveConsoleBase({}) === "https://agent.dracodev.net/", "console : défaut prod");
|
||||
|
||||
|
||||
/* ---- Alignement console (agent-commun /api/v1/recherche) ---- */
|
||||
ok(rp.CONSOLE_SEARCH_PATH === "api/v1/recherche", "console : chemin reel /api/v1/recherche");
|
||||
ok(rp.CONSOLE_SEARCH_LIMIT === 20, "console : limite 20 (plafond par type)");
|
||||
let rc = rp.normalizeConsole({ resultats: [
|
||||
{ type: "discussion", id: "c42", titre: "Conv", extrait: "ex", statut: null, domaine: "pro", section: "conversation", objet: "c42", lien: "/?section=conversation&disc=c42" },
|
||||
{ type: "mission", id: "t1", titre: "Mission", extrait: "ex", statut: "paused_budget", domaine: "pro", section: "missions", objet: "t1", lien: "/?section=missions&objet=t1" },
|
||||
{ type: "approbation", id: "a9", titre: "publier", extrait: "ex", statut: "pending", domaine: null, section: "approbations", objet: "a9", lien: "/?section=approbations&objet=a9" },
|
||||
{ type: "objectif", id: "o3", titre: "Obj", extrait: "ex", statut: "actif", domaine: "pro", section: "objectifs", objet: "o3", lien: "/?section=objectifs&objet=o3" },
|
||||
{ type: "activite", id: "a7", titre: "Act", extrait: "ex", statut: "terminee", domaine: "pro", section: null, objet: "a7", lien: "/?activite=a7" },
|
||||
{ type: "livrable", id: "l1", titre: "Liv", extrait: "ex", statut: "terminee", domaine: "pro", section: "livrables", objet: null, lien: "/?section=livrables" },
|
||||
{ type: "planification", id: "p1", titre: "Plan", extrait: "ex", statut: "active", domaine: "pro", section: "planifie", objet: null, lien: "/?section=planifie" },
|
||||
{ type: "identite", id: "i1", titre: "Id", extrait: "ex", statut: "active", domaine: null, section: "identites", objet: null, lien: "/?section=identites" },
|
||||
] }, "https://agent-dev.dracodev.net/");
|
||||
ok(rc.recognized === true && rc.items.length === 8, "console : 8 resultats reconnus");
|
||||
ok(rc.items[0].url === "https://agent-dev.dracodev.net/?section=conversation&disc=c42", "console : lien profond discussion");
|
||||
ok(rc.items[1].url === "https://agent-dev.dracodev.net/?section=missions&objet=t1" && rc.items[1].context === "Mission \u00b7 paused_budget \u00b7 pro", "console : lien mission + contexte statut/domaine");
|
||||
ok(rc.items[2].url === "https://agent-dev.dracodev.net/?section=approbations&objet=a9", "console : lien profond approbation");
|
||||
ok(rc.items[3].url === "https://agent-dev.dracodev.net/?section=objectifs&objet=o3", "console : lien profond objectif");
|
||||
ok(rc.items[4].url === "https://agent-dev.dracodev.net/?activite=a7", "console : lien activite sans section");
|
||||
ok(rc.items[5].url === "https://agent-dev.dracodev.net/?section=livrables", "console : livrable = vue seulement");
|
||||
ok(rc.items[6].url === "https://agent-dev.dracodev.net/?section=planifie", "console : planification = vue seulement");
|
||||
ok(rc.items[7].url === "https://agent-dev.dracodev.net/?section=identites", "console : identite = vue seulement");
|
||||
rc = rp.normalizeConsole({ resultats: [
|
||||
{ type: "mission", id: "t9", titre: "Sans lien", extrait: "", statut: null, domaine: null, section: "missions", objet: "t9" },
|
||||
{ type: "activite", id: "a9", titre: "Act sans lien", extrait: "", statut: null, domaine: null, section: null, objet: "a9" },
|
||||
] }, "https://agent.dracodev.net/");
|
||||
ok(rc.items[0].url === "https://agent.dracodev.net/?section=missions&objet=t9", "console : lien reconstruit sans lien servi");
|
||||
ok(rc.items[1].url === "https://agent.dracodev.net/?activite=a9", "console : lien activite reconstruit");
|
||||
rc = rp.normalizeConsole({ resultats: [] }, "https://agent.dracodev.net/");
|
||||
ok(rc.recognized === true && rc.items.length === 0, "console : resultats vide reconnu (vide honnete, pas indisponible)");
|
||||
ok(rp.consoleLinkFor({ type: "discussion", id: "c1" }) === "/?section=conversation&disc=c1", "consoleLinkFor discussion");
|
||||
ok(rp.resolveConsoleBase({ host: "x.example", tabHrefs: { agent: "https://agent-perso.example/" } }) === "https://agent-perso.example/", "console : href onglet barre agent accepte");
|
||||
|
||||
console.log("recherche.test.js : " + n + " assertions OK");
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
"""Page /recherche Flowdeck : onglets, base console, anti-cache (defaut 6).
|
||||
|
||||
Audit prealable (main 7.84.4, tag v7.84.4 = 984e92b) : la route
|
||||
(app/routers/search.py:51), le gabarit (app/templates/recherche.html),
|
||||
le JS (static/js/recherche.js) et le CSS (static/css/recherche.css)
|
||||
contenaient DEJA la page a onglets des PR #33/#36 — aucune route
|
||||
fantome, aucun bloc `scripts` manquant dans base.html:869, aucune
|
||||
regle CSP ne bloquant le script (script-src 'self'). Le symptome
|
||||
deploie (« pas d'onglets, resultats Flowdeck seuls ») ne s'explique
|
||||
donc PAS par un mauvais gabarit dans main, mais par deux causes
|
||||
reelles, corrigees ici :
|
||||
|
||||
1. Assets recherche servis CACHE-FIRST par le service worker
|
||||
(static/sw.js) SANS version (?v= absent) sous un CACHE_NAME
|
||||
inchange depuis A20 ('flowdeck-v8') : un navigateur ayant visite
|
||||
Flowdeck avant #33 gardait le recherche.js pre-#33 (sans onglets,
|
||||
sans compteurs, sans logique console, « Tout voir » naviguant
|
||||
ailleurs) et le recherche.css pre-#33 (sans .rp-tabs) — la page
|
||||
paraissait donc « non corrigee » malgre les deplois 7.84.3/7.84.4,
|
||||
et le repli networkFirst du HTML vers le cache couvrait aussi le
|
||||
cas reseau lent. Correctif : liens versionnes (?v=asset_version,
|
||||
comme tous les autres assets de base.html) + bump CACHE_NAME
|
||||
(purge a l'activation du nouveau worker).
|
||||
2. Flowdeck etait le seul hote a ne pas rendre data-base-console
|
||||
(son propre commentaire l'admettait) : la base console de l'onglet
|
||||
Console Spark etait devine (derivation -dev / href d'onglet de
|
||||
barre) au lieu d'etre explicite. Correctif : la route expose
|
||||
`bases.agent` et le gabarit le rend en data-base-console.
|
||||
|
||||
La logique d'onglets elle-meme (focus, compteurs, « Tout voir » sur
|
||||
place, etats par application, liens -dev, console sur
|
||||
GET /api/v1/recherche) est deja couverte par tests/recherche.test.js
|
||||
(suite Node, lancee en sandbox) ; ce fichier-ci verrouille le
|
||||
branchement gabarit/route/service worker, sans serveur ni base.
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
ONGLETS_ATTENDUS = ["all", "obsigate", "flowdeck", "tiktak", "kanbante",
|
||||
"console"]
|
||||
|
||||
|
||||
def test_gabarit_recherche_onglets_complets():
|
||||
gabarit = (ROOT / "app/templates/recherche.html").read_text()
|
||||
trouves = re.findall(r'data-rp-tab="([^"]+)"', gabarit)
|
||||
assert trouves == ONGLETS_ATTENDUS, trouves
|
||||
for section in ("obsigate", "flowdeck", "tiktak", "kanbante", "console"):
|
||||
assert f'data-rp-section="{section}"' in gabarit
|
||||
assert 'id="rp-q"' in gabarit and "autofocus" in gabarit
|
||||
|
||||
|
||||
def test_gabarit_recherche_base_console_explicite():
|
||||
gabarit = (ROOT / "app/templates/recherche.html").read_text()
|
||||
assert 'data-base-console="{{ bases.agent }}"' in gabarit
|
||||
|
||||
|
||||
def test_gabarit_recherche_assets_versionnes():
|
||||
"""Sans ?v=, le SW cache-first ressert les copies pre-#33."""
|
||||
gabarit = (ROOT / "app/templates/recherche.html").read_text()
|
||||
assert "/static/css/recherche.css?v={{ asset_version }}" in gabarit
|
||||
assert "/static/js/recherche.js?v={{ asset_version }}" in gabarit
|
||||
|
||||
|
||||
def test_route_recherche_expose_base_agent():
|
||||
route = (ROOT / "app/routers/search.py").read_text()
|
||||
assert '"agent"' in route or "'agent'" in route
|
||||
assert "bases.agent" in (ROOT / "app/templates/recherche.html").read_text()
|
||||
|
||||
|
||||
def test_service_worker_purge_anciens_caches():
|
||||
"""Le bump de CACHE_NAME purge les caches pre-#33 a l'activation."""
|
||||
sw = (ROOT / "static/sw.js").read_text()
|
||||
assert "const CACHE_NAME = 'flowdeck-v9'" in sw
|
||||
assert "flowdeck-v8" not in sw
|
||||
assert "cache-first" in sw or "cacheFirst" in sw
|
||||
|
||||
|
||||
def test_recherche_js_branche_console_v1():
|
||||
"""Rappel : l'onglet console appelle GET /api/v1/recherche (cf. #36)."""
|
||||
js = (ROOT / "static/js/recherche.js").read_text()
|
||||
assert 'CONSOLE_SEARCH_PATH = "api/v1/recherche"' in js
|
||||
assert "CONSOLE_SEARCH_LIMIT = 20" in js
|
||||
Reference in New Issue
Block a user