Skip to content

refactor: consolidate development endpoint helpers - #2242

Merged
epipav merged 3 commits into
mainfrom
refactor/IN-1347-consolidate-development-helpers
Sep 21, 2026
Merged

epipav merged 3 commits into
mainfrom
refactor/IN-1347-consolidate-development-helpers

Conversation

@epipav

@epipav epipav commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

IN-1331 to IN-1335 (#2236, #2237, #2238, #2239, #2240) kept their helpers route-local so five parallel PRs would not conflict. This PR hoists each duplicated piece into the shared modules and switches all six /v1-alpha routes (projects.ts and the five under development/) to them. Routes stay autoloaded from development/; every shared piece lives in lib/, schemas/ or clients/, and the routes no longer import the Tinybird client at all.

Wire behaviour is unchanged except where the ticket owner decided otherwise (below). The OpenAPI diff is limited to descriptions and the integer/number types listed under "OpenAPI diff".

What moved where

Piece (copies on main) Now
Tinybird YYYY-MM-DD 00:00:00 range formatter (5: toTinybirdDay, toTinybirdRange x3, toTinybirdDateTime + tinybirdRange) toTinybirdRange(range) in api/src/lib/period.ts; takes a Partial<DateRange> so issues-resolution can keep forwarding an omitted date as absent
UTC ISO bucket-bound formatter (4: toUtcMidnight x2, toUtcDateTime, toIsoUtc) toIsoUtc(value) in lib/period.ts; utcMidnight(day) is exported next to it
Bucket-bounds row check (2: hasBounds, hasBucketDates) hasBucketBounds(row) in lib/period.ts; active-days filters with it, the three activities_count series routes pass it to fetchPipe
Tinybird 503 fetch wrapper (6: fromTinybird x3, rowsOf + inline catch, inline catch x2) fetchPipe<T>(request, path, params, isRow?) in api/src/clients/tinybird.ts
Bucket lookup + unknown-slug short-circuit (5) withBucket(request, slug, query) in clients/tinybird.ts; resolves null for a slug without a bucket so the route answers zeros without a pipe call
Empty ?repos= guard (2 had it, 3 forwarded repos= raw) repoFilter(repos) in clients/tinybird.ts, applied on all five routes
Bucket cache adapter (0; the client re-fetched project_buckets on every request) createInMemoryBucketCache() in api/src/clients/bucket-cache.ts, wired into getTinybirdClient()
Described PeriodSummary (5) + describe() (2) periodSummary({ measure, unit, changeUnit?, kind, title?, description? }) and describe() in api/src/schemas/common.ts
Described granularity query param (4) description on the shared Granularity; SeriesQuery = DateRangeQuery + required granularity
InvalidDateRangeError never set this.name (nit from IN-1330) constructor sets it

Decisions (settled once here; raised on #2237, #2238, #2239, #2240)

  1. Query-bound semantics: keep what is sent to Tinybird. Both bounds go out at 00:00:00 and the pipes compare with > / <. The docs now say so: DateRangeQuery.startDate is inclusive from 00:00 UTC, endDate is exclusive at 00:00 UTC of that day, matching the Insights widgets; each route description states the same rule. No numeric change.
  2. ISO bucket bounds keep the time when a pipe returns a DateTime (pull-requests' toIsoUtc behaviour, the more general one): 2025-01-05 -> 2025-01-05T00:00:00Z, 2025-01-05 23:59:59 -> 2025-01-05T23:59:59Z. issues-resolution and active-days used to truncate; their pipes return Date columns, so real output is unchanged and their "at UTC midnight" bucket descriptions stay accurate.
  3. Logging: one request.log.error per failed pipe call, with the pipe path in the message. The shared wrapper owns it. pull-requests moves from one log per request to one per failed pipe, like the other four.
  4. Count summaries are Type.Integer; the percent share in contributions-outside-work-hours stays Type.Number. periodSummary takes the unit and the numeric kind. issues-resolution, pull-requests and active-days flip current/previous/changeValue from number to integer in the spec; the values were always whole numbers.
  5. fetchPipe maps every failure to UpstreamUnavailableError (any status, network error, non-JSON body) and adopts commit-activities' row check: a non-array data, or a row failing the optional isRow predicate, becomes TinybirdInvalidResponseError inside the wrapper, so 503 too. Two consequences: projects.ts now answers 503 for a malformed projects_list body (was a TypeError 500), and a series row without bucket bounds is 503 on issues-resolution and pull-requests as well (was a TypeError 500 in their reshaping). withBucket resolves the bucket once per request; the in-memory cache makes it once per process per slug (TTL from the client library, 24h). The frontend uses a Nitro Redis adapter; a shared cache for the api can follow once it runs more than one instance.
  6. InvalidDateRangeError sets this.name.

OpenAPI diff (/v1-alpha/openapi.json, main dfd5f4d9 vs this branch)

39 leaf differences, every one a description (24) or a type (15). No path, parameter, required, enum or ordering change.

  • 5 route descriptions: one added sentence, "The period runs from 00:00 UTC on startDate up to, and excluding, 00:00 UTC on endDate." (folded into the existing UTC-timestamp sentence on contributions-outside-work-hours). pull-requests also states its call count as "up to 11 Tinybird calls", with the project lookup only when the process has no cached bucket for the slug (Copilot review, 150d995).
  • 10 startDate / endDate parameter descriptions (all five routes): inclusive / exclusive wording from decision 1.
  • 2 granularity descriptions (issues-resolution, pull-requests) now read "Width of each bucket in data." like commit-activities and active-days already did.
  • 6 summary field descriptions from the factory wording: active-days previous (gains the comparison-period sentence) and changeValue; commit-activities changeValue ("(count of commits)" -> "(count)"); contributions-outside-work-hours current/previous ("in percent" -> "(percent)") and changeValue ("(percentage points)").
  • 15 number -> integer: issues-resolution summary (3), pull-requests opened/merged/closed summaries (9), active-days summary (3). Decision 4.

Dumped with scripts/export-openapi.ts on both commits and compared leaf by leaf; re-checked after 150d995.

Verification (in api/)

  • pnpm test: 20 files, 412 tests (main: 19 files, 340). New: tests/tinybird-client.test.ts (fetchPipe, withBucket, cache wiring, in-memory adapter), plus periodSummary, describe, SeriesQuery, DateRangeQuery wording, toTinybirdRange, toIsoUtc, utcMidnight, InvalidDateRangeError.name, and a check on the served /v1-alpha spec for the wording and integer types above.
  • pnpm tsc-check, pnpm lint, pnpm format:check: green.
  • Existing route tests: assertions unchanged. The only edit is a beforeEach that clears the bucket cache in the five development test files, so each test still counts its own project_buckets lookup regardless of order.

Size

1732 lines: 1038 in api/src (six routes shrink; three shared modules and the 41-line adapter grow; pull-requests moves its pipe fan-out into the withBucket callback, which re-indents about 60 lines) and 694 in api/tests (unit tests for every new shared helper). Over the 1000-line target, one concern: the runtime consolidation. Splitting it per piece would leave the routes half-switched between PRs.

Left for follow-ups

  • Test scaffolding (ticket piece 7, api/tests/helpers/tinybird.ts: app builder, env stubs, fetch stub routed by pipe path, response builder, pipe-call recorder, OpenAPI types): second PR after this one merges. That PR should also pin the two new 503 paths from decision 5 at route level (issues-resolution and pull-requests series row without bounds; projects_list non-array data); the mechanism is unit-tested in tests/tinybird-client.test.ts today.
  • issues-resolution is the only route that forwards omitted startDate/endDate as absent to its current-period pipe calls (its test pins that); the other four send the resolved default range. Kept as is.
  • repoFilter is applied on all five development routes (b48e818, after review). A bare ?repos= reached Ajv as [''] and went to the pipes as repos=, a filter matching nothing; each route now drops empty values and pins it with two tests.
  • describe() has no route call site left once SeriesQuery and periodSummary exist; kept per the ticket with a unit test. Drop it if no endpoint picks it up.
  • Library-level: give the Tinybird client the Fastify logger (register it as a plugin) so its own console lines go through pino; move the in-memory adapter into libs/tinybird-client and use it as the frontend's local fallback; move the Array.isArray(data) check into the library's parseResponse.

Jira: IN-1347

Signed-off-by: anilb <epipav@gmail.com>
Copilot AI balanced review requested due to automatic review settings September 21, 2026 16:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The documented date-bound and comparison-period semantics conflict with the stated strict Tinybird predicates.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Consolidates duplicated /v1-alpha development endpoint logic into shared Tinybird, period, cache, and schema helpers.

Changes:

  • Adds shared Tinybird fetching, bucket caching, date formatting, and schema factories.
  • Migrates six routes to shared helpers while preserving response behavior.
  • Expands unit and OpenAPI coverage.
File Description
api/​src/​clients/​bucket-cache.ts Adds in-memory bucket cache.
api/​src/​clients/​tinybird.ts Adds shared Tinybird helpers.
api/​src/​lib/​period.ts Adds shared date and bucket utilities.
api/​src/​schemas/​common.ts Adds shared query and summary schemas.
api/​src/​versions/​v1-alpha/​projects.ts Uses shared pipe fetching.
api/​src/​versions/​v1-alpha/​development/​active-days.ts Consolidates route helpers.
api/​src/​versions/​v1-alpha/​development/​commit-activities.ts Consolidates route helpers.
api/​src/​versions/​v1-alpha/​development/​contributions-outside-work-hours.ts Consolidates route helpers.
api/​src/​versions/​v1-alpha/​development/​issues-resolution.ts Consolidates route helpers.
api/​src/​versions/​v1-alpha/​development/​pull-requests.ts Consolidates route helpers.
api/​tests/​tinybird-client.test.ts Tests Tinybird and cache helpers.
api/​tests/​period.test.ts Tests shared period utilities.
api/​tests/​common-schemas.test.ts Tests schemas and OpenAPI output.
api/​tests/​development-active-days.test.ts Resets bucket cache between tests.
api/​tests/​development-commit-activities.test.ts Resets bucket cache between tests.
api/​tests/​development-contributions-outside-work-hours.test.ts Resets bucket cache between tests.
api/​tests/​development-issues-resolution.test.ts Resets bucket cache between tests.
api/​tests/​development-pull-requests.test.ts Resets bucket cache between tests.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread api/src/schemas/common.ts
Comment thread api/src/versions/v1-alpha/development/pull-requests.ts Outdated
Signed-off-by: anilb <epipav@gmail.com>
Copilot AI review requested due to automatic review settings September 21, 2026 16:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The documented inclusive start bound conflicts with the stated strict Tinybird comparison and must be reconciled.

Review effort: Balanced
Findings: None

Resolved since last review (2)

@epipav

epipav commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

@cursor review

@epipav epipav self-assigned this Sep 21, 2026

@themarolt themarolt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm - one question on repoFilter, the other three routes still forward repos raw

Comment thread api/src/versions/v1-alpha/development/issues-resolution.ts Outdated
Copilot AI review requested due to automatic review settings September 21, 2026 18:14
@epipav
epipav merged commit a6f37b3 into main Sep 21, 2026
12 checks passed
@epipav
epipav deleted the refactor/IN-1347-consolidate-development-helpers branch September 21, 2026 18:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Newly introduced malformed-response paths need route-level regression tests before approval.

Review effort: Balanced
Findings: None

Previously missed (3)

In code that hasn't changed since last review

Low severity Add route test for series rows missing bucket bounds

api/​src/​versions/​v1-alpha/​development/​issues-resolution.ts:133

This newly maps a series row without bucket bounds to 503, but the route suite does not exercise that behavior. Add a route-level malformed-row case so removing or miswiring this predicate cannot pass the shared helper tests unnoticed.

Low severity Verify route passes missing-bounds predicate to series requests

api/​src/​versions/​v1-alpha/​development/​pull-requests.ts:179

The new 503 path for a series row without bucket bounds is only covered at the shared-helper level. Add a route-level malformed-row case to verify this endpoint passes the predicate to each series request.

Low severity Add project route test for non-array projects data

api/​src/​versions/​v1-alpha/​projects.ts:51

Switching to fetchPipe newly maps a non-array projects_list.data response to 503, but the project endpoint failure suite does not cover that path. Add a route-level malformed-data regression test alongside its existing Tinybird failure cases.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants