Skip to content

Make long window uptime honest#19

Open
maquchizi wants to merge 11 commits into
mainfrom
make-long-window-uptime-honest
Open

Make long window uptime honest#19
maquchizi wants to merge 11 commits into
mainfrom
make-long-window-uptime-honest

Conversation

@maquchizi

Copy link
Copy Markdown
Collaborator

What & why

On the free Grafana Cloud plan only ~14 days of metrics are retained, but the status page presented the 30-day and 1-year windows as fully covered — reporting a confident, false 100% uptime (see #12). The app is read-only and can't recover dropped data, so this makes coverage honest and environment-driven instead of trying to recover history.

  • New METRICS_RETENTION_DAYS config (lib/config.ts, server-side). Unset ⇒ unlimited ⇒ no change for existing deployments; set to 14 for the free tier.
  • Honest numbers: for any window beyond retention, the PromQL query is skipped and uptime/response are marked insufficient (render ) rather than a misleading ratio over a partial range — on the overview list, the per-site uptime grid, and the window selector. Bonus: fewer Grafana queries per refresh.
  • Clamped charts: the selected-window bars, response line, and min/avg/max are clamped to the retained span, so e.g. the 1y view shows the ~14 real days at usable density instead of a near-empty strip.
  • Coverage note: a small, theme-aware info banner explaining the limit, shown on both pages. It's dismissable — an explicit close is remembered for 7 days (localStorage), and it soft-auto-hides after 10s for the current view (reappears on reload).
  • Retention-aware default window: the overview opens on the largest covered window (30d7d under a 14-day plan) so visitors don't land on a grid of .
  • Mock data honors the same var, so MOCK=1 METRICS_RETENTION_DAYS=14 reproduces the behavior locally.

Stays stateless and read-only; keeps all windows visible rather than removing them.

Closes #12
Closes #13

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Refactor / chore

Checklist

  • I read [AGENTS.md](https://claude.ai/AGENTS.md) and [CONTRIBUTING.md](https://claude.ai/CONTRIBUTING.md)
  • pnpm check passes
  • pnpm test passes (134 tests)
  • pnpm test:types passes
  • pnpm build succeeds
  • Added/updated tests for new logic (config/retention helper, bucket clamp, synthetics skip+clamp, mock retention, coverage-note dismiss/auto-hide, overview default)
  • Updated docs (.env.example, docs/configuration.md, docs/architecture.md)
  • No secrets committed and no Grafana credential given a NEXT_PUBLIC_ prefix — METRICS_RETENTION_DAYS is server-side and reaches Client Components only as a resolved retentionDays prop

Notes for reviewers

  • Verified live against a real 14-day Grafana stack: /site/api-sensorsafrica-africa?window=1y (the bad-state example from [Bug] Long time windows report 100% uptime when data is beyond the plan's retention #12) now shows for the 30d/1y figures instead of a false 100%, with the uptime strip and response chart clamped to 2 Jul – 16 Jul (14 days) rather than a sparse year.
  • Retention read lazily (retentionSec() / coveredWindows() in lib/synthetics.ts, retentionSec() in lib/mock.ts) rather than as module-level constants — config is static in production, but this keeps the query-skip/clamp logic unit-testable by letting tests vary config.retentionDays.
  • Window-equality semantics: windowWithinRetention covers a window whose span is ≤ retention (so 7d is covered at exactly retentionDays=7).
  • CoverageNote is a Client Component used from both a client page (overview) and a server page (site detail); both share the same localStorage key, so a dismissal applies across surfaces.
image image

@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
probeview Ready Ready Preview, Comment Jul 17, 2026 10:48am

Request Review

@kilemensi

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 325dedcab7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread components/CoverageNote.tsx Outdated
@kilemensi

Copy link
Copy Markdown
Member

Can't we infer the retention based on the oldest data returned? 👇🏾 shows for a 1y query, we probably just have 14 days of data.

Screenshot 2026-07-17 at 11 30 24

Can't we also completely disable durations that are longer than retentions period? e.g. if my retention period is 14 days, may be 30d and 1y should be disabled (or completely not visible to me).

@maquchizi

Copy link
Copy Markdown
Collaborator Author

@kilemensi These were all alternatives I considered in #13:

  • Hide the 30d/1y selectors/cards beyond retention — cleaner visually, but removes UI. Disclosing coverage is more transparent than hiding it.
  • Detect the earliest available sample dynamically rather than a config var — more truthful ("data since "), but adds per-check query cost and is fiddly in Prometheus over long ranges. A static, env-driven limit fits the project's stateless, configurable philosophy better.
  • Leave PromQL as-is and only annotate — less code, but a 30d figure could still silently equal the 14d figure without saying so; clamping the ranges keeps within-retention numbers computed over real data.

Ultimately, the approach where we show an alert about the retention period is the one that covers most cases without turning the code into a nest of conditions. Disabling/hiding durations can be confusing for users and doesn't translate well either. This solution also covers people running self-hosted instances of Grafana with custom retention periods.

One thing we should consider is inferring the retention based on the oldest data returned but outside the hot path i.e. we make a cheap, periodic query that determines how far back your data goes then cache that somewhere and use it for all other queries. I would be cautious though because if this query returned inaccurate data for some reason, there would be inconsistencies in the UI.

@kilemensi

Copy link
Copy Markdown
Member

I hear you @maquchizi. I don't have the deeper context that you have so if this direction makes more sense all good. My point was, as a user, I'd like to see actions I can actually take i.e. make sense to me & my setup. There is no point in showing me a year option if all I have is 14d worth of data. You can still show a warning/info about this... and if we're pre-building pages, there shouldn't be any runtime cost of detecting the earliest available sample dynamically I believe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants