Skip to content

Commit 37a3b42

Browse files
authored
feat: add last_used search params to workspaces (#9230)
* feat: add last_used search params to workspaces
1 parent e57d635 commit 37a3b42

File tree

7 files changed

+107
-8
lines changed

7 files changed

+107
-8
lines changed

coderd/database/dbfake/dbfake.go

+12
Original file line numberDiff line numberDiff line change
@@ -6064,6 +6064,18 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
60646064
continue
60656065
}
60666066

6067+
if !arg.LastUsedBefore.IsZero() {
6068+
if workspace.LastUsedAt.After(arg.LastUsedBefore) {
6069+
continue
6070+
}
6071+
}
6072+
6073+
if !arg.LastUsedAfter.IsZero() {
6074+
if workspace.LastUsedAt.Before(arg.LastUsedAfter) {
6075+
continue
6076+
}
6077+
}
6078+
60676079
if arg.Status != "" {
60686080
build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID)
60696081
if err != nil {

coderd/database/dbgen/dbgen.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,9 @@ func ProvisionerJob(t testing.TB, db database.Store, orig database.ProvisionerJo
317317
// Make sure when we acquire the job, we only get this one.
318318
orig.Tags[id.String()] = "true"
319319
}
320+
jobID := takeFirst(orig.ID, uuid.New())
320321
job, err := db.InsertProvisionerJob(genCtx, database.InsertProvisionerJobParams{
321-
ID: takeFirst(orig.ID, uuid.New()),
322+
ID: jobID,
322323
CreatedAt: takeFirst(orig.CreatedAt, database.Now()),
323324
UpdatedAt: takeFirst(orig.UpdatedAt, database.Now()),
324325
OrganizationID: takeFirst(orig.OrganizationID, uuid.New()),
@@ -343,7 +344,7 @@ func ProvisionerJob(t testing.TB, db database.Store, orig database.ProvisionerJo
343344

344345
if !orig.CompletedAt.Time.IsZero() || orig.Error.String != "" {
345346
err := db.UpdateProvisionerJobWithCompleteByID(genCtx, database.UpdateProvisionerJobWithCompleteByIDParams{
346-
ID: job.ID,
347+
ID: jobID,
347348
UpdatedAt: job.UpdatedAt,
348349
CompletedAt: orig.CompletedAt,
349350
Error: orig.Error,
@@ -353,14 +354,14 @@ func ProvisionerJob(t testing.TB, db database.Store, orig database.ProvisionerJo
353354
}
354355
if !orig.CanceledAt.Time.IsZero() {
355356
err := db.UpdateProvisionerJobWithCancelByID(genCtx, database.UpdateProvisionerJobWithCancelByIDParams{
356-
ID: job.ID,
357+
ID: jobID,
357358
CanceledAt: orig.CanceledAt,
358359
CompletedAt: orig.CompletedAt,
359360
})
360361
require.NoError(t, err)
361362
}
362363

363-
job, err = db.GetProvisionerJobByID(genCtx, job.ID)
364+
job, err = db.GetProvisionerJobByID(genCtx, jobID)
364365
require.NoError(t, err)
365366

366367
return job

coderd/database/modelqueries.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,13 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
218218
arg.HasAgent,
219219
arg.AgentInactiveDisconnectTimeoutSeconds,
220220
arg.LockedAt,
221+
arg.LastUsedBefore,
222+
arg.LastUsedAfter,
221223
arg.Offset,
222224
arg.Limit,
223225
)
224226
if err != nil {
225-
return nil, xerrors.Errorf("get authorized workspaces: %w", err)
227+
return nil, err
226228
}
227229
defer rows.Close()
228230
var items []GetWorkspacesRow

coderd/database/queries.sql.go

+18-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/workspaces.sql

+11
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,17 @@ WHERE
267267
ELSE
268268
locked_at IS NULL
269269
END
270+
-- Filter by last_used
271+
AND CASE
272+
WHEN @last_used_before :: timestamp with time zone > '0001-01-01 00:00:00Z' THEN
273+
workspaces.last_used_at <= @last_used_before
274+
ELSE true
275+
END
276+
AND CASE
277+
WHEN @last_used_after :: timestamp with time zone > '0001-01-01 00:00:00Z' THEN
278+
workspaces.last_used_at >= @last_used_after
279+
ELSE true
280+
END
270281
-- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces
271282
-- @authorize_filter
272283
ORDER BY

coderd/searchquery/search.go

+2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectT
115115
filter.Status = string(httpapi.ParseCustom(parser, values, "", "status", httpapi.ParseEnum[database.WorkspaceStatus]))
116116
filter.HasAgent = parser.String(values, "", "has-agent")
117117
filter.LockedAt = parser.Time(values, time.Time{}, "locked_at", "2006-01-02")
118+
filter.LastUsedAfter = parser.Time3339Nano(values, time.Time{}, "last_used_after")
119+
filter.LastUsedBefore = parser.Time3339Nano(values, time.Time{}, "last_used_before")
118120

119121
if _, ok := values["deleting_by"]; ok {
120122
postFilter.DeletingBy = ptr.Ref(parser.Time(values, time.Time{}, "deleting_by", "2006-01-02"))

coderd/workspaces_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,62 @@ func TestWorkspaceFilterManual(t *testing.T) {
14471447
require.Len(t, res.Workspaces, 1)
14481448
require.NotNil(t, res.Workspaces[0].LockedAt)
14491449
})
1450+
1451+
t.Run("LastUsed", func(t *testing.T) {
1452+
t.Parallel()
1453+
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
1454+
IncludeProvisionerDaemon: true,
1455+
})
1456+
user := coderdtest.CreateFirstUser(t, client)
1457+
authToken := uuid.NewString()
1458+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
1459+
Parse: echo.ParseComplete,
1460+
ProvisionPlan: echo.ProvisionComplete,
1461+
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
1462+
})
1463+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
1464+
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
1465+
1466+
// update template with inactivity ttl
1467+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1468+
defer cancel()
1469+
1470+
now := database.Now()
1471+
before := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
1472+
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, before.LatestBuild.ID)
1473+
1474+
after := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
1475+
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, after.LatestBuild.ID)
1476+
1477+
//nolint:gocritic // Unit testing context
1478+
err := api.Database.UpdateWorkspaceLastUsedAt(dbauthz.AsSystemRestricted(ctx), database.UpdateWorkspaceLastUsedAtParams{
1479+
ID: before.ID,
1480+
LastUsedAt: now.UTC().Add(time.Hour * -1),
1481+
})
1482+
require.NoError(t, err)
1483+
1484+
// Unit testing context
1485+
//nolint:gocritic // Unit testing context
1486+
err = api.Database.UpdateWorkspaceLastUsedAt(dbauthz.AsSystemRestricted(ctx), database.UpdateWorkspaceLastUsedAtParams{
1487+
ID: after.ID,
1488+
LastUsedAt: now.UTC().Add(time.Hour * 1),
1489+
})
1490+
require.NoError(t, err)
1491+
1492+
beforeRes, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
1493+
FilterQuery: fmt.Sprintf("last_used_before:%q", now.Format(time.RFC3339)),
1494+
})
1495+
require.NoError(t, err)
1496+
require.Len(t, beforeRes.Workspaces, 1)
1497+
require.Equal(t, before.ID, beforeRes.Workspaces[0].ID)
1498+
1499+
afterRes, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
1500+
FilterQuery: fmt.Sprintf("last_used_after:%q", now.Format(time.RFC3339)),
1501+
})
1502+
require.NoError(t, err)
1503+
require.Len(t, afterRes.Workspaces, 1)
1504+
require.Equal(t, after.ID, afterRes.Workspaces[0].ID)
1505+
})
14501506
}
14511507

14521508
func TestOffsetLimit(t *testing.T) {

0 commit comments

Comments
 (0)