From 50081619d2ca0921b0dcb5c1972bee6240e88995 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 7 Aug 2024 14:38:28 -0500 Subject: [PATCH 1/3] chore: add fuzzy name search for templates --- coderd/database/dbmem/dbmem.go | 5 +++++ coderd/database/modelqueries.go | 1 + coderd/database/queries.sql.go | 16 ++++++++++++---- coderd/database/queries/templates.sql | 6 ++++++ coderd/searchquery/search.go | 1 + coderd/searchquery/search_test.go | 7 +++++++ 6 files changed, 32 insertions(+), 4 deletions(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 387263fbe18c4..2ad54acd21473 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -9647,6 +9647,11 @@ func (q *FakeQuerier) GetAuthorizedTemplates(ctx context.Context, arg database.G if arg.Deprecated.Valid && arg.Deprecated.Bool == (template.Deprecated != "") { continue } + if arg.FuzzyName != "" { + if !strings.Contains(strings.ToLower(template.Name), strings.ToLower(arg.FuzzyName)) { + continue + } + } if len(arg.IDs) > 0 { match := false diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 532449089535f..83763ca55ec92 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -76,6 +76,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate arg.Deleted, arg.OrganizationID, arg.ExactName, + arg.FuzzyName, pq.Array(arg.IDs), arg.Deprecated, ) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 421fcc9a864d5..61ea2cd3305fa 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -7831,17 +7831,23 @@ WHERE LOWER("name") = LOWER($3) ELSE true END + -- Filter by name, matching on substring + AND CASE + WHEN $4 :: text != '' THEN + lower(name) ILIKE '%' || lower($4) || '%' + ELSE true + END -- Filter by ids AND CASE - WHEN array_length($4 :: uuid[], 1) > 0 THEN - id = ANY($4) + WHEN array_length($5 :: uuid[], 1) > 0 THEN + id = ANY($5) ELSE true END -- Filter by deprecated AND CASE - WHEN $5 :: boolean IS NOT NULL THEN + WHEN $6 :: boolean IS NOT NULL THEN CASE - WHEN $5 :: boolean THEN + WHEN $6 :: boolean THEN deprecated != '' ELSE deprecated = '' @@ -7857,6 +7863,7 @@ type GetTemplatesWithFilterParams struct { Deleted bool `db:"deleted" json:"deleted"` OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` ExactName string `db:"exact_name" json:"exact_name"` + FuzzyName string `db:"fuzzy_name" json:"fuzzy_name"` IDs []uuid.UUID `db:"ids" json:"ids"` Deprecated sql.NullBool `db:"deprecated" json:"deprecated"` } @@ -7866,6 +7873,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate arg.Deleted, arg.OrganizationID, arg.ExactName, + arg.FuzzyName, pq.Array(arg.IDs), arg.Deprecated, ) diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index 31beb11b4e1ca..a2bfa8ae01497 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -28,6 +28,12 @@ WHERE LOWER("name") = LOWER(@exact_name) ELSE true END + -- Filter by name, matching on substring + AND CASE + WHEN @fuzzy_name :: text != '' THEN + lower(name) ILIKE '%' || lower(@fuzzy_name) || '%' + ELSE true + END -- Filter by ids AND CASE WHEN array_length(@ids :: uuid[], 1) > 0 THEN diff --git a/coderd/searchquery/search.go b/coderd/searchquery/search.go index 2ad2a04f57356..78966a255de92 100644 --- a/coderd/searchquery/search.go +++ b/coderd/searchquery/search.go @@ -198,6 +198,7 @@ func Templates(ctx context.Context, db database.Store, query string) (database.G parser := httpapi.NewQueryParamParser() filter := database.GetTemplatesWithFilterParams{ + FuzzyName: parser.String(values, "", "name"), Deleted: parser.Boolean(values, false, "deleted"), ExactName: parser.String(values, "", "exact_name"), IDs: parser.UUIDs(values, []uuid.UUID{}, "ids"), diff --git a/coderd/searchquery/search_test.go b/coderd/searchquery/search_test.go index 536f0ead85170..98f7bed13bac2 100644 --- a/coderd/searchquery/search_test.go +++ b/coderd/searchquery/search_test.go @@ -469,6 +469,13 @@ func TestSearchTemplates(t *testing.T) { Query: "", Expected: database.GetTemplatesWithFilterParams{}, }, + { + Name: "OnlyName", + Query: "foobar", + Expected: database.GetTemplatesWithFilterParams{ + FuzzyName: "foobar", + }, + }, } for _, c := range testCases { From 0ae88056b34bedcd45799c16f2a3bef9957dc19e Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 7 Aug 2024 14:41:21 -0500 Subject: [PATCH 2/3] chore: implement fuzzy name matching for templates Templates search query defaults to a fuzzy name match --- coderd/templates_test.go | 29 +++++++++++++++++++++++++++-- codersdk/organizations.go | 5 +++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/coderd/templates_test.go b/coderd/templates_test.go index 9e20557cafd49..4d6073d6ab835 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -438,8 +438,12 @@ func TestTemplatesByOrganization(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) version2 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) - coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - coderdtest.CreateTemplate(t, client, user.OrganizationID, version2.ID) + foo := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(request *codersdk.CreateTemplateRequest) { + request.Name = "foobar" + }) + bar := coderdtest.CreateTemplate(t, client, user.OrganizationID, version2.ID, func(request *codersdk.CreateTemplateRequest) { + request.Name = "barbaz" + }) ctx := testutil.Context(t, testutil.WaitLong) @@ -460,6 +464,27 @@ func TestTemplatesByOrganization(t *testing.T) { require.Equal(t, tmpl.OrganizationDisplayName, org.DisplayName, "organization display name") require.Equal(t, tmpl.OrganizationIcon, org.Icon, "organization display name") } + + // Check fuzzy name matching + templates, err = client.Templates(ctx, codersdk.TemplateFilter{ + FuzzyName: "bar", + }) + require.NoError(t, err) + require.Len(t, templates, 2) + + templates, err = client.Templates(ctx, codersdk.TemplateFilter{ + FuzzyName: "foo", + }) + require.NoError(t, err) + require.Len(t, templates, 1) + require.Equal(t, foo.ID, templates[0].ID) + + templates, err = client.Templates(ctx, codersdk.TemplateFilter{ + FuzzyName: "baz", + }) + require.NoError(t, err) + require.Len(t, templates, 1) + require.Equal(t, bar.ID, templates[0].ID) }) } diff --git a/codersdk/organizations.go b/codersdk/organizations.go index 277d41cf9ae52..cfb471aa78b8a 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -407,6 +407,7 @@ func (c *Client) TemplatesByOrganization(ctx context.Context, organizationID uui type TemplateFilter struct { OrganizationID uuid.UUID ExactName string + FuzzyName string } // asRequestOption returns a function that can be used in (*Client).Request. @@ -424,6 +425,10 @@ func (f TemplateFilter) asRequestOption() RequestOption { params = append(params, fmt.Sprintf("exact_name:%q", f.ExactName)) } + if f.FuzzyName != "" { + params = append(params, fmt.Sprintf("name:%q", f.FuzzyName)) + } + q := r.URL.Query() q.Set("q", strings.Join(params, " ")) r.URL.RawQuery = q.Encode() From f03a08a1023ead745077519d289b9bccf995cf2c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 7 Aug 2024 15:26:44 -0500 Subject: [PATCH 3/3] make gen --- codersdk/organizations.go | 10 +++++++--- site/src/api/typesGenerated.ts | 3 +-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/codersdk/organizations.go b/codersdk/organizations.go index cfb471aa78b8a..05ebadacf3154 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -405,9 +405,10 @@ func (c *Client) TemplatesByOrganization(ctx context.Context, organizationID uui } type TemplateFilter struct { - OrganizationID uuid.UUID - ExactName string - FuzzyName string + OrganizationID uuid.UUID `typescript:"-"` + ExactName string `typescript:"-"` + FuzzyName string `typescript:"-"` + SearchQuery string `json:"q,omitempty"` } // asRequestOption returns a function that can be used in (*Client).Request. @@ -428,6 +429,9 @@ func (f TemplateFilter) asRequestOption() RequestOption { if f.FuzzyName != "" { params = append(params, fmt.Sprintf("name:%q", f.FuzzyName)) } + if f.SearchQuery != "" { + params = append(params, f.SearchQuery) + } q := r.URL.Query() q.Set("q", strings.Join(params, " ")) diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 5c2dc816fea1e..27d310cb83515 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1291,8 +1291,7 @@ export interface TemplateExample { // From codersdk/organizations.go export interface TemplateFilter { - readonly OrganizationID: string; - readonly ExactName: string; + readonly q?: string; } // From codersdk/templates.go