Repository navigation
feat: add active days endpoint under v1-alpha - #2239
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The previous-period calculation can violate the documented equal-length contract, and two smaller documentation and test-reliability issues remain.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (3)
What changed in this PR
Adds the v1-alpha Active Days development metric endpoint backed by Tinybird.
Changes:
- Registers
GET /v1-alpha/projects/{slug}/development/active-days. - Adds response mapping, validation, error handling, and OpenAPI schemas.
- Adds comprehensive integration tests.
| File | Description |
|---|---|
api/src/versions/v1-alpha/index.ts |
Registers the endpoint. |
api/src/versions/v1-alpha/development/active-days.ts |
Implements the route and schemas. |
api/tests/development-active-days.test.ts |
Tests behavior, failures, and OpenAPI output. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
gaspergrom
left a comment
There was a problem hiding this comment.
Checked the includeCollaborations fan-out across all three pipe calls and the TinybirdProjectNotFoundError catch, both hold up, and the null avgContributionsPerDay path lands on 0. Tests cover the edge cases well. LGTM.
1a8ae90 to
79c89ae
Compare
79c89ae to
8207332
Compare
themarolt
left a comment
There was a problem hiding this comment.
lgtm - one question on the nullable bucket dates in the series
8207332 to
8c01f4f
Compare
Signed-off-by: anilb <epipav@gmail.com>
Signed-off-by: anilb <epipav@gmail.com>
8c01f4f to
df2ce44
Compare
Signed-off-by: anilb <epipav@gmail.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Concurrent upstream failures can produce multiple route-level error logs despite the stated once-per-request behavior.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (1)
Resolved since last review (1)
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Malformed but valid JSON Tinybird responses can escape the 503 mapping and produce a 500.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (1)
Resolved since last review (1)
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Malformed Tinybird data can bypass the 503 mapping and produce an incorrect zero response or an internal 500.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (1)
Resolved since last review (1)
| current: Type.Number({ description: 'Active days in the current period (count of days).' }), | ||
| previous: Type.Number({ description: 'Active days in the previous period (count of days).' }), | ||
| changeValue: Type.Number({ description: 'current minus previous (count of days).' }), |



What
Adds
GET /v1-alpha/projects/{slug}/development/active-days, the API port of the Active Days widget handler (frontend/server/api/widget/development/active-days.get.ts).Query:
repos[],startDate,endDate(UTC calendar days),granularity(required: daily, weekly, monthly, quarterly, yearly) andincludeCollaborations(optional boolean, defaultfalse).Response:
{ "summary": { "current": 80, "previous": 64, "percentageChange": 25, "changeValue": 16, "periodFrom": "2025-01-01T00:00:00Z", "periodTo": "2025-03-31T00:00:00Z" }, "avgContributionsPerDay": 21.5, "data": [ { "startDate": "2025-01-01T00:00:00Z", "endDate": "2025-01-31T00:00:00Z", "contributions": 698 } ] }summarycounts active days (days with at least one development activity) in the current period against the previous one, which has the same length and ends the day beforestartDate.datahas one bucket per granularity step with the contributions in it.Three concurrent calls to the
active_dayspipe (current summary, previous summary, series), as the Nuxt data layer makes them, with the slug sent asproject.Changes vs the Nuxt handler
day(the 1-based bucket index) is dropped.avgContributionsis renamed toavgContributionsPerDay, the pipe's own name.data: []. Nuxt readscurrentSummary.data[0]unguarded and returns a 500.percentageChangeis signed (Nuxt usedMath.abs).YYYY-MM-DD; response dates are ISO-8601 UTC; Tinybird getsYYYY-MM-DD 00:00:00exactly as Nuxt's Luxon serializer sends it.Decisions
includeCollaborationsis exposed (optional boolean, defaultfalse) and passed to the pipe. The Nuxt handler hardcodesfalse, which is why the widget's collaboration toggle does nothing. The pipe accepts the flag: Nuxt's series call already sendsincludeCodeContributions=true&includeCollaborations=false(it spreads the whole filter), andActiveDaysFilterdeclares both flags. Defaultfalsekeeps the default response identical to today's widget. Same param as IN-1335.includeCollaborations=true. This follows theactive_contributorsdata layer, which sends both flags on all three of its calls.includeCodeContributionsstays hardcoded totrue, as in Nuxt. Exposing it later is additive.granularityis required. Nuxt relies on a pipe default that is not visible from this repo; a required param keeps the series shape explicit instead of documenting a default we cannot verify.TinybirdProjectNotFoundErrorwhen the bucket lookup finds no row for the slug; the route catches exactly that class and treats the call as an empty result. A 404 from the pipe itself still maps to 503, and a test pins that.avgContributionsPerDaybecomes0. The column isNullable(Float64)and is null only with zero active days, which the empty case already reports as0.repos=is dropped rather than sent to the pipe as a filter on an empty repository name (Ajv coerces it to['']), matching the Nuxt handler.upstream_unavailable, logged once per request. A missingAPI_TB_*variable stays a 500, as on/projects/{slug}. Cache-Control is inherited from the alpha plugin. OpenAPI tagDevelopment; every response field and added query param carries a description with its unit.Tests
api/tests/development-active-days.test.ts: 32 cases throughbuildApp()withfetchstubbed at the HTTP boundary, so the real Tinybird client builds the URLs, resolves the bucket and classifies the errors. Covers the response shape and key set, the exact params of the three pipe calls and their concurrency, theincludeCollaborationspass-through and validation, emptyrepos, signed and nullpercentageChange, empty results, unknown slug, date and granularity validation, the 503 mapping (pipe 500/404/401/429, bucket lookup 500, network error, non-JSON), Cache-Control, and the OpenAPI spec (tag, field descriptions, query params, absent from/v1).From
api/:pnpm test(14 files, 194 tests),pnpm tsc-check,pnpm lint,pnpm format:checkall pass. No new runtime dependency.Includes the routing-test parser fix from the base branch (62b136b).
Local helpers to fold into a shared module once the five Development PRs land:
toTinybirdRange,toUtcDateTime(same form asperiod.ts's privateutcMidnight), thefetchRowswrapper (bucket-miss to empty rows) with the.catch503 mapping, and theType.Composite([DateRangeQuery, ...])query schema. The api's Tinybird client has no bucket cache, so each request makes threeproject_bucketslookups for the same slug; wiring the client's in-process cache fixes that for every route.Based on main, which includes #2241 (IN-1348, directory-based route registration) as dd37a7a. Jira: IN-1334
main