Skip to content

Commit e4fa212

Browse files
authored
fix: always return count of workspaces (#12407)
1 parent 0016b02 commit e4fa212

File tree

6 files changed

+217
-57
lines changed

6 files changed

+217
-57
lines changed

coderd/database/dbmem/dbmem.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ func mapAgentStatus(dbAgent database.WorkspaceAgent, agentInactiveDisconnectTime
345345
return status
346346
}
347347

348-
func (q *FakeQuerier) convertToWorkspaceRowsNoLock(ctx context.Context, workspaces []database.Workspace, count int64) []database.GetWorkspacesRow {
348+
func (q *FakeQuerier) convertToWorkspaceRowsNoLock(ctx context.Context, workspaces []database.Workspace, count int64, withSummary bool) []database.GetWorkspacesRow { //nolint:revive // withSummary flag ensures the extra technical row
349349
rows := make([]database.GetWorkspacesRow, 0, len(workspaces))
350350
for _, w := range workspaces {
351351
wr := database.GetWorkspacesRow{
@@ -389,6 +389,12 @@ func (q *FakeQuerier) convertToWorkspaceRowsNoLock(ctx context.Context, workspac
389389

390390
rows = append(rows, wr)
391391
}
392+
if withSummary {
393+
rows = append(rows, database.GetWorkspacesRow{
394+
Name: "**TECHNICAL_ROW**",
395+
Count: count,
396+
})
397+
}
392398
return rows
393399
}
394400

@@ -8278,12 +8284,12 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
82788284
}
82798285
if arg.Limit > 0 {
82808286
if int(arg.Limit) > len(workspaces) {
8281-
return q.convertToWorkspaceRowsNoLock(ctx, workspaces, int64(beforePageCount)), nil
8287+
return q.convertToWorkspaceRowsNoLock(ctx, workspaces, int64(beforePageCount), arg.WithSummary), nil
82828288
}
82838289
workspaces = workspaces[:arg.Limit]
82848290
}
82858291

8286-
return q.convertToWorkspaceRowsNoLock(ctx, workspaces, int64(beforePageCount)), nil
8292+
return q.convertToWorkspaceRowsNoLock(ctx, workspaces, int64(beforePageCount), arg.WithSummary), nil
82878293
}
82888294

82898295
func (q *FakeQuerier) GetAuthorizedUsers(ctx context.Context, arg database.GetUsersParams, prepared rbac.PreparedAuthorized) ([]database.GetUsersRow, error) {

coderd/database/modelqueries.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
231231
arg.RequesterID,
232232
arg.Offset,
233233
arg.Limit,
234+
arg.WithSummary,
234235
)
235236
if err != nil {
236237
return nil, err
@@ -258,6 +259,11 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
258259
&i.TemplateName,
259260
&i.TemplateVersionID,
260261
&i.TemplateVersionName,
262+
&i.Username,
263+
&i.LatestBuildCompletedAt,
264+
&i.LatestBuildCanceledAt,
265+
&i.LatestBuildError,
266+
&i.LatestBuildTransition,
261267
&i.Count,
262268
); err != nil {
263269
return nil, err

coderd/database/queries.sql.go

Lines changed: 106 additions & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/workspaces.sql

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,17 @@ WHERE
7777
);
7878

7979
-- name: GetWorkspaces :many
80+
WITH filtered_workspaces AS (
8081
SELECT
8182
workspaces.*,
8283
COALESCE(template.name, 'unknown') as template_name,
8384
latest_build.template_version_id,
8485
latest_build.template_version_name,
85-
COUNT(*) OVER () as count
86+
users.username as username,
87+
latest_build.completed_at as latest_build_completed_at,
88+
latest_build.canceled_at as latest_build_canceled_at,
89+
latest_build.error as latest_build_error,
90+
latest_build.transition as latest_build_transition
8691
FROM
8792
workspaces
8893
JOIN
@@ -266,23 +271,75 @@ WHERE
266271
END
267272
-- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces
268273
-- @authorize_filter
269-
ORDER BY
270-
-- To ensure that 'favorite' workspaces show up first in the list only for their owner.
271-
CASE WHEN workspaces.owner_id = @requester_id AND workspaces.favorite THEN 0 ELSE 1 END ASC,
272-
(latest_build.completed_at IS NOT NULL AND
273-
latest_build.canceled_at IS NULL AND
274-
latest_build.error IS NULL AND
275-
latest_build.transition = 'start'::workspace_transition) DESC,
276-
LOWER(users.username) ASC,
277-
LOWER(workspaces.name) ASC
278-
LIMIT
279-
CASE
280-
WHEN @limit_ :: integer > 0 THEN
281-
@limit_
282-
END
283-
OFFSET
284-
@offset_
285-
;
274+
), filtered_workspaces_order AS (
275+
SELECT
276+
fw.*
277+
FROM
278+
filtered_workspaces fw
279+
ORDER BY
280+
-- To ensure that 'favorite' workspaces show up first in the list only for their owner.
281+
CASE WHEN owner_id = @requester_id AND favorite THEN 0 ELSE 1 END ASC,
282+
(latest_build_completed_at IS NOT NULL AND
283+
latest_build_canceled_at IS NULL AND
284+
latest_build_error IS NULL AND
285+
latest_build_transition = 'start'::workspace_transition) DESC,
286+
LOWER(username) ASC,
287+
LOWER(name) ASC
288+
LIMIT
289+
CASE
290+
WHEN @limit_ :: integer > 0 THEN
291+
@limit_
292+
END
293+
OFFSET
294+
@offset_
295+
), filtered_workspaces_order_with_summary AS (
296+
SELECT
297+
fwo.*
298+
FROM
299+
filtered_workspaces_order fwo
300+
-- Return a technical summary row with total count of workspaces.
301+
-- It is used to present the correct count if pagination goes beyond the offset.
302+
UNION ALL
303+
SELECT
304+
'00000000-0000-0000-0000-000000000000'::uuid, -- id
305+
'0001-01-01 00:00:00+00'::timestamp, -- created_at
306+
'0001-01-01 00:00:00+00'::timestamp, -- updated_at
307+
'00000000-0000-0000-0000-000000000000'::uuid, -- owner_id
308+
'00000000-0000-0000-0000-000000000000'::uuid, -- organization_id
309+
'00000000-0000-0000-0000-000000000000'::uuid, -- template_id
310+
false, -- deleted
311+
'**TECHNICAL_ROW**', -- name
312+
'', -- autostart_schedule
313+
0, -- ttl
314+
'0001-01-01 00:00:00+00'::timestamp, -- last_used_at
315+
'0001-01-01 00:00:00+00'::timestamp, -- dormant_at
316+
'0001-01-01 00:00:00+00'::timestamp, -- deleting_at
317+
'never'::automatic_updates, -- automatic_updates
318+
false, -- favorite
319+
-- Extra columns added to `filtered_workspaces`
320+
'', -- template_name
321+
'00000000-0000-0000-0000-000000000000'::uuid, -- template_version_id
322+
'', -- template_version_name
323+
'', -- username
324+
'0001-01-01 00:00:00+00'::timestamp, -- latest_build_completed_at,
325+
'0001-01-01 00:00:00+00'::timestamp, -- latest_build_canceled_at,
326+
'', -- latest_build_error
327+
'start'::workspace_transition -- latest_build_transition
328+
WHERE
329+
@with_summary :: boolean = true
330+
), total_count AS (
331+
SELECT
332+
count(*) AS count
333+
FROM
334+
filtered_workspaces
335+
)
336+
SELECT
337+
fwos.*,
338+
tc.count
339+
FROM
340+
filtered_workspaces_order_with_summary fwos
341+
CROSS JOIN
342+
total_count tc;
286343

287344
-- name: GetWorkspaceByOwnerIDAndName :one
288345
SELECT

coderd/workspaces.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) {
173173
// the workspace owner_id when ordering the rows.
174174
filter.RequesterID = apiKey.UserID
175175

176+
// We need the technical row to present the correct count on every page.
177+
filter.WithSummary = true
178+
176179
workspaceRows, err := api.Database.GetAuthorizedWorkspaces(ctx, filter, prepared)
177180
if err != nil {
178181
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
@@ -181,6 +184,23 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) {
181184
})
182185
return
183186
}
187+
if len(workspaceRows) == 0 {
188+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
189+
Message: "Internal error fetching workspaces.",
190+
Detail: "Workspace summary row is missing.",
191+
})
192+
return
193+
}
194+
if len(workspaceRows) == 1 {
195+
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspacesResponse{
196+
Workspaces: []codersdk.Workspace{},
197+
Count: int(workspaceRows[0].Count),
198+
})
199+
return
200+
}
201+
// Skip technical summary row
202+
workspaceRows = workspaceRows[:len(workspaceRows)-1]
203+
184204
if len(workspaceRows) == 0 {
185205
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspacesResponse{
186206
Workspaces: []codersdk.Workspace{},

coderd/workspaces_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1753,6 +1753,7 @@ func TestOffsetLimit(t *testing.T) {
17531753
})
17541754
require.NoError(t, err)
17551755
require.Len(t, ws.Workspaces, 0)
1756+
require.Equal(t, ws.Count, 3) // can't find workspaces, but count is non-zero
17561757
}
17571758

17581759
func TestWorkspaceUpdateAutostart(t *testing.T) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy