Suche ueber Aufgaben
CI / pruefen (pull_request) Canceled after 0s

Zwei Luecken, die im Issue zusammen stehen.

**Im Aufgabendashboard fehlte ein Suchfeld.** Es gab Filter nach Status und
Projekt, aber keine Moeglichkeit, nach einem Wort zu suchen. Das Suchfeld
deckt Titel, Beschreibung, Labels und Projektname ab. Gefiltert wird im
Browser - die Aufgaben liegen dort ohnehin alle vor, es braucht also keine
zusaetzliche Abfrage und es gibt keine Verzoegerung beim Tippen.

Wer sucht, sucht dabei auch in Erledigtem: sonst faende man die Aufgabe von
letzter Woche nicht wieder, obwohl man genau sie meint. Der Schalter
"Erledigte einblenden" gilt nur ohne Suchbegriff.

**Die Volltextsuche kannte nur Notizen.** Task ist jetzt ebenfalls dabei,
nach demselben Muster: deutsche Textsuche, ts_headline fuer den Ausschnitt,
Hervorhebung als [[…]] statt als HTML. Deutsche Stammformen greifen damit
auch hier - "Rechnung" findet "Rechnungen".

Dazu ein Ausdrucksindex Task_fulltext_idx wie Note_fulltext_idx. Der
Ausdruck muss Zeichen fuer Zeichen dem in der Abfrage entsprechen, sonst
greift er nicht; nachgeprueft mit EXPLAIN, der Plan zeigt einen
Bitmap Index Scan darauf. "prisma migrate diff" laesst ihn in Ruhe -
Gegenprobe kommt leer heraus.

Das Antwortfeld heisst weiterhin "hits" und meint die Notizen. Umbenennen
haette nur Aufrufer gebrochen, ohne etwas zu verbessern; Aufgaben kommen als
eigenes Feld dazu. Beide Stellen, die Treffer anzeigen - Projektbrett und
Schnellsuche -, zeigen sie jetzt.

Geprueft: 261 Unit-Tests, 36 E2E-Tests. Die Volltextsuche zusaetzlich gegen
die laufende Anwendung: Treffer in Titel und Beschreibung, Erledigtes
inbegriffen, Stammformen greifen, fremde Konten finden nichts.

Refs #4
This commit is contained in:
2026-08-18 03:39:02 +02:00
parent 0ef0b3d5ca
commit afe57c4428
7 changed files with 333 additions and 25 deletions
+52
View File
@@ -817,6 +817,58 @@ test("Verlauf blaettert per Cursor, ohne Zeilen zu verlieren", async ({ request,
await fremd.dispose();
});
test("Volltextsuche findet auch Aufgaben", async ({ request, playwright }) => {
await login(request, ADMIN);
const projekt = (
await (await request.post("/api/projects", { data: { name: "Suchprojekt" } })).json()
).project;
for (const task of [
{
title: "Dockerfile aufraeumen",
description: "Die Schichten im Container neu ordnen, damit der Bau schneller wird.",
},
{ title: "Rechnungen exportieren", description: "Monatlicher Export als CSV." },
{ title: "Erledigtes mit Container", description: "Auch das soll gefunden werden.", status: "DONE" },
{ title: "Ohne Bezug", description: "Voellig anderer Inhalt." },
]) {
expect((await request.post(`/api/projects/${projekt.id}/tasks`, { data: task })).status()).toBe(
201,
);
}
// Titel und Beschreibung werden durchsucht.
const container = await (await request.get("/api/search?q=Container")).json();
const titel = container.tasks.map((treffer: { title: string }) => treffer.title);
expect(titel).toContain("Dockerfile aufraeumen");
// Erledigtes bleibt drin – wer sucht, sucht oft gerade das Abgehakte.
expect(titel).toContain("Erledigtes mit Container");
expect(titel).not.toContain("Ohne Bezug");
// Deutsche Stammformen: "Rechnung" findet "Rechnungen".
const rechnung = await (await request.get("/api/search?q=Rechnung")).json();
expect(rechnung.tasks.map((t: { title: string }) => t.title)).toContain(
"Rechnungen exportieren",
);
// Das Projekt taucht in projectIds auf, damit das Brett es einblendet.
expect(container.projectIds).toContain(projekt.id);
// Zu kurze Eingaben liefern leere Listen statt Unsinn.
const kurz = await (await request.get("/api/search?q=a")).json();
expect(kurz.tasks).toEqual([]);
expect(kurz.hits).toEqual([]);
// Und ein fremdes Konto findet nichts davon.
const fremd = await playwright.request.newContext({
baseURL: test.info().project.use.baseURL,
});
await login(fremd, ZWEITER);
expect((await (await fremd.get("/api/search?q=Container")).json()).tasks).toHaveLength(0);
await fremd.dispose();
});
test("Der Bildoptimierer ist zu", async ({ playwright }) => {
// /_next/image reicht jedes Bild durch sharp und damit durch libvips, fuer
// das es regelmaessig Schwachstellen gibt. Nebula benutzt next/image