diff --git a/pkg/db/helpers.go b/pkg/db/helpers.go index 5e30c85d9..41819ebe4 100644 --- a/pkg/db/helpers.go +++ b/pkg/db/helpers.go @@ -22,7 +22,7 @@ import ( ) // ILIKE returns an ILIKE query on postgres and a LIKE query on all other platforms. -// Postgres' is case sensitive by default. +// Postgres' is case-sensitive by default. // To work around this, we're using ILIKE as opposed to normal LIKE statements. // ILIKE is preferred over LOWER(text) LIKE for performance reasons. // See https://stackoverflow.com/q/7005302/10924593 @@ -31,5 +31,9 @@ func ILIKE(column, search string) builder.Cond { return builder.Expr(column+" ILIKE ?", "%"+search+"%") } + if Type() == schemas.SQLITE { + return builder.Expr("username = ? COLLATE NOCASE", "%"+search+"%") + } + return &builder.Like{column, "%" + search + "%"} } diff --git a/pkg/models/user_project.go b/pkg/models/user_project.go index a929541ef..75588b1bd 100644 --- a/pkg/models/user_project.go +++ b/pkg/models/user_project.go @@ -105,6 +105,7 @@ func ListUsersFromProject(s *xorm.Session, l *Project, search string) (users []* users, err = user.ListUsers(s, search, &user.ProjectUserOpts{ AdditionalCond: cond, ReturnAllIfNoSearchProvided: true, + MatchFuzzily: true, }) return } diff --git a/pkg/models/user_project_test.go b/pkg/models/user_project_test.go index ca770d7ca..0a630b261 100644 --- a/pkg/models/user_project_test.go +++ b/pkg/models/user_project_test.go @@ -24,7 +24,7 @@ import ( "gopkg.in/d4l3k/messagediff.v1" ) -func TestProjectUsersFromProject(t *testing.T) { +func TestListUsersFromProject(t *testing.T) { testuser1 := &user.User{ ID: 1, Username: "user1", @@ -219,6 +219,11 @@ func TestProjectUsersFromProject(t *testing.T) { args: args{l: &Project{ID: 19, OwnerID: 7}, search: "user1"}, wantUsers: []*user.User{ testuser1, // Shared Via Team readonly + + testuser10, // Matches Partially, Shared Via NamespaceTeam admin + testuser11, // Matches Partially, Shared Via NamespaceUser readonly + testuser12, // Matches Partially, Shared Via NamespaceUser write + testuser13, // Matches Partially, Shared Via NamespaceUser admin }, }, } diff --git a/pkg/user/user_test.go b/pkg/user/user_test.go index c3cab481d..10550b7c3 100644 --- a/pkg/user/user_test.go +++ b/pkg/user/user_test.go @@ -503,6 +503,17 @@ func TestProjectUsers(t *testing.T) { "username": "user7", }, false) }) + t.Run("discoverable by partial username, email and name when matching fuzzily", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + all, err := ListUsers(s, "user", &ProjectUserOpts{ + MatchFuzzily: true, + }) + assert.NoError(t, err) + assert.Len(t, all, 15) + }) } func TestUserPasswordReset(t *testing.T) { diff --git a/pkg/user/users_project.go b/pkg/user/users_project.go index 647f10e21..71e397c32 100644 --- a/pkg/user/users_project.go +++ b/pkg/user/users_project.go @@ -29,6 +29,7 @@ import ( type ProjectUserOpts struct { AdditionalCond builder.Cond ReturnAllIfNoSearchProvided bool + MatchFuzzily bool } // ListUsers returns a project with all users, filtered by an optional search string @@ -48,6 +49,16 @@ func ListUsers(s *xorm.Session, search string, opts *ProjectUserOpts) (users []* if search != "" { for _, queryPart := range strings.Split(search, ",") { + + if opts.MatchFuzzily { + conds = append(conds, + db.ILIKE("name", queryPart), + db.ILIKE("username", queryPart), + db.ILIKE("email", queryPart), + ) + continue + } + var usernameCond builder.Cond = builder.Eq{"username": queryPart} if db.Type() == schemas.POSTGRES { usernameCond = builder.Expr("username ILIKE ?", queryPart)