K8s: Add sorting by more than titles (#102403)
This commit is contained in:
@@ -34,11 +34,33 @@ func NewDashboardSearchClient(dashboardStore dashboards.Store, sorter sort.Servi
|
||||
}
|
||||
|
||||
var sortByMapping = map[string]string{
|
||||
unisearch.DASHBOARD_VIEWS_LAST_30_DAYS: "viewed-recently-",
|
||||
unisearch.DASHBOARD_VIEWS_TOTAL: "viewed-",
|
||||
unisearch.DASHBOARD_ERRORS_LAST_30_DAYS: "errors-recently-",
|
||||
unisearch.DASHBOARD_ERRORS_TOTAL: "errors-",
|
||||
"title": "alpha-",
|
||||
unisearch.DASHBOARD_VIEWS_LAST_30_DAYS: "viewed-recently",
|
||||
unisearch.DASHBOARD_VIEWS_TOTAL: "viewed",
|
||||
unisearch.DASHBOARD_ERRORS_LAST_30_DAYS: "errors-recently",
|
||||
unisearch.DASHBOARD_ERRORS_TOTAL: "errors",
|
||||
"title": "alpha",
|
||||
}
|
||||
|
||||
func ParseSortName(sortName string) (string, bool, error) {
|
||||
if sortName == "" {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
isDesc := strings.HasSuffix(sortName, "-desc")
|
||||
isAsc := strings.HasSuffix(sortName, "-asc")
|
||||
// default to desc if no suffix is provided
|
||||
if !isDesc && !isAsc {
|
||||
isDesc = true
|
||||
}
|
||||
|
||||
prefix := strings.TrimSuffix(strings.TrimSuffix(sortName, "-desc"), "-asc")
|
||||
for key, mappedPrefix := range sortByMapping {
|
||||
if prefix == mappedPrefix {
|
||||
return key, isDesc, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", false, fmt.Errorf("no matching sort field found for: %s", sortName)
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
@@ -99,9 +121,9 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
|
||||
sorterName := sortByMapping[sortByField]
|
||||
|
||||
if sort.Desc {
|
||||
sorterName += "desc"
|
||||
sorterName += "-desc"
|
||||
} else {
|
||||
sorterName += "asc"
|
||||
sorterName += "-asc"
|
||||
}
|
||||
|
||||
if sorter, ok := c.sorter.GetSortOption(sorterName); ok {
|
||||
@@ -207,22 +229,27 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
|
||||
}
|
||||
}
|
||||
searchFields := resource.StandardSearchFields()
|
||||
columns := []*resource.ResourceTableColumnDefinition{
|
||||
searchFields.Field(resource.SEARCH_FIELD_TITLE),
|
||||
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
|
||||
searchFields.Field(resource.SEARCH_FIELD_TAGS),
|
||||
{
|
||||
Name: unisearch.DASHBOARD_LEGACY_ID,
|
||||
Type: resource.ResourceTableColumnDefinition_INT64,
|
||||
Description: "Deprecated legacy id of the dashboard",
|
||||
},
|
||||
}
|
||||
|
||||
if sortByField != "" {
|
||||
columns = append(columns, &resource.ResourceTableColumnDefinition{
|
||||
Name: sortByField,
|
||||
Type: resource.ResourceTableColumnDefinition_INT64,
|
||||
})
|
||||
}
|
||||
|
||||
list := &resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
searchFields.Field(resource.SEARCH_FIELD_TITLE),
|
||||
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
|
||||
searchFields.Field(resource.SEARCH_FIELD_TAGS),
|
||||
{
|
||||
Name: unisearch.DASHBOARD_LEGACY_ID,
|
||||
Type: resource.ResourceTableColumnDefinition_INT64,
|
||||
Description: "Deprecated legacy id of the dashboard",
|
||||
},
|
||||
{
|
||||
Name: sortByField,
|
||||
Type: resource.ResourceTableColumnDefinition_INT64,
|
||||
},
|
||||
},
|
||||
Columns: columns,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -249,11 +276,22 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
|
||||
}
|
||||
|
||||
for _, dashboard := range dashes {
|
||||
cells := [][]byte{
|
||||
[]byte(dashboard.Title),
|
||||
[]byte(dashboard.FolderUID),
|
||||
[]byte("[]"), // no tags retrieved for provisioned dashboards
|
||||
[]byte(strconv.FormatInt(dashboard.ID, 10)),
|
||||
}
|
||||
|
||||
if sortByField != "" {
|
||||
cells = append(cells, []byte("0"))
|
||||
}
|
||||
|
||||
list.Results.Rows = append(list.Results.Rows, &resource.ResourceTableRow{
|
||||
Key: getResourceKey(&dashboards.DashboardSearchProjection{
|
||||
UID: dashboard.UID,
|
||||
}, req.Options.Key.Namespace),
|
||||
Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), []byte(strconv.FormatInt(dashboard.ID, 10)), {}, {}},
|
||||
Cells: cells,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -275,9 +313,20 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cells := [][]byte{
|
||||
[]byte(dashboard.Title),
|
||||
[]byte(dashboard.FolderUID),
|
||||
tags,
|
||||
[]byte(strconv.FormatInt(dashboard.ID, 10)),
|
||||
}
|
||||
|
||||
if sortByField != "" {
|
||||
cells = append(cells, []byte(strconv.FormatInt(dashboard.SortMeta, 10)))
|
||||
}
|
||||
|
||||
list.Results.Rows = append(list.Results.Rows, &resource.ResourceTableRow{
|
||||
Key: getResourceKey(dashboard, req.Options.Key.Namespace),
|
||||
Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), tags, []byte(strconv.FormatInt(dashboard.ID, 10)), []byte(strconv.FormatInt(dashboard.SortMeta, 10))},
|
||||
Cells: cells,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -77,10 +77,6 @@ func TestDashboardSearchClient_Search(t *testing.T) {
|
||||
Type: resource.ResourceTableColumnDefinition_INT64,
|
||||
Description: "Deprecated legacy id of the dashboard",
|
||||
},
|
||||
{
|
||||
Name: "", // sort by should be empty if title is what we sorted by
|
||||
Type: resource.ResourceTableColumnDefinition_INT64,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
@@ -94,7 +90,6 @@ func TestDashboardSearchClient_Search(t *testing.T) {
|
||||
[]byte("folder1"),
|
||||
tags,
|
||||
[]byte("1"),
|
||||
[]byte(strconv.FormatInt(0, 10)),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -108,7 +103,6 @@ func TestDashboardSearchClient_Search(t *testing.T) {
|
||||
[]byte("folder2"),
|
||||
emptyTags,
|
||||
[]byte("2"),
|
||||
[]byte(strconv.FormatInt(0, 10)),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -501,4 +495,131 @@ func TestDashboardSearchClient_Search(t *testing.T) {
|
||||
}
|
||||
require.Equal(t, resp.TotalHits, int64(1))
|
||||
})
|
||||
|
||||
t.Run("Should set empty sort field when sorting by title", func(t *testing.T) {
|
||||
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
||||
SignedInUser: user,
|
||||
Sort: sort.SortAlphaAsc,
|
||||
Type: "dash-db",
|
||||
}).Return([]dashboards.DashboardSearchProjection{
|
||||
{ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
||||
}, nil).Once()
|
||||
|
||||
req := &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Key: dashboardKey,
|
||||
},
|
||||
SortBy: []*resource.ResourceSearchRequest_Sort{
|
||||
{
|
||||
Field: resource.SEARCH_FIELD_TITLE,
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := client.Search(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
require.Len(t, resp.Results.Columns, 4)
|
||||
mockStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should set correct sort field when sorting by views", func(t *testing.T) {
|
||||
mockStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{
|
||||
{ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", SortMeta: 100},
|
||||
}, nil).Once()
|
||||
|
||||
req := &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Key: dashboardKey,
|
||||
},
|
||||
SortBy: []*resource.ResourceSearchRequest_Sort{
|
||||
{
|
||||
Field: resource.SEARCH_FIELD_PREFIX + unisearch.DASHBOARD_VIEWS_TOTAL,
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := client.Search(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
|
||||
require.Len(t, resp.Results.Columns, 5)
|
||||
i := len(resp.Results.Columns) - 1
|
||||
require.Equal(t, "views_total", resp.Results.Columns[i].Name)
|
||||
require.Equal(t, []byte(strconv.FormatInt(100, 10)), resp.Results.Rows[0].Cells[i]) // views should be set to 100
|
||||
mockStore.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseSortName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sortName string
|
||||
wantField string
|
||||
wantDesc bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty sort name",
|
||||
sortName: "",
|
||||
wantField: "",
|
||||
wantDesc: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "viewed-recently with desc suffix",
|
||||
sortName: "viewed-recently-desc",
|
||||
wantField: unisearch.DASHBOARD_VIEWS_LAST_30_DAYS,
|
||||
wantDesc: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "defaults to desc",
|
||||
sortName: "viewed",
|
||||
wantField: unisearch.DASHBOARD_VIEWS_TOTAL,
|
||||
wantDesc: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "errors-recentlyy with asc suffix",
|
||||
sortName: "errors-recently-asc",
|
||||
wantField: unisearch.DASHBOARD_ERRORS_LAST_30_DAYS,
|
||||
wantDesc: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "errors - defaults to desc too",
|
||||
sortName: "errors",
|
||||
wantField: unisearch.DASHBOARD_ERRORS_TOTAL,
|
||||
wantDesc: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "alpha sort with asc suffix",
|
||||
sortName: "alpha-asc",
|
||||
wantField: "title",
|
||||
wantDesc: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid sort name",
|
||||
sortName: "invalid-sort-desc",
|
||||
wantField: "",
|
||||
wantDesc: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
field, isDesc, err := ParseSortName(tt.sortName)
|
||||
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.wantField, field)
|
||||
require.Equal(t, tt.wantDesc, isDesc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user