Skip to content

fix: use is-dormant instead of dormant_at #10191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions coderd/database/dbfake/dbfake.go
Original file line number Diff line number Diff line change
Expand Up @@ -6818,12 +6818,11 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
}

// We omit locked workspaces by default.
if arg.DormantAt.IsZero() && workspace.DormantAt.Valid {
if arg.IsDormant == "" && workspace.DormantAt.Valid {
continue
}

// Filter out workspaces that are locked after the timestamp.
if !arg.DormantAt.IsZero() && workspace.DormantAt.Time.Before(arg.DormantAt) {
if arg.IsDormant != "" && !workspace.DormantAt.Valid {
continue
}

Expand Down
2 changes: 1 addition & 1 deletion coderd/database/modelqueries.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
arg.Name,
arg.HasAgent,
arg.AgentInactiveDisconnectTimeoutSeconds,
arg.DormantAt,
arg.IsDormant,
arg.LastUsedBefore,
arg.LastUsedAfter,
arg.Offset,
Expand Down
8 changes: 4 additions & 4 deletions coderd/database/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions coderd/database/queries/workspaces.sql
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ WHERE
-- Filter by dormant workspaces. By default we do not return dormant
-- workspaces since they are considered soft-deleted.
AND CASE
WHEN @dormant_at :: timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN
dormant_at IS NOT NULL AND dormant_at >= @dormant_at
WHEN @is_dormant :: text != '' THEN
dormant_at IS NOT NULL
ELSE
dormant_at IS NULL
END
Expand Down
3 changes: 2 additions & 1 deletion coderd/externalauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import (

"golang.org/x/sync/errgroup"

"github.com/sqlc-dev/pqtype"

"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/externalauth"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/codersdk"
"github.com/sqlc-dev/pqtype"
)

// @Summary Get external auth by ID
Expand Down
26 changes: 5 additions & 21 deletions coderd/searchquery/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
)

Expand Down Expand Up @@ -70,22 +69,16 @@ func Users(query string) (database.GetUsersParams, []codersdk.ValidationError) {
return filter, parser.Errors
}

type PostFilter struct {
DeletingBy *time.Time `json:"deleting_by" format:"date-time"`
}

func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectTimeout time.Duration) (database.GetWorkspacesParams, PostFilter, []codersdk.ValidationError) {
func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectTimeout time.Duration) (database.GetWorkspacesParams, []codersdk.ValidationError) {
filter := database.GetWorkspacesParams{
AgentInactiveDisconnectTimeoutSeconds: int64(agentInactiveDisconnectTimeout.Seconds()),

Offset: int32(page.Offset),
Limit: int32(page.Limit),
}

var postFilter PostFilter

if query == "" {
return filter, postFilter, nil
return filter, nil
}

// Always lowercase for all searches.
Expand All @@ -105,7 +98,7 @@ func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectT
return nil
})
if len(errors) > 0 {
return filter, postFilter, errors
return filter, errors
}

parser := httpapi.NewQueryParamParser()
Expand All @@ -114,21 +107,12 @@ func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectT
filter.Name = parser.String(values, "", "name")
filter.Status = string(httpapi.ParseCustom(parser, values, "", "status", httpapi.ParseEnum[database.WorkspaceStatus]))
filter.HasAgent = parser.String(values, "", "has-agent")
filter.DormantAt = parser.Time(values, time.Time{}, "dormant_at", "2006-01-02")
filter.IsDormant = parser.String(values, "", "is-dormant")
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"))
// We want to make sure to grab dormant workspaces since they
// are omitted by default.
if filter.DormantAt.IsZero() {
filter.DormantAt = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)
}
}

parser.ErrorExcessParams(values)
return filter, postFilter, parser.Errors
return filter, parser.Errors
}

func searchTerms(query string, defaultKey func(term string, values url.Values) error) (url.Values, []codersdk.ValidationError) {
Expand Down
47 changes: 2 additions & 45 deletions coderd/searchquery/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/searchquery"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
)

Expand Down Expand Up @@ -150,7 +149,7 @@ func TestSearchWorkspace(t *testing.T) {
c := c
t.Run(c.Name, func(t *testing.T) {
t.Parallel()
values, postFilter, errs := searchquery.Workspaces(c.Query, codersdk.Pagination{}, 0)
values, errs := searchquery.Workspaces(c.Query, codersdk.Pagination{}, 0)
if c.ExpectedErrorContains != "" {
assert.True(t, len(errs) > 0, "expect some errors")
var s strings.Builder
Expand All @@ -159,7 +158,6 @@ func TestSearchWorkspace(t *testing.T) {
}
assert.Contains(t, s.String(), c.ExpectedErrorContains)
} else {
assert.Empty(t, postFilter)
assert.Len(t, errs, 0, "expected no error")
assert.Equal(t, c.Expected, values, "expected values")
}
Expand All @@ -170,51 +168,10 @@ func TestSearchWorkspace(t *testing.T) {

query := ``
timeout := 1337 * time.Second
values, _, errs := searchquery.Workspaces(query, codersdk.Pagination{}, timeout)
values, errs := searchquery.Workspaces(query, codersdk.Pagination{}, timeout)
require.Empty(t, errs)
require.Equal(t, int64(timeout.Seconds()), values.AgentInactiveDisconnectTimeoutSeconds)
})

t.Run("TestSearchWorkspacePostFilter", func(t *testing.T) {
t.Parallel()
testCases := []struct {
Name string
Query string
Expected searchquery.PostFilter
}{
{
Name: "Empty",
Query: "",
Expected: searchquery.PostFilter{},
},
{
Name: "DeletingBy",
Query: "deleting_by:2023-06-09",
Expected: searchquery.PostFilter{
DeletingBy: ptr.Ref(time.Date(
2023, 6, 9, 0, 0, 0, 0, time.UTC)),
},
},
{
Name: "MultipleParams",
Query: "deleting_by:2023-06-09 name:workspace-name",
Expected: searchquery.PostFilter{
DeletingBy: ptr.Ref(time.Date(
2023, 6, 9, 0, 0, 0, 0, time.UTC)),
},
},
}

for _, c := range testCases {
c := c
t.Run(c.Name, func(t *testing.T) {
t.Parallel()
_, postFilter, errs := searchquery.Workspaces(c.Query, codersdk.Pagination{}, 0)
assert.Len(t, errs, 0, "expected no error")
assert.Equal(t, c.Expected, postFilter, "expected values")
})
}
})
}

func TestSearchAudit(t *testing.T) {
Expand Down
22 changes: 2 additions & 20 deletions coderd/workspaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) {
}

queryStr := r.URL.Query().Get("q")
filter, postFilter, errs := searchquery.Workspaces(queryStr, page, api.AgentInactiveDisconnectTimeout)
filter, errs := searchquery.Workspaces(queryStr, page, api.AgentInactiveDisconnectTimeout)
if len(errs) > 0 {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid workspace search query.",
Expand Down Expand Up @@ -191,26 +191,8 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) {
return
}

var filteredWorkspaces []codersdk.Workspace
// apply post filters, if they exist
if postFilter.DeletingBy == nil {
filteredWorkspaces = append(filteredWorkspaces, wss...)
} else {
for _, v := range wss {
if v.DeletingAt == nil {
continue
}
// get the beginning of the day on which deletion is scheduled
truncatedDeletionAt := time.Date(v.DeletingAt.Year(), v.DeletingAt.Month(), v.DeletingAt.Day(), 0, 0, 0, 0, v.DeletingAt.Location())
if truncatedDeletionAt.After(*postFilter.DeletingBy) {
continue
}
filteredWorkspaces = append(filteredWorkspaces, v)
}
}

httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspacesResponse{
Workspaces: filteredWorkspaces,
Workspaces: wss,
Count: int(workspaceRows[0].Count),
})
}
Expand Down
61 changes: 2 additions & 59 deletions coderd/workspaces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"net/http"
"os"
"strings"
"sync/atomic"
"testing"
"time"

Expand Down Expand Up @@ -1395,63 +1394,7 @@ func TestWorkspaceFilterManual(t *testing.T) {
}, testutil.IntervalMedium, "agent status timeout")
})

t.Run("FilterQueryHasDeletingByAndUnlicensed", func(t *testing.T) {
// this test has a licensed counterpart in enterprise/coderd/workspaces_test.go: FilterQueryHasDeletingByAndLicensed
t.Parallel()
inactivityTTL := 1 * 24 * time.Hour
var setCalled int64

client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
TemplateScheduleStore: schedule.MockTemplateScheduleStore{
SetFn: func(ctx context.Context, db database.Store, template database.Template, options schedule.TemplateScheduleOptions) (database.Template, error) {
if atomic.AddInt64(&setCalled, 1) == 2 {
assert.Equal(t, inactivityTTL, options.TimeTilDormant)
}
template.TimeTilDormant = int64(options.TimeTilDormant)
return template, nil
},
},
})
user := coderdtest.CreateFirstUser(t, client)
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)

// update template with inactivity ttl
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()

template, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
TimeTilDormantMillis: inactivityTTL.Milliseconds(),
})

assert.NoError(t, err)
assert.Equal(t, inactivityTTL.Milliseconds(), template.TimeTilDormantMillis)

workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)

// stop build so workspace is inactive
stopBuild := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, stopBuild.ID)

res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
FilterQuery: fmt.Sprintf("deleting_by:%s", time.Now().Add(inactivityTTL).Format("2006-01-02")),
})

assert.NoError(t, err)
// we are expecting that no workspaces are returned as user is unlicensed
// and template.TimeTilDormant should be 0
assert.Len(t, res.Workspaces, 0)
})

t.Run("DormantAt", func(t *testing.T) {
t.Run("IsDormant", func(t *testing.T) {
// this test has a licensed counterpart in enterprise/coderd/workspaces_test.go: FilterQueryHasDeletingByAndLicensed
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
Expand Down Expand Up @@ -1484,7 +1427,7 @@ func TestWorkspaceFilterManual(t *testing.T) {
require.NoError(t, err)

res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
FilterQuery: fmt.Sprintf("dormant_at:%s", time.Now().Add(-time.Minute).Format("2006-01-02")),
FilterQuery: "is-dormant:true",
})
require.NoError(t, err)
require.Len(t, res.Workspaces, 1)
Expand Down
Loading