mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
chore: implement fuzzy name matching for templates (#14211)
* chore: add fuzzy name search for templates * chore: implement fuzzy name matching for templates Templates search query defaults to a fuzzy name match
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -405,8 +405,10 @@ func (c *Client) TemplatesByOrganization(ctx context.Context, organizationID uui
|
||||
}
|
||||
|
||||
type TemplateFilter struct {
|
||||
OrganizationID uuid.UUID
|
||||
ExactName 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.
|
||||
@@ -424,6 +426,13 @@ 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))
|
||||
}
|
||||
if f.SearchQuery != "" {
|
||||
params = append(params, f.SearchQuery)
|
||||
}
|
||||
|
||||
q := r.URL.Query()
|
||||
q.Set("q", strings.Join(params, " "))
|
||||
r.URL.RawQuery = q.Encode()
|
||||
|
||||
Generated
+1
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user