Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4450c5f63 | ||
|
|
669b165785 |
@@ -194,6 +194,27 @@ dem nächsten Agenten das Parsen des Stacktrace.
|
||||
Schweregrade: `idea` (Gedanke für später), `wishlist` (Backlog),
|
||||
`low`, `medium`, `high`, `critical`.
|
||||
|
||||
### Was ein `feature_request` ist — und was nicht
|
||||
|
||||
`type: "feature_request"` ist ein **Vorschlag an ein anderes Projekt**: Dir
|
||||
fehlt bei der Arbeit etwas, das dort gebaut werden müsste, damit die
|
||||
Zusammenarbeit besser wird. Beispiel: Du arbeitest an PolyTrader, brauchst zum
|
||||
Einrichten die Besitzerart deines Tokens und stellst fest, dass das
|
||||
Deployment Center sie nirgends herausgibt — also schlägst du unter
|
||||
`deploymentcenter` einen Endpunkt dafür vor.
|
||||
|
||||
**Nicht dafür gedacht ist die eigene Planung.** Was in der Roadmap deines
|
||||
Projekts steht, ist kein Wunsch an jemanden, sondern dein Vorhaben. Es gehört
|
||||
ebenfalls in den Tracker — aber als Roadmap-Eintrag, also mit einem
|
||||
Themenbereich (`epic`), siehe [ROADMAP_MIGRATION.md](./ROADMAP_MIGRATION.md).
|
||||
Die Bugliste zeigt Roadmap-Einträge ab Werk nicht mehr an; sie stehen im
|
||||
Roadmap-Modul.
|
||||
|
||||
> Der Unterschied ist heute nicht am `type` ablesbar — `type` kennt nur `bug`
|
||||
> und `feature_request`, und ein Roadmap-Eintrag muss sich deshalb als Wunsch
|
||||
> ausgeben. Unterschieden wird am Themenbereich. Dass das zu eng ist, ist als
|
||||
> Item #285 gemeldet.
|
||||
|
||||
### Laufzeitfehler automatisch melden
|
||||
Für den globalen Exception-Handler einer Anwendung gibt es einen schlankeren
|
||||
Eingang — Titel und Dringlichkeit leitet der Server ab:
|
||||
|
||||
@@ -126,6 +126,25 @@ angepasst.
|
||||
|
||||
Jedes Vorhaben wird ein Item. Mit MCP-Adapter:
|
||||
|
||||
> ⚠️ **`type: "feature_request"` ist hier ein Behelf, keine Aussage.** `type`
|
||||
> kennt nur `bug` und `feature_request` — ein Vorhaben muss sich deshalb als
|
||||
> Wunsch ausgeben, obwohl es die eigene Planung ist und kein Vorschlag an
|
||||
> jemanden. Entscheidend ist Schritt 4: **Erst der Themenbereich macht das Item
|
||||
> zum Roadmap-Eintrag**, und erst dann verschwindet es aus der Bugliste und
|
||||
> erscheint im Roadmap-Modul.
|
||||
>
|
||||
> Wer die Übernahme nach Schritt 3 abbricht, hinterlässt Dutzende
|
||||
> vermeintlicher Feature Requests im Eingang des Projekts — **Schritt 3 und 4
|
||||
> gehören zusammen.**
|
||||
>
|
||||
> Bei der ersten Übernahme (August 2026, vier Projekte) ist das gutgegangen:
|
||||
> 217 von 229 Einträgen haben ihren Themenbereich bekommen. Auffällig wurde
|
||||
> trotzdem etwas anderes — die Bugliste zeigte Roadmap-Einträge damals noch
|
||||
> mit an, und die zwölf echten Vorschläge lagen unter der Planung begraben.
|
||||
> Seit 2.7.1 blendet die Bugliste sie ab Werk aus.
|
||||
>
|
||||
> Dass der `type` zu eng ist, ist als Item #285 gemeldet.
|
||||
|
||||
```
|
||||
dc_report(title: "Symbol-Upload und dc_symbolicate",
|
||||
type: "feature_request",
|
||||
|
||||
@@ -73,7 +73,7 @@ $spec = [
|
||||
'properties' => [
|
||||
'id' => ['type' => 'integer'],
|
||||
'project_slug' => ['type' => 'string'],
|
||||
'type' => ['type' => 'string', 'enum' => BugRepo::TYPES],
|
||||
'type' => ['type' => 'string', 'enum' => BugRepo::TYPES, 'description' => 'feature_request ist ein Vorschlag an ein FREMDES Projekt - etwas, das dort gebaut werden muesste, damit die Zusammenarbeit besser wird. Die eigene Planung gehoert nicht hierher: Sie wird zum Roadmap-Eintrag, sobald das Item einen Themenbereich (epic) hat, und erscheint dann im Roadmap-Modul statt in der Bugliste.'],
|
||||
'title' => ['type' => 'string'],
|
||||
'description' => ['type' => 'string', 'nullable' => true],
|
||||
'error_message' => ['type' => 'string', 'nullable' => true],
|
||||
|
||||
@@ -165,7 +165,7 @@ curl -X POST https://dc.mhdf.de/api/bugtracker/v1/report \
|
||||
|---|---|---|
|
||||
| `title` | ja | Kurze Beschreibung, max. 255 Zeichen |
|
||||
| `project_slug` | empfohlen | Aus der Projektliste; Vorgabe `default` |
|
||||
| `type` | nein | `bug` (Vorgabe) oder `feature_request` |
|
||||
| `type` | nein | `bug` (Vorgabe) oder `feature_request`. Ein Feature Request ist ein **Vorschlag an ein anderes Projekt** — nicht die eigene Planung; die gehört als Roadmap-Eintrag in den Tracker, also mit Themenbereich |
|
||||
| `severity` | nein | `idea`, `wishlist`, `low`, `medium` (Vorgabe), `high`, `critical` |
|
||||
| `environment` | nein | `production` (Vorgabe), `development`, `staging`, `testing` |
|
||||
| `client_ref` | empfohlen | Idempotenz-Schlüssel, alternativ Header `Idempotency-Key` |
|
||||
|
||||
@@ -62,6 +62,30 @@
|
||||
"breaking": false,
|
||||
"title": "Der Parameter search ist jetzt beschrieben",
|
||||
"text": "action=list kennt search seit jeher, in der OpenAPI-Beschreibung stand er nicht. Nachgetragen, mitsamt dem Verhalten bei Zahlen."
|
||||
},
|
||||
{
|
||||
"area": "webui",
|
||||
"breaking": false,
|
||||
"title": "Statusfilter als Mehrfachauswahl, mit Vorgabe auf das Laufende",
|
||||
"text": "Vorher liess sich genau eine Lage waehlen; wer \"offen und in Nachpruefung\" sehen wollte, brauchte eine vorgefertigte Kombination - stand sie nicht in der Liste, ging es nicht. Jetzt Ankreuzfelder, gruppiert nach Laufend / Verlangt Aufmerksamkeit / Abgeschlossen. Angehakt ist ab Werk, was laeuft oder Aufmerksamkeit verlangt (open, planned, in_progress, triage_external, awaiting_verification, in_verification, fix_insufficient); Erledigtes, Geschlossenes und Abgelehntes bleibt draussen - es ist der groesste Teil des Bestandes und verdeckt sonst das, was noch zu tun ist. \"deferred\" fehlt mit Absicht: Der Sinn dieses Status ist \"nicht jetzt\". Kein Haken bedeutet keine Einschraenkung; das versteckte Feld im Formular macht \"alle Haken entfernt\" von \"erster Aufruf\" unterscheidbar, denn HTML sendet fuer leere Ankreuzfelder nichts."
|
||||
},
|
||||
{
|
||||
"area": "webui",
|
||||
"breaking": false,
|
||||
"title": "Die Suche sagt, wenn ein Filter Treffer verschluckt",
|
||||
"text": "Eine Suche, die schweigt, obwohl sie etwas gefunden hat, ist schlimmer als gar keine Suche: Wer \"288\" eingibt und nichts bekommt, schliesst daraus, es gebe den Eintrag nicht - dabei ist er nur erledigt und deshalb ausgeblendet. Bei aktiver Suche wird jetzt zusaetzlich gezaehlt, wie viele Treffer die aktuellen Einschraenkungen wegnehmen, und das Ergebnis steht als Hinweis ueber der Liste, mit einem Verweis \"Ueberall suchen\". Die zweite Abfrage laeuft nur bei aktiver Suche und holt eine einzige Zeile."
|
||||
},
|
||||
{
|
||||
"area": "webui",
|
||||
"breaking": false,
|
||||
"title": "Roadmap-Eintraege stehen nicht mehr in der Bugliste",
|
||||
"text": "Seit 2.7.0 IST ein Roadmap-Eintrag ein bugtracker_item - richtig so, es gibt keine zweite Ablage. Nur landet damit die gesamte Planung aller Projekte in derselben Liste wie die Meldungen: Nach der Uebernahme der Projekt-Roadmaps waren 217 von 229 Feature Requests Roadmap-Eintraege, und die zwoelf Vorschlaege, um die es in dieser Liste geht, lagen darunter begraben. Die Bugliste zeigt jetzt ab Werk nur, was NICHT auf der Roadmap steht (Kennzeichen ist unveraendert der Themenbereich). Sichtbar bleibt beides, nur an getrennten Orten; der neue Filter holt die Planung jederzeit wieder herein, und die Suche weist auf verdeckte Treffer hin."
|
||||
},
|
||||
{
|
||||
"area": "docs",
|
||||
"breaking": false,
|
||||
"title": "Was ein Feature Request ist - und was nicht",
|
||||
"text": "Ein feature_request ist ein Vorschlag an ein FREMDES Projekt: etwas, das dort gebaut werden muesste, damit die Zusammenarbeit besser wird. Die eigene Planung ist es nicht - die gehoert als Roadmap-Eintrag in den Tracker, also mit Themenbereich. Der Unterschied war nirgends aufgeschrieben und ist am type auch nicht ablesbar, weil der nur bug und feature_request kennt; unterschieden wird am Themenbereich. Steht jetzt in der Agent-Vorlage, im Agenten-Handbuch und in der OpenAPI-Beschreibung. ROADMAP_MIGRATION.md warnt zusaetzlich, dass Schritt 3 und 4 zusammengehoeren: Wer nach dem Anlegen abbricht, hinterlaesst Dutzende vermeintlicher Feature Requests im Eingang des Projekts."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+283
-27
@@ -1897,18 +1897,101 @@ $coreAllTokens = $coreTokenManager->getAllTokens();
|
||||
$btPerPage = 50;
|
||||
$btPage = max(1, (int)($_GET['bt_page'] ?? 1));
|
||||
|
||||
/**
|
||||
* Status, die beim ersten Aufruf angehakt sind.
|
||||
*
|
||||
* Alles, was gerade laeuft oder Aufmerksamkeit verlangt. Erledigtes,
|
||||
* Geschlossenes und Abgelehntes bleibt draussen - es ist der weitaus groesste
|
||||
* Teil des Bestandes und verdeckt sonst das, was noch zu tun ist.
|
||||
*
|
||||
* "deferred" fehlt mit Absicht: Der ganze Sinn dieses Status ist "nicht jetzt".
|
||||
* Ein zurueckgestelltes Vorhaben in der Standardliste waere ein Widerspruch in
|
||||
* sich - es steht unter "Zurueckgestellt" bereit, wenn jemand danach sucht.
|
||||
*/
|
||||
const BT_STATUS_VORGABE = [
|
||||
'open', 'planned', 'in_progress', 'triage_external',
|
||||
'awaiting_verification', 'in_verification', 'fix_insufficient',
|
||||
];
|
||||
|
||||
/**
|
||||
* Liest die angehakten Status aus der Anfrage.
|
||||
*
|
||||
* Drei Faelle, und der mittlere ist der Grund fuer das versteckte Feld im
|
||||
* Formular:
|
||||
*
|
||||
* Parameter fehlt ganz -> erster Aufruf, es gilt die Vorgabe
|
||||
* Parameter ist leer -> alle Haken entfernt, also KEINE Einschraenkung
|
||||
* Parameter hat Werte -> genau diese Status
|
||||
*
|
||||
* Ohne den zweiten Fall waere "alle Haken weg" von "noch nie etwas gewaehlt"
|
||||
* nicht zu unterscheiden - HTML sendet fuer leere Ankreuzfelder nichts. Die
|
||||
* Vorgabe kaeme dann bei jedem Versuch zurueck, sie loszuwerden.
|
||||
*
|
||||
* Zeichenketten werden weiterhin angenommen: Aeltere Lesezeichen und die
|
||||
* Verweise aus den Kennzahlen kommen als bt_status=open,in_progress herein.
|
||||
*
|
||||
* @return list<string> Leer bedeutet "keine Einschraenkung".
|
||||
*/
|
||||
function btStatusAuswahl(): array
|
||||
{
|
||||
if (!array_key_exists('bt_status', $_GET)) {
|
||||
return BT_STATUS_VORGABE;
|
||||
}
|
||||
|
||||
$roh = $_GET['bt_status'];
|
||||
$werte = is_array($roh) ? $roh : explode(',', (string)$roh);
|
||||
|
||||
$werte = array_map(static fn($v): string => trim((string)$v), $werte);
|
||||
$werte = array_values(array_filter(
|
||||
$werte,
|
||||
static fn(string $v): bool => in_array($v, BugRepo::STATUSES, true)
|
||||
));
|
||||
|
||||
// "all" als einzelner Wert ist die alte Schreibweise fuer "ohne Filter".
|
||||
if (!is_array($roh) && trim((string)$roh) === 'all') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_unique($werte));
|
||||
}
|
||||
|
||||
$btStatusAuswahl = btStatusAuswahl();
|
||||
|
||||
/**
|
||||
* Roadmap-Eintraege gehoeren in die Roadmap, nicht in die Bugliste.
|
||||
*
|
||||
* Seit 2.7.0 IST ein Roadmap-Eintrag ein Bugtracker-Item - es gibt keine
|
||||
* zweite Ablage, und ein Item steht auf der Roadmap, sobald es einen
|
||||
* Themenbereich hat. Richtig so; nur landet damit die gesamte Planung aller
|
||||
* Projekte in derselben Liste wie die Meldungen. Nach der Uebernahme der
|
||||
* Projekt-Roadmaps waren das 217 von 229 Feature Requests - die zwoelf
|
||||
* Vorschlaege, um die es in dieser Liste geht, lagen darunter begraben.
|
||||
*
|
||||
* Die Bugliste zeigt deshalb ab Werk nur, was NICHT auf der Roadmap steht.
|
||||
* Sichtbar bleibt beides, nur an getrennten Orten - und der Filter holt die
|
||||
* Planung jederzeit wieder herein.
|
||||
*/
|
||||
$btRoadmap = (string)($_GET['bt_roadmap'] ?? 'ohne');
|
||||
if (!in_array($btRoadmap, ['ohne', 'nur', 'alle'], true)) {
|
||||
$btRoadmap = 'ohne';
|
||||
}
|
||||
|
||||
$btFilters = [
|
||||
'project_slug' => (string)($_GET['bt_project'] ?? 'all'),
|
||||
'environment' => (string)($_GET['bt_env'] ?? 'all'),
|
||||
'type' => (string)($_GET['bt_type'] ?? 'all'),
|
||||
'severity' => (string)($_GET['bt_severity'] ?? 'all'),
|
||||
'status' => (string)($_GET['bt_status'] ?? 'all'),
|
||||
'status' => $btStatusAuswahl === [] ? 'all' : $btStatusAuswahl,
|
||||
'search' => (string)($_GET['bt_q'] ?? ''),
|
||||
'order' => (string)($_GET['bt_order'] ?? 'newest'),
|
||||
'limit' => $btPerPage,
|
||||
'offset' => ($btPage - 1) * $btPerPage,
|
||||
];
|
||||
|
||||
if ($btRoadmap !== 'alle') {
|
||||
$btFilters['on_roadmap'] = ($btRoadmap === 'nur');
|
||||
}
|
||||
|
||||
$bugRepo = new BugRepo($pdo);
|
||||
$bugtrackerResult = $bugRepo->getItems($btFilters);
|
||||
$bugtrackerItems = $bugtrackerResult['items'];
|
||||
@@ -1918,6 +2001,29 @@ $bugtrackerStats = $bugRepo->getStats(
|
||||
$btFilters['project_slug'] !== 'all' ? $btFilters['project_slug'] : null
|
||||
);
|
||||
|
||||
/**
|
||||
* Wie viele Treffer der Statusfilter gerade verschluckt.
|
||||
*
|
||||
* Der Fall, um den es geht: Jemand sucht nach "288", bekommt nichts und
|
||||
* schliesst daraus, dass es den Eintrag nicht gibt - dabei ist er nur
|
||||
* erledigt und deshalb ausgeblendet. Eine Suche, die schweigt, obwohl sie
|
||||
* etwas gefunden hat, ist schlimmer als gar keine Suche.
|
||||
*
|
||||
* Die zusaetzliche Abfrage laeuft nur bei aktiver Suche UND aktivem
|
||||
* Statusfilter - also genau dann, wenn die Verwechslung moeglich ist. Sie
|
||||
* holt eine einzige Zeile; gebraucht wird nur die Gesamtzahl.
|
||||
*/
|
||||
$btVerdeckt = 0;
|
||||
if ($btFilters['search'] !== '' && ($btStatusAuswahl !== [] || $btRoadmap !== 'alle')) {
|
||||
$ohneEinschraenkung = $btFilters;
|
||||
unset($ohneEinschraenkung['on_roadmap']);
|
||||
$ohneEinschraenkung['status'] = 'all';
|
||||
$ohneEinschraenkung['limit'] = 1;
|
||||
$ohneEinschraenkung['offset'] = 0;
|
||||
|
||||
$btVerdeckt = max(0, $bugRepo->getItems($ohneEinschraenkung)['total'] - $bugtrackerTotal);
|
||||
}
|
||||
|
||||
/** Baut eine URL mit den aktuellen Bugtracker-Filtern und einzelnen Ueberschreibungen. */
|
||||
function btUrl(array $overrides = []): string
|
||||
{
|
||||
@@ -1927,6 +2033,7 @@ function btUrl(array $overrides = []): string
|
||||
'bt_type' => $_GET['bt_type'] ?? null,
|
||||
'bt_severity' => $_GET['bt_severity'] ?? null,
|
||||
'bt_status' => $_GET['bt_status'] ?? null,
|
||||
'bt_roadmap' => $_GET['bt_roadmap'] ?? null,
|
||||
'bt_q' => $_GET['bt_q'] ?? null,
|
||||
'bt_order' => $_GET['bt_order'] ?? null,
|
||||
'bt_page' => $_GET['bt_page'] ?? null,
|
||||
@@ -2428,6 +2535,41 @@ $csrfField = Csrf::field();
|
||||
.zelle-aktionen > div { display: flex; gap: 0.3rem; flex-wrap: nowrap; }
|
||||
|
||||
.tab-content, .subtab-content { display: none; }
|
||||
|
||||
/* Mehrfachauswahl im Filterbalken.
|
||||
<details> statt eigenem Klappmechanismus: Auf- und Zuklappen kann der
|
||||
Browser, und ohne JavaScript bleibt es bedienbar. */
|
||||
.filter-mehrfach { position: relative; display: inline-block; }
|
||||
.filter-mehrfach > summary {
|
||||
list-style: none; cursor: pointer; white-space: nowrap;
|
||||
padding: 0.3rem 0.6rem; font-size: 0.8rem;
|
||||
border: 1px solid var(--border-glass, rgba(255,255,255,0.12));
|
||||
border-radius: 6px; background: rgba(255,255,255,0.04);
|
||||
}
|
||||
.filter-mehrfach > summary::-webkit-details-marker { display: none; }
|
||||
.filter-mehrfach[open] > summary { border-color: #7aa2ff; }
|
||||
.filter-mehrfach-inhalt {
|
||||
position: absolute; z-index: 50; top: calc(100% + 4px); left: 0;
|
||||
min-width: 240px; padding: 0.6rem 0.75rem;
|
||||
background: #161c28;
|
||||
border: 1px solid var(--border-glass, rgba(255,255,255,0.12));
|
||||
border-radius: 8px; box-shadow: 0 12px 32px rgba(0,0,0,0.5);
|
||||
}
|
||||
.filter-mehrfach-inhalt label {
|
||||
display: flex; align-items: center; gap: 0.45rem;
|
||||
padding: 0.15rem 0; font-size: 0.82rem; cursor: pointer; font-weight: 400;
|
||||
}
|
||||
.filter-gruppe {
|
||||
font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em;
|
||||
opacity: 0.55; margin: 0.55rem 0 0.2rem;
|
||||
}
|
||||
.filter-gruppe:first-child { margin-top: 0; }
|
||||
.filter-mehrfach-fuss {
|
||||
display: flex; gap: 0.35rem; margin-top: 0.7rem;
|
||||
padding-top: 0.6rem;
|
||||
border-top: 1px solid var(--border-glass, rgba(255,255,255,0.12));
|
||||
}
|
||||
.filter-hinweis { font-size: 0.7rem; opacity: 0.5; margin-top: 0.4rem; }
|
||||
.tab-content.active, .subtab-content.active { display: block; }
|
||||
</style>
|
||||
</head>
|
||||
@@ -4706,34 +4848,100 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="bt_status" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<?php
|
||||
$statusOptions = [
|
||||
'all' => '📌 Alle Status',
|
||||
'open,planned,in_progress' => '📋 Nur offene',
|
||||
'open' => 'Offen',
|
||||
'planned' => 'Geplant',
|
||||
'in_progress' => 'In Bearbeitung',
|
||||
'resolved' => 'Gelöst',
|
||||
'closed' => 'Geschlossen',
|
||||
'rejected' => 'Abgelehnt',
|
||||
// Meldungen aus einem anderen Projekt. Ohne eigenen
|
||||
// Filtereintrag wären sie nur unter "Alle Status" zu
|
||||
// finden - ein Eingang, den niemand sieht, ist keiner.
|
||||
'triage_external' => '📥 Fremdmeldungen',
|
||||
// Die Nachpruefung braucht einen eigenen Einstieg:
|
||||
// Wer wissen will, welche Fixes noch unbestaetigt
|
||||
// sind, findet sie sonst nur unter "Alle Status"
|
||||
// zwischen allem anderen.
|
||||
'awaiting_verification,in_verification'
|
||||
=> '🔎 In Nachprüfung',
|
||||
'fix_insufficient' => '❗ Fix trägt nicht',
|
||||
];
|
||||
foreach ($statusOptions as $value => $label): ?>
|
||||
<option value="<?= e($value) ?>" <?= $btFilters['status'] === $value ? 'selected' : '' ?>><?= e($label) ?></option>
|
||||
<!--
|
||||
Status als Mehrfachauswahl statt als Klappliste.
|
||||
Vorher liess sich genau eine Lage waehlen, und wer
|
||||
"offen und in Nachpruefung" sehen wollte, brauchte
|
||||
eine vorgefertigte Kombination - stand sie nicht in
|
||||
der Liste, ging es nicht.
|
||||
|
||||
Das versteckte Feld davor ist kein Schoenheitsfehler:
|
||||
HTML sendet fuer nicht angehakte Kaestchen NICHTS.
|
||||
Ohne es waere "alle Haken entfernt" von "erster
|
||||
Aufruf" nicht zu unterscheiden, und die Vorgabe kaeme
|
||||
bei jedem Versuch zurueck, sie loszuwerden.
|
||||
|
||||
Kein Absenden bei jedem Klick: Wer drei Status waehlt,
|
||||
will einmal laden und nicht dreimal. Deshalb der
|
||||
eigene Knopf im Klappfeld.
|
||||
-->
|
||||
<select name="bt_roadmap" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)"
|
||||
title="Ein Vorhaben steht auf der Roadmap, sobald es einen Themenbereich hat. Es bleibt ein Item wie jedes andere - es wird hier nur nicht doppelt gezeigt.">
|
||||
<?php foreach ([
|
||||
'ohne' => '🗺️ Ohne Roadmap',
|
||||
'nur' => '🗺️ Nur Roadmap',
|
||||
'alle' => '🗺️ Mit Roadmap',
|
||||
] as $wert => $beschriftung): ?>
|
||||
<option value="<?= e($wert) ?>" <?= $btRoadmap === $wert ? 'selected' : '' ?>><?= e($beschriftung) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<details class="filter-mehrfach">
|
||||
<summary title="Status auswaehlen">
|
||||
<?php
|
||||
$statusGruppen = [
|
||||
'Laufend' => [
|
||||
'open' => 'Offen',
|
||||
'planned' => 'Geplant',
|
||||
'in_progress' => 'In Bearbeitung',
|
||||
],
|
||||
'Verlangt Aufmerksamkeit' => [
|
||||
'triage_external' => '📥 Fremdmeldung',
|
||||
'awaiting_verification' => '🔎 Wartet auf Nachpruefung',
|
||||
'in_verification' => '🔎 In Nachpruefung',
|
||||
'fix_insufficient' => '❗ Fix traegt nicht',
|
||||
],
|
||||
'Abgeschlossen' => [
|
||||
'resolved' => 'Gelöst',
|
||||
'closed' => 'Geschlossen',
|
||||
'rejected' => 'Abgelehnt',
|
||||
'ignored' => 'Ignoriert',
|
||||
'deferred' => 'Zurückgestellt',
|
||||
],
|
||||
];
|
||||
|
||||
$btStatusLabels = [];
|
||||
foreach ($statusGruppen as $eintraege) {
|
||||
$btStatusLabels += $eintraege;
|
||||
}
|
||||
|
||||
$anzahl = count($btStatusAuswahl);
|
||||
if ($anzahl === 0) {
|
||||
$btStatusText = '📌 Alle Status';
|
||||
} elseif ($anzahl === 1) {
|
||||
$btStatusText = '📌 ' . ($btStatusLabels[$btStatusAuswahl[0]] ?? $btStatusAuswahl[0]);
|
||||
} else {
|
||||
$btStatusText = sprintf('📌 %d Status', $anzahl);
|
||||
}
|
||||
?>
|
||||
<?= e($btStatusText) ?> ▾
|
||||
</summary>
|
||||
|
||||
<div class="filter-mehrfach-inhalt">
|
||||
<input type="hidden" name="bt_status[]" value="">
|
||||
|
||||
<?php foreach ($statusGruppen as $gruppe => $eintraege): ?>
|
||||
<div class="filter-gruppe"><?= e($gruppe) ?></div>
|
||||
<?php foreach ($eintraege as $wert => $beschriftung): ?>
|
||||
<label>
|
||||
<input type="checkbox" name="bt_status[]" value="<?= e($wert) ?>"
|
||||
<?= in_array($wert, $btStatusAuswahl, true) ? 'checked' : '' ?>>
|
||||
<?= e($beschriftung) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="filter-mehrfach-fuss">
|
||||
<button type="button" class="btn btn-sm btn-secondary"
|
||||
onclick="dcAlleHaken(this, false)">Keine</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary"
|
||||
onclick="dcAlleHaken(this, true)">Alle</button>
|
||||
<button type="submit" class="btn btn-sm">Übernehmen</button>
|
||||
</div>
|
||||
<div class="filter-hinweis">Kein Haken = keine Einschränkung.</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<select name="bt_order" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<?php
|
||||
$orderOptions = [
|
||||
@@ -4753,10 +4961,45 @@ chmod +x update-agent
|
||||
title="Volltext ueber Titel, Beschreibung, Fehlermeldung, Tags und Dateipfad. Eine Zahl findet zusaetzlich das Item mit dieser Nummer; mit Raute (#288) nur dieses eine.">
|
||||
|
||||
<button type="submit" class="btn btn-sm">Filtern</button>
|
||||
<?php if ($btFilters['search'] !== '' || $btFilters['project_slug'] !== 'all' || $btFilters['status'] !== 'all' || $btFilters['severity'] !== 'all' || $btFilters['type'] !== 'all' || $btFilters['environment'] !== 'all'): ?>
|
||||
<?php
|
||||
// Der Statusfilter zaehlt hier nicht mehr als "gesetzt":
|
||||
// Er ist ab Werk eingeschraenkt, sonst stuende der
|
||||
// Zuruecksetzen-Knopf immer da und bedeutete nichts.
|
||||
$btAbweichend = $btStatusAuswahl !== BT_STATUS_VORGABE || $btRoadmap !== 'ohne';
|
||||
if ($btFilters['search'] !== '' || $btFilters['project_slug'] !== 'all'
|
||||
|| $btAbweichend || $btFilters['severity'] !== 'all'
|
||||
|| $btFilters['type'] !== 'all' || $btFilters['environment'] !== 'all'): ?>
|
||||
<a href="index.php#tab-bugtracker" class="btn btn-sm btn-secondary">Zurücksetzen</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<?php if ($btVerdeckt > 0): ?>
|
||||
<!--
|
||||
Der Fall: Jemand sucht "288", bekommt nichts und
|
||||
schliesst daraus, es gebe den Eintrag nicht - dabei ist
|
||||
er nur erledigt und deshalb ausgeblendet. Eine Suche,
|
||||
die schweigt, obwohl sie etwas gefunden hat, ist
|
||||
schlimmer als gar keine.
|
||||
-->
|
||||
<div class="alert" style="margin-top:0.75rem; display:flex; flex-wrap:wrap; align-items:center; gap:0.6rem; background:rgba(122,162,255,0.12); border:1px solid #7aa2ff; color:#cfe0ff;">
|
||||
<span>
|
||||
🔎 <strong><?= (int)$btVerdeckt ?></strong>
|
||||
<?= $btVerdeckt === 1 ? 'weiterer Treffer' : 'weitere Treffer' ?>
|
||||
für „<?= e($btFilters['search']) ?>"
|
||||
<?= $btVerdeckt === 1 ? 'wird' : 'werden' ?>
|
||||
ausgeblendet - vom Statusfilter oder weil sie auf der Roadmap stehen.
|
||||
</span>
|
||||
<a class="btn btn-sm btn-secondary"
|
||||
<?php
|
||||
// Ein leerer Eintrag, keine Zeichenkette "all":
|
||||
// btUrl() wirft 'all' als "nicht gesetzt" heraus,
|
||||
// und dann griffe wieder die Vorgabe. Als Liste mit
|
||||
// einem leeren Wert bleibt der Parameter in der URL
|
||||
// stehen und bedeutet ausdruecklich "ohne Filter".
|
||||
?>
|
||||
href="<?= e(btUrl(['bt_status' => [''], 'bt_roadmap' => 'alle', 'bt_page' => null])) ?>">Überall suchen</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
@@ -6350,6 +6593,19 @@ SYSTEM
|
||||
* requestSubmit() tut es. Wo es fehlt, wird die Ansicht vorher von
|
||||
* Hand angehaengt.
|
||||
*/
|
||||
/**
|
||||
* Setzt oder entfernt alle Haken einer Mehrfachauswahl.
|
||||
*
|
||||
* Ohne das waere "ich will jetzt wirklich alles sehen" ein Dutzend
|
||||
* Klicks - und genau dann braucht man es, wenn man etwas sucht und
|
||||
* nicht findet.
|
||||
*/
|
||||
function dcAlleHaken(knopf, gesetzt) {
|
||||
knopf.closest('.filter-mehrfach-inhalt')
|
||||
.querySelectorAll('input[type=checkbox]')
|
||||
.forEach(k => { k.checked = gesetzt; });
|
||||
}
|
||||
|
||||
function dcSubmit(form) {
|
||||
if (typeof form.requestSubmit === 'function') {
|
||||
form.requestSubmit();
|
||||
|
||||
@@ -67,6 +67,25 @@ def _(ctx):
|
||||
% (r.feld('version'), erwartet))
|
||||
|
||||
|
||||
@test('kern', 'Das WebUI antwortet, statt mit einem Serverfehler abzubrechen')
|
||||
def _(ctx):
|
||||
"""
|
||||
index.php ist mit Abstand die groesste Datei des Projekts und wurde bisher
|
||||
von keinem Testfall auch nur angefasst: Die Suite prueft /api/. Ein
|
||||
Syntaxfehler darin waere durch das Deploy-Tor gelaufen und haette die
|
||||
Oberflaeche lahmgelegt, ohne dass ein einziger Test rot geworden waere.
|
||||
|
||||
Mehr als das kann dieser Fall nicht: Ohne Sitzung bricht index.php in
|
||||
Zeile 45 mit einer Weiterleitung zur Anmeldung ab, der Rumpf laeuft also
|
||||
nie. Geprueft wird damit, dass PHP die Datei ueberhaupt uebersetzen kann -
|
||||
und genau das ist der Fehler, der im Betrieb am teuersten ist.
|
||||
"""
|
||||
r = ctx.client.get('/index.php', token=None)
|
||||
erwarte(r.status != 500,
|
||||
"index.php antwortet mit 500 - vermutlich ein Syntaxfehler: %s" % r.kurz(400))
|
||||
erwarte_status(r, 200, 302, 303)
|
||||
|
||||
|
||||
@test('kern', 'OpenAPI-Beschreibung ist gueltig')
|
||||
def _(ctx):
|
||||
"""Die Beschreibung ist der Vertrag mit den SDKs - sie muss ladbar bleiben."""
|
||||
|
||||
Reference in New Issue
Block a user