Skip to content

Commit

Permalink
Sort user and usergroup by most match order (#18273)
Browse files Browse the repository at this point in the history
fixes #17859

Signed-off-by: stonezdj <daojunz@vmware.com>
  • Loading branch information
stonezdj committed Mar 1, 2023
1 parent 9973d99 commit 320c64e
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 77 deletions.
26 changes: 26 additions & 0 deletions src/common/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,29 @@ func NextSchedule(cron string, curTime time.Time) time.Time {
func CronParser() cronlib.Parser {
return cronlib.NewParser(cronlib.Second | cronlib.Minute | cronlib.Hour | cronlib.Dom | cronlib.Month | cronlib.Dow)
}

// MostMatchSorter is a sorter for the most match, usually invoked in sort Less function
// usage:
//
// sort.Slice(input, func(i, j int) bool {
// return MostMatchSorter(input[i].GroupName, input[j].GroupName, matchWord)
// })
// a is the field to be used for sorting, b is the other field, matchWord is the word to be matched
// the return value is true if a is less than b
// for example, search with "user", input is {"harbor_user", "user", "users, "admin_user"}
// it returns with this order {"user", "users", "admin_user", "harbor_user"}

func MostMatchSorter(a, b string, matchWord string) bool {
// exact match always first
if a == matchWord {
return true
}
if b == matchWord {
return false
}
// sort by length, then sort by alphabet
if len(a) == len(b) {
return a < b
}
return len(a) < len(b)
}
42 changes: 42 additions & 0 deletions src/common/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"encoding/base64"
"net/http/httptest"
"reflect"
"sort"
"strconv"
"strings"
"testing"
Expand Down Expand Up @@ -394,3 +395,44 @@ func TestNextSchedule(t *testing.T) {
})
}
}

type UserGroupSearchItem struct {
GroupName string
}

func Test_sortMostMatch(t *testing.T) {
type args struct {
input []*UserGroupSearchItem
matchWord string
expected []*UserGroupSearchItem
}
tests := []struct {
name string
args args
}{
{"normal", args{[]*UserGroupSearchItem{
{GroupName: "user"}, {GroupName: "harbor_user"}, {GroupName: "admin_user"}, {GroupName: "users"},
}, "user", []*UserGroupSearchItem{
{GroupName: "user"}, {GroupName: "users"}, {GroupName: "admin_user"}, {GroupName: "harbor_user"},
}}},
{"duplicate_item", args{[]*UserGroupSearchItem{
{GroupName: "user"}, {GroupName: "user"}, {GroupName: "harbor_user"}, {GroupName: "admin_user"}, {GroupName: "users"},
}, "user", []*UserGroupSearchItem{
{GroupName: "user"}, {GroupName: "user"}, {GroupName: "users"}, {GroupName: "admin_user"}, {GroupName: "harbor_user"},
}}},
{"miss_exact_match", args{[]*UserGroupSearchItem{
{GroupName: "harbor_user"}, {GroupName: "admin_user"}, {GroupName: "users"},
}, "user", []*UserGroupSearchItem{
{GroupName: "users"}, {GroupName: "admin_user"}, {GroupName: "harbor_user"},
}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

sort.Slice(tt.args.input, func(i, j int) bool {
return MostMatchSorter(tt.args.input[i].GroupName, tt.args.input[j].GroupName, tt.args.matchWord)
})
assert.True(t, reflect.DeepEqual(tt.args.input, tt.args.expected))
})
}
}
4 changes: 4 additions & 0 deletions src/server/v2.0/handler/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"fmt"
"regexp"
"sort"
"strings"
"time"

Expand Down Expand Up @@ -290,6 +291,9 @@ func (u *usersAPI) SearchUsers(ctx context.Context, params operation.SearchUsers
m := &model.User{User: us}
result = append(result, m.ToSearchRespItem())
}
sort.Slice(result, func(i, j int) bool {
return utils.MostMatchSorter(result[i].Username, result[j].Username, params.Username)
})
return operation.NewSearchUsersOK().
WithXTotalCount(total).
WithPayload(result).
Expand Down
25 changes: 4 additions & 21 deletions src/server/v2.0/handler/usergroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/utils"
ugCtl "github.com/goharbor/harbor/src/controller/usergroup"
"github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/lib/errors"
Expand Down Expand Up @@ -208,28 +209,10 @@ func (u *userGroupAPI) SearchUserGroups(ctx context.Context, params operation.Se
return u.SendError(ctx, err)
}
result := getUserGroupSearchItem(ug)
sortMostMatch(result, params.Groupname)
sort.Slice(result, func(i, j int) bool {
return utils.MostMatchSorter(result[i].GroupName, result[j].GroupName, params.Groupname)
})
return operation.NewSearchUserGroupsOK().WithXTotalCount(total).
WithPayload(result).
WithLink(u.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String())
}

// sortMostMatch given a matchWord, sort the input by the most match,
// for example, search with "user", input is {"harbor_user", "user", "users, "admin_user"}
// it returns with this order {"user", "users", "admin_user", "harbor_user"}
func sortMostMatch(input []*models.UserGroupSearchItem, matchWord string) {
sort.Slice(input, func(i, j int) bool {
// exact match always first
if input[i].GroupName == matchWord {
return true
}
if input[j].GroupName == matchWord {
return false
}
// sort by length, then sort by alphabet
if len(input[i].GroupName) == len(input[j].GroupName) {
return input[i].GroupName < input[j].GroupName
}
return len(input[i].GroupName) < len(input[j].GroupName)
})
}
56 changes: 0 additions & 56 deletions src/server/v2.0/handler/usergroup_test.go

This file was deleted.

0 comments on commit 320c64e

Please sign in to comment.