mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add last_used search params to workspaces (#9230)
* feat: add last_used search params to workspaces
This commit is contained in:
@@ -6064,6 +6064,18 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
|
|||||||
continue
|
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 != "" {
|
if arg.Status != "" {
|
||||||
build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID)
|
build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -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.
|
// Make sure when we acquire the job, we only get this one.
|
||||||
orig.Tags[id.String()] = "true"
|
orig.Tags[id.String()] = "true"
|
||||||
}
|
}
|
||||||
|
jobID := takeFirst(orig.ID, uuid.New())
|
||||||
job, err := db.InsertProvisionerJob(genCtx, database.InsertProvisionerJobParams{
|
job, err := db.InsertProvisionerJob(genCtx, database.InsertProvisionerJobParams{
|
||||||
ID: takeFirst(orig.ID, uuid.New()),
|
ID: jobID,
|
||||||
CreatedAt: takeFirst(orig.CreatedAt, database.Now()),
|
CreatedAt: takeFirst(orig.CreatedAt, database.Now()),
|
||||||
UpdatedAt: takeFirst(orig.UpdatedAt, database.Now()),
|
UpdatedAt: takeFirst(orig.UpdatedAt, database.Now()),
|
||||||
OrganizationID: takeFirst(orig.OrganizationID, uuid.New()),
|
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 != "" {
|
if !orig.CompletedAt.Time.IsZero() || orig.Error.String != "" {
|
||||||
err := db.UpdateProvisionerJobWithCompleteByID(genCtx, database.UpdateProvisionerJobWithCompleteByIDParams{
|
err := db.UpdateProvisionerJobWithCompleteByID(genCtx, database.UpdateProvisionerJobWithCompleteByIDParams{
|
||||||
ID: job.ID,
|
ID: jobID,
|
||||||
UpdatedAt: job.UpdatedAt,
|
UpdatedAt: job.UpdatedAt,
|
||||||
CompletedAt: orig.CompletedAt,
|
CompletedAt: orig.CompletedAt,
|
||||||
Error: orig.Error,
|
Error: orig.Error,
|
||||||
@@ -353,14 +354,14 @@ func ProvisionerJob(t testing.TB, db database.Store, orig database.ProvisionerJo
|
|||||||
}
|
}
|
||||||
if !orig.CanceledAt.Time.IsZero() {
|
if !orig.CanceledAt.Time.IsZero() {
|
||||||
err := db.UpdateProvisionerJobWithCancelByID(genCtx, database.UpdateProvisionerJobWithCancelByIDParams{
|
err := db.UpdateProvisionerJobWithCancelByID(genCtx, database.UpdateProvisionerJobWithCancelByIDParams{
|
||||||
ID: job.ID,
|
ID: jobID,
|
||||||
CanceledAt: orig.CanceledAt,
|
CanceledAt: orig.CanceledAt,
|
||||||
CompletedAt: orig.CompletedAt,
|
CompletedAt: orig.CompletedAt,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
job, err = db.GetProvisionerJobByID(genCtx, job.ID)
|
job, err = db.GetProvisionerJobByID(genCtx, jobID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return job
|
return job
|
||||||
|
|||||||
@@ -218,11 +218,13 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
|
|||||||
arg.HasAgent,
|
arg.HasAgent,
|
||||||
arg.AgentInactiveDisconnectTimeoutSeconds,
|
arg.AgentInactiveDisconnectTimeoutSeconds,
|
||||||
arg.LockedAt,
|
arg.LockedAt,
|
||||||
|
arg.LastUsedBefore,
|
||||||
|
arg.LastUsedAfter,
|
||||||
arg.Offset,
|
arg.Offset,
|
||||||
arg.Limit,
|
arg.Limit,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("get authorized workspaces: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var items []GetWorkspacesRow
|
var items []GetWorkspacesRow
|
||||||
|
|||||||
@@ -9498,6 +9498,17 @@ WHERE
|
|||||||
ELSE
|
ELSE
|
||||||
locked_at IS NULL
|
locked_at IS NULL
|
||||||
END
|
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 clause will be injected below in GetAuthorizedWorkspaces
|
||||||
-- @authorize_filter
|
-- @authorize_filter
|
||||||
ORDER BY
|
ORDER BY
|
||||||
@@ -9509,11 +9520,11 @@ ORDER BY
|
|||||||
LOWER(workspaces.name) ASC
|
LOWER(workspaces.name) ASC
|
||||||
LIMIT
|
LIMIT
|
||||||
CASE
|
CASE
|
||||||
WHEN $12 :: integer > 0 THEN
|
WHEN $14 :: integer > 0 THEN
|
||||||
$12
|
$14
|
||||||
END
|
END
|
||||||
OFFSET
|
OFFSET
|
||||||
$11
|
$13
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetWorkspacesParams struct {
|
type GetWorkspacesParams struct {
|
||||||
@@ -9527,6 +9538,8 @@ type GetWorkspacesParams struct {
|
|||||||
HasAgent string `db:"has_agent" json:"has_agent"`
|
HasAgent string `db:"has_agent" json:"has_agent"`
|
||||||
AgentInactiveDisconnectTimeoutSeconds int64 `db:"agent_inactive_disconnect_timeout_seconds" json:"agent_inactive_disconnect_timeout_seconds"`
|
AgentInactiveDisconnectTimeoutSeconds int64 `db:"agent_inactive_disconnect_timeout_seconds" json:"agent_inactive_disconnect_timeout_seconds"`
|
||||||
LockedAt time.Time `db:"locked_at" json:"locked_at"`
|
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_"`
|
Offset int32 `db:"offset_" json:"offset_"`
|
||||||
Limit int32 `db:"limit_" json:"limit_"`
|
Limit int32 `db:"limit_" json:"limit_"`
|
||||||
}
|
}
|
||||||
@@ -9563,6 +9576,8 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams)
|
|||||||
arg.HasAgent,
|
arg.HasAgent,
|
||||||
arg.AgentInactiveDisconnectTimeoutSeconds,
|
arg.AgentInactiveDisconnectTimeoutSeconds,
|
||||||
arg.LockedAt,
|
arg.LockedAt,
|
||||||
|
arg.LastUsedBefore,
|
||||||
|
arg.LastUsedAfter,
|
||||||
arg.Offset,
|
arg.Offset,
|
||||||
arg.Limit,
|
arg.Limit,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -267,6 +267,17 @@ WHERE
|
|||||||
ELSE
|
ELSE
|
||||||
locked_at IS NULL
|
locked_at IS NULL
|
||||||
END
|
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 clause will be injected below in GetAuthorizedWorkspaces
|
||||||
-- @authorize_filter
|
-- @authorize_filter
|
||||||
ORDER BY
|
ORDER BY
|
||||||
|
|||||||
@@ -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.Status = string(httpapi.ParseCustom(parser, values, "", "status", httpapi.ParseEnum[database.WorkspaceStatus]))
|
||||||
filter.HasAgent = parser.String(values, "", "has-agent")
|
filter.HasAgent = parser.String(values, "", "has-agent")
|
||||||
filter.LockedAt = parser.Time(values, time.Time{}, "locked_at", "2006-01-02")
|
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 {
|
if _, ok := values["deleting_by"]; ok {
|
||||||
postFilter.DeletingBy = ptr.Ref(parser.Time(values, time.Time{}, "deleting_by", "2006-01-02"))
|
postFilter.DeletingBy = ptr.Ref(parser.Time(values, time.Time{}, "deleting_by", "2006-01-02"))
|
||||||
|
|||||||
@@ -1447,6 +1447,62 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
|||||||
require.Len(t, res.Workspaces, 1)
|
require.Len(t, res.Workspaces, 1)
|
||||||
require.NotNil(t, res.Workspaces[0].LockedAt)
|
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 // 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)
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
})
|
||||||
|
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) {
|
func TestOffsetLimit(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user