apiserver/folders: use exact match on GetFolderByTitle in legacy (#106867)
* apiserver/folders: use exact match on GetFolderByTitle in legacy Signed-off-by: Maicon Costa <maiconscosta@gmail.com> --------- Signed-off-by: Maicon Costa <maiconscosta@gmail.com>
This commit is contained in:
@@ -229,6 +229,13 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso
|
||||
return nil, fmt.Errorf("only one repo name is supported")
|
||||
}
|
||||
query.ManagerIdentity = vals[0]
|
||||
case resource.SEARCH_FIELD_TITLE_PHRASE:
|
||||
if len(vals) != 1 {
|
||||
return nil, fmt.Errorf("only one title supported")
|
||||
}
|
||||
|
||||
query.Title = vals[0]
|
||||
query.TitleExactMatch = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -309,6 +309,38 @@ func TestDashboardSearchClient_Search(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("When searching for SEARCH_FIELD_TITLE_PHRASE, value should be set as title, and ExactMatch should be enabled", func(t *testing.T) {
|
||||
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
||||
Title: "test",
|
||||
TitleExactMatch: true,
|
||||
SignedInUser: user, // user from context should be used
|
||||
Type: "dash-db", // should set type based off of key
|
||||
}).Return([]dashboards.DashboardSearchProjection{
|
||||
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
||||
}, nil).Once()
|
||||
|
||||
req := &resourcepb.ResourceSearchRequest{
|
||||
Options: &resourcepb.ListOptions{
|
||||
Key: dashboardKey,
|
||||
Fields: []*resourcepb.Requirement{
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_TITLE_PHRASE, // nolint:staticcheck
|
||||
Operator: string(selection.Equals),
|
||||
Values: []string{"test"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := client.Search(ctx, req)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
mockStore.AssertExpectations(t)
|
||||
for _, row := range resp.Results.Rows {
|
||||
require.Equal(t, len(row.Cells), len(resp.Results.Columns))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Should read labels for the dashboard ids", func(t *testing.T) {
|
||||
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
||||
DashboardIds: []int64{1, 2},
|
||||
|
||||
@@ -882,7 +882,7 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F
|
||||
}
|
||||
|
||||
if len(query.Title) > 0 {
|
||||
filters = append(filters, searchstore.TitleFilter{Dialect: d.store.GetDialect(), Title: query.Title})
|
||||
filters = append(filters, searchstore.TitleFilter{Dialect: d.store.GetDialect(), Title: query.Title, TitleExactMatch: query.TitleExactMatch})
|
||||
}
|
||||
|
||||
if len(query.Type) > 0 {
|
||||
|
||||
@@ -888,25 +888,47 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) {
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
title string
|
||||
expectedResult res
|
||||
typ string
|
||||
desc string
|
||||
title string
|
||||
titleExactMatch bool
|
||||
expectedResult *res
|
||||
typ string
|
||||
}{
|
||||
{
|
||||
desc: "find dashboard under general",
|
||||
title: "dashboard under general",
|
||||
expectedResult: res{title: "dashboard under general"},
|
||||
expectedResult: &res{title: "dashboard under general"},
|
||||
},
|
||||
{
|
||||
desc: "find dashboard under f0",
|
||||
title: "dashboard under f0",
|
||||
expectedResult: res{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
expectedResult: &res{title: "dashboard under f0", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
},
|
||||
{
|
||||
desc: "find dashboard under subfolder",
|
||||
title: "dashboard under subfolder",
|
||||
expectedResult: res{title: "dashboard under subfolder", folderUID: subfolder.UID, folderTitle: subfolder.Title},
|
||||
expectedResult: &res{title: "dashboard under subfolder", folderUID: subfolder.UID, folderTitle: subfolder.Title},
|
||||
},
|
||||
{
|
||||
desc: "find folder 'sub' using partial match: 1 result found",
|
||||
title: "sub",
|
||||
titleExactMatch: false,
|
||||
typ: "dash-folder",
|
||||
expectedResult: &res{title: "subfolder", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
},
|
||||
{
|
||||
desc: "find folder 'sub' using exact match: no results",
|
||||
title: "sub",
|
||||
titleExactMatch: true,
|
||||
typ: "dash-folder",
|
||||
expectedResult: nil,
|
||||
},
|
||||
{
|
||||
desc: "find folder 'subfolder' using exact match: 1 result",
|
||||
title: "subfolder",
|
||||
titleExactMatch: true,
|
||||
typ: "dash-folder",
|
||||
expectedResult: &res{title: "subfolder", folderUID: f0.UID, folderTitle: f0.Title},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -915,11 +937,18 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) {
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore))
|
||||
require.NoError(t, err)
|
||||
res, err := dashboardStore.FindDashboards(context.Background(), &dashboards.FindPersistedDashboardsQuery{
|
||||
SignedInUser: user,
|
||||
Type: tc.typ,
|
||||
Title: tc.title,
|
||||
SignedInUser: user,
|
||||
Type: tc.typ,
|
||||
Title: tc.title,
|
||||
TitleExactMatch: tc.titleExactMatch,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.expectedResult == nil {
|
||||
require.Equal(t, 0, len(res))
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, 1, len(res))
|
||||
|
||||
r := tc.expectedResult
|
||||
|
||||
@@ -430,12 +430,13 @@ type DashboardACLInfoDTO struct {
|
||||
}
|
||||
|
||||
type FindPersistedDashboardsQuery struct {
|
||||
Title string
|
||||
OrgId int64
|
||||
SignedInUser identity.Requester
|
||||
DashboardIds []int64
|
||||
DashboardUIDs []string
|
||||
Type string
|
||||
Title string
|
||||
TitleExactMatch bool
|
||||
OrgId int64
|
||||
SignedInUser identity.Requester
|
||||
DashboardIds []int64
|
||||
DashboardUIDs []string
|
||||
Type string
|
||||
// Deprecated: use FolderUIDs instead
|
||||
FolderIds []int64
|
||||
FolderUIDs []string
|
||||
|
||||
@@ -310,11 +310,16 @@ func (s *Service) getFolderByTitleFromApiServer(ctx context.Context, orgID int64
|
||||
|
||||
request := &resourcepb.ResourceSearchRequest{
|
||||
Options: &resourcepb.ListOptions{
|
||||
Key: folderkey,
|
||||
Fields: []*resourcepb.Requirement{},
|
||||
Key: folderkey,
|
||||
Fields: []*resourcepb.Requirement{
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_TITLE_PHRASE, // nolint:staticcheck
|
||||
Operator: string(selection.Equals),
|
||||
Values: []string{title},
|
||||
},
|
||||
},
|
||||
Labels: []*resourcepb.Requirement{},
|
||||
},
|
||||
Query: title,
|
||||
Limit: folderSearchLimit}
|
||||
|
||||
if parentUID != nil {
|
||||
|
||||
@@ -792,11 +792,16 @@ func TestGetFoldersFromApiServer(t *testing.T) {
|
||||
service.unifiedStore = fakeFolderStore
|
||||
fakeK8sClient.On("Search", mock.Anything, int64(1), &resourcepb.ResourceSearchRequest{
|
||||
Options: &resourcepb.ListOptions{
|
||||
Key: folderkey,
|
||||
Fields: []*resourcepb.Requirement{},
|
||||
Key: folderkey,
|
||||
Fields: []*resourcepb.Requirement{
|
||||
&resourcepb.Requirement{
|
||||
Key: resource.SEARCH_FIELD_TITLE_PHRASE, // nolint:staticcheck
|
||||
Operator: string(selection.Equals),
|
||||
Values: []string{"foo title"},
|
||||
},
|
||||
},
|
||||
Labels: []*resourcepb.Requirement{},
|
||||
},
|
||||
Query: "foo title",
|
||||
Limit: folderSearchLimit}).
|
||||
Return(&resourcepb.ResourceSearchResponse{
|
||||
Results: &resourcepb.ResourceTable{
|
||||
|
||||
@@ -43,11 +43,16 @@ func (f OrgFilter) Where() (string, []any) {
|
||||
}
|
||||
|
||||
type TitleFilter struct {
|
||||
Dialect migrator.Dialect
|
||||
Title string
|
||||
Dialect migrator.Dialect
|
||||
Title string
|
||||
TitleExactMatch bool
|
||||
}
|
||||
|
||||
func (f TitleFilter) Where() (string, []any) {
|
||||
if f.TitleExactMatch {
|
||||
return "dashboard.title = ?", []any{f.Title}
|
||||
}
|
||||
|
||||
sql, params := f.Dialect.LikeOperator("dashboard.title", true, f.Title, true)
|
||||
return sql, []any{params}
|
||||
}
|
||||
|
||||
@@ -91,3 +91,44 @@ func TestFolderUIDFilter(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTitleFilter(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
title string
|
||||
exactMatch bool
|
||||
expectedSql string
|
||||
expectedParams []any
|
||||
}{
|
||||
{
|
||||
description: "searching foo folder - partial match",
|
||||
title: "foo",
|
||||
expectedSql: "dashboard.title LIKE ?",
|
||||
expectedParams: []any{"%foo%"},
|
||||
},
|
||||
{
|
||||
description: "searching foo folder - exact match",
|
||||
title: "foo",
|
||||
exactMatch: true,
|
||||
expectedSql: "dashboard.title = ?",
|
||||
expectedParams: []any{"foo"},
|
||||
},
|
||||
}
|
||||
|
||||
store := setupTestEnvironment(t)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
f := searchstore.TitleFilter{
|
||||
Dialect: store.GetDialect(),
|
||||
Title: tc.title,
|
||||
TitleExactMatch: tc.exactMatch,
|
||||
}
|
||||
|
||||
sql, params := f.Where()
|
||||
|
||||
assert.Equal(t, tc.expectedSql, sql)
|
||||
assert.Equal(t, tc.expectedParams, params)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user