Skip to content

Commit 138879e

Browse files
committed
fix(tempo): send "to" with "from" when collecting worklogs
Since #9164 a full sync asks Tempo for worklogs from timeAfter with no end date. Tempo rejects that period on every page: {"errors":[{"message":"Non valid period, from '2026-01-28' to 'null'"}]} (400) so full syncs (and the first run of each team after upgrading to beta18, whose collector state starts fresh) fail in collect_worklogs. Whenever the query has "from" and no "to", set "to" to the date the collection started (one value for every page). Incremental runs keep using updatedFrom, which Tempo accepts on its own.
1 parent 02dab0c commit 138879e

2 files changed

Lines changed: 26 additions & 11 deletions

File tree

‎backend/plugins/tempo/tasks/worklog_collector.go‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ func CollectWorklogs(taskCtx plugin.SubTaskContext) errors.Error {
6464
urlTemplate = fmt.Sprintf("worklogs/team/%d", data.Options.TeamId)
6565
}
6666

67+
// one period end for every page, even if the collection crosses midnight
68+
startedAt := time.Now()
6769
err = apiCollector.InitCollector(api.ApiCollectorArgs{
6870
RawDataSubTaskArgs: rawDataSubTaskArgs,
6971
ApiClient: data.ApiClient,
@@ -74,7 +76,7 @@ func CollectWorklogs(taskCtx plugin.SubTaskContext) errors.Error {
7476
// fetched until one comes back short.
7577
Query: func(reqData *api.RequestData) (url.Values, errors.Error) {
7678
return buildWorklogQuery(data.Options, reqData.Pager,
77-
apiCollector.IsIncremental(), apiCollector.GetSince()), nil
79+
apiCollector.IsIncremental(), apiCollector.GetSince(), startedAt), nil
7880
},
7981
ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) {
8082
var response struct {
@@ -98,8 +100,9 @@ func CollectWorklogs(taskCtx plugin.SubTaskContext) errors.Error {
98100
// buildWorklogQuery builds the query for one page of worklogs. Explicit
99101
// fromDate/toDate options win; otherwise an incremental run asks for what
100102
// changed since the last successful collection (updatedFrom) and a full sync
101-
// starts at the sync policy's timeAfter (from).
102-
func buildWorklogQuery(opts *TempoOptions, pager *api.Pager, incremental bool, since *time.Time) url.Values {
103+
// starts at the sync policy's timeAfter (from). Tempo rejects a period with
104+
// "from" but no "to" ("Non valid period ... to 'null'"), so "to" defaults to now.
105+
func buildWorklogQuery(opts *TempoOptions, pager *api.Pager, incremental bool, since *time.Time, now time.Time) url.Values {
103106
if pager == nil {
104107
pager = &api.Pager{Page: 1, Skip: 0, Size: 1000}
105108
}
@@ -120,5 +123,8 @@ func buildWorklogQuery(opts *TempoOptions, pager *api.Pager, incremental bool, s
120123
case since != nil:
121124
query.Set("from", since.UTC().Format("2006-01-02"))
122125
}
126+
if query.Get("from") != "" && query.Get("to") == "" {
127+
query.Set("to", now.UTC().Format("2006-01-02"))
128+
}
123129
return query
124130
}

‎backend/plugins/tempo/tasks/worklog_collector_test.go‎

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,41 +29,50 @@ func TestBuildWorklogQuery(t *testing.T) {
2929
pager := &api.Pager{Page: 3, Skip: 2000, Size: 1000}
3030
timeAfter := time.Date(2026, 1, 28, 0, 0, 0, 0, time.UTC)
3131
lastSuccess := time.Date(2026, 9, 22, 0, 0, 13, 0, time.UTC)
32+
now := time.Date(2026, 10, 7, 15, 0, 0, 0, time.UTC)
3233

3334
t.Run("pages with offset and limit", func(t *testing.T) {
34-
q := buildWorklogQuery(&TempoOptions{TeamId: 4}, pager, false, nil)
35+
q := buildWorklogQuery(&TempoOptions{TeamId: 4}, pager, false, nil, now)
3536
assert.Equal(t, "2000", q.Get("offset"))
3637
assert.Equal(t, "1000", q.Get("limit"))
3738
})
3839

39-
t.Run("full sync starts at timeAfter, without a rolling window", func(t *testing.T) {
40-
q := buildWorklogQuery(&TempoOptions{TeamId: 4}, pager, false, &timeAfter)
40+
t.Run("full sync covers timeAfter up to today, without a rolling window", func(t *testing.T) {
41+
// Tempo rejects "from" without "to": "Non valid period, from '2026-01-28' to 'null'" (400)
42+
q := buildWorklogQuery(&TempoOptions{TeamId: 4}, pager, false, &timeAfter, now)
4143
assert.Equal(t, "2026-01-28", q.Get("from"))
42-
assert.Empty(t, q.Get("to"))
44+
assert.Equal(t, "2026-10-07", q.Get("to"))
4345
assert.Empty(t, q.Get("updatedFrom"))
4446
})
4547

4648
t.Run("incremental sync fetches what changed since the last success", func(t *testing.T) {
47-
q := buildWorklogQuery(&TempoOptions{TeamId: 4}, pager, true, &lastSuccess)
49+
q := buildWorklogQuery(&TempoOptions{TeamId: 4}, pager, true, &lastSuccess, now)
4850
assert.Equal(t, "2026-09-22T00:00:13Z", q.Get("updatedFrom"))
4951
assert.Empty(t, q.Get("from"))
5052
})
5153

5254
t.Run("global endpoint gets the same bounds", func(t *testing.T) {
53-
q := buildWorklogQuery(&TempoOptions{}, pager, false, &timeAfter)
55+
q := buildWorklogQuery(&TempoOptions{}, pager, false, &timeAfter, now)
5456
assert.Equal(t, "2026-01-28", q.Get("from"))
57+
assert.Equal(t, "2026-10-07", q.Get("to"))
58+
})
59+
60+
t.Run("a fromDate option alone also ends today", func(t *testing.T) {
61+
q := buildWorklogQuery(&TempoOptions{TeamId: 4, FromDate: "2026-03-01"}, pager, false, nil, now)
62+
assert.Equal(t, "2026-03-01", q.Get("from"))
63+
assert.Equal(t, "2026-10-07", q.Get("to"))
5564
})
5665

5766
t.Run("explicit fromDate/toDate options win", func(t *testing.T) {
5867
opts := &TempoOptions{TeamId: 4, FromDate: "2026-03-01", ToDate: "2026-03-31"}
59-
q := buildWorklogQuery(opts, pager, true, &lastSuccess)
68+
q := buildWorklogQuery(opts, pager, true, &lastSuccess, now)
6069
assert.Equal(t, "2026-03-01", q.Get("from"))
6170
assert.Equal(t, "2026-03-31", q.Get("to"))
6271
assert.Empty(t, q.Get("updatedFrom"))
6372
})
6473

6574
t.Run("no bounds when nothing is known", func(t *testing.T) {
66-
q := buildWorklogQuery(&TempoOptions{TeamId: 4}, pager, false, nil)
75+
q := buildWorklogQuery(&TempoOptions{TeamId: 4}, pager, false, nil, now)
6776
assert.Empty(t, q.Get("from"))
6877
assert.Empty(t, q.Get("updatedFrom"))
6978
})

0 commit comments

Comments
 (0)