Repository navigation
feat(jira): read my worklogs in a span of time - #148
Merged
Merged
Conversation
The time service, the next step of time-tracking, reports the hours I logged on each day. The shared Jira client now reads them: my account from /myself, the tickets I logged on from a worklogAuthor search, then each ticket's worklogs, keeping mine that start in the span, the earliest first (decision 180).
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. The shared Jira client reads my worklogs in a span of time, for the
timeservice that the next step brings:Client.Worklogs(ctx, from, to)returns each worklog of mine that starts fromfromup to, but not including,to, as when it started and how long it took, the earliest first. The second oftime-tracking's eleven steps (decision 180).Scope.
internal/jira/worklog.go: the typeWorklog(Started,Spent) andWorklogs:GET /rest/api/3/myself; an answer without one fails;worklogAuthor = currentUser() AND worklogDate >= "<the day before from's UTC date>" AND worklogDate < "<the second day after to's UTC date>" ORDER BY key,fields=id, 100 a page, at most 10 pages;GET /rest/api/3/issue/{id}/worklogwithstartedAftera millisecond beforefrom,startedBeforeatto,startAt, and 1,000 a page, paging ontotal, at most 10 pages;author.accountId, starting within the span;startedread with Jira's layout2006-01-02T15:04:05.000-0700.internal/jira/client.go: the search's paging and the GET request shared bySearchandWorklogs.docs/plan.md: decision 180 brought up to date with what Jira's spec says (below), §0 row, §8.2 marked done.Out of scope. The
timeservice and its day report (time-service); adding worklogs (worklog-add); keeping the account ID between calls; asking for several tickets' worklogs at once.Acceptance criteria.
Worklogsasks Jira for my account, then searches with the JQL above,fields=idandmaxResults=100, then asks for each ticket's worklogs by its ID, path-escaped, withstartedAfter,startedBefore,startAtandmaxResults=1000; every request is a GET with Basic auth andAccept: application/json.fromcounts, one attodoesn't; another account's doesn't; the earliest first, across tickets.nextPageToken) and of each ticket's worklogs (startAtuntiltotal), stops at an empty page, and fails after 10 pages of either.from,toand the Jira profile.startedisn't in Jira's layout, Jira out of reach, an ended context (with no request). Another account's worklog with a badstartedis skipped, not a failure.Searchbehaves as before: its tests pass unchanged.mise run checkpasses.Facts checked in Jira's OpenAPI spec (2026-10-29).
GET /rest/api/3/myselfanswersaccountIdunder the classic scoperead:jira-user, andGET /rest/api/3/issue/{issueIdOrKey}/worklogtakesstartAt,maxResults(default 5,000),startedAfterandstartedBefore(UNIX milliseconds) underread:jira-work: both scopes are on the token (decision 103). The search'sfieldsdefaults toid, and "the greatest number of items returned per page is achieved when requestingidorkeyonly". The spec's examplestartedis2021-01-17T12:34:00.000+0000. Jira Cloud's JQL reference callsworklogDatea DATE field that finds "work logged on a specific date", and doesn't say in which zone.What changed
internal/jira/worklog.go(new):WorklogandClient.Worklogs(ctx, start, end), as in the brief. The code names the span's boundsstartandendrather thanfromandto, which the linter'svarnamelenrefuses. Worklogs that start together keep the order Jira gave them (a stable sort).internal/jira/client.go:Searchkeeps its behaviour and now builds its list from a sharedsearch(ctx, jql, fields), which holds the paging.pagetakes the JQL and the fields. The newgetbuilds a GET and hands it todo, forpage,accountandworklogPage.issuereads the ticket'sidtoo.docs/plan.md: decision 180's "Reading worklogs" bullet now says what was built and why, with what Jira's spec says:fields=id, worklogs read by the ticket's ID, 1,000 a page under the 4 MiB cap,startedAftera millisecond early, a badstartedfailing only for my own worklogs. JQL searches only each ticket's 1,000 latest worklogs, which joins §2's risk. Also the §0 row and §8.2 marked done.QA
Search's tests and the fake Jira they share are unchanged (AC6).go test ./internal/jira/, with a dead proxy so no mutant reaches a real Jira). They cover the JQL's dates and zones, every query parameter, the paths, the paging and its caps, the author and span checks, the sort, every dropped error and the shared client code. One mutant survived the first run:slices.SortFuncinstead ofSortStableFunc. The tie test had 14 worklogs that all started at once, and pdqsort leaves a list of equal keys as it is, since it sees it as sorted already. The test now alternates two start times, so the unstable sort reorders the ties, and it's caught.internal/jira: 98.6%. The gaps areget's error from building the request, which can't happen (a parsed URL, a fixed method, a context), andfetch's, which was already there.Review
No blocking findings. Non-blocking:
Startedkeeps the offset Jira sends, the Jira profile's zone. The sort and the span checks compare instants, so they don't mind, buttime-servicemust turn each start totime.Localbefore it counts it on a day.startedBeforeis the end's millisecond, rounded down. If Jira doesn't count its own bound, an end with a fraction of a millisecond would miss a worklog that starts in that millisecond.time-servicewill pass whole days, so this can't happen there.read:jira-userscope for/myself(decision 103 lists it).time-servicewill show a failure if it's missing.Gates
mise run --jobs 1 checkpassed locally: Go fmt, golangci-lint (0 issues), buf, govulncheck (none), Go tests with-race, Go coverage 95.8% total, Biome, svelte-check,tsc, knip,pnpm audit, Vitest 1,082 tests (99.8% of statements), Playwright E2E 319 passed and 7 skipped. 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.