Issue list: last seen, count and sparkline are not scoped to the selected environment
Description
When the issue list is filtered by environment, the filter selects which issues appear but none of the numbers on the row are scoped to that environment. lastSeen, count, and the sparkline all come from IssueIndex, which is environment-blind.
To reproduce:
- One project, two environments (
devandprod). - Send events for the same issue from both.
- Fix the bug in
devonly, sodevstops receiving events whileprodkeeps firing. - Filter the issue list to
environment=dev.
The issue correctly still appears — it does have dev events. But its "last seen" shows the most recent prod event, so it looks like the bug is still happening in dev when it isn't. There is no way to tell from the list whether the dev fix worked.
The environment filter is implemented as a row-selection predicate (apps/issue_events/services.py:148 joins IssueTag to decide which issues match) while users reasonably read it as a scope.
Related but distinct: glitchtip#90 covers the event detail view — that prev/next navigation and events/latest/ should also respect the filter. This issue is about the list page's own aggregate values. The two share a cause but the list half is much smaller and does not need the nested-list-in-list design work that #90 (closed) does.
Proposed Solution(s)
When an environment filter is active, scope lastSeen and count to it and label them visibly (for example "last seen in dev"), so the numbers can't be misread as global. Keep status global and say so explicitly.
Deliberately out of scope: per-environment status. Making "resolved" per-environment would change what is:unresolved means in an unfiltered list, what alert rules fire on, and what regression detection compares against. Per-environment first_seen/last_seen answers "did my fix hold?" without any of that.
Needs a frontend follow-up to display and label the scoped values.
Data layer constraint
Worth recording, because it is the reason this isn't a small patch.
There is no per-environment last-seen anywhere in the schema. IssueTag holds (issue, org, tag_key, tag_value, day) -> count, so MAX(date) per environment gives a day-granular answer for free, and adding an exact last_seen column to IssueTag measures as approximately free at ingest (a whole-batch cost within measurement noise, provided the value is passed as a single scalar bind rather than a parallel array).
But IssueTag rows expire at aggregate retention while Issue rows do not — cleanup_old_issues keys off the issue's global last_seen. So for an issue still active in prod but quiet in dev for longer than the retention window, that approach cannot distinguish "never occurred in dev" from "last occurred in dev four months ago". That is exactly the long-lived case where "did my fix hold?" matters most.
A durable fix wants per-environment first_seen/last_seen on a small group-index table keyed (issue, environment), which lives as long as the issue does, generalises to release (which resolved_in_release regression detection already implies), and allows an index-driven sort for environment-filtered lists. That in turn depends on giving the aggregate layer a retention policy of its own — see the companion issue.
AI disclosure: investigated and drafted with Claude Code; benchmarking and data-layer analysis AI-assisted, reviewed by a maintainer before filing.