feat(search): update search pattern matching rules (#1257)

Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
LaurentiuNiculae
2023-03-22 19:31:53 +02:00
committed by GitHub
parent fb85c8678c
commit f8a77bc42f
14 changed files with 83 additions and 264 deletions
@@ -897,7 +897,7 @@ func (bdw *DBWrapper) SearchRepos(ctx context.Context, searchText string, filter
return err
}
if score := common.ScoreRepoName(searchText, string(repoName)); score != -1 {
if rank := common.RankRepoName(searchText, string(repoName)); rank != -1 {
var (
// specific values used for sorting that need to be calculated based on all manifests from the repo
repoDownloads = 0
@@ -1006,7 +1006,7 @@ func (bdw *DBWrapper) SearchRepos(ctx context.Context, searchText string, filter
pageFinder.Add(repodb.DetailedRepoMeta{
RepoMeta: repoMeta,
Score: score,
Rank: rank,
Downloads: repoDownloads,
UpdateTime: repoLastUpdated,
})
+2 -2
View File
@@ -9,7 +9,7 @@ import (
// by iterating the manifests, etc.)
type DetailedRepoMeta struct {
RepoMeta RepoMetadata
Score int
Rank int
Downloads int
UpdateTime time.Time
}
@@ -38,7 +38,7 @@ func SortByAlphabeticDsc(pageBuffer []DetailedRepoMeta) func(i, j int) bool {
func SortByRelevance(pageBuffer []DetailedRepoMeta) func(i, j int) bool {
return func(i, j int) bool {
return pageBuffer[i].Score < pageBuffer[j].Score
return pageBuffer[i].Rank < pageBuffer[j].Rank
}
}
+55 -18
View File
@@ -62,7 +62,21 @@ func ValidateRepoReferenceInput(repo, reference string, manifestDigest godigest.
return nil
}
func ScoreRepoName(searchText string, repoName string) int {
// These constants are meant used to describe how high or low in rank a match is.
// Note that the "higher rank" relates to a lower number so ranks are sorted in a
// ascending order.
const (
lowPriority = 100
mediumPriority = 10
highPriority = 1
perfectMatchPriority = 0
)
// RankRepoName associates a rank to a given repoName given a searchText.
// The imporance of the value grows inversly proportional to the int value it has.
// For example: rank(1) > rank(10) > rank(100)...
func RankRepoName(searchText string, repoName string) int {
searchText = strings.Trim(searchText, "/")
searchTextSlice := strings.Split(searchText, "/")
repoNameSlice := strings.Split(repoName, "/")
@@ -70,37 +84,60 @@ func ScoreRepoName(searchText string, repoName string) int {
return -1
}
if searchText == repoName {
return perfectMatchPriority
}
// searchText containst just 1 diretory name
if len(searchTextSlice) == 1 {
// check if it maches first or last name in path
if index := strings.Index(repoNameSlice[len(repoNameSlice)-1], searchTextSlice[0]); index != -1 {
return index + 1
lastNameInRepoPath := repoNameSlice[len(repoNameSlice)-1]
// searchText: "bar" | repoName: "foo/bar" lastNameInRepoPath: "bar"
if index := strings.Index(lastNameInRepoPath, searchText); index != -1 {
return (index + 1) * highPriority
}
// we'll make repos that match the first name in path less important than matching the last name in path
if index := strings.Index(repoNameSlice[0], searchTextSlice[0]); index != -1 {
return (index + 1) * 10
firstNameInRepoPath := repoNameSlice[0]
// searchText: "foo" | repoName: "foo/bar" firstNameInRepoPath: "foo"
if index := strings.Index(firstNameInRepoPath, searchText); index != -1 {
return (index + 1) * mediumPriority
}
return -1
}
if len(searchTextSlice) < len(repoNameSlice) &&
strings.HasPrefix(repoName, searchText) {
return 1
}
foundPrefixInRepoName := true
// searchText and repoName match perfectly up until the last name in path
// searchText: "foo/bar/rep" | repoName: "foo/bar/baz/repo" foundPrefixInRepoName: true
// searchText: "foo/baz/rep" | repoName: "foo/bar/baz/repo" foundPrefixInRepoName: false
for i := 0; i < len(searchTextSlice)-1; i++ {
if searchTextSlice[i] != repoNameSlice[i] {
return -1
foundPrefixInRepoName = false
break
}
}
// check the last
if index := strings.Index(repoNameSlice[len(repoNameSlice)-1], searchTextSlice[len(searchTextSlice)-1]); index != -1 {
return (index + 1)
if foundPrefixInRepoName {
lastNameInRepoPath := repoNameSlice[len(repoNameSlice)-1]
lastNameInSearchText := searchTextSlice[len(searchTextSlice)-1]
// searchText: "foo/bar/epo" | repoName: "foo/bar/baz/repo" -> Index(repo, epo) = 1
if index := strings.Index(lastNameInRepoPath, lastNameInSearchText); index != -1 {
return (index + 1) * highPriority
}
}
// searchText: "foo/bar/b" | repoName: "foo/bar/baz/repo"
if strings.HasPrefix(repoName, searchText) {
return mediumPriority
}
// searchText: "bar/ba" | repoName: "foo/bar/baz/repo"
if index := strings.Index(repoName, searchText); index != -1 {
return (index + 1) * lowPriority
}
// no match
return -1
}
@@ -829,7 +829,7 @@ func (dwr *DBWrapper) SearchRepos(ctx context.Context, searchText string, filter
continue
}
if score := common.ScoreRepoName(searchText, repoMeta.Name); score != -1 {
if rank := common.RankRepoName(searchText, repoMeta.Name); rank != -1 {
var (
// specific values used for sorting that need to be calculated based on all manifests from the repo
repoDownloads = 0
@@ -935,7 +935,7 @@ func (dwr *DBWrapper) SearchRepos(ctx context.Context, searchText string, filter
pageFinder.Add(repodb.DetailedRepoMeta{
RepoMeta: repoMeta,
Score: score,
Rank: rank,
Downloads: repoDownloads,
UpdateTime: repoLastUpdated,
})
+16 -11
View File
@@ -1962,17 +1962,22 @@ func RunRepoDBTests(repoDB repodb.RepoDB, preparationFuncs ...func() error) {
func TestRelevanceSorting(t *testing.T) {
Convey("Test Relevance Sorting", t, func() {
So(common.ScoreRepoName("alpine", "alpine"), ShouldEqual, 1)
So(common.ScoreRepoName("test/alpine", "alpine"), ShouldEqual, -1)
So(common.ScoreRepoName("alpine", "test/alpine"), ShouldEqual, 1)
So(common.ScoreRepoName("test", "test/alpine"), ShouldEqual, 10)
So(common.ScoreRepoName("pine", "test/alpine"), ShouldEqual, 3)
So(common.ScoreRepoName("pine", "alpine/alpine"), ShouldEqual, 3)
So(common.ScoreRepoName("pine", "alpine/test"), ShouldEqual, 30)
So(common.ScoreRepoName("test/pine", "alpine"), ShouldEqual, -1)
So(common.ScoreRepoName("repo/test", "repo/test/alpine"), ShouldEqual, 1)
So(common.ScoreRepoName("repo/test/golang", "repo/test2/alpine"), ShouldEqual, -1)
So(common.ScoreRepoName("repo/test/pine", "repo/test/alpine"), ShouldEqual, 3)
So(common.RankRepoName("alpine", "alpine"), ShouldEqual, 0)
So(common.RankRepoName("test/alpine", "test/alpine"), ShouldEqual, 0)
So(common.RankRepoName("test/alpine", "alpine"), ShouldEqual, -1)
So(common.RankRepoName("alpine", "test/alpine"), ShouldEqual, 1)
So(common.RankRepoName("test", "test/alpine"), ShouldEqual, 10)
So(common.RankRepoName("pine", "test/alpine"), ShouldEqual, 3)
So(common.RankRepoName("pine", "alpine/alpine"), ShouldEqual, 3)
So(common.RankRepoName("pine", "alpine/test"), ShouldEqual, 30)
So(common.RankRepoName("test/pine", "alpine"), ShouldEqual, -1)
So(common.RankRepoName("repo/test", "repo/test/alpine"), ShouldEqual, 10)
So(common.RankRepoName("repo/test/golang", "repo/test2/alpine"), ShouldEqual, -1)
So(common.RankRepoName("repo/test/pine", "repo/test/alpine"), ShouldEqual, 3)
So(common.RankRepoName("debian", "c3/debian/base-amd64"), ShouldEqual, 400)
So(common.RankRepoName("debian/base-amd64", "c3/debian/base-amd64"), ShouldEqual, 400)
So(common.RankRepoName("debian/base-amd64", "c3/aux/debian/base-amd64"), ShouldEqual, 800)
So(common.RankRepoName("aux/debian", "c3/aux/debian/base-amd64"), ShouldEqual, 400)
Convey("Integration", func() {
filePath := path.Join(t.TempDir(), "repo.db")