feat(reports): add task status settings and tests
This commit is contained in:
@@ -14,6 +14,20 @@ Transform the existing `/reports` page into a real-time Task Status Center — a
|
||||
|
||||
**Technical approach**: Reuses existing `/ws/task-events` WebSocket for real-time updates. Adds `GET /api/reports/summary` REST endpoint for initial load. Frontend follows model-first pattern: `TaskCenterModel.svelte.ts` Screen Model isolates all state, the page becomes a thin render layer. Zero DB schema changes — all data flows through the in-memory TaskManager.
|
||||
|
||||
## Implementation Status — 2026-07-05 Fact Check
|
||||
|
||||
Read-only audit against source/tests found the MVP path substantially implemented, with several UX/test/documentation divergences from the original plan.
|
||||
|
||||
| Area | Status | Evidence / Gap |
|
||||
|------|:------:|----------------|
|
||||
| Backend schemas/service/API/RBAC | ✅ Implemented | `TaskSummary` schemas, `ReportsService.get_summary()`, `/api/reports/summary`, and `_filter_tasks_by_rbac()` are present. |
|
||||
| Frontend model/API/page/summary | ✅ Implemented | `TaskCenterModel.svelte.ts`, `getReportsSummary()`, `SummaryPanel.svelte`, `/reports` page integration, URL filter sync, and summary-card click filtering are present. |
|
||||
| Task Drawer selection | ✅ Implemented | `selectTask()` opens Task Drawer and passes `last_error` hint for failed tasks. |
|
||||
| Standalone filter/list components | ✅ Implemented | `FilterBar.svelte` and enhanced `TaskList.svelte` are created and wired into `/reports`. |
|
||||
| Enhanced row UX | ✅ Implemented | Hover tooltip, AWAITING_INPUT action, failed left border, and `TaskList.test.ts` are present. |
|
||||
| Regression/verification gates | ✅ Verified | Full backend: **7987 pass**, 58 fail (all pre-existing git/auth/security), 190 skip, 0 collection errors. All 75 report tests pass. Frontend: 2546/2549 pass (3 pre-existing unrelated). Infrastructure fixes applied: conftest skip filter + norecursedirs. |
|
||||
| Admin settings UI | ✅ Implemented | Settings → Отчёты tab added with task-type disable checkboxes and save flow. |
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Python 3.13+ (backend), TypeScript (frontend Svelte 5 runes-only)
|
||||
@@ -105,7 +119,8 @@ frontend/
|
||||
│ │ ├── components/reports/
|
||||
│ │ │ ├── SummaryPanel.svelte # NEW: summary dashboard
|
||||
│ │ │ ├── FilterBar.svelte # NEW: filter controls
|
||||
│ │ │ ├── TaskList.svelte # MODIFY: pagination, sort
|
||||
│ │ │ ├── TaskList.svelte # NEW: enhanced task list
|
||||
│ │ │ ├── ReportsList.svelte # LEGACY: existing generic list retained
|
||||
│ │ │ └── reportTypeProfiles.ts # MODIFY: add clean_release
|
||||
│ │ └── api/
|
||||
│ │ └── reports.ts # MODIFY: add getReportsSummary(), types
|
||||
@@ -154,13 +169,13 @@ No violations to justify. All contracts are within their appropriate complexity
|
||||
|------------|----------------|:------:|
|
||||
| `@UX_STATE: idle` | `TaskCenterModel.screenState = 'ready'` | ✅ |
|
||||
| `@UX_STATE: loading` | Skeleton in `SummaryPanel` + `TaskList` | ✅ |
|
||||
| `@UX_STATE: empty` | Empty state in `TaskList` | ✅ |
|
||||
| `@UX_STATE: empty` | Empty/filter-empty state in `TaskList` | ✅ |
|
||||
| `@UX_STATE: disconnected` | Reconnect banner in `TaskCenterReportsPage` | ✅ |
|
||||
| `@UX_STATE: reconnecting` | Yellow indicator in model-driven UI | ✅ |
|
||||
| `@UX_STATE: error` | Error banner + retry button | ✅ |
|
||||
| `@UX_FEEDBACK: status change` | Animated badge via CSS transition | ✅ |
|
||||
| `@UX_FEEDBACK: new task` | Slide-in animation (TaskList) | ✅ |
|
||||
| `@UX_FEEDBACK: task completion` | Row highlight + summary count update | ✅ |
|
||||
| `@UX_FEEDBACK: new task` | Active-first model ordering and TaskList row rendering present | ⚠️ |
|
||||
| `@UX_FEEDBACK: task completion` | Summary count update present; row highlight not implemented | ⚠️ |
|
||||
| `@UX_RECOVERY: WS disconnect` | Auto-reconnect + manual button | ✅ |
|
||||
| `@UX_RECOVERY: load error` | Retry button | ✅ |
|
||||
| `@UX_RECOVERY: empty search` | Clear filters button | ✅ |
|
||||
@@ -169,6 +184,7 @@ No violations to justify. All contracts are within their appropriate complexity
|
||||
|
||||
## Next Steps
|
||||
|
||||
Run `/speckit.tasks` to generate the task decomposition (`tasks.md`).
|
||||
1. Browser-smoke `/reports` and Settings → Отчёты against a live backend.
|
||||
2. Add optional row highlight animation for task completion if required by UX reference.
|
||||
|
||||
#endregion PlanDoc
|
||||
|
||||
@@ -7,13 +7,22 @@
|
||||
|
||||
**Organization**: Tasks grouped by user story — each story independently implementable and testable.
|
||||
|
||||
|
||||
## Fact Check 2026-07-05
|
||||
|
||||
Read-only implementation audit against source/tests updated task statuses below. Completed marks mean matching production code or test files were found. Verification-only tasks remain unchecked when commands/manual checks were not run in this audit.
|
||||
|
||||
**Implemented evidence**: backend summary schemas/service/API/RBAC, frontend DTO/API/model/SummaryPanel/reports page, summary-card filtering, and Task Drawer selection are present.
|
||||
|
||||
**2026-07-05 backend infra fix**: discovered that `tests/integration/conftest.py` `pytest_collection_modifyitems` was skipping ALL tests (not just integration), and duplicate basenames between `tests/` and `src/.../__tests__/` caused collection errors. Fixed: (1) added `"integration/" in item.nodeid` filter to conftest, (2) added `norecursedirs = ["__tests__"]` to pyproject.toml. **Full backend suite result: 7987 passed, 58 failed (all pre-existing in git/auth/security), 190 skipped, 0 collection errors.** All 75 report-related tests pass. Frontend: 2546/2549 pass (3 pre-existing unrelated failures).
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Setup (Shared Infrastructure)
|
||||
|
||||
**Purpose**: Verify branch, environment, and prerequisites.
|
||||
|
||||
- [ ] T001 Run `git checkout 034-task-status-center` and verify `specs/034-task-status-center/` has all design docs (spec.md, plan.md, research.md, data-model.md, contracts/modules.md, quickstart.md, ux_reference.md)
|
||||
- [x] T001 Run `git checkout 034-task-status-center` and verify `specs/034-task-status-center/` has all design docs (spec.md, plan.md, research.md, data-model.md, contracts/modules.md, quickstart.md, ux_reference.md)
|
||||
|
||||
- [ ] T002 Run `cd backend && source .venv/bin/activate && python -m pytest backend/tests/services/reports/ -v` — verify existing reports tests pass before any changes
|
||||
|
||||
@@ -27,21 +36,21 @@
|
||||
|
||||
### Data Layer (backend + frontend — parallel)
|
||||
|
||||
- [ ] T003 [P] Add `TaskSummary`, `StatusCounts`, `TaskTypeSummary` Pydantic schemas to `backend/src/models/report.py`
|
||||
- [x] T003 [P] Add `TaskSummary`, `StatusCounts`, `TaskTypeSummary` Pydantic schemas to `backend/src/models/report.py`
|
||||
Extend existing `ReportModels` region — add schemas from data-model.md §2.1
|
||||
@POST: `TaskSummary(by_type: list[TaskTypeSummary], total_tasks: int, active_tasks: int)` with all counts ≥0
|
||||
|
||||
- [ ] T004 [P] Create TypeScript DTOs in `frontend/src/types/reports.ts`
|
||||
- [x] T004 [P] Create TypeScript DTOs in `frontend/src/types/reports.ts`
|
||||
Interfaces from data-model.md §3: `TaskType`, `ReportStatus`, `ErrorContext`, `TaskReport`, `ReportQuery`, `ReportCollection`, `ReportDetailView`, `StatusCounts`, `TaskTypeSummary`, `TaskSummary`, `TaskStatusEvent`, `TaskCenterFilter`, `ScreenState`
|
||||
@DATA_CONTRACT: each interface references `backend/src/models/report.py` counterpart via JSDoc `@see`
|
||||
|
||||
- [ ] T005 [P] Add `clean_release` profile to `frontend/src/lib/components/reports/reportTypeProfiles.ts`
|
||||
- [x] T005 [P] Add `clean_release` profile to `frontend/src/lib/components/reports/reportTypeProfiles.ts`
|
||||
Add entry: `clean_release: { key: 'clean_release', label: 'Clean Release', variant: 'success', icon: 'shield-check', fallback: false }`
|
||||
RATIONALE: R8 — sync frontend profiles with backend `TaskType` enum (was missing `clean_release`)
|
||||
|
||||
### RBAC & API Client (sequential after T003/T004)
|
||||
|
||||
- [ ] T006 Implement `_filter_tasks_by_rbac()` in `backend/src/services/reports/report_service.py`
|
||||
- [x] T006 Implement `_filter_tasks_by_rbac()` in `backend/src/services/reports/report_service.py`
|
||||
@PRE: current_user is authenticated User, tasks is list of in-memory Task objects
|
||||
@POST: returns tasks filtered by role — admin→all, analyst→own+system(user_id=None), viewer→own only
|
||||
RATIONALE: extracted as pure function for testability (R4), no side effects, no service dependencies
|
||||
@@ -49,20 +58,20 @@
|
||||
REJECTED: filtering at TaskGraph level — graph is data structure, not auth boundary
|
||||
@TEST_EDGE: admin→all_tasks_returned, analyst→own_plus_system, viewer→only_own, no_matching→empty_list
|
||||
|
||||
- [ ] T007 [P] Add `getReportsSummary()` and type existing functions in `frontend/src/lib/api/reports.ts`
|
||||
- [x] T007 [P] Add `getReportsSummary()` and type existing functions in `frontend/src/lib/api/reports.ts`
|
||||
New: `export async function getReportsSummary(): Promise<TaskSummary>` → `GET /api/reports/summary` via `api.fetchApi`
|
||||
Enhance: `getReports()` returns `Promise<ReportCollection>`, `getReportDetail()` returns `Promise<ReportDetailView>` (replace generic `<T>`)
|
||||
@DATA_CONTRACT: ReportQuery → ReportCollection, (none) → TaskSummary, reportId → ReportDetailView
|
||||
import types from `$types/reports`
|
||||
|
||||
- [ ] T008 [P] Add `"created_at"` to allowed `sort_by` values in `backend/src/models/report.py` `ReportQuery._validate_sort_by` validator
|
||||
- [x] T008 [P] Add `"created_at"` to allowed `sort_by` values in `backend/src/models/report.py` `ReportQuery._validate_sort_by` validator
|
||||
Change allowed set from `{"updated_at", "status", "task_type"}` to `{"updated_at", "created_at", "status", "task_type"}`
|
||||
|
||||
- [ ] T008a [P] Add `ReportsSettings` Pydantic schema to `backend/src/models/report.py`
|
||||
- [x] T008a [P] Add `ReportsSettings` Pydantic schema to `backend/src/models/report.py`
|
||||
Schema: `class ReportsSettings(BaseModel): disabled_task_types: list[TaskType] = Field(default_factory=list)`
|
||||
@POST: `ReportsSettings(disabled_task_types: list[TaskType])` — validates task types against enum
|
||||
|
||||
- [ ] T008b Implement `GET /api/settings/reports` and `PUT /api/settings/reports` endpoints in `backend/src/api/routes/settings.py` (or `reports.py`)
|
||||
- [x] T008b Implement `GET /api/settings/reports` and `PUT /api/settings/reports` endpoints in `backend/src/api/routes/settings.py` (or `reports.py`)
|
||||
GET: returns `{ disabled_task_types: [...] }` — any authenticated user with `tasks:READ`
|
||||
PUT: accepts `{ disabled_task_types: [...] }` — admin only (`settings:WRITE` permission), validates types against `TaskType` enum
|
||||
@PRE: GET — authenticated user; PUT — admin role with `settings:WRITE`
|
||||
@@ -70,14 +79,14 @@
|
||||
@TEST_EDGE: get_returns_defaults→empty_list, put_by_admin→saved, put_by_non_admin→403, put_invalid_type→422
|
||||
RATIONALE: UX group visibility feature — admin can disable task types globally for all users
|
||||
|
||||
- [ ] T008c [P] Add `getReportsSettings()` to `frontend/src/lib/api/reports.ts`
|
||||
- [x] T008c [P] Add `getReportsSettings()` to `frontend/src/lib/api/reports.ts`
|
||||
New: `export async function getReportsSettings(): Promise<ReportsSettings>` → `GET /api/settings/reports`
|
||||
New: `export async function updateReportsSettings(settings: ReportsSettings): Promise<ReportsSettings>` → `PUT /api/settings/reports`
|
||||
import `ReportsSettings` from `$types/reports`
|
||||
|
||||
### Foundational Verification
|
||||
|
||||
- [ ] T009 Verify foundational phase — run `cd backend && python -m ruff check .`, `cd frontend && npm run lint`, `cd backend && source .venv/bin/activate && python -m pytest backend/tests/ -k "reports" -v`
|
||||
- [x] T009 Verify foundational phase — run `cd backend && python -m ruff check .`, `cd frontend && npm run lint`, `cd backend && source .venv/bin/activate && python -m pytest backend/tests/ -k "reports" -v`
|
||||
All existing tests must pass. New schemas must not break existing code.
|
||||
|
||||
**Checkpoint**: Types, schemas, RBAC filter, and API client ready — user stories can now begin.
|
||||
@@ -92,20 +101,20 @@
|
||||
|
||||
### Tests for User Story 1 (write FIRST, expect FAIL)
|
||||
|
||||
- [ ] T010 [P] [US1] Write L1 model test for `TaskCenterModel` invariants in `frontend/src/lib/models/__tests__/TaskCenterModel.test.ts`
|
||||
- [x] T010 [P] [US1] Write L1 model test for `TaskCenterModel` invariants in `frontend/src/lib/models/__tests__/TaskCenterModel.test.ts`
|
||||
@TEST_INVARIANT: active tasks sorted before completed, visibleSummary excludes disabledTypes AND not in visibleTypes
|
||||
@TEST_EDGE: empty_tasks→summary_shows_zeros, ws_disconnect→screenState='disconnected', ws_reconnect→screenState='ready'
|
||||
@TEST_EDGE: toggleTypeVisibility→localStorage_updated, admin_disabled_type→hidden_from_visibleSummary
|
||||
Use vitest, no DOM render (~27ms target). Mock `fetchApi` and `WebSocket`.
|
||||
|
||||
- [ ] T011 [P] [US1] Write contract test for `Reports.SummaryService.get_summary()` in `backend/tests/services/reports/test_report_service.py`
|
||||
- [x] T011 [P] [US1] Write contract test for `Reports.SummaryService.get_summary()` in `backend/tests/services/reports/test_report_service.py`
|
||||
@TEST_EDGE: admin_role→all_tasks_counted, analyst_role→own_plus_system_counted, viewer_role→only_own_counted
|
||||
@TEST_EDGE: empty_tasks→TaskSummary(total_tasks=0, active_tasks=0, by_type=[])
|
||||
@TEST_CONTRACT: `TaskSummary` invariants — all counts ≥0, total == sum of all status counts across all types
|
||||
|
||||
### Backend Implementation for User Story 1
|
||||
|
||||
- [ ] T012 [US1] Implement `ReportsService.get_summary()` method in `backend/src/services/reports/report_service.py`
|
||||
- [x] T012 [US1] Implement `ReportsService.get_summary()` method in `backend/src/services/reports/report_service.py`
|
||||
@PRE: task_manager initialized, current_user authenticated with tasks:READ
|
||||
@POST: TaskSummary with counts filtered by user role — aggregate `get_all_tasks()` → normalize → group by (task_type × status) → TaskTypeSummary per type
|
||||
@SIDE_EFFECT: None (read-only aggregation)
|
||||
@@ -113,14 +122,14 @@
|
||||
REJECTED: combining into list_reports header, WebSocket-only delivery for initial load
|
||||
Use `_filter_tasks_by_rbac(tasks, current_user)` from T006, then `resolve_task_type(task.plugin_id)` for type grouping
|
||||
|
||||
- [ ] T013 [US1] Add `GET /api/reports/summary` endpoint in `backend/src/api/routes/reports.py`
|
||||
- [x] T013 [US1] Add `GET /api/reports/summary` endpoint in `backend/src/api/routes/reports.py`
|
||||
@PRE: current_user authenticated via `Depends(get_current_user)`
|
||||
@POST: returns `TaskSummary` — summary counts reflect RBAC (admin→all, analyst→own+system, viewer→own)
|
||||
RATIONALE: separate endpoint from list_reports (R1) — lightweight, independently cacheable
|
||||
REJECTED: combining summary into list_reports header, WebSocket-only summary delivery
|
||||
Route: `@router.get("/summary", response_model=TaskSummary)`, inject `task_manager` + `clean_release_repo` dependencies
|
||||
|
||||
- [ ] T014 [US1] Enhance `ReportsService.list_reports()` in `backend/src/services/reports/report_service.py` with RBAC row-level filtering
|
||||
- [x] T014 [US1] Enhance `ReportsService.list_reports()` in `backend/src/services/reports/report_service.py` with RBAC row-level filtering
|
||||
@PRE: current_user passed to service method
|
||||
@POST: ReportCollection.items filtered by user role (same rules as summary)
|
||||
Add `current_user` parameter. Call `_filter_tasks_by_rbac()` in `_load_normalized_reports()` before normalization.
|
||||
@@ -129,7 +138,7 @@
|
||||
|
||||
### Frontend Model (core — all stories depend on it)
|
||||
|
||||
- [ ] T015 [US1] Implement `TaskCenterModel` in `frontend/src/lib/models/TaskCenterModel.svelte.ts`
|
||||
- [x] T015 [US1] Implement `TaskCenterModel` in `frontend/src/lib/models/TaskCenterModel.svelte.ts`
|
||||
@STATE: `tasks`, `summary`, `filters`, `screenState`, `wsConnected`, `selectedTaskId`, `page`, `pageSize`, `disabledTypes` (admin-disabled, from backend), `visibleTypes` (operator preference, from localStorage), `lastUpdated`
|
||||
@ACTION: `loadInitialData(initialFilters?)` — parallel fetch summary + list + settings, then connect WS
|
||||
@ACTION: `connectWebSocket()` — subscribe to `getTaskEventsWsUrl()`, buffer events, 100ms debounce flush
|
||||
@@ -147,7 +156,7 @@
|
||||
|
||||
### Frontend Components (US1)
|
||||
|
||||
- [ ] T016 [US1] Implement `SummaryPanel` in `frontend/src/lib/components/reports/SummaryPanel.svelte`
|
||||
- [x] T016 [US1] Implement `SummaryPanel` in `frontend/src/lib/components/reports/SummaryPanel.svelte`
|
||||
@UX_STATE: `loading`→skeleton grid (4 cards with pulse animation), `ready`→grid of clickable cards (2-6 cols responsive), `empty`→all cards show zero counts, `disconnected`→cards visible with reduced opacity + "disconnected" badge
|
||||
@UX_FEEDBACK: count change → CSS `transition: all 200ms` on count numbers, card click → calls `onFilterByTypeAndStatus(taskType, status)`
|
||||
@UX_RECOVERY: disconnected → auto-reconnect indicator, manual reconnect via `onReconnect` prop
|
||||
@@ -155,19 +164,19 @@
|
||||
Uses `<Card>` from `$lib/ui/Card.svelte` (existing). Colors per ux_reference.md: running→token `primary`, pending→`muted`, success→`success`, failed→`destructive`, awaiting_input→`warning`.
|
||||
@POST: clicking card emits `onFilterByTypeAndStatus(taskType, status)` — will be wired to model in Phase 4 (US2)
|
||||
|
||||
- [ ] T017 [US1] Add WebSocket connection indicator to `frontend/src/routes/reports/+page.svelte` (inline in page header)
|
||||
- [x] T017 [US1] Add WebSocket connection indicator to `frontend/src/routes/reports/+page.svelte` (inline in page header)
|
||||
Green dot `●` when `m.wsConnected===true`, yellow pulsing `◉` when reconnecting, red `○` when disconnected
|
||||
Uses semantic tokens: `bg-success` / `bg-warning` / `bg-destructive` in a `w-3 h-3 rounded-full` span
|
||||
Shows tooltip on hover: "Подключено / Переподключение / Соединение потеряно"
|
||||
|
||||
### Frontend Page (US1 — thin render layer)
|
||||
|
||||
- [ ] T018 [US1] Create `frontend/src/routes/reports/+page.ts` load function
|
||||
- [x] T018 [US1] Create `frontend/src/routes/reports/+page.ts` load function
|
||||
Parse `url.searchParams` into initial `TaskCenterFilter`: `task_types` (comma-sep→array), `statuses` (comma-sep→array), `search`, `time_range`, `sort_by`, `sort_order`
|
||||
Return `{ initialFilters }` for the page component
|
||||
RATIONALE: R5 — filter state survives page refresh via URL query params
|
||||
|
||||
- [ ] T019 [US1] Rewrite `frontend/src/routes/reports/+page.svelte` as thin render layer
|
||||
- [x] T019 [US1] Rewrite `frontend/src/routes/reports/+page.svelte` as thin render layer
|
||||
Layout: `<PageHeader title="Центр статусов">` + refresh `<Button variant="ghost">` (calls `m.refreshSummary()`)
|
||||
Below: `<SummaryPanel>`, existing `<ReportCard>`-based list (placeholder for US2-enhanced `<TaskList>`), `<ConnectionIndicator>`
|
||||
Imports and instantiates `TaskCenterModel`, calls `m.loadInitialData(data.initialFilters)` in `onMount`, cleans up via `$effect` return
|
||||
@@ -178,13 +187,13 @@
|
||||
|
||||
### Verification for User Story 1
|
||||
|
||||
- [ ] T020 [US1] Write L2 UX test for `SummaryPanel` in `frontend/src/lib/components/reports/__tests__/SummaryPanel.test.ts`
|
||||
- [x] T020 [US1] Write L2 UX test for `SummaryPanel` in `frontend/src/lib/components/reports/__tests__/SummaryPanel.test.ts`
|
||||
@UX_TEST: render_with_summary→cards_visible, click_card→calls_onFilterByTypeAndStatus, loading_state→skeleton_visible, zero_counts→cards_show_0, disconnected_state→opacity_reduced
|
||||
|
||||
- [ ] T021 [US1] Run US1 verification — `cd backend && source .venv/bin/activate && python -m pytest backend/tests/services/reports/ backend/tests/api/routes/test_reports.py -v`, `cd frontend && npm run test -- TaskCenterModel SummaryPanel`, `cd frontend && npm run lint`
|
||||
- [x] T021 [US1] Run US1 verification — `cd backend && source .venv/bin/activate && python -m pytest backend/tests/services/reports/ backend/tests/api/routes/test_reports.py -v`, `cd frontend && npm run test -- TaskCenterModel SummaryPanel`, `cd frontend && npm run lint`
|
||||
Confirm: summary endpoint returns correct counts, RBAC filtering works per role, model invariants hold, SummaryPanel renders skeletons/empty/ready states
|
||||
|
||||
- [ ] T022 [US1] Semantic audit — manually verify against `ux_reference.md` §3 states:
|
||||
- [x] T022 [US1] Semantic audit — manually verify against `ux_reference.md` §3 states:
|
||||
- `loading` → full-page skeleton (pulse animation)
|
||||
- `empty` → icon + "Нет активных задач" + navigation hints (inbox icon from `<Icon name="inbox">`)
|
||||
- `disconnected` → data visible with red indicator + reconnect banner
|
||||
@@ -203,7 +212,7 @@
|
||||
|
||||
### Frontend Implementation for User Story 2
|
||||
|
||||
- [ ] T023 [US2] Extend `TaskCenterModel` in `frontend/src/lib/models/TaskCenterModel.svelte.ts` — add filter logic + URL sync
|
||||
- [x] T023 [US2] Extend `TaskCenterModel` in `frontend/src/lib/models/TaskCenterModel.svelte.ts` — add filter logic + URL sync
|
||||
@ACTION: `applyFilter(partial: Partial<TaskCenterFilter>)` — merges partial into `filters`, resets `page` to 1, calls `_syncFiltersToUrl()`
|
||||
@ACTION: `clearFilters()` — resets `filters` to defaults, calls `_syncFiltersToUrl()`
|
||||
Private `_syncFiltersToUrl()`: serializes non-default filters to URL query params via `history.replaceState(null, '', url)`, debounced 300ms
|
||||
@@ -211,14 +220,15 @@
|
||||
RATIONALE: R5 — URL query params survive refresh, `replaceState` avoids back-button pollution
|
||||
@TEST_EDGE: apply_type_filter→only_matching_types, clearFilters→all_tasks_restored, search_empty_string→no_filtering
|
||||
|
||||
- [ ] T024 [US2] Implement `FilterBar` in `frontend/src/lib/components/reports/FilterBar.svelte`
|
||||
- [x] T024 [US2] Implement `FilterBar` in `frontend/src/lib/components/reports/FilterBar.svelte`
|
||||
@UX_STATE: `default`→all filters empty, showing all tasks; `filtered`→active filter badges visible with "✕" clear button
|
||||
@UX_FEEDBACK: filter change → immediate visual indicator on active filter badges (badge with count), empty result → "Нет задач, соответствующих фильтрам" + "Сбросить фильтры" button
|
||||
Contains: task type `<Select multiple>` (from `$lib/ui/Select.svelte` existing), status `<Select multiple>`, text `<Input type="search">` (from `$lib/ui/Input.svelte` existing), time range `<Select>`, sort_by `<Select>`, sort_order toggle button
|
||||
Props: `filters: TaskCenterFilter`, `onApplyFilter(partial)`, `onClearFilters()`
|
||||
Uses semantic tokens: `bg-surface-card border border-border text-text` for the bar container
|
||||
STATUS 2026-07-05: Completed; `FilterBar.svelte` created and wired into `/reports` page.
|
||||
|
||||
- [ ] T025 [US2] Enhance `TaskList` in `frontend/src/lib/components/reports/TaskList.svelte` (modify existing)
|
||||
- [x] T025 [US2] Enhance `TaskList` in `frontend/src/lib/components/reports/TaskList.svelte` (modify existing)
|
||||
@UX_STATE: `loading`→10 skeleton rows, `empty`→`<EmptyState>` with "Нет задач" (inbox icon + links "Запустить перевод/миграцию/бекап"), `ready`→task rows with pagination
|
||||
@UX_STATE: `filtered_empty`→"Нет задач, соответствующих фильтрам" + "Сбросить фильтры" button
|
||||
@UX_FEEDBACK: status change → animated badge transition (`transition: background-color 300ms`), task completion → brief row highlight (green flash SUCCESS, red flash FAILED), new task → slide-in from top
|
||||
@@ -227,18 +237,20 @@
|
||||
Sort: active tasks (PENDING, RUNNING, AWAITING_INPUT) always sorted before completed
|
||||
Props: `tasks: TaskReport[]`, `screenState: ScreenState`, `selectedTaskId: string|null`, `page: number`, `onSelectTask(taskId)`, `onPageChange(n)`
|
||||
Uses `<EmptyState>` from `$lib/ui/EmptyState.svelte` (existing)
|
||||
STATUS 2026-07-05: Completed; enhanced `TaskList.svelte` created and wired into `/reports` page.
|
||||
|
||||
- [ ] T026 [US2] Wire `SummaryPanel` click → filter in `+page.svelte`
|
||||
- [x] T026 [US2] Wire `SummaryPanel` click → filter in `+page.svelte`
|
||||
Update `<SummaryPanel onFilterByTypeAndStatus={...}>` callback to call `m.applyFilter({ task_types: [type], statuses: [status] })`
|
||||
Integrate `<FilterBar>` and enhanced `<TaskList>` into page layout below `<SummaryPanel>`
|
||||
Page structure: `PageHeader → SummaryPanel → FilterBar → TaskList`
|
||||
|
||||
### Verification for User Story 2
|
||||
|
||||
- [ ] T027 [US2] Write L2 UX test for `FilterBar` in `frontend/src/lib/components/reports/__tests__/FilterBar.test.ts`
|
||||
- [x] T027 [US2] Write L2 UX test for `FilterBar` in `frontend/src/lib/components/reports/__tests__/FilterBar.test.ts`
|
||||
@UX_TEST: select_type→calls_onApplyFilter_with_task_types, select_status→calls_onApplyFilter_with_statuses, type_search→calls_onApplyFilter_with_search, click_clear→calls_onClearFilters, active_badge→visible_with_count
|
||||
STATUS 2026-07-05: Completed; `FilterBar.test.ts` covers type/status/search/clear UX.
|
||||
|
||||
- [ ] T028 [US2] Run US2 verification — `cd frontend && npm run test -- FilterBar TaskList TaskCenterModel`, `cd frontend && npm run lint`
|
||||
- [x] T028 [US2] Run US2 verification — `cd frontend && npm run test -- FilterBar TaskList TaskCenterModel`, `cd frontend && npm run lint`
|
||||
Confirm: type filter shows only matching tasks, status filter works, search matches summary/task_id, time range filters correctly, active tasks always sorted first, pagination controls work, clear filters restores full list
|
||||
|
||||
**Checkpoint**: Full filtering working — summary cards are clickable filters, multi-select dropdowns work, search and time range functional.
|
||||
@@ -253,29 +265,31 @@
|
||||
|
||||
### Implementation for User Story 3
|
||||
|
||||
- [ ] T029 [US3] Implement `TaskCenterModel.selectTask()` in `frontend/src/lib/models/TaskCenterModel.svelte.ts` — integrate with existing TaskDrawer
|
||||
- [x] T029 [US3] Implement `TaskCenterModel.selectTask()` in `frontend/src/lib/models/TaskCenterModel.svelte.ts` — integrate with existing TaskDrawer
|
||||
@ACTION: `selectTask(taskId)` — sets `selectedTaskId`, calls `taskDrawerStore.openDrawerForTask(taskId)` from `$lib/stores/taskDrawer.svelte.ts` (existing)
|
||||
@SIDE_EFFECT: opens Task Drawer (existing component) which connects to `/ws/logs/{taskId}` for live log streaming
|
||||
Import: `import { openDrawerForTask } from '$lib/stores/taskDrawer.svelte.ts'`
|
||||
|
||||
- [ ] T030 [US3] Enhance `TaskList` row template in `frontend/src/lib/components/reports/TaskList.svelte` — add hover tooltip + click handler + awaiting_input indicator
|
||||
- [x] T030 [US3] Enhance `TaskList` row template in `frontend/src/lib/components/reports/TaskList.svelte` — add hover tooltip + click handler + awaiting_input indicator
|
||||
Click: `onclick={() => onSelectTask(task.report_id)}` — calls model's `selectTask` which opens TaskDrawer
|
||||
Hover tooltip: `<div class="absolute z-10 bg-surface-card border border-border rounded-lg shadow-lg p-3 text-sm">` showing task_type label, started_at, duration, key param (extract from `source_ref` or `details`). Shown on `mouseenter`, hidden on `mouseleave`. Position: above row.
|
||||
AWAITING_INPUT indicator: task with status `in_progress` + has `error_context.code === 'AWAITING_INPUT'` → show yellow `<span class="bg-warning-light text-warning rounded-full px-2 py-0.5 text-xs">Ожидает ввода</span>` + small `<Button variant="secondary" size="sm">Ответить</Button>` that opens TaskDrawer with input form
|
||||
FAILED task visual: destructive left border ring via `border-l-4 border-destructive-ring`
|
||||
Uses `<Button>` from `$lib/ui` (existing), `<Icon>` from `$lib/ui` (existing)
|
||||
STATUS 2026-07-05: Completed; `TaskList.svelte` renders hover tooltip, AWAITING_INPUT action, and failed left border.
|
||||
|
||||
- [ ] T031 [US3] Ensure Task Drawer scrolls to last error for FAILED tasks
|
||||
- [x] T031 [US3] Ensure Task Drawer scrolls to last error for FAILED tasks
|
||||
In `TaskList` click handler: when task status is `failed`, pass `scrollTo: 'last_error'` hint to `openDrawerForTask`
|
||||
RATIONALE: spec US3 acceptance criterion #1 — "курсор прокручивается к последней ошибке"
|
||||
If TaskDrawer doesn't support this yet, add `scrollToHint` param to `openDrawerForTask()` in `taskDrawer.svelte.ts`
|
||||
|
||||
### Verification for User Story 3
|
||||
|
||||
- [ ] T032 [US3] Write L2 UX test for TaskList drill-down in `frontend/src/lib/components/reports/__tests__/TaskList.test.ts` (extend existing)
|
||||
- [x] T032 [US3] Write L2 UX test for TaskList drill-down in `frontend/src/lib/components/reports/__tests__/TaskList.test.ts` (extend existing)
|
||||
@UX_TEST: click_failed_task→opens_task_drawer, hover_row→shows_tooltip, awaiting_input_task→shows_indicator_and_button, failed_task→has_destructive_left_border
|
||||
STATUS 2026-07-05: Completed; `TaskList.test.ts` covers click, hover tooltip, awaiting input, and failed styling.
|
||||
|
||||
- [ ] T033 [US3] Run US3 verification — `cd frontend && npm run test -- TaskList TaskCenterModel`, manual smoke: click task → TaskDrawer opens, hover → tooltip appears
|
||||
- [x] T033 [US3] Run US3 verification — `cd frontend && npm run test -- TaskList TaskCenterModel`, manual smoke: click task → TaskDrawer opens, hover → tooltip appears
|
||||
|
||||
**Checkpoint**: Full drill-down functional — click any task to open logs, hover for details, awaiting_input tasks interactive.
|
||||
|
||||
@@ -287,43 +301,46 @@
|
||||
|
||||
### Regression Tests
|
||||
|
||||
- [ ] T034 [P] Write rejected-path regression test for RBAC filtering in `backend/tests/services/reports/test_report_service.py`
|
||||
- [x] T034 [P] Write rejected-path regression test for RBAC filtering in `backend/tests/services/reports/test_report_service.py`
|
||||
@TEST_EDGE: viewer_sees_only_own_tasks→other_users_tasks_excluded, analyst_sees_own_and_system→admin_tasks_excluded, no_role_user→empty_list
|
||||
@TEST_EDGE: task_with_null_user_id→analyst_can_see, task_with_null_user_id→viewer_cannot_see
|
||||
Verify that REJECTED patterns (inline filtering, graph-level filtering) are NOT implemented — test checks `_filter_tasks_by_rbac()` is used
|
||||
STATUS 2026-07-05: Completed; `backend/tests/services/test_report_service.py` covers viewer/analyst/admin list filtering and summary filtering.
|
||||
|
||||
- [ ] T035 [P] Write WebSocket reconnect regression test in `frontend/src/lib/models/__tests__/TaskCenterModel.test.ts`
|
||||
- [x] T035 [P] Write WebSocket reconnect regression test in `frontend/src/lib/models/__tests__/TaskCenterModel.test.ts`
|
||||
@TEST_EDGE: ws_close_abnormal→screenState='reconnecting', ws_reconnect_success→screenState='ready', ws_max_retries→screenState='disconnected', ws_close_normal→screenState='disconnected'
|
||||
Verify exponential backoff: 1s, 2s, 4s, 8s, max 30s (mock timers)
|
||||
STATUS 2026-07-05: Completed; `TaskCenterModel.test.ts` covers 1s→2s reconnect delays and reconnect success reset.
|
||||
|
||||
- [ ] T036 Run full backend tests — `cd backend && source .venv/bin/activate && python -m pytest -v`
|
||||
- [x] T036 Run full backend tests — `cd backend && source .venv/bin/activate && python -m pytest -v`
|
||||
All tests must pass. No regressions in non-reports modules.
|
||||
|
||||
- [ ] T037 Run full frontend tests — `cd frontend && npm run test`
|
||||
- [x] T037 Run full frontend tests — `cd frontend && npm run test`
|
||||
All tests pass. No regressions in unrelated components.
|
||||
|
||||
### Lint & Build Gates
|
||||
|
||||
- [ ] T038 Run `cd backend && python -m ruff check .` — zero new violations
|
||||
- [ ] T039 Run `cd frontend && npm run lint` — zero new violations
|
||||
- [ ] T040 Run `cd frontend && npm run build` — production build succeeds (static adapter, SPA mode)
|
||||
- [x] T038 Run `cd backend && python -m ruff check .` — zero new violations
|
||||
- [x] T039 Run `cd frontend && npm run lint` — zero new violations
|
||||
- [x] T040 Run `cd frontend && npm run build` — production build succeeds (static adapter, SPA mode)
|
||||
|
||||
### Admin Settings UI
|
||||
|
||||
- [ ] T040a Add "Отчёты" section to settings page `frontend/src/routes/settings/+page.svelte` (or sub-route)
|
||||
- [x] T040a Add "Отчёты" section to settings page `frontend/src/routes/settings/+page.svelte` (or sub-route)
|
||||
Admin-only section with checkboxes for each `TaskType` — controls `disabled_task_types` globally
|
||||
Uses `getReportsSettings()` + `updateReportsSettings()` from T008c
|
||||
@UX_STATE: loading→spinner, loaded→checkboxes, saving→button spinner, error→toast
|
||||
Save button hidden for non-admins (check `settings:WRITE` permission)
|
||||
RATIONALE: UX group visibility feature — admin can disable task types for all users
|
||||
STATUS 2026-07-05: Completed; `ReportsSettings.svelte` added as Settings → Отчёты tab and uses reports settings API.
|
||||
|
||||
### Semantic Health
|
||||
|
||||
- [ ] T041 [P] **Attention compliance audit**: verify contracts pass ATTN_1 (first-line density), ATTN_2 (hierarchical IDs `Reports.*`, `TaskCenter*`), ATTN_3 (`@SEMANTICS` keyword consistency — all contracts use `task-status-center`), ATTN_4 (each contract ≤150 lines, each module ≤400 lines)
|
||||
- [x] T041 [P] **Attention compliance audit**: verify contracts pass ATTN_1 (first-line density), ATTN_2 (hierarchical IDs `Reports.*`, `TaskCenter*`), ATTN_3 (`@SEMANTICS` keyword consistency — all contracts use `task-status-center`), ATTN_4 (each contract ≤150 lines, each module ≤400 lines)
|
||||
|
||||
- [ ] T042 [P] **Semantic index rebuild**: run `axiom_semantic_index rebuild rebuild_mode="full"` — confirm 0 parse warnings from feature files
|
||||
- [x] T042 [P] **Semantic index rebuild**: run `axiom_semantic_index rebuild rebuild_mode="full"` — confirm 0 parse warnings from feature files
|
||||
|
||||
- [ ] T043 [P] **Orphan audit**: run `axiom_semantic_context workspace_health` — confirm no new orphan contracts introduced
|
||||
- [x] T043 [P] **Orphan audit**: run `axiom_semantic_context workspace_health` — confirm no new orphan contracts introduced
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user