Repository navigation
feat(time): serve the time report from my Jira worklogs - #150
Merged
Merged
Conversation
A new time service polls Jira for my worklogs of the last 7 days as it starts and every 5 minutes after, and answers the time report at GET /time/report. It reads todos' Jira settings and token, runs on port 8088 under process-compose, and Gitea's workflow builds its image. The gateway and the laptop come with later steps (decision 182).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brief
Goal. A new
timeservice that polls my worklogs of the last 7 days on Jira, as it starts and every 5 minutes after, and answers the time report atGET /time/report. The fourth oftime-tracking's twelve steps (decisions 180 to 182).Scope.
services/time/main.go: the service's wiring, as pulls' is: Jira's settings and token throughjira.FromEnv, chi withGetHead,/healthz, the API.services/time/jira.go: starts the poller when Jira is set up.services/time/internal/worklogs: the poller. Each poll reads the clock in the service's zone, reads the worklogs ofreport.Span(now)and keepsreport.New(worklogs, now), throughinternal/poll.services/time/internal/api:GET /time/report, or 404Jira is not set up.services/time/internal/report/report.go:omitzeroon the report's two fields, so the answer before the first poll to succeed has neither.services/time/Dockerfile,.gitea/workflows/images.yml,process-compose.yaml(port 8088).docs/setup.md,mise.local.example.toml: time reads Jira's settings and token too.docs/plan.md: decision 182, §0 row, §8.2 marked done, §3's polling default for logged time.Out of scope.
time-gateway) and the laptop (homelab-time).worklog-add).Acceptance criteria.
GETandHEAD /time/reportanswer 404Jira is not set up, other methods 405, and/healthz200{"status":"ok"}.GET /time/reportwaits for the first poll to end, then answers{"days":[…],"week":{…},"searched":"…"}. A failed poll keeps the last report, adds itsfailureand is logged aspoll Jira worklogs. Until a poll succeeds there's nodaysorweek:{}when the service stops first,{"failure":"…"}when the poll fails.{}; a report thatNewworks out encodes as before.services/time/Dockerfileon distroless static, whose tzdata letsTZname the zone; Gitea's images workflow builds and pushestime;mise run upruns time on 127.0.0.1:8088.mise run checkpasses.What changed
services/time/main.goandjira.go(new): the service, wired as pulls' and todos' are.routesreads the Jira settings and token withjira.FromEnv, then serves/healthzand the API behind chi'sGetHead.pollstarts the poller with the client'sWorklogsandtime.Localwhen Jira is set up, and returns nil when it isn't.services/time/internal/worklogs/poll.go(new):NewPoller(read, loc, logger), apoll.Pollerof reports (decision 135). Each poll takestime.Now().In(loc)once, readsreport.Span(now)and keepsreport.New(worklogs, now). It polls every 5 minutes and logs a failed poll aspoll Jira worklogs.services/time/internal/api/api.go(new):GET /time/reportanswers through the poller's handler (decision 145), or 404Jira is not set upwithout Jira.services/time/internal/report/report.go:daysandweekareomitzero, so the zero report encodes as{}.services/time/Dockerfile(new), as pulls'..gitea/workflows/images.ymlbuilds and pushestime.process-compose.yamlruns it on 127.0.0.1:8088.docs/setup.mdandmise.local.example.toml: time reads the Jira settings and token too; what it answers and how it fails.docs/plan.md: decision 182, the §0 row, §8.2'stime-servicemarked done, and §3's polling default for logged time (5 minutes, which amends "15 min + on demand").QA
api,reportandworklogs100%, Go 95.9% total.go test ./services/time/...). The first run left three:read(context.Background(), …)). A cancelled poll counts as no news, so no test saw it. QA addedTestPollerCutsAReadShortWhenItStops.time.UTCfortime.Localinjira.go). A unit test can't catch it, becausetime.Localis fixed once per process. A self-exec test with anotherTZwould need a//nolint:gosec(G204). The live check catches it instead.searchedin UTC to the second, and no failure. The read spans the zone's 7 days. HEAD answers 200 with no body, POST answers 405,/healthzanswers ok, and SIGTERM ends it with exit 0.{"failure":"read the Jira account: 401 Unauthorized"}and logspoll Jira worklogs. With a 3-second poll, a later failure keeps the days andsearchedand adds the failure. The next poll to succeed drops it, and the only ERROR lines are the polls'.Jira is not set upand POST 405. With onlyBAKHYARD_JIRA_SITEit exits 1, sayingBAKHYARD_JIRA_API is not set; with no token file it saysno secret file jira-token.process-compose: time comes up healthy on 8088, answers without Jira and stops with it./usr/local/bin/timeas its entrypoint, and holdsusr/share/zoneinfo. The arm64 container is healthy and answers without Jira. The distroless base has no/etc/localtime, so withoutTZthe service keeps UTC, as decision 182 says.Review
No blocking findings. Non-blocking:
docs/setup.md's list of the services that read secrets leaves out notify and nudge, which read theirs too. This predates this step.time-gateway), and the laptop doesn't run it yet (homelab-time, which setsTZ).Gates
mise run --jobs 1 checkpassed locally:-race, Go coverage 95.9% total;tsc, knip,pnpm audit;SonarQube Cloud runs on demand (decision 132).
Merged under the user's standing OK (2026-10-26): merge once CI's
checkis green, and review everything at the end.