mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 10:45:45 +01:00
Sort user and usergroup by most match order (#18273)
fixes #17859 Signed-off-by: stonezdj <daojunz@vmware.com>
This commit is contained in:
parent
9973d99f3e
commit
320c64e433
@ -300,3 +300,29 @@ func NextSchedule(cron string, curTime time.Time) time.Time {
|
|||||||
func CronParser() cronlib.Parser {
|
func CronParser() cronlib.Parser {
|
||||||
return cronlib.NewParser(cronlib.Second | cronlib.Minute | cronlib.Hour | cronlib.Dom | cronlib.Month | cronlib.Dow)
|
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)
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -290,6 +291,9 @@ func (u *usersAPI) SearchUsers(ctx context.Context, params operation.SearchUsers
|
|||||||
m := &model.User{User: us}
|
m := &model.User{User: us}
|
||||||
result = append(result, m.ToSearchRespItem())
|
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().
|
return operation.NewSearchUsersOK().
|
||||||
WithXTotalCount(total).
|
WithXTotalCount(total).
|
||||||
WithPayload(result).
|
WithPayload(result).
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
"github.com/goharbor/harbor/src/common"
|
"github.com/goharbor/harbor/src/common"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
ugCtl "github.com/goharbor/harbor/src/controller/usergroup"
|
ugCtl "github.com/goharbor/harbor/src/controller/usergroup"
|
||||||
"github.com/goharbor/harbor/src/lib/config"
|
"github.com/goharbor/harbor/src/lib/config"
|
||||||
"github.com/goharbor/harbor/src/lib/errors"
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
@ -208,28 +209,10 @@ func (u *userGroupAPI) SearchUserGroups(ctx context.Context, params operation.Se
|
|||||||
return u.SendError(ctx, err)
|
return u.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
result := getUserGroupSearchItem(ug)
|
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).
|
return operation.NewSearchUserGroupsOK().WithXTotalCount(total).
|
||||||
WithPayload(result).
|
WithPayload(result).
|
||||||
WithLink(u.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String())
|
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
// Copyright Project Harbor Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_sortMostMatch(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
input []*models.UserGroupSearchItem
|
|
||||||
matchWord string
|
|
||||||
expected []*models.UserGroupSearchItem
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
}{
|
|
||||||
{"normal", args{[]*models.UserGroupSearchItem{
|
|
||||||
{GroupName: "user"}, {GroupName: "harbor_user"}, {GroupName: "admin_user"}, {GroupName: "users"},
|
|
||||||
}, "user", []*models.UserGroupSearchItem{
|
|
||||||
{GroupName: "user"}, {GroupName: "users"}, {GroupName: "admin_user"}, {GroupName: "harbor_user"},
|
|
||||||
}}},
|
|
||||||
{"duplicate_item", args{[]*models.UserGroupSearchItem{
|
|
||||||
{GroupName: "user"}, {GroupName: "user"}, {GroupName: "harbor_user"}, {GroupName: "admin_user"}, {GroupName: "users"},
|
|
||||||
}, "user", []*models.UserGroupSearchItem{
|
|
||||||
{GroupName: "user"}, {GroupName: "user"}, {GroupName: "users"}, {GroupName: "admin_user"}, {GroupName: "harbor_user"},
|
|
||||||
}}},
|
|
||||||
{"miss_exact_match", args{[]*models.UserGroupSearchItem{
|
|
||||||
{GroupName: "harbor_user"}, {GroupName: "admin_user"}, {GroupName: "users"},
|
|
||||||
}, "user", []*models.UserGroupSearchItem{
|
|
||||||
{GroupName: "users"}, {GroupName: "admin_user"}, {GroupName: "harbor_user"},
|
|
||||||
}}},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
sortMostMatch(tt.args.input, tt.args.matchWord)
|
|
||||||
assert.True(t, reflect.DeepEqual(tt.args.input, tt.args.expected))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user