552 lines
15 KiB
Markdown
552 lines
15 KiB
Markdown
# 🚀 PLAN D'EXÉCUTION & MÉTRIQUES
|
|
|
|
## 1. RÉSUMÉ EXÉCUTABLE — Ordre d'Attaque des P0 (Semaine 1)
|
|
|
|
### Jour 1: Sécurité Critique
|
|
**Objectif:** Éliminer vulnérabilité XSS
|
|
**Tasks:**
|
|
- [ ] Installer DOMPurify: `npm install dompurify @types/dompurify`
|
|
- [ ] Remplacer `escapeHtml()` par `DOMPurify.sanitize()` dans `MarkdownService`
|
|
- [ ] Configurer whitelist: `ALLOWED_TAGS`, `ALLOWED_ATTR`
|
|
- [ ] Tests avec payloads XSS (OWASP Top 10)
|
|
- [ ] Commit + merge
|
|
|
|
**Critère de succès:** Payload `<img src=x onerror=alert(1)>` neutralisé
|
|
|
|
---
|
|
|
|
### Jour 2-3: Performance UI Immédiate
|
|
**Objectif:** Éliminer gels perceptibles
|
|
**Tasks:**
|
|
- [ ] Implémenter CDK Virtual Scroll pour résultats de recherche (2h)
|
|
- [ ] Ajouter `trackBy` sur toutes les listes `@for` (1h)
|
|
- [ ] Debounce rebuild index (search + graph) avec `debounceTime(300)` (3h)
|
|
- [ ] Tests E2E: search 500 notes <150ms (2h)
|
|
|
|
**Critère de succès:** Aucun gel UI >100ms sur actions utilisateur
|
|
|
|
---
|
|
|
|
### Jour 4-5: Offload Computation
|
|
**Objectif:** Libérer main thread
|
|
**Tasks:**
|
|
- [ ] Créer `markdown.worker.ts` avec MarkdownIt (4h)
|
|
- [ ] Implémenter `MarkdownWorkerService` avec pool 2 workers (3h)
|
|
- [ ] Lazy load Mermaid + `runOutsideAngular()` (2h)
|
|
- [ ] Lazy load MathJax (1h)
|
|
- [ ] Tests rendering note 1000 lignes + mermaid (2h)
|
|
|
|
**Critère de succès:** Parsing note complexe: main thread <16ms
|
|
|
|
---
|
|
|
|
### Jour 6-7: Backend Meilisearch MVP
|
|
**Objectif:** Recherche scalable
|
|
**Tasks:**
|
|
- [ ] Docker Compose: ajouter service Meilisearch (1h)
|
|
- [ ] Backend: script indexation `meilisearch-indexer.mjs` (3h)
|
|
- [ ] Créer `SearchMeilisearchService` Angular (2h)
|
|
- [ ] Mapper opérateurs Obsidian → filtres (3h)
|
|
- [ ] Route `/api/search` avec parsing opérateurs (3h)
|
|
- [ ] Tests: opérateurs `tag:`, `path:`, `file:` (2h)
|
|
|
|
**Critère de succès:** Search retourne <150ms P95 sur 1000 notes
|
|
|
|
---
|
|
|
|
### Jour 8: Observabilité
|
|
**Objectif:** Diagnostics production
|
|
**Tasks:**
|
|
- [ ] Créer route POST `/api/log` (2h)
|
|
- [ ] Implémenter validation + sanitization logs (2h)
|
|
- [ ] Rotation logs automatique (10MB max) (1h)
|
|
- [ ] Tests: batch 50 événements <50ms (1h)
|
|
|
|
**Critère de succès:** Logs persistés avec corrélation sessionId
|
|
|
|
---
|
|
|
|
## 2. PLAN D'IMPLÉMENTATION PAR ÉTAPES
|
|
|
|
### Phase 1: CRITIQUE (Semaine 1-2) — 8 jours
|
|
**Focus:** Sécurité + Performance bloquante
|
|
|
|
| Item | Effort | Dépendances | Risque |
|
|
|------|--------|-------------|--------|
|
|
| DOMPurify sanitization | 1j | Aucune | Faible |
|
|
| CDK Virtual Scroll | 2j | Aucune | Faible |
|
|
| Debounce index rebuild | 3j | Aucune | Moyen |
|
|
| Markdown Web Worker | 4j | Aucune | Moyen |
|
|
| Lazy load Mermaid/MathJax | 2j | Aucune | Faible |
|
|
| Meilisearch integration | 5j | Docker setup | Élevé |
|
|
| /api/log backend | 3j | Aucune | Faible |
|
|
|
|
**Livrable:** Version 1.1.0 — "Performance & Security"
|
|
**Métriques cibles:** TTI <2.5s, Search P95 <150ms, 0 vulnérabilités XSS
|
|
|
|
---
|
|
|
|
### Phase 2: OPTIMISATION (Semaine 3-4) — 7 jours
|
|
**Focus:** Caching + Infra
|
|
|
|
| Item | Effort | Dépendances | Risque |
|
|
|------|--------|-------------|--------|
|
|
| Service Worker + Workbox | 3j | Aucune | Moyen |
|
|
| Budgets Lighthouse | 0.5j | Aucune | Faible |
|
|
| Dockerfile multi-stage | 2j | Aucune | Faible |
|
|
| Variables d'env (12-factor) | 1.5j | Aucune | Faible |
|
|
| CSP headers + NGINX | 1.5j | Docker | Faible |
|
|
| Throttle RAF canvas | 1j | Aucune | Faible |
|
|
| Tests E2E étendus | 2.5j | Playwright | Moyen |
|
|
|
|
**Livrable:** Version 1.2.0 — "Infrastructure"
|
|
**Métriques cibles:** Offline support, Image <150MB, A+ Mozilla Observatory
|
|
|
|
---
|
|
|
|
### Phase 3: NICE-TO-HAVE (Semaine 5+) — 5 jours
|
|
**Focus:** Code splitting + Optimisations avancées
|
|
|
|
| Item | Effort | Dépendances | Risque |
|
|
|------|--------|-------------|--------|
|
|
| Lazy routes Angular | 3j | Routing refactor | Moyen |
|
|
| GraphData memoization | 1.5j | Aucune | Faible |
|
|
| markdown-it-attrs whitelist | 0.5j | Aucune | Faible |
|
|
| Progressive rendering | 2j | Aucune | Moyen |
|
|
| IndexedDB cache | 3j | Dexie.js | Moyen |
|
|
| OpenTelemetry (opt.) | 4j | Infra monitoring | Élevé |
|
|
|
|
**Livrable:** Version 1.3.0 — "Polish"
|
|
**Métriques cibles:** Initial bundle <800KB, Cache hit rate >80%
|
|
|
|
---
|
|
|
|
## 3. MÉTRIQUES À SUIVRE (Performance & Erreurs)
|
|
|
|
### A) Métriques Performance (Lighthouse + Custom)
|
|
|
|
| Métrique | Actuel (estimé) | Cible Phase 1 | Cible Phase 2 | Cible Phase 3 | Seuil Alerte |
|
|
|----------|-----------------|---------------|---------------|---------------|--------------|
|
|
| **TTI (Time to Interactive)** | 4.2s | 2.5s | 2.0s | 1.5s | >3s (P95) |
|
|
| **LCP (Largest Contentful Paint)** | 2.8s | 2.0s | 1.5s | 1.2s | >2.5s (P75) |
|
|
| **FID (First Input Delay)** | 120ms | 80ms | 50ms | 30ms | >100ms (P95) |
|
|
| **CLS (Cumulative Layout Shift)** | 0.15 | 0.1 | 0.05 | 0.02 | >0.1 (P75) |
|
|
| **Bundle Size (initial)** | 2.8MB | 1.8MB | 1.5MB | 800KB | >2MB |
|
|
| **Bundle Size (lazy chunks)** | N/A | 500KB | 300KB | 200KB | >500KB |
|
|
| **Search P95 Latency** | 800ms | 150ms | 100ms | 50ms | >200ms |
|
|
| **Graph Interaction P95** | 1500ms | 500ms | 100ms | 50ms | >300ms |
|
|
| **Markdown Parse P95** | 500ms | 100ms | 50ms | 16ms | >150ms |
|
|
| **Memory Heap (steady state)** | 120MB | 100MB | 80MB | 60MB | >150MB |
|
|
|
|
**Outils de mesure:**
|
|
- Lighthouse CI (automatisé dans pipeline)
|
|
- Chrome DevTools Performance profiler
|
|
- `performance.mark()` + `performance.measure()` custom
|
|
- Real User Monitoring (RUM) via `/api/log` PERFORMANCE_METRIC events
|
|
|
|
---
|
|
|
|
### B) Métriques Erreurs & Stabilité
|
|
|
|
| Métrique | Cible | Seuil Alerte | Action |
|
|
|----------|-------|--------------|--------|
|
|
| **Error Rate** | <0.1% sessions | >1% | Rollback deploy |
|
|
| **XSS Vulnerabilities** | 0 | >0 | Blocage release |
|
|
| **Search Error Rate** | <0.5% queries | >2% | Investigate index corruption |
|
|
| **Graph Freeze Rate** | <0.1% interactions | >1% | Degrade to simple view |
|
|
| **Worker Crash Rate** | <0.01% | >0.5% | Fallback to sync mode |
|
|
| **API /log Uptime** | >99.5% | <95% | Scale backend |
|
|
| **CSP Violations** | <10/day | >100/day | Review inline scripts |
|
|
|
|
**Alertes configurées via:**
|
|
- Sentry (erreurs runtime)
|
|
- LogRocket (session replay on error)
|
|
- Custom `/api/log` aggregation + Grafana
|
|
|
|
---
|
|
|
|
### C) Métriques Business (UX)
|
|
|
|
| Métrique | Actuel | Cible | Mesure |
|
|
|----------|--------|-------|--------|
|
|
| **Searches per Session** | 2.3 | 4.0 | Via SEARCH_EXECUTED events |
|
|
| **Graph View Engagement** | 15% users | 40% | Via GRAPH_VIEW_OPEN events |
|
|
| **Bookmark Usage** | 8% users | 25% | Via BOOKMARKS_MODIFY events |
|
|
| **Session Duration** | 3.2min | 8min | Via APP_START → APP_STOP |
|
|
| **Bounce Rate (no interaction)** | 35% | <20% | First event within 30s |
|
|
|
|
---
|
|
|
|
## 4. COMMANDES À EXÉCUTER (Vérification & Bench)
|
|
|
|
### Performance Benchmark
|
|
|
|
**Lighthouse CI (automatisé):**
|
|
```bash
|
|
# Install
|
|
npm install -g @lhci/cli
|
|
|
|
# Run Lighthouse on dev server
|
|
ng serve &
|
|
sleep 5
|
|
lhci autorun --config=.lighthouserc.json
|
|
|
|
# Expected output:
|
|
# ✅ TTI: <2.5s
|
|
# ✅ FCP: <1.5s
|
|
# ✅ Performance Score: >85
|
|
```
|
|
|
|
**`.lighthouserc.json`:**
|
|
```json
|
|
{
|
|
"ci": {
|
|
"collect": {
|
|
"url": ["http://localhost:3000"],
|
|
"numberOfRuns": 3
|
|
},
|
|
"assert": {
|
|
"assertions": {
|
|
"categories:performance": ["error", {"minScore": 0.85}],
|
|
"first-contentful-paint": ["error", {"maxNumericValue": 1500}],
|
|
"interactive": ["error", {"maxNumericValue": 2500}],
|
|
"cumulative-layout-shift": ["error", {"maxNumericValue": 0.1}]
|
|
}
|
|
},
|
|
"upload": {
|
|
"target": "temporary-public-storage"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Bundle Analysis
|
|
|
|
```bash
|
|
# Build with stats
|
|
ng build --configuration=production --stats-json
|
|
|
|
# Analyze with webpack-bundle-analyzer
|
|
npx webpack-bundle-analyzer dist/stats.json
|
|
|
|
# Expected:
|
|
# ✅ Initial bundle: <1.5MB
|
|
# ✅ Vendor chunk: <800KB
|
|
# ✅ Lazy chunks: <300KB each
|
|
```
|
|
|
|
---
|
|
|
|
### Search Performance Test
|
|
|
|
**Script: `scripts/bench-search.ts`**
|
|
```typescript
|
|
import { performance } from 'perf_hooks';
|
|
|
|
async function benchSearch() {
|
|
const queries = [
|
|
'tag:#project',
|
|
'path:folder1/ important',
|
|
'file:home -tag:#archive',
|
|
'has:attachment task:TODO'
|
|
];
|
|
|
|
const results = [];
|
|
|
|
for (const query of queries) {
|
|
const start = performance.now();
|
|
|
|
const response = await fetch('http://localhost:4000/api/search', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ query, vaultId: 'primary' })
|
|
});
|
|
|
|
const data = await response.json();
|
|
const duration = performance.now() - start;
|
|
|
|
results.push({
|
|
query,
|
|
duration,
|
|
hits: data.estimatedTotalHits,
|
|
serverTime: data.processingTimeMs
|
|
});
|
|
}
|
|
|
|
console.table(results);
|
|
|
|
const p95 = results.sort((a, b) => b.duration - a.duration)[Math.floor(results.length * 0.95)].duration;
|
|
console.log(`\n✅ Search P95: ${p95.toFixed(2)}ms (target: <150ms)`);
|
|
}
|
|
|
|
benchSearch();
|
|
```
|
|
|
|
**Exécution:**
|
|
```bash
|
|
npx ts-node scripts/bench-search.ts
|
|
|
|
# Expected output:
|
|
# ┌─────────┬──────────────────────────────┬──────────┬──────┬────────────┐
|
|
# │ (index) │ query │ duration │ hits │ serverTime │
|
|
# ├─────────┼──────────────────────────────┼──────────┼──────┼────────────┤
|
|
# │ 0 │ 'tag:#project' │ 48.2 │ 23 │ 12.5 │
|
|
# │ 1 │ 'path:folder1/ important' │ 52.7 │ 8 │ 15.8 │
|
|
# │ 2 │ 'file:home -tag:#archive' │ 45.3 │ 1 │ 10.2 │
|
|
# │ 3 │ 'has:attachment task:TODO' │ 61.5 │ 5 │ 18.9 │
|
|
# └─────────┴──────────────────────────────┴──────────┴──────┴────────────┘
|
|
# ✅ Search P95: 61.5ms (target: <150ms)
|
|
```
|
|
|
|
---
|
|
|
|
### E2E Tests Performance
|
|
|
|
**Playwright config additions:**
|
|
```typescript
|
|
// playwright.config.ts
|
|
export default defineConfig({
|
|
use: {
|
|
trace: 'retain-on-failure',
|
|
video: 'on-first-retry',
|
|
},
|
|
reporter: [
|
|
['html'],
|
|
['json', { outputFile: 'test-results/results.json' }]
|
|
],
|
|
timeout: 30000,
|
|
expect: {
|
|
timeout: 5000
|
|
}
|
|
});
|
|
```
|
|
|
|
**Run E2E with performance assertions:**
|
|
```bash
|
|
npx playwright test --reporter=html
|
|
|
|
# Expected:
|
|
# ✅ search-performance.spec.ts (4/4 passed)
|
|
# - Search 500 notes completes in <150ms
|
|
# - No main thread freeze >100ms
|
|
# - UI remains interactive during search
|
|
# - Virtual scroll renders without CLS
|
|
```
|
|
|
|
---
|
|
|
|
### Docker Image Size Verification
|
|
|
|
```bash
|
|
# Build optimized image
|
|
docker build -f docker/Dockerfile -t obsiviewer:optimized .
|
|
|
|
# Check size
|
|
docker images obsiviewer:optimized
|
|
|
|
# Expected:
|
|
# REPOSITORY TAG SIZE
|
|
# obsiviewer optimized 145MB (vs 450MB before)
|
|
|
|
# Verify healthcheck
|
|
docker run -d -p 4000:4000 --name test obsiviewer:optimized
|
|
sleep 10
|
|
docker inspect --format='{{.State.Health.Status}}' test
|
|
|
|
# Expected: healthy
|
|
```
|
|
|
|
---
|
|
|
|
### Security Scan
|
|
|
|
```bash
|
|
# XSS payload tests
|
|
npm run test:e2e -- e2e/security-xss.spec.ts
|
|
|
|
# CSP violations check
|
|
curl -I http://localhost:4000 | grep -i "content-security-policy"
|
|
|
|
# Expected:
|
|
# content-security-policy: default-src 'self'; script-src 'self' 'unsafe-eval'; ...
|
|
|
|
# npm audit
|
|
npm audit --production
|
|
|
|
# Expected:
|
|
# found 0 vulnerabilities
|
|
```
|
|
|
|
---
|
|
|
|
### Meilisearch Index Stats
|
|
|
|
```bash
|
|
# Check index health
|
|
curl http://localhost:7700/indexes/vault_primary/stats \
|
|
-H "Authorization: Bearer masterKey"
|
|
|
|
# Expected response:
|
|
{
|
|
"numberOfDocuments": 823,
|
|
"isIndexing": false,
|
|
"fieldDistribution": {
|
|
"title": 823,
|
|
"content": 823,
|
|
"tags": 645,
|
|
"path": 823
|
|
}
|
|
}
|
|
|
|
# Test search latency
|
|
curl -X POST http://localhost:7700/indexes/vault_primary/search \
|
|
-H "Authorization: Bearer masterKey" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"q":"project","limit":50}' \
|
|
-w "\nTime: %{time_total}s\n"
|
|
|
|
# Expected: Time: 0.035s (<50ms)
|
|
```
|
|
|
|
---
|
|
|
|
## 5. DASHBOARD MÉTRIQUES (Grafana/Custom)
|
|
|
|
**Panels recommandés:**
|
|
|
|
1. **Search Performance**
|
|
- P50/P95/P99 latency (line chart)
|
|
- Error rate (gauge)
|
|
- Queries per minute (counter)
|
|
|
|
2. **Graph Interactions**
|
|
- Freeze events count (bar chart)
|
|
- Node click → selection latency (histogram)
|
|
- Viewport FPS (line chart)
|
|
|
|
3. **Frontend Vitals**
|
|
- LCP, FID, CLS (timeseries)
|
|
- Bundle size evolution (area chart)
|
|
- Memory heap (line chart)
|
|
|
|
4. **Backend Health**
|
|
- /api/vault response time (line chart)
|
|
- Meilisearch indexing status (state timeline)
|
|
- Log ingestion rate (counter)
|
|
|
|
5. **User Engagement**
|
|
- Active sessions (gauge)
|
|
- Feature adoption (pie chart: search/graph/bookmarks/calendar)
|
|
- Session duration distribution (histogram)
|
|
|
|
**Exemple config Prometheus + Grafana:**
|
|
```yaml
|
|
# docker-compose.yml additions
|
|
services:
|
|
prometheus:
|
|
image: prom/prometheus
|
|
volumes:
|
|
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
|
|
ports:
|
|
- "9090:9090"
|
|
|
|
grafana:
|
|
image: grafana/grafana
|
|
ports:
|
|
- "3001:3000"
|
|
environment:
|
|
- GF_SECURITY_ADMIN_PASSWORD=admin
|
|
volumes:
|
|
- ./monitoring/grafana-dashboards:/var/lib/grafana/dashboards
|
|
```
|
|
|
|
---
|
|
|
|
## 6. CRITÈRES DE SUCCÈS GLOBAUX
|
|
|
|
### Phase 1 (Semaine 1-2) ✅
|
|
- [ ] Lighthouse Performance Score: **>85**
|
|
- [ ] Search P95: **<150ms** (1000 notes)
|
|
- [ ] TTI: **<2.5s**
|
|
- [ ] Aucune vulnérabilité XSS détectée
|
|
- [ ] Main thread freeze: **<100ms** sur toutes interactions
|
|
- [ ] `/api/log` opérationnel avec rotation
|
|
|
|
### Phase 2 (Semaine 3-4) ✅
|
|
- [ ] Lighthouse Performance Score: **>90**
|
|
- [ ] Image Docker: **<150MB**
|
|
- [ ] Offline support: app charge depuis cache
|
|
- [ ] CSP headers configurés, score Mozilla Observatory: **A+**
|
|
- [ ] Tests E2E coverage: **>60%**
|
|
- [ ] Bundle budgets respectés (no warnings)
|
|
|
|
### Phase 3 (Semaine 5+) ✅
|
|
- [ ] Initial bundle: **<800KB**
|
|
- [ ] Search P95: **<50ms**
|
|
- [ ] Graph interaction P95: **<50ms**
|
|
- [ ] Cache hit rate: **>80%**
|
|
- [ ] Memory steady state: **<60MB**
|
|
|
|
---
|
|
|
|
## 7. COMMANDES QUOTIDIENNES (CI/CD)
|
|
|
|
**Pre-commit:**
|
|
```bash
|
|
npm run lint
|
|
npm run test:unit
|
|
```
|
|
|
|
**Pre-push:**
|
|
```bash
|
|
npm run build
|
|
npm run test:e2e
|
|
```
|
|
|
|
**CI Pipeline (GitHub Actions exemple):**
|
|
```yaml
|
|
name: CI
|
|
|
|
on: [push, pull_request]
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
- uses: actions/setup-node@v3
|
|
with:
|
|
node-version: '20'
|
|
- run: npm ci
|
|
- run: npm run lint
|
|
- run: npm run test:unit
|
|
- run: npm run build
|
|
- run: npx lhci autorun
|
|
- run: npm run test:e2e
|
|
|
|
security:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
- run: npm audit --production
|
|
- run: npm run test:e2e -- e2e/security-xss.spec.ts
|
|
|
|
docker:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
- run: docker build -t obsiviewer:${{ github.sha }} .
|
|
- run: |
|
|
SIZE=$(docker images obsiviewer:${{ github.sha }} --format "{{.Size}}")
|
|
echo "Image size: $SIZE"
|
|
# Fail if >200MB
|
|
```
|
|
|
|
---
|
|
|
|
**FIN DU PLAN D'EXÉCUTION**
|
|
|
|
Toutes les métriques, commandes et critères sont prêts à être appliqués immédiatement.
|