mirror of https://github.com/grafana/grafana
Team: Support `sort` query param for teams search endpoint (#75622)
* Teams: Implement backend sorting * Add docs * Make name ordering case insensitive * lint * Fix no lowercasing on memberCount * Add test to double check the filters or correctly OrderBypull/75628/head^2
parent
a2964731eb
commit
6ffd4a23de
@ -0,0 +1,99 @@ |
|||||||
|
package sortopts |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"sort" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/search/model" |
||||||
|
"github.com/grafana/grafana/pkg/util/errutil" |
||||||
|
"golang.org/x/text/cases" |
||||||
|
"golang.org/x/text/language" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// SortOptionsByQueryParam is a map to translate the "sort" query param values to SortOption(s)
|
||||||
|
SortOptionsByQueryParam = map[string]model.SortOption{ |
||||||
|
"name-asc": newSortOption("name", false, true, 0), // Lower case the name ordering
|
||||||
|
"name-desc": newSortOption("name", true, true, 0), |
||||||
|
"email-asc": newSortOption("email", false, false, 1), // Not to slow down the request let's not lower case the email ordering
|
||||||
|
"email-desc": newSortOption("email", true, false, 1), |
||||||
|
"memberCount-asc": newIntSortOption("member_count", false, 2), |
||||||
|
"memberCount-desc": newIntSortOption("member_count", true, 2), |
||||||
|
} |
||||||
|
|
||||||
|
ErrorUnknownSortingOption = errutil.BadRequest("unknown sorting option") |
||||||
|
) |
||||||
|
|
||||||
|
type Sorter struct { |
||||||
|
Field string |
||||||
|
LowerCase bool |
||||||
|
Descending bool |
||||||
|
WithTableName bool |
||||||
|
} |
||||||
|
|
||||||
|
func (s Sorter) OrderBy() string { |
||||||
|
orderBy := "team." |
||||||
|
if !s.WithTableName { |
||||||
|
orderBy = "" |
||||||
|
} |
||||||
|
orderBy += s.Field |
||||||
|
if s.LowerCase { |
||||||
|
orderBy = fmt.Sprintf("LOWER(%v)", orderBy) |
||||||
|
} |
||||||
|
if s.Descending { |
||||||
|
return orderBy + " DESC" |
||||||
|
} |
||||||
|
return orderBy + " ASC" |
||||||
|
} |
||||||
|
|
||||||
|
func newSortOption(field string, desc bool, lowerCase bool, index int) model.SortOption { |
||||||
|
direction := "asc" |
||||||
|
description := ("A-Z") |
||||||
|
if desc { |
||||||
|
direction = "desc" |
||||||
|
description = ("Z-A") |
||||||
|
} |
||||||
|
return model.SortOption{ |
||||||
|
Name: fmt.Sprintf("%v-%v", field, direction), |
||||||
|
DisplayName: fmt.Sprintf("%v (%v)", cases.Title(language.Und).String(field), description), |
||||||
|
Description: fmt.Sprintf("Sort %v in an alphabetically %vending order", field, direction), |
||||||
|
Index: index, |
||||||
|
Filter: []model.SortOptionFilter{Sorter{Field: field, LowerCase: lowerCase, Descending: desc, WithTableName: true}}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func newIntSortOption(field string, desc bool, index int) model.SortOption { |
||||||
|
direction := "asc" |
||||||
|
description := ("Fewest-Most") |
||||||
|
if desc { |
||||||
|
direction = "desc" |
||||||
|
description = ("Most-Fewest") |
||||||
|
} |
||||||
|
return model.SortOption{ |
||||||
|
Name: fmt.Sprintf("%v-%v", field, direction), |
||||||
|
DisplayName: fmt.Sprintf("%v (%v)", cases.Title(language.Und).String(field), description), |
||||||
|
Description: fmt.Sprintf("Sort %v in a numerically %vending order", field, direction), |
||||||
|
Index: index, |
||||||
|
Filter: []model.SortOptionFilter{Sorter{Field: field, LowerCase: false, Descending: desc, WithTableName: false}}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ParseSortQueryParam parses the "sort" query param and returns an ordered list of SortOption(s)
|
||||||
|
func ParseSortQueryParam(param string) ([]model.SortOption, error) { |
||||||
|
opts := []model.SortOption{} |
||||||
|
if param != "" { |
||||||
|
optsStr := strings.Split(param, ",") |
||||||
|
for i := range optsStr { |
||||||
|
if opt, ok := SortOptionsByQueryParam[optsStr[i]]; !ok { |
||||||
|
return nil, ErrorUnknownSortingOption.Errorf("%v option unknown", optsStr[i]) |
||||||
|
} else { |
||||||
|
opts = append(opts, opt) |
||||||
|
} |
||||||
|
} |
||||||
|
sort.Slice(opts, func(i, j int) bool { |
||||||
|
return opts[i].Index < opts[j].Index || (opts[i].Index == opts[j].Index && opts[i].Name < opts[j].Name) |
||||||
|
}) |
||||||
|
} |
||||||
|
return opts, nil |
||||||
|
} |
@ -0,0 +1,74 @@ |
|||||||
|
package sortopts |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
) |
||||||
|
|
||||||
|
func TestSorter_Filters(t *testing.T) { |
||||||
|
require.Equal(t, SortOptionsByQueryParam["name-asc"].Filter[0].OrderBy(), "LOWER(team.name) ASC") |
||||||
|
require.Equal(t, SortOptionsByQueryParam["name-desc"].Filter[0].OrderBy(), "LOWER(team.name) DESC") |
||||||
|
require.Equal(t, SortOptionsByQueryParam["email-asc"].Filter[0].OrderBy(), "team.email ASC") |
||||||
|
require.Equal(t, SortOptionsByQueryParam["email-desc"].Filter[0].OrderBy(), "team.email DESC") |
||||||
|
require.Equal(t, SortOptionsByQueryParam["memberCount-asc"].Filter[0].OrderBy(), "member_count ASC") |
||||||
|
require.Equal(t, SortOptionsByQueryParam["memberCount-desc"].Filter[0].OrderBy(), "member_count DESC") |
||||||
|
} |
||||||
|
|
||||||
|
func TestSorter_OrderBy(t *testing.T) { |
||||||
|
type fields struct { |
||||||
|
Field string |
||||||
|
LowerCase bool |
||||||
|
Descending bool |
||||||
|
WithTableName bool |
||||||
|
} |
||||||
|
tests := []struct { |
||||||
|
name string |
||||||
|
fields fields |
||||||
|
want string |
||||||
|
}{ |
||||||
|
{ |
||||||
|
name: "team.email case sensitive desc", |
||||||
|
fields: fields{ |
||||||
|
Field: "email", |
||||||
|
LowerCase: false, |
||||||
|
Descending: true, |
||||||
|
WithTableName: true, |
||||||
|
}, |
||||||
|
want: "team.email DESC", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "member_count sensitive desc", |
||||||
|
fields: fields{ |
||||||
|
Field: "member_count", |
||||||
|
LowerCase: false, |
||||||
|
Descending: true, |
||||||
|
WithTableName: false, |
||||||
|
}, |
||||||
|
want: "member_count DESC", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "team.name case insensitive asc", |
||||||
|
fields: fields{ |
||||||
|
Field: "name", |
||||||
|
LowerCase: true, |
||||||
|
Descending: false, |
||||||
|
WithTableName: true, |
||||||
|
}, |
||||||
|
want: "LOWER(team.name) ASC", |
||||||
|
}, |
||||||
|
} |
||||||
|
for _, tt := range tests { |
||||||
|
t.Run(tt.name, func(t *testing.T) { |
||||||
|
s := Sorter{ |
||||||
|
Field: tt.fields.Field, |
||||||
|
LowerCase: tt.fields.LowerCase, |
||||||
|
Descending: tt.fields.Descending, |
||||||
|
WithTableName: tt.fields.WithTableName, |
||||||
|
} |
||||||
|
|
||||||
|
got := s.OrderBy() |
||||||
|
require.Equal(t, tt.want, got) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue