Skip to content

Commit 3312c81

Browse files
authored
feat: Workspace filters case insensitive (#2646)
1 parent 90815e5 commit 3312c81

File tree

7 files changed

+90
-37
lines changed

7 files changed

+90
-37
lines changed

coderd/database/databasefake/databasefake.go

+10-12
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,9 @@ func (q *fakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams
289289
if len(params.Status) > 0 {
290290
usersFilteredByStatus := make([]database.User, 0, len(users))
291291
for i, user := range users {
292-
if slice.Contains(params.Status, user.Status) {
292+
if slice.ContainsCompare(params.Status, user.Status, func(a, b database.UserStatus) bool {
293+
return strings.EqualFold(string(a), string(b))
294+
}) {
293295
usersFilteredByStatus = append(usersFilteredByStatus, users[i])
294296
}
295297
}
@@ -299,7 +301,7 @@ func (q *fakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams
299301
if len(params.RbacRole) > 0 && !slice.Contains(params.RbacRole, rbac.RoleMember()) {
300302
usersFilteredByRole := make([]database.User, 0, len(users))
301303
for i, user := range users {
302-
if slice.Overlap(params.RbacRole, user.RBACRoles) {
304+
if slice.OverlapCompare(params.RbacRole, user.RBACRoles, strings.EqualFold) {
303305
usersFilteredByRole = append(usersFilteredByRole, users[i])
304306
}
305307
}
@@ -385,25 +387,21 @@ func (q *fakeQuerier) GetWorkspaces(_ context.Context, arg database.GetWorkspace
385387
}
386388
if arg.OwnerUsername != "" {
387389
owner, err := q.GetUserByID(context.Background(), workspace.OwnerID)
388-
if err == nil && arg.OwnerUsername != owner.Username {
390+
if err == nil && !strings.EqualFold(arg.OwnerUsername, owner.Username) {
389391
continue
390392
}
391393
}
392394
if arg.TemplateName != "" {
393-
templates, err := q.GetTemplatesWithFilter(context.Background(), database.GetTemplatesWithFilterParams{
394-
ExactName: arg.TemplateName,
395-
})
396-
// Add to later param
397-
if err == nil {
398-
for _, t := range templates {
399-
arg.TemplateIds = append(arg.TemplateIds, t.ID)
400-
}
395+
template, err := q.GetTemplateByID(context.Background(), workspace.TemplateID)
396+
if err == nil && !strings.EqualFold(arg.TemplateName, template.Name) {
397+
continue
401398
}
402399
}
403400
if !arg.Deleted && workspace.Deleted {
404401
continue
405402
}
406-
if arg.Name != "" && !strings.Contains(workspace.Name, arg.Name) {
403+
404+
if arg.Name != "" && !strings.Contains(strings.ToLower(workspace.Name), strings.ToLower(arg.Name)) {
407405
continue
408406
}
409407
if len(arg.TemplateIds) > 0 {

coderd/database/queries.sql.go

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

coderd/database/queries/workspaces.sql

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ WHERE
2525
-- Filter by owner_name
2626
AND CASE
2727
WHEN @owner_username :: text != '' THEN
28-
owner_id = (SELECT id FROM users WHERE username = @owner_username)
28+
owner_id = (SELECT id FROM users WHERE lower(username) = lower(@owner_username))
2929
ELSE true
3030
END
3131
-- Filter by template_name
3232
-- There can be more than 1 template with the same name across organizations.
3333
-- Use the organization filter to restrict to 1 org if needed.
3434
AND CASE
3535
WHEN @template_name :: text != '' THEN
36-
template_id = ANY(SELECT id FROM templates WHERE name = @template_name)
36+
template_id = ANY(SELECT id FROM templates WHERE lower(name) = lower(@template_name))
3737
ELSE true
3838
END
3939
-- Filter by template_ids
@@ -45,7 +45,7 @@ WHERE
4545
-- Filter by name, matching on substring
4646
AND CASE
4747
WHEN @name :: text != '' THEN
48-
LOWER(name) LIKE '%' || LOWER(@name) || '%'
48+
name ILIKE '%' || @name || '%'
4949
ELSE true
5050
END
5151
;

coderd/util/slice/slice.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
11
package slice
22

3-
func Contains[T comparable](haystack []T, needle T) bool {
3+
func ContainsCompare[T any](haystack []T, needle T, equal func(a, b T) bool) bool {
44
for _, hay := range haystack {
5-
if needle == hay {
5+
if equal(needle, hay) {
66
return true
77
}
88
}
99
return false
1010
}
1111

12+
func Contains[T comparable](haystack []T, needle T) bool {
13+
return ContainsCompare(haystack, needle, func(a, b T) bool {
14+
return a == b
15+
})
16+
}
17+
1218
// Overlap returns if the 2 sets have any overlap (element(s) in common)
1319
func Overlap[T comparable](a []T, b []T) bool {
20+
return OverlapCompare(a, b, func(a, b T) bool {
21+
return a == b
22+
})
23+
}
24+
25+
func OverlapCompare[T any](a []T, b []T, equal func(a, b T) bool) bool {
1426
// For each element in b, if at least 1 is contained in 'a',
1527
// return true.
1628
for _, element := range b {
17-
if Contains(a, element) {
29+
if ContainsCompare(a, element, equal) {
1830
return true
1931
}
2032
}

coderd/workspaces.go

+1
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,7 @@ func workspaceSearchQuery(query string) (database.GetWorkspacesParams, []httpapi
959959
// No filter
960960
return database.GetWorkspacesParams{}, nil
961961
}
962+
query = strings.ToLower(query)
962963
// Because we do this in 2 passes, we want to maintain quotes on the first
963964
// pass.Further splitting occurs on the second pass and quotes will be
964965
// dropped.

coderd/workspaces_internal_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ func TestSearchWorkspace(t *testing.T) {
2727
Name: "Owner/Name",
2828
Query: "Foo/Bar",
2929
Expected: database.GetWorkspacesParams{
30-
OwnerUsername: "Foo",
31-
Name: "Bar",
30+
OwnerUsername: "foo",
31+
Name: "bar",
3232
},
3333
},
3434
{
@@ -40,15 +40,15 @@ func TestSearchWorkspace(t *testing.T) {
4040
},
4141
{
4242
Name: "Name+Param",
43-
Query: "workspace-name template:docker",
43+
Query: "workspace-name TEMPLATE:docker",
4444
Expected: database.GetWorkspacesParams{
4545
Name: "workspace-name",
4646
TemplateName: "docker",
4747
},
4848
},
4949
{
5050
Name: "OnlyParams",
51-
Query: "name:workspace-name template:docker owner:alice",
51+
Query: "name:workspace-name template:docker OWNER:Alice",
5252
Expected: database.GetWorkspacesParams{
5353
Name: "workspace-name",
5454
TemplateName: "docker",

coderd/workspaces_test.go

+54-12
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,13 @@ func TestWorkspaceFilter(t *testing.T) {
358358
user, err := userClient.User(context.Background(), codersdk.Me)
359359
require.NoError(t, err, "fetch me")
360360

361+
if i%3 == 0 {
362+
user, err = client.UpdateUserProfile(context.Background(), user.ID.String(), codersdk.UpdateUserProfileRequest{
363+
Username: strings.ToUpper(user.Username),
364+
})
365+
require.NoError(t, err, "uppercase username")
366+
}
367+
361368
org, err := userClient.CreateOrganization(context.Background(), codersdk.CreateOrganizationRequest{
362369
Name: user.Username + "-org",
363370
})
@@ -378,16 +385,32 @@ func TestWorkspaceFilter(t *testing.T) {
378385

379386
availTemplates := make([]codersdk.Template, 0)
380387
allWorkspaces := make([]madeWorkspace, 0)
388+
upperTemplates := make([]string, 0)
381389

382390
// Create some random workspaces
383-
for _, user := range users {
391+
var count int
392+
for i, user := range users {
384393
version := coderdtest.CreateTemplateVersion(t, client, user.Org.ID, nil)
385394

386395
// Create a template & workspace in the user's org
387396
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
388-
template := coderdtest.CreateTemplate(t, client, user.Org.ID, version.ID)
397+
398+
var template codersdk.Template
399+
if i%3 == 0 {
400+
template = coderdtest.CreateTemplate(t, client, user.Org.ID, version.ID, func(request *codersdk.CreateTemplateRequest) {
401+
request.Name = strings.ToUpper(request.Name)
402+
})
403+
upperTemplates = append(upperTemplates, template.Name)
404+
} else {
405+
template = coderdtest.CreateTemplate(t, client, user.Org.ID, version.ID)
406+
}
407+
389408
availTemplates = append(availTemplates, template)
390-
workspace := coderdtest.CreateWorkspace(t, user.Client, template.OrganizationID, template.ID)
409+
workspace := coderdtest.CreateWorkspace(t, user.Client, template.OrganizationID, template.ID, func(request *codersdk.CreateWorkspaceRequest) {
410+
if count%3 == 0 {
411+
request.Name = strings.ToUpper(request.Name)
412+
}
413+
})
391414
allWorkspaces = append(allWorkspaces, madeWorkspace{
392415
Workspace: workspace,
393416
Template: template,
@@ -428,19 +451,28 @@ func TestWorkspaceFilter(t *testing.T) {
428451
{
429452
Name: "Owner",
430453
Filter: codersdk.WorkspaceFilter{
431-
Owner: users[2].User.Username,
454+
Owner: strings.ToUpper(users[2].User.Username),
432455
},
433456
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
434-
return workspace.Owner.Username == f.Owner
457+
return strings.EqualFold(workspace.Owner.Username, f.Owner)
435458
},
436459
},
437460
{
438461
Name: "TemplateName",
439462
Filter: codersdk.WorkspaceFilter{
440-
Template: allWorkspaces[5].Template.Name,
463+
Template: strings.ToUpper(allWorkspaces[5].Template.Name),
441464
},
442465
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
443-
return workspace.Template.Name == f.Template
466+
return strings.EqualFold(workspace.Template.Name, f.Template)
467+
},
468+
},
469+
{
470+
Name: "UpperTemplateName",
471+
Filter: codersdk.WorkspaceFilter{
472+
Template: upperTemplates[0],
473+
},
474+
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
475+
return strings.EqualFold(workspace.Template.Name, f.Template)
444476
},
445477
},
446478
{
@@ -450,16 +482,21 @@ func TestWorkspaceFilter(t *testing.T) {
450482
Name: "a",
451483
},
452484
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
453-
return strings.Contains(workspace.Workspace.Name, f.Name)
485+
return strings.ContainsAny(workspace.Workspace.Name, "Aa")
454486
},
455487
},
456488
{
457489
Name: "Q-Owner/Name",
458490
Filter: codersdk.WorkspaceFilter{
459-
FilterQuery: allWorkspaces[5].Owner.Username + "/" + allWorkspaces[5].Workspace.Name,
491+
FilterQuery: allWorkspaces[5].Owner.Username + "/" + strings.ToUpper(allWorkspaces[5].Workspace.Name),
460492
},
461-
FilterF: func(_ codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
462-
return workspace.Workspace.ID == allWorkspaces[5].Workspace.ID
493+
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
494+
if strings.EqualFold(workspace.Owner.Username, allWorkspaces[5].Owner.Username) &&
495+
strings.Contains(strings.ToLower(workspace.Workspace.Name), strings.ToLower(allWorkspaces[5].Workspace.Name)) {
496+
return true
497+
}
498+
499+
return false
463500
},
464501
},
465502
{
@@ -470,7 +507,12 @@ func TestWorkspaceFilter(t *testing.T) {
470507
Name: allWorkspaces[3].Workspace.Name,
471508
},
472509
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
473-
return workspace.Workspace.ID == allWorkspaces[3].Workspace.ID
510+
if strings.EqualFold(workspace.Owner.Username, f.Owner) &&
511+
strings.Contains(strings.ToLower(workspace.Workspace.Name), strings.ToLower(f.Name)) &&
512+
strings.EqualFold(workspace.Template.Name, f.Template) {
513+
return true
514+
}
515+
return false
474516
},
475517
},
476518
}

0 commit comments

Comments
 (0)