142 lines
9.4 KiB
Markdown
142 lines
9.4 KiB
Markdown
---
|
|
phase: 04-heatmap-interaction
|
|
verified: 2026-04-08T15:37:00Z
|
|
status: human_needed
|
|
score: 3/4 must-haves verified
|
|
gaps:
|
|
- truth: "A dropdown allows filtering the heatmap to show data for a single project or activity"
|
|
status: partial
|
|
reason: "Only project filtering is implemented. Activity filtering was explicitly descoped in 04-CONTEXT.md ('Projects only -- no activity filter') but the roadmap SC and INTR-01 both specify 'project or activity'. No later phase addresses this."
|
|
artifacts:
|
|
- path: "assets/src/heatmap.ts"
|
|
issue: "No activity filter option in dropdown, no activity query param in fetch"
|
|
- path: "Service/HeatmapService.php"
|
|
issue: "No getUserActivities() method"
|
|
- path: "Controller/HeatmapController.php"
|
|
issue: "No ?activity= query parameter handling"
|
|
missing:
|
|
- "getUserActivities() service method analogous to getUserProjects()"
|
|
- "Activity filter option in dropdown (or combined project/activity dropdown)"
|
|
- "Controller support for ?activity=N query parameter"
|
|
- "HeatmapService::getDailyAggregation() support for activityId parameter"
|
|
human_verification:
|
|
- test: "Click a heatmap day cell and verify navigation to Kimai timesheet filtered to that date"
|
|
expected: "Browser navigates to /en/timesheet/?daterange=YYYY-MM-DD%20-%20YYYY-MM-DD"
|
|
why_human: "Requires running Kimai instance with the plugin loaded to verify URL construction and Kimai's timesheet page accepting the daterange parameter"
|
|
- test: "Select a project from the filter dropdown and verify heatmap re-renders"
|
|
expected: "Heatmap cells update to show only data for the selected project, color scale adjusts"
|
|
why_human: "Requires running Kimai with real time entries across multiple projects to verify visual filtering"
|
|
- test: "Verify filter dropdown appearance and positioning"
|
|
expected: "Dropdown appears to the right of the heatmap SVG, styled with Tabler form-select classes, responsive layout"
|
|
why_human: "Visual layout verification requires browser rendering"
|
|
---
|
|
|
|
# Phase 4: Heatmap Interaction Verification Report
|
|
|
|
**Phase Goal:** Users can click through to daily details and filter the heatmap by project or activity
|
|
**Verified:** 2026-04-08T15:37:00Z
|
|
**Status:** human_needed
|
|
**Re-verification:** No -- initial verification
|
|
|
|
## Goal Achievement
|
|
|
|
### Observable Truths
|
|
|
|
| # | Truth | Status | Evidence |
|
|
|---|-------|--------|----------|
|
|
| 1 | Clicking a day cell navigates to Kimai's timesheet view filtered to that specific date | VERIFIED | `heatmap.ts` lines 217-224: `onCellClick` constructs URL with `daterange` param and assigns to `window.location.href`. Tests in `interaction.test.ts` verify URL format. |
|
|
| 2 | A dropdown allows filtering the heatmap to show data for a single project or activity | PARTIAL | Project filtering fully implemented (dropdown, fetch with `?project=N`, re-render). Activity filtering intentionally omitted per 04-CONTEXT.md line 25. Roadmap SC and INTR-01 both specify "or activity". |
|
|
| 3 | Filtering updates the heatmap in place without a full page reload | VERIFIED | `heatmap.ts` lines 256-271: `select.addEventListener('change', ...)` triggers `fetch()` then `renderHeatmap()` on the existing `svgArea` div. Tests in `filter.test.ts` verify re-render. |
|
|
| 4 | JavaScript tests verify click navigation and tooltip interaction behavior | VERIFIED | `interaction.test.ts` has 7 tests (callback, URL format, project in URL, fallback). `filter.test.ts` has 9 tests (dropdown rendering, fetch params, re-render, empty state). All 52 tests pass. |
|
|
|
|
**Score:** 3/4 truths fully verified (1 partial)
|
|
|
|
### Required Artifacts
|
|
|
|
| Artifact | Expected | Status | Details |
|
|
|----------|----------|--------|---------|
|
|
| `Service/HeatmapService.php` | getUserProjects() method | VERIFIED | Lines 54-68: queries distinct projects with IDENTITY join, returns `[{id, name}]` |
|
|
| `Widget/HeatmapWidget.php` | Injects service, getData returns projects | VERIFIED | Lines 11-12: constructor injection. Lines 40-47: getData returns projects array. No `final` keyword. |
|
|
| `Resources/views/widget/heatmap.html.twig` | data-projects and data-timesheet-url attributes | VERIFIED | Lines 9-10: both data attributes present with correct Twig expressions |
|
|
| `assets/src/heatmap.ts` | Click handler, filter dropdown, re-fetch | VERIFIED | Lines 198-201: click handler. Lines 236-276: filter dropdown. Lines 256-271: re-fetch on change. |
|
|
| `assets/src/types.ts` | ProjectOption interface | VERIFIED | Lines 22-25: `export interface ProjectOption { id: number; name: string; }` |
|
|
| `Resources/public/heatmap.css` | Click affordance, flex layout | VERIFIED | Lines 15-21: cursor:pointer, transition, pointer-events. Lines 38-58: flex wrapper layout. |
|
|
| `Resources/public/heatmap.js` | Rebuilt bundle | VERIFIED | 81,943 bytes, contains KimaiHeatmap IIFE |
|
|
| `assets/test/interaction.test.ts` | Click navigation tests | VERIFIED | 7 tests covering onCellClick callback, URL construction, project filter in URL |
|
|
| `assets/test/filter.test.ts` | Filter dropdown tests | VERIFIED | 9 tests covering rendering, options, fetch params, re-render, empty state |
|
|
|
|
### Key Link Verification
|
|
|
|
| From | To | Via | Status | Details |
|
|
|------|----|-----|--------|---------|
|
|
| `heatmap.ts` | Kimai timesheet | `window.location.href` with daterange | WIRED | Line 223: `window.location.href = url` with `encodeURIComponent(daterange)` |
|
|
| `heatmap.ts` | HeatmapController::data() | `fetch` with `?project=N` | WIRED | Line 259: `fetch(fetchUrl)` where `fetchUrl = baseUrl?project=val` |
|
|
| `HeatmapWidget.php` | `HeatmapService::getUserProjects()` | getData() calls service | WIRED | Line 45: `$this->service->getUserProjects($user)` |
|
|
| `interaction.test.ts` | `heatmap.ts` | imports renderHeatmap and init | WIRED | Line 3: `import { renderHeatmap, init } from '../src/heatmap'` |
|
|
| `filter.test.ts` | `heatmap.ts` | imports init | WIRED | Line 2: `import { init } from '../src/heatmap'` |
|
|
|
|
### Data-Flow Trace (Level 4)
|
|
|
|
| Artifact | Data Variable | Source | Produces Real Data | Status |
|
|
|----------|---------------|--------|--------------------|--------|
|
|
| `heatmap.ts` (init) | projects | `data-projects` attr from Twig | Server-rendered from DB query via getUserProjects() | FLOWING |
|
|
| `heatmap.ts` (init) | HeatmapData | `fetch(baseUrl)` | API endpoint queries TimesheetRepository | FLOWING |
|
|
| `heatmap.ts` (filter) | HeatmapData | `fetch(baseUrl?project=N)` | API endpoint filters by projectId | FLOWING |
|
|
|
|
### Behavioral Spot-Checks
|
|
|
|
| Behavior | Command | Result | Status |
|
|
|----------|---------|--------|--------|
|
|
| All tests pass | `npx vitest run` | 52 passed (26 unique + symlink dupes), 0 failed | PASS |
|
|
| Bundle builds | `ls Resources/public/heatmap.js` | 81,943 bytes | PASS |
|
|
| No TODOs/FIXMEs in phase code | grep across modified files | No matches | PASS |
|
|
|
|
### Requirements Coverage
|
|
|
|
| Requirement | Source Plan | Description | Status | Evidence |
|
|
|-------------|------------|-------------|--------|----------|
|
|
| HEAT-07 | 04-01 | Clicking a day cell navigates to Kimai timesheet view filtered to that date | SATISFIED | `heatmap.ts` onCellClick with window.location.href, daterange param |
|
|
| INTR-01 | 04-01 | Dropdown filter to view heatmap for a specific project or activity | PARTIAL | Project filtering implemented. Activity filtering omitted (intentional descope in 04-CONTEXT.md but not reflected in requirement text). |
|
|
| TEST-04 | 04-02 | JavaScript tests for tooltip and click interaction behavior | SATISFIED | interaction.test.ts (7 tests) + filter.test.ts (9 tests) all passing |
|
|
|
|
### Anti-Patterns Found
|
|
|
|
| File | Line | Pattern | Severity | Impact |
|
|
|------|------|---------|----------|--------|
|
|
| None found | - | - | - | - |
|
|
|
|
### Human Verification Required
|
|
|
|
### 1. Click Navigation End-to-End
|
|
|
|
**Test:** Click a heatmap day cell in a running Kimai instance
|
|
**Expected:** Browser navigates to `/en/timesheet/?daterange=YYYY-MM-DD%20-%20YYYY-MM-DD` showing time entries for that date
|
|
**Why human:** Requires running Kimai to verify the timesheet page accepts and applies the daterange parameter correctly
|
|
|
|
### 2. Project Filter Visual Behavior
|
|
|
|
**Test:** Select a project from the filter dropdown with multiple projects having time data
|
|
**Expected:** Heatmap re-renders showing only that project's data, color scale recalculates, cells update
|
|
**Why human:** Requires running Kimai with real multi-project time data to verify visual filtering
|
|
|
|
### 3. Layout and Styling
|
|
|
|
**Test:** View the widget on the Kimai dashboard
|
|
**Expected:** Filter dropdown appears to the right of the heatmap, cells show pointer cursor on hover with opacity change, layout is clean
|
|
**Why human:** Visual layout verification requires browser rendering with Tabler CSS
|
|
|
|
### Gaps Summary
|
|
|
|
One partial gap identified: the roadmap success criteria and INTR-01 both specify filtering by "project **or activity**" but only project filtering was implemented. This was an intentional design decision documented in 04-CONTEXT.md ("Projects only -- no activity filter"), but it represents a deviation from the stated roadmap contract. No later phase addresses activity filtering.
|
|
|
|
The gap is flagged as partial rather than failed because:
|
|
- Project filtering is fully functional and tested
|
|
- The descope was a conscious decision, not an oversight
|
|
- The core interaction value (filtering the heatmap) is delivered for the primary dimension (projects)
|
|
|
|
All other success criteria are fully met with solid test coverage (16 new tests).
|
|
|
|
---
|
|
|
|
_Verified: 2026-04-08T15:37:00Z_
|
|
_Verifier: Claude (gsd-verifier)_
|