recherche: onglet Console Spark sur GET /api/v1/recherche (alignement)
FlowDeck CI / lint (push) Failing after 2s
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped

This commit is contained in:
2026-10-28 14:44:30 -04:00
parent f708fa77e2
commit d4fef9c1bf
+128 -31
View File
@@ -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 = {