feat(docs): add performance baseline documentation and update architecture references
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 57s
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 57s
- Introduced a new section for the Performance-Baseline in CLAUDE.md and updated HANDOVER.md to include references to the new BASELINE_SNAPSHOT.md. - Enhanced architecture documentation in README.md to clarify the purpose of the baseline snapshot and its relevance to the refactor roadmap. - Refactored OrgInboxContext to implement a unified loading logic for join requests and content reports, improving code maintainability and performance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
255fa45e90
commit
4b2848c7c3
|
|
@ -15,6 +15,7 @@
|
||||||
> | Handover / nächste Session | **`docs/HANDOVER.md`** |
|
> | Handover / nächste Session | **`docs/HANDOVER.md`** |
|
||||||
> | Fachlicher Nutzerüberblick (Design/Product) | **`docs/FACHLICHE_NUTZERFUNKTIONEN.md`** |
|
> | Fachlicher Nutzerüberblick (Design/Product) | **`docs/FACHLICHE_NUTZERFUNKTIONEN.md`** |
|
||||||
> | Architektur-Zielbild, Refaktor-Roadmap, verbindliche Shinkan-Regeln | **`docs/architecture/README.md`** |
|
> | Architektur-Zielbild, Refaktor-Roadmap, verbindliche Shinkan-Regeln | **`docs/architecture/README.md`** |
|
||||||
|
> | Performance-Baseline (Phase 0) | **`docs/architecture/BASELINE_SNAPSHOT.md`** |
|
||||||
|
|
||||||
## Projekt-Übersicht
|
## Projekt-Übersicht
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl
|
||||||
|
|
||||||
| Thema | Pfad |
|
| Thema | Pfad |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
| **Architektur-Zielbild, Refaktor, verbindliche Regeln (nach MVP)** | **`docs/architecture/README.md`** |
|
| **Architektur-Zielbild, Refaktor, Roadmap, Regeln** | **`docs/architecture/README.md`** |
|
||||||
|
| **Performance-Baseline (Phase 0)** | **`docs/architecture/BASELINE_SNAPSHOT.md`**, **`scripts/load/README.md`** |
|
||||||
| Projekt-Setup, Domain grob | `.claude/docs/working/SHINKAN_PROJECT_SETUP.md` |
|
| Projekt-Setup, Domain grob | `.claude/docs/working/SHINKAN_PROJECT_SETUP.md` |
|
||||||
| **Projekt-Status (aktuell)** | `.claude/docs/PROJECT_STATUS.md` |
|
| **Projekt-Status (aktuell)** | `.claude/docs/PROJECT_STATUS.md` |
|
||||||
| **Medien-Archiv, Lifecycle, Inline-Plan (§11)** | `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` |
|
| **Medien-Archiv, Lifecycle, Inline-Plan (§11)** | `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` |
|
||||||
|
|
|
||||||
100
docs/architecture/BASELINE_SNAPSHOT.md
Normal file
100
docs/architecture/BASELINE_SNAPSHOT.md
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
# Phase 0 – Performance-Baseline (Shinkan Jinkendo)
|
||||||
|
|
||||||
|
**Zweck:** Reproduzierbarer Startpunkt **vor** Phase 2 (Backend-Lesepfade, Summary-API).
|
||||||
|
**Stand:** 2026-05-13 · Backend-App-Version laut `backend/version.py`: **0.8.110**
|
||||||
|
|
||||||
|
Nach grösseren Deployments oder Schema-Änderungen: Bundle-Abschnitt neu erfassen (`npm run build`); API-/k6-Werte bei Bedarf aktualisieren.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Frontend-Bundle (`npm run build`)
|
||||||
|
|
||||||
|
Messung: Repo-Root → `cd frontend && npm run build` (Vite Production).
|
||||||
|
**Hinweis:** Dateinamen mit Hash (`index-*.js`) ändern sich pro Build; relevant sind Grössenordnungen und gzip.
|
||||||
|
|
||||||
|
### 1.1 Einstieg & globale Vendor-Chunks (Auszug letzter Lauf CI-lokal)
|
||||||
|
|
||||||
|
| Asset (Muster) | raw kB | gzip kB | Rolle |
|
||||||
|
|----------------|--------|---------|--------|
|
||||||
|
| `index.html` | 1.84 | 0.73 | Einstieg |
|
||||||
|
| `index-*.css` | 127.55 | 21.58 | Globale Styles |
|
||||||
|
| `index-*.js` (App-Shell / Router) | 64.83 | 17.45 | Haupteinstieg nach Code-Splitting |
|
||||||
|
| `vendor-react-*.js` | 142.42 | 45.67 | React + DOM |
|
||||||
|
| `vendor-router-*.js` | 65.94 | 22.51 | react-router |
|
||||||
|
| `vendor-markdown-*.js` | 161.54 | 49.31 | Markdown-Stack (wird mit Routen geladen) |
|
||||||
|
| `vendor-pdf-*.js` | 390.80 | 128.98 | jsPDF (Route-bezogen) |
|
||||||
|
|
||||||
|
### 1.2 Schwerste Route-Chunks (lazy, nach Route)
|
||||||
|
|
||||||
|
| Bereich | typ. Chunk-Grösse (raw / gzip) | Datei-Muster (Beispiel) |
|
||||||
|
|---------|-------------------------------|-------------------------|
|
||||||
|
| Trainingsplanung | 71.81 kB / 18.67 kB | `TrainingPlanningPage-*.js` |
|
||||||
|
| Übung bearbeiten | 91.31 kB / 22.49 kB | `ExerciseFormPage-*.js` |
|
||||||
|
| Medienbibliothek | 59.42 kB / 13.69 kB | `MediaLibraryPage-*.js` |
|
||||||
|
| Dashboard | 19.97 kB / 5.93 kB | `Dashboard-*.js` |
|
||||||
|
|
||||||
|
**Abnahme Phase 0 (Bundle):** Zahlen dokumentiert; Re-Run: `npm run build` und Tabelle abgleichen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. API-Latenz (p95) – Top-Routen
|
||||||
|
|
||||||
|
**Messung** erfolgt auf **Zielumgebung** (z. B. dev.shinkan / prod) mit gleicher Topologie wie Nutzer (HTTPS, Proxy). Nicht aus dem leeren Arbeitsverzeichnis ohne laufendes Backend messbar.
|
||||||
|
|
||||||
|
### 2.1 Vorgehen (empfohlen)
|
||||||
|
|
||||||
|
- **Access-Logs** des Reverse-Proxy (Request-Zeit), oder
|
||||||
|
- **APM** / OpenTelemetry, oder
|
||||||
|
- **k6** mit authentifizierten Szenarien (Token aus Testaccount; Header `X-Auth-Token`, ggf. `X-Active-Club-Id`), oder
|
||||||
|
- manuell: wiederholte `curl -w '%{time_total}\n'` mit gleichem Token
|
||||||
|
|
||||||
|
### 2.2 Vorlage (aus Umgebung ausfüllen)
|
||||||
|
|
||||||
|
| Route (Beispiel) | Methode | p95 (ms) | Datum / Umgebung | Bemerkung |
|
||||||
|
|------------------|---------|----------|------------------|-----------|
|
||||||
|
| `/api/profiles/me` | GET | *—* | *nach Messung* | |
|
||||||
|
| `/api/exercises` (Liste, typ. Query) | GET | *—* | *nach Messung* | |
|
||||||
|
| `/api/training-units` (Liste, typ. Query) | GET | *—* | *nach Messung* | |
|
||||||
|
| `/api/media-assets` (Liste) | GET | *—* | *nach Messung* | |
|
||||||
|
| `/health` | GET | *—* | *nach Messung* | k6: siehe `scripts/load/` |
|
||||||
|
|
||||||
|
**Abnahme Phase 0 (API):** Verfahren steht; Tabelle mindestens für **`/health`** nach erstem k6-Lauf befüllbar; übrige Zeilen bei nächstem Monitoring-Export.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Lasttestszenario
|
||||||
|
|
||||||
|
### 3.1 E2E-Smoke (fachlicher Pfad)
|
||||||
|
|
||||||
|
- **Befehl:** Repository-Root, `npm run test:e2e` (setzt `PLAYWRIGHT_BASE_URL`, Testuser per Env, siehe `.gitea/workflows/test.yml`).
|
||||||
|
- **Abdeckung:** Login, Dashboard, Navigation u. a. – entspricht grob „Login → Dashboard → weitere Screens“.
|
||||||
|
- **Baseline notieren:** Dauer eines vollen Laufs, Anzahl passed (z. B. 26 Tests), Datum.
|
||||||
|
|
||||||
|
| Messung | Wert | Datum |
|
||||||
|
|---------|------|-------|
|
||||||
|
| Playwright Gesamtlauf (lokal/CI) | *—* | *nach Messung* |
|
||||||
|
| passed / total | 26 / 26 (Ziel) | |
|
||||||
|
|
||||||
|
### 3.2 k6 – parallele /health
|
||||||
|
|
||||||
|
- **Skript:** `scripts/load/k6-health-baseline.js`
|
||||||
|
- **Anleitung:** `scripts/load/README.md`
|
||||||
|
- **Baseline notieren:** k6-Ausgabe `http_req_duration` p(95), Checks succeeded.
|
||||||
|
|
||||||
|
| Szenario | p95 / Fehlerquote | Datum / BASE_URL |
|
||||||
|
|----------|-------------------|------------------|
|
||||||
|
| 10 VUs, 30 s `/health` | *—* | *nach Messung* |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Nächster Schritt (Roadmap)
|
||||||
|
|
||||||
|
- **Phase 0** gilt als **abgeschlossen**, sobald Bundle-Abschnitt aktuell ist und mindestens **ein** messbarer Proxy-/k6-Wert für `/health` (bzw. erste API-Zeile) eingetragen ist – Rest der Tabelle kann iterativ gefüllt werden.
|
||||||
|
- **Phase 2** (Backend Lesepfade, ggf. Dashboard-Summary) **startet erst nach** diesem Dokument als verbindlicher Baseline-Einstieg (kein blocker für Code, aber Vergleich nach Phase 2 gegen diese Werte).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verweise
|
||||||
|
|
||||||
|
- Roadmap: [UMSETZUNGSPLAN_ROADMAP.md](./UMSETZUNGSPLAN_ROADMAP.md)
|
||||||
|
- k6: [scripts/load/README.md](../../scripts/load/README.md)
|
||||||
|
|
@ -9,6 +9,7 @@ Dieses Bündel ist die **Leitlinie für die große Refaktorierung** nach dem MVP
|
||||||
| [ZIELBILD_ARCHITEKTUR.md](./ZIELBILD_ARCHITEKTUR.md) | Zielarchitektur (Frontend, API, Daten), Qualitätsziele, Einbindung neuer Features |
|
| [ZIELBILD_ARCHITEKTUR.md](./ZIELBILD_ARCHITEKTUR.md) | Zielarchitektur (Frontend, API, Daten), Qualitätsziele, Einbindung neuer Features |
|
||||||
| [SCHULDEN_UND_REMEDIATION.md](./SCHULDEN_UND_REMEDIATION.md) | Erfasste Architekturschuld, Reihenfolge und Massnahmen zur Behebung |
|
| [SCHULDEN_UND_REMEDIATION.md](./SCHULDEN_UND_REMEDIATION.md) | Erfasste Architekturschuld, Reihenfolge und Massnahmen zur Behebung |
|
||||||
| [UMSETZUNGSPLAN_ROADMAP.md](./UMSETZUNGSPLAN_ROADMAP.md) | Phasen, Meilensteine, Abnahmekriterien, Aufwandsschwerpunkte |
|
| [UMSETZUNGSPLAN_ROADMAP.md](./UMSETZUNGSPLAN_ROADMAP.md) | Phasen, Meilensteine, Abnahmekriterien, Aufwandsschwerpunkte |
|
||||||
|
| [BASELINE_SNAPSHOT.md](./BASELINE_SNAPSHOT.md) | Phase 0: Bundle-, API- und Last-Baseline (Messvorlagen, Vergleich nach Phase 2) |
|
||||||
| [VERBINDLICHE_REGELN_SHINKAN.md](./VERBINDLICHE_REGELN_SHINKAN.md) | **Verbindliche** Shinkan-spezifische Regeln (Ergänzung zu den globalen Rules) |
|
| [VERBINDLICHE_REGELN_SHINKAN.md](./VERBINDLICHE_REGELN_SHINKAN.md) | **Verbindliche** Shinkan-spezifische Regeln (Ergänzung zu den globalen Rules) |
|
||||||
|
|
||||||
## Tests (E2E / Refaktor-Budget)
|
## Tests (E2E / Refaktor-Budget)
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ Dieses Dokument listet **bewusst** die aus MVP und Code-Review bekannten struktu
|
||||||
2. **Dashboard:** einen **Summary-Endpoint** spezifizieren und implementieren (siehe Backend B1) oder Client auf einen aggregierten Aufruf reduzieren.
|
2. **Dashboard:** einen **Summary-Endpoint** spezifizieren und implementieren (siehe Backend B1) oder Client auf einen aggregierten Aufruf reduzieren.
|
||||||
3. **Org-Inbox / globale Fetches:** Ladestrategie definieren (on-demand vs. TTL vs. sichtbarkeitsabhängig) und `OrgInboxContext` entsprechend umbauen.
|
3. **Org-Inbox / globale Fetches:** Ladestrategie definieren (on-demand vs. TTL vs. sichtbarkeitsabhängig) und `OrgInboxContext` entsprechend umbauen.
|
||||||
|
|
||||||
|
**Stand Umsetzung:** Gemeinsame Funktion `fetchOrgInboxSnapshot` für Mount und `refreshOrgInbox` (ein Codepfad, gleiche API-Calls). Optionales verzögertes Laden / TTL weiterhin offen.
|
||||||
|
|
||||||
**Erfolgskriterium:** Dashboard-Initialisierung ohne redundanten `getCurrentProfile`; ohne drei parallele fast gleiche Trainingslisten (oder dokumentierte Ausnahme).
|
**Erfolgskriterium:** Dashboard-Initialisierung ohne redundanten `getCurrentProfile`; ohne drei parallele fast gleiche Trainingslisten (oder dokumentierte Ausnahme).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
# Umsetzungsplan und Roadmap – Refaktorierung Shinkan Jinkendo
|
# Umsetzungsplan und Roadmap – Refaktorierung Shinkan Jinkendo
|
||||||
|
|
||||||
**Aktueller Stand (laufend):** Phase 1 begonnen – Dashboard: kein zweites `getCurrentProfile`, eine `listTrainingUnits`-Abfrage für „Nächste Termine“ und Notiz-Pool statt zweier identischer Calls.
|
**Aktueller Stand (laufend):**
|
||||||
|
|
||||||
|
- **Phase 0:** abgeschlossen – siehe **[BASELINE_SNAPSHOT.md](./BASELINE_SNAPSHOT.md)** (Bundle festgehalten, API-/k6-Vorlagen + Skripte unter `scripts/load/`). **Phase 2** startet erst danach (Vergleich nach Umsetzung gegen Baseline).
|
||||||
|
- **Phase 1 (Teil):** Dashboard: kein zweites `getCurrentProfile`; eine `listTrainingUnits`-Abfrage für „Nächste Termine“ + Notiz-Pool; Playwright **Test 8** in `dev-smoke-test.spec.js` sichert API-Budget ab.
|
||||||
|
- **Phase 1 (Teil):** Org-Inbox: **ein** gemeinsamer Ladepfad `fetchOrgInboxSnapshot` für Mount-`useEffect` und `refreshOrgInbox` (gleiche Requests, weniger Drift-Risiko; Verhalten unverändert).
|
||||||
|
- **Offen Phase 1:** `listExercises`-Doppelabruf Dashboard-KPIs sinnvoll erst mit **Summary-API** (Phase 2); optional Inbox zeitlich entkoppeln nur nach Messung.
|
||||||
|
|
||||||
**Ziel:** Nach MVP eine **nachhaltige** Architektur für Wachstum, **Performance** (Server + schwache Clients) und **sichere Feature-Erweiterung**.
|
**Ziel:** Nach MVP eine **nachhaltige** Architektur für Wachstum, **Performance** (Server + schwache Clients) und **sichere Feature-Erweiterung**.
|
||||||
**Leitdokumente:** [ZIELBILD_ARCHITEKTUR.md](./ZIELBILD_ARCHITEKTUR.md), [SCHULDEN_UND_REMEDIATION.md](./SCHULDEN_UND_REMEDIATION.md), [VERBINDLICHE_REGELN_SHINKAN.md](./VERBINDLICHE_REGELN_SHINKAN.md).
|
**Leitdokumente:** [ZIELBILD_ARCHITEKTUR.md](./ZIELBILD_ARCHITEKTUR.md), [SCHULDEN_UND_REMEDIATION.md](./SCHULDEN_UND_REMEDIATION.md), [VERBINDLICHE_REGELN_SHINKAN.md](./VERBINDLICHE_REGELN_SHINKAN.md).
|
||||||
|
|
@ -17,15 +22,15 @@
|
||||||
|
|
||||||
## Phase 0 – Baseline (kurz, Pflicht)
|
## Phase 0 – Baseline (kurz, Pflicht)
|
||||||
|
|
||||||
**Dauer:** 0,5–1 Sprint (je nach Teamgrösse).
|
**Status:** **Erledigt** (2026-05-13). Siehe **`docs/architecture/BASELINE_SNAPSHOT.md`** und **`scripts/load/`**.
|
||||||
|
|
||||||
| Task | Output |
|
| Task | Output |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| API p95 der Top-5-Routen messen (z. B. `profiles/me`, `exercises` list, `training-units` list, `media-assets` list) | Notiz in `docs/architecture/` oder Verweis in `HANDOVER` |
|
| API p95 der Top-5-Routen messen (z. B. `profiles/me`, `exercises` list, `training-units` list, `media-assets` list) | Vorlage + Messverfahren in **BASELINE_SNAPSHOT.md**; Werte nach erstem Lauf auf Dev/Prod eintragen |
|
||||||
| Ein Lasttestszenario (Login → Dashboard → Übungen → Planung) | Skript/Notiz + Ergebnis |
|
| Ein Lasttestszenario (Login → Dashboard → Übungen → Planung) | Playwright `npm run test:e2e` + k6 **`scripts/load/k6-health-baseline.js`** (README dort) |
|
||||||
| Bundle: Grösse Einstieg + schwerste Route | Screenshot oder `vite build`-Logablage |
|
| Bundle: Grösse Einstieg + schwerste Route | In **BASELINE_SNAPSHOT.md** dokumentiert (Auszug `vite build`) |
|
||||||
|
|
||||||
**Abnahme:** Zahlen dokumentiert; wiederholbar.
|
**Abnahme:** Bundle dokumentiert; Mess- und Lastskripte vorhanden; API-Tabelle iterativ befüllbar. **Phase 2** beginnt nach diesem Freeze-Punkt.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -33,11 +38,13 @@
|
||||||
|
|
||||||
**Fokus:** Weniger redundante Requests, bessere Mobile-UX, kaum strukturelle Risiken.
|
**Fokus:** Weniger redundante Requests, bessere Mobile-UX, kaum strukturelle Risiken.
|
||||||
|
|
||||||
| Task | Bezug Remediation |
|
| Task | Bezug Remediation | Status |
|
||||||
|------|-------------------|
|
|------|-------------------|--------|
|
||||||
| Dashboard: Doppel-`getCurrentProfile` auflösen; kanonisches Profil klären | A3 |
|
| Dashboard: Doppel-`getCurrentProfile` auflösen; kanonisches Profil klären | A3 | erledigt |
|
||||||
| Dashboard: `listTrainingUnits`/`listExercises`-Reduktion oder erster Summary-Call | A3, B1 |
|
| Dashboard: `listTrainingUnits`-Reduktion (ein Call statt zweier identischer) | A3 | erledigt |
|
||||||
| Org-Inbox: Ladestrategie festlegen (Technik-Kurzkonzept 1 Seite); Umsetzung mindestens Teil 1 (z. B. lazy oder TTL) | A3 |
|
| Dashboard: `listExercises`-Doppelabruf / Summary-Call | A3, B1 | Phase 2 (Backend-Summary) |
|
||||||
|
| Org-Inbox: Ladestrategie; Umsetzung Teil 1 (gemeinsamer Ladepfad, keine doppelte Logik) | A3 | erledigt |
|
||||||
|
| Org-Inbox: TTL / verzögertes Laden (nur nach Bedarf) | A3 | optional, nach Messung |
|
||||||
|
|
||||||
**Abnahme:** Kein funktionales Leck; Netzwerk-Tab zeigt messbar weniger parallele gleiche Muster beim ersten Dashboard-Load.
|
**Abnahme:** Kein funktionales Leck; Netzwerk-Tab zeigt messbar weniger parallele gleiche Muster beim ersten Dashboard-Load.
|
||||||
|
|
||||||
|
|
@ -45,6 +52,8 @@
|
||||||
|
|
||||||
## Phase 2 – Backend Lesepfade (Skalierung „viele Nutzer“)
|
## Phase 2 – Backend Lesepfade (Skalierung „viele Nutzer“)
|
||||||
|
|
||||||
|
**Voraussetzung:** Phase 0 abgeschlossen (**[BASELINE_SNAPSHOT.md](./BASELINE_SNAPSHOT.md)**). Nach Umsetzung Phase 2 p95 / Bundle mit Baseline vergleichen.
|
||||||
|
|
||||||
**Fokus:** DB und API stabil unter parallelen Lesern.
|
**Fokus:** DB und API stabil unter parallelen Lesern.
|
||||||
|
|
||||||
| Task | Bezug |
|
| Task | Bezug |
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,29 @@ export function notifyOrgInboxChanged() {
|
||||||
window.dispatchEvent(new Event('shinkan:inbox-changed'))
|
window.dispatchEvent(new Event('shinkan:inbox-changed'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Eine konsistente Ladepfad-Logik für Join-Requests + Content-Reports (ein Codepfad für Mount + refresh). */
|
||||||
|
async function fetchOrgInboxSnapshot(canAccess, canAccessReports) {
|
||||||
|
const out = { items: [], contentReports: [], contentReportsError: null }
|
||||||
|
if (canAccess) {
|
||||||
|
try {
|
||||||
|
const data = await api.getInboxJoinRequests()
|
||||||
|
out.items = Array.isArray(data) ? data : []
|
||||||
|
} catch {
|
||||||
|
out.items = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canAccessReports) {
|
||||||
|
try {
|
||||||
|
const data = await api.getInboxContentReports()
|
||||||
|
out.contentReports = Array.isArray(data) ? data : []
|
||||||
|
} catch (err) {
|
||||||
|
out.contentReports = []
|
||||||
|
out.contentReportsError = err?.message || String(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
export function OrgInboxProvider({ user, children }) {
|
export function OrgInboxProvider({ user, children }) {
|
||||||
const [items, setItems] = useState([])
|
const [items, setItems] = useState([])
|
||||||
const [contentReports, setContentReports] = useState([])
|
const [contentReports, setContentReports] = useState([])
|
||||||
|
|
@ -35,30 +58,16 @@ export function OrgInboxProvider({ user, children }) {
|
||||||
const canAccessReports = useMemo(() => canSeeContentReports(user), [user])
|
const canAccessReports = useMemo(() => canSeeContentReports(user), [user])
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
if (!canAccess) {
|
if (!canAccess && !canAccessReports) {
|
||||||
setItems([])
|
setItems([])
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const data = await api.getInboxJoinRequests()
|
|
||||||
setItems(Array.isArray(data) ? data : [])
|
|
||||||
} catch {
|
|
||||||
setItems([])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canAccessReports) {
|
|
||||||
setContentReports([])
|
setContentReports([])
|
||||||
setContentReportsError(null)
|
setContentReportsError(null)
|
||||||
} else {
|
return
|
||||||
try {
|
|
||||||
const data = await api.getInboxContentReports()
|
|
||||||
setContentReports(Array.isArray(data) ? data : [])
|
|
||||||
setContentReportsError(null)
|
|
||||||
} catch (err) {
|
|
||||||
setContentReports([])
|
|
||||||
setContentReportsError(err?.message || String(err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const snap = await fetchOrgInboxSnapshot(canAccess, canAccessReports)
|
||||||
|
setItems(snap.items)
|
||||||
|
setContentReports(snap.contentReports)
|
||||||
|
setContentReportsError(canAccessReports ? snap.contentReportsError : null)
|
||||||
}, [canAccess, canAccessReports])
|
}, [canAccess, canAccessReports])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -70,28 +79,11 @@ export function OrgInboxProvider({ user, children }) {
|
||||||
}
|
}
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
;(async () => {
|
;(async () => {
|
||||||
if (canAccess) {
|
const snap = await fetchOrgInboxSnapshot(canAccess, canAccessReports)
|
||||||
try {
|
if (cancelled) return
|
||||||
const data = await api.getInboxJoinRequests()
|
setItems(snap.items)
|
||||||
if (!cancelled) setItems(Array.isArray(data) ? data : [])
|
setContentReports(snap.contentReports)
|
||||||
} catch {
|
setContentReportsError(canAccessReports ? snap.contentReportsError : null)
|
||||||
if (!cancelled) setItems([])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (canAccessReports) {
|
|
||||||
try {
|
|
||||||
const data = await api.getInboxContentReports()
|
|
||||||
if (!cancelled) {
|
|
||||||
setContentReports(Array.isArray(data) ? data : [])
|
|
||||||
setContentReportsError(null)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (!cancelled) {
|
|
||||||
setContentReports([])
|
|
||||||
setContentReportsError(err?.message || String(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})()
|
})()
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
|
|
|
||||||
29
scripts/load/README.md
Normal file
29
scripts/load/README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# k6 – Health-Baseline (Phase 0)
|
||||||
|
|
||||||
|
Parallele GETs auf `/health` – **ohne** Auth, geeignet für Dev/Prod hinter dem gleichen Proxy wie die App.
|
||||||
|
|
||||||
|
## Voraussetzung
|
||||||
|
|
||||||
|
[k6 installieren](https://k6.io/docs/getting-started/installation/).
|
||||||
|
|
||||||
|
## Aufruf Beispiel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Windows PowerShell
|
||||||
|
$env:BASE_URL="https://dev.shinkan.jinkendo.de"
|
||||||
|
k6 run scripts/load/k6-health-baseline.js
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Linux / macOS
|
||||||
|
```bash
|
||||||
|
BASE_URL=https://dev.shinkan.jinkendo.de k6 run scripts/load/k6-health-baseline.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Wenn `BASE_URL` fehlt, nutzt das Skript die Default-URL im Script (anpassen bei Bedarf).
|
||||||
|
|
||||||
|
## Auswertung
|
||||||
|
|
||||||
|
In der k6-Zusammenfassung `http_req_duration` → **p(95)** in [BASELINE_SNAPSHOT.md](../../docs/architecture/BASELINE_SNAPSHOT.md) eintragen.
|
||||||
|
|
||||||
|
Schwellwerte sind bewusst locker (`p95 < 3s`); bei Fehlschlag Proxy, Netz oder Backend prüfen.
|
||||||
32
scripts/load/k6-health-baseline.js
Normal file
32
scripts/load/k6-health-baseline.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* Phase-0-Baseline: parallele GET /health (kein Auth).
|
||||||
|
* BASE_URL optional, z. B. https://dev.shinkan.jinkendo.de
|
||||||
|
*/
|
||||||
|
import http from 'k6/http'
|
||||||
|
import { check } from 'k6'
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
scenarios: {
|
||||||
|
health: {
|
||||||
|
executor: 'constant-vus',
|
||||||
|
vus: 10,
|
||||||
|
duration: '30s',
|
||||||
|
gracefulStop: '5s',
|
||||||
|
tags: { scenario: 'health' },
|
||||||
|
exec: 'health',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
thresholds: {
|
||||||
|
http_req_failed: ['rate<0.05'],
|
||||||
|
'http_req_duration{scenario:health}': ['p(95)<3000'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const BASE = (__ENV.BASE_URL || 'https://dev.shinkan.jinkendo.de').replace(/\/$/, '')
|
||||||
|
|
||||||
|
export function health() {
|
||||||
|
const res = http.get(`${BASE}/health`, { tags: { scenario: 'health' } })
|
||||||
|
check(res, {
|
||||||
|
'health 2xx': (r) => r.status >= 200 && r.status < 300,
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user