From fb88b92ca09dd406c13a04e1ae37d2eb47a8b040 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 21 Aug 2023 14:48:36 -0500 Subject: [PATCH 1/3] feat: add last_used search params to workspaces --- coderd/database/dbfake/dbfake.go | 12 ++++++ coderd/database/dbgen/dbgen.go | 9 +++-- coderd/database/modelqueries.go | 4 +- coderd/database/models.go | 2 +- coderd/database/querier.go | 2 +- coderd/database/queries.sql.go | 25 +++++++++--- coderd/database/queries/workspaces.sql | 11 ++++++ coderd/searchquery/search.go | 2 + coderd/workspaces_test.go | 55 ++++++++++++++++++++++++++ 9 files changed, 110 insertions(+), 12 deletions(-) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 589c17efcae0a..85c442ddbb75e 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -6064,6 +6064,18 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. continue } + if !arg.LastUsedBefore.IsZero() { + if workspace.LastUsedAt.After(arg.LastUsedBefore) { + continue + } + } + + if !arg.LastUsedAfter.IsZero() { + if workspace.LastUsedAt.Before(arg.LastUsedAfter) { + continue + } + } + if arg.Status != "" { build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID) if err != nil { diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 369c3974a7b7f..2c3088b9be3b0 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -317,8 +317,9 @@ func ProvisionerJob(t testing.TB, db database.Store, orig database.ProvisionerJo // Make sure when we acquire the job, we only get this one. orig.Tags[id.String()] = "true" } + jobID := takeFirst(orig.ID, uuid.New()) job, err := db.InsertProvisionerJob(genCtx, database.InsertProvisionerJobParams{ - ID: takeFirst(orig.ID, uuid.New()), + ID: jobID, CreatedAt: takeFirst(orig.CreatedAt, database.Now()), UpdatedAt: takeFirst(orig.UpdatedAt, database.Now()), OrganizationID: takeFirst(orig.OrganizationID, uuid.New()), @@ -343,7 +344,7 @@ func ProvisionerJob(t testing.TB, db database.Store, orig database.ProvisionerJo if !orig.CompletedAt.Time.IsZero() || orig.Error.String != "" { err := db.UpdateProvisionerJobWithCompleteByID(genCtx, database.UpdateProvisionerJobWithCompleteByIDParams{ - ID: job.ID, + ID: jobID, UpdatedAt: job.UpdatedAt, CompletedAt: orig.CompletedAt, Error: orig.Error, @@ -353,14 +354,14 @@ func ProvisionerJob(t testing.TB, db database.Store, orig database.ProvisionerJo } if !orig.CanceledAt.Time.IsZero() { err := db.UpdateProvisionerJobWithCancelByID(genCtx, database.UpdateProvisionerJobWithCancelByIDParams{ - ID: job.ID, + ID: jobID, CanceledAt: orig.CanceledAt, CompletedAt: orig.CompletedAt, }) require.NoError(t, err) } - job, err = db.GetProvisionerJobByID(genCtx, job.ID) + job, err = db.GetProvisionerJobByID(genCtx, jobID) require.NoError(t, err) return job diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 725ce690c106d..193a046f5cec1 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -218,11 +218,13 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa arg.HasAgent, arg.AgentInactiveDisconnectTimeoutSeconds, arg.LockedAt, + arg.LastUsedBefore, + arg.LastUsedAfter, arg.Offset, arg.Limit, ) if err != nil { - return nil, xerrors.Errorf("get authorized workspaces: %w", err) + return nil, err } defer rows.Close() var items []GetWorkspacesRow diff --git a/coderd/database/models.go b/coderd/database/models.go index e795049c16413..2dae727da7c2c 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.20.0 +// sqlc v1.19.1 package database diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 2c0cc90277f9a..f5c8d9ecfe1d2 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.20.0 +// sqlc v1.19.1 package database diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 86c953643bdeb..927b4befb15de 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.20.0 +// sqlc v1.19.1 package database @@ -6482,7 +6482,7 @@ func (q *sqlQuerier) GetWorkspaceAgentAndOwnerByAuthToken(ctx context.Context, a &i.WorkspaceAgent.StartupScriptBehavior, &i.WorkspaceAgent.StartedAt, &i.WorkspaceAgent.ReadyAt, - pq.Array(&i.WorkspaceAgent.Subsystems), + &i.WorkspaceAgent.Subsystems, &i.WorkspaceID, &i.OwnerID, &i.OwnerName, @@ -9498,6 +9498,17 @@ WHERE ELSE locked_at IS NULL END + -- Filter by last_used + AND CASE + WHEN $11 :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN + workspaces.last_used_at <= $11 + ELSE true + END + AND CASE + WHEN $12 :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN + workspaces.last_used_at >= $12 + ELSE true + END -- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces -- @authorize_filter ORDER BY @@ -9509,11 +9520,11 @@ ORDER BY LOWER(workspaces.name) ASC LIMIT CASE - WHEN $12 :: integer > 0 THEN - $12 + WHEN $14 :: integer > 0 THEN + $14 END OFFSET - $11 + $13 ` type GetWorkspacesParams struct { @@ -9527,6 +9538,8 @@ type GetWorkspacesParams struct { HasAgent string `db:"has_agent" json:"has_agent"` AgentInactiveDisconnectTimeoutSeconds int64 `db:"agent_inactive_disconnect_timeout_seconds" json:"agent_inactive_disconnect_timeout_seconds"` LockedAt time.Time `db:"locked_at" json:"locked_at"` + LastUsedBefore time.Time `db:"last_used_before" json:"last_used_before"` + LastUsedAfter time.Time `db:"last_used_after" json:"last_used_after"` Offset int32 `db:"offset_" json:"offset_"` Limit int32 `db:"limit_" json:"limit_"` } @@ -9563,6 +9576,8 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) arg.HasAgent, arg.AgentInactiveDisconnectTimeoutSeconds, arg.LockedAt, + arg.LastUsedBefore, + arg.LastUsedAfter, arg.Offset, arg.Limit, ) diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 9dd8aa00b5f55..54b904cda0262 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -267,6 +267,17 @@ WHERE ELSE locked_at IS NULL END + -- Filter by last_used + AND CASE + WHEN @last_used_before :: timestamp with time zone > '0001-01-01 00:00:00Z' THEN + workspaces.last_used_at <= @last_used_before + ELSE true + END + AND CASE + WHEN @last_used_after :: timestamp with time zone > '0001-01-01 00:00:00Z' THEN + workspaces.last_used_at >= @last_used_after + ELSE true + END -- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces -- @authorize_filter ORDER BY diff --git a/coderd/searchquery/search.go b/coderd/searchquery/search.go index 821518c832eec..17d1990880727 100644 --- a/coderd/searchquery/search.go +++ b/coderd/searchquery/search.go @@ -115,6 +115,8 @@ func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectT filter.Status = string(httpapi.ParseCustom(parser, values, "", "status", httpapi.ParseEnum[database.WorkspaceStatus])) filter.HasAgent = parser.String(values, "", "has-agent") filter.LockedAt = parser.Time(values, time.Time{}, "locked_at", "2006-01-02") + filter.LastUsedAfter = parser.Time3339Nano(values, time.Time{}, "last_used_after") + filter.LastUsedBefore = parser.Time3339Nano(values, time.Time{}, "last_used_before") if _, ok := values["deleting_by"]; ok { postFilter.DeletingBy = ptr.Ref(parser.Time(values, time.Time{}, "deleting_by", "2006-01-02")) diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 140265f58073d..5cdf4af801d8d 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -1447,6 +1447,61 @@ func TestWorkspaceFilterManual(t *testing.T) { require.Len(t, res.Workspaces, 1) require.NotNil(t, res.Workspaces[0].LockedAt) }) + + t.Run("LastUsed", func(t *testing.T) { + t.Parallel() + client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }) + user := coderdtest.CreateFirstUser(t, client) + authToken := uuid.NewString() + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: echo.ProvisionComplete, + ProvisionApply: echo.ProvisionApplyWithAgent(authToken), + }) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + _ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + + // update template with inactivity ttl + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + now := database.Now() + before := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + _ = coderdtest.AwaitWorkspaceBuildJob(t, client, before.LatestBuild.ID) + + after := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + _ = coderdtest.AwaitWorkspaceBuildJob(t, client, after.LatestBuild.ID) + + //nolint:gocritic -- testing + err := api.Database.UpdateWorkspaceLastUsedAt(dbauthz.AsSystemRestricted(ctx), database.UpdateWorkspaceLastUsedAtParams{ + ID: before.ID, + LastUsedAt: now.UTC().Add(time.Hour * -1), + }) + require.NoError(t, err) + + //nolint:gocritic -- testing + err = api.Database.UpdateWorkspaceLastUsedAt(dbauthz.AsSystemRestricted(ctx), database.UpdateWorkspaceLastUsedAtParams{ + ID: after.ID, + LastUsedAt: now.UTC().Add(time.Hour * 1), + }) + require.NoError(t, err) + + beforeRes, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ + FilterQuery: fmt.Sprintf("last_used_before:%q", now.Format(time.RFC3339)), + }) + require.NoError(t, err) + require.Len(t, beforeRes.Workspaces, 1) + require.Equal(t, before.ID, beforeRes.Workspaces[0].ID) + + afterRes, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ + FilterQuery: fmt.Sprintf("last_used_after:%q", now.Format(time.RFC3339)), + }) + require.NoError(t, err) + require.Len(t, afterRes.Workspaces, 1) + require.Equal(t, after.ID, afterRes.Workspaces[0].ID) + }) } func TestOffsetLimit(t *testing.T) { From 16d509b29330cc9da223b0494c0b257a9a19cf7c Mon Sep 17 00:00:00 2001 From: Emyrk Date: Mon, 21 Aug 2023 20:01:33 +0000 Subject: [PATCH 2/3] make gen --- coderd/database/models.go | 2 +- coderd/database/querier.go | 2 +- coderd/database/queries.sql.go | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/coderd/database/models.go b/coderd/database/models.go index 2dae727da7c2c..e795049c16413 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.19.1 +// sqlc v1.20.0 package database diff --git a/coderd/database/querier.go b/coderd/database/querier.go index f5c8d9ecfe1d2..2c0cc90277f9a 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.19.1 +// sqlc v1.20.0 package database diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 927b4befb15de..c7bddab3e52a3 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.19.1 +// sqlc v1.20.0 package database @@ -6482,7 +6482,7 @@ func (q *sqlQuerier) GetWorkspaceAgentAndOwnerByAuthToken(ctx context.Context, a &i.WorkspaceAgent.StartupScriptBehavior, &i.WorkspaceAgent.StartedAt, &i.WorkspaceAgent.ReadyAt, - &i.WorkspaceAgent.Subsystems, + pq.Array(&i.WorkspaceAgent.Subsystems), &i.WorkspaceID, &i.OwnerID, &i.OwnerName, @@ -9500,12 +9500,12 @@ WHERE END -- Filter by last_used AND CASE - WHEN $11 :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN + WHEN $11 :: timestamp with time zone > '0001-01-01 00:00:00Z' THEN workspaces.last_used_at <= $11 ELSE true END AND CASE - WHEN $12 :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN + WHEN $12 :: timestamp with time zone > '0001-01-01 00:00:00Z' THEN workspaces.last_used_at >= $12 ELSE true END From 60c42eafd18d4c8b3f8e36d728022fd0fb65668a Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 21 Aug 2023 15:17:21 -0500 Subject: [PATCH 3/3] Linting --- coderd/workspaces_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 5cdf4af801d8d..b42f4517db82d 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -1474,14 +1474,15 @@ func TestWorkspaceFilterManual(t *testing.T) { after := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) _ = coderdtest.AwaitWorkspaceBuildJob(t, client, after.LatestBuild.ID) - //nolint:gocritic -- testing + //nolint:gocritic // Unit testing context err := api.Database.UpdateWorkspaceLastUsedAt(dbauthz.AsSystemRestricted(ctx), database.UpdateWorkspaceLastUsedAtParams{ ID: before.ID, LastUsedAt: now.UTC().Add(time.Hour * -1), }) require.NoError(t, err) - //nolint:gocritic -- testing + // Unit testing context + //nolint:gocritic // Unit testing context err = api.Database.UpdateWorkspaceLastUsedAt(dbauthz.AsSystemRestricted(ctx), database.UpdateWorkspaceLastUsedAtParams{ ID: after.ID, LastUsedAt: now.UTC().Add(time.Hour * 1),