Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API endpoint for searching teams. #8108

Merged
merged 23 commits into from
Oct 1, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7cfa3d5
Api endpoint for searching teams.
Sep 5, 2019
62c798f
Merge branch 'master' into api-endpoint-teams-search
davidsvantesson Sep 5, 2019
ebecd5a
Move API to /orgs/:org/teams/search
davidsvantesson Sep 6, 2019
2476663
Regenerate swagger
davidsvantesson Sep 6, 2019
0f075c1
Fix search is Get
davidsvantesson Sep 6, 2019
bfbc4ab
Add test for search team API.
davidsvantesson Sep 6, 2019
ff4520b
Update routers/api/v1/org/team.go
davidsvantesson Sep 6, 2019
a1e1a9a
Fix review comments
davidsvantesson Sep 13, 2019
76a5901
Merge branch 'master' into api-endpoint-teams-search
davidsvantesson Sep 13, 2019
30e014c
Merge branch 'master' into api-endpoint-teams-search
davidsvantesson Sep 23, 2019
57bf392
Fix some issues in repo collaboration team search, after changes in t…
davidsvantesson Sep 23, 2019
0745ee4
Remove teamUser which is not used and replace with actual user id.
davidsvantesson Sep 23, 2019
e74347a
Remove unused search variable UserIsAdmin.
davidsvantesson Sep 24, 2019
f1de2b1
Add paging to team search.
davidsvantesson Sep 24, 2019
a632034
Re-genereate swagger
davidsvantesson Sep 24, 2019
03861f1
Merge branch 'master' into api-endpoint-teams-search
davidsvantesson Sep 24, 2019
9704d49
Fix review comments
davidsvantesson Sep 28, 2019
b101793
fix
davidsvantesson Sep 29, 2019
4288527
Regenerate swagger
davidsvantesson Sep 29, 2019
8755739
Merge branch 'master' into api-endpoint-teams-search
lunny Sep 30, 2019
fc66f6a
Merge branch 'master' into api-endpoint-teams-search
lunny Sep 30, 2019
b31fc41
Merge branch 'master' into api-endpoint-teams-search
lunny Sep 30, 2019
5785acd
Merge branch 'master' into api-endpoint-teams-search
lunny Sep 30, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions integrations/api_team_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,33 @@ func checkTeamBean(t *testing.T, id int64, name, description string, permission
assert.NoError(t, team.GetUnits(), "GetUnits")
checkTeamResponse(t, convert.ToTeam(team), name, description, permission, units)
}

type TeamSearchResults struct {
OK bool `json:"ok"`
Data []*api.Team `json:"data"`
}

func TestAPITeamSearch(t *testing.T) {
prepareTestEnv(t)

teamUser := models.AssertExistsAndLoadBean(t, &models.TeamUser{}).(*models.TeamUser)
davidsvantesson marked this conversation as resolved.
Show resolved Hide resolved
user := models.AssertExistsAndLoadBean(t, &models.User{ID: teamUser.UID}).(*models.User)
org := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User)

var results TeamSearchResults

session := loginUser(t, user.Name)
req := NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "_team")
resp := session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &results)
assert.NotEmpty(t, results.Data)
assert.Equal(t, 1, len(results.Data))
assert.Equal(t, "test_team", results.Data[0].Name)

// no access if not organization member
user5 := models.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User)
session = loginUser(t, user5.Name)
req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "team")
resp = session.MakeRequest(t, req, http.StatusForbidden)

}
59 changes: 59 additions & 0 deletions models/org_team.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/setting"

"github.com/go-xorm/xorm"
"xorm.io/builder"
)

const ownerTeamName = "Owners"
Expand All @@ -34,6 +35,64 @@ type Team struct {
Units []*TeamUnit `xorm:"-"`
}

// SearchTeamOptions holds the search options
type SearchTeamOptions struct {
UserID int64
UserIsAdmin bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserIsAdmin is not used? I guess the router takes care of the permissions, but why is it here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Keyword string
OrgID int64
IncludeDesc bool
Limit int
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't check other API endpoints, but isn't it a bit strange to limit the query while not supporting pagination?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pagination added to the API.

}

// SearchTeam search for teams. Caller is responsible to check permissions.
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
if opts.Limit <= 0 {
opts.Limit = 10
}
var cond = builder.NewCond()

if len(opts.Keyword) > 0 {
lowerKeyword := strings.ToLower(opts.Keyword)
var keywordCond = builder.NewCond()
Copy link
Member

@lunny lunny Sep 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var keywordCond = builder.Like{"lower_name"}
if opts.IncludeDesc {
      keywordCond = keywordCond.Or(builder.Like{"LOWER(description)", lowerKeyword})
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:(
var keywordCond builder.Cond = builder.Like{"lower_name"}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, ran out of laptop battery before I could finish properly. Will fix later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I think I got it right.

if opts.IncludeDesc {
keywordCond = builder.Or(
builder.Like{"lower_name", lowerKeyword},
builder.Like{"LOWER(description)", lowerKeyword},
)
} else {
keywordCond = builder.Or(
builder.Like{"lower_name", lowerKeyword},
)
}
cond = cond.And(keywordCond)
}

cond = cond.And(builder.Eq{"org_id": opts.OrgID})

sess := x.NewSession()
defer sess.Close()

count, err := sess.
Where(cond).
Count(new(Team))

if err != nil {
return nil, 0, fmt.Errorf("Count: %v", err)
lafriks marked this conversation as resolved.
Show resolved Hide resolved
}

teams := make([]*Team, 0, opts.Limit)
if err = sess.
Where(cond).
OrderBy("lower_name").
Limit(opts.Limit).
Find(&teams); err != nil {
return nil, 0, fmt.Errorf("Team: %v", err)
lafriks marked this conversation as resolved.
Show resolved Hide resolved
}

return teams, count, nil
}

// ColorFormat provides a basic color format for a Team
func (t *Team) ColorFormat(s fmt.State) {
log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v",
Expand Down
7 changes: 5 additions & 2 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -802,8 +802,11 @@ func RegisterRoutes(m *macaron.Macaron) {
Put(reqToken(), reqOrgMembership(), org.PublicizeMember).
Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
})
m.Combo("/teams", reqToken(), reqOrgMembership()).Get(org.ListTeams).
Post(reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam)
m.Group("/teams", func() {
m.Combo("", reqToken()).Get(org.ListTeams).
Post(reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam)
m.Get("/search", org.SearchTeam)
}, reqOrgMembership())
m.Group("/hooks", func() {
m.Combo("").Get(org.ListHooks).
Post(bind(api.CreateHookOption{}), org.CreateHook)
Expand Down
74 changes: 74 additions & 0 deletions routers/api/v1/org/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
package org

import (
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
Expand Down Expand Up @@ -495,3 +497,75 @@ func RemoveTeamRepository(ctx *context.APIContext) {
}
ctx.Status(204)
}

// SearchTeam api for searching teams
func SearchTeam(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/teams/search organization teamSearch
// ---
// summary: Search for teams within organization
davidsvantesson marked this conversation as resolved.
Show resolved Hide resolved
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: q
// in: query
// description: keywords to search
// required: true
lafriks marked this conversation as resolved.
Show resolved Hide resolved
// type: string
// - name: inclDesc
// in: query
// description: include search within team description (defaults to true)
// type: boolean
// - name: limit
// in: query
// description: limit size of results
// type: integer
// responses:
// "200":
// description: "SearchResults of a successful search"
// schema:
// type: object
// properties:
// ok:
// type: boolean
// data:
// type: array
// items:
// "$ref": "#/definitions/Team"
opts := &models.SearchTeamOptions{
UserID: ctx.Data["SignedUserID"].(int64),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ctx.User.ID

UserIsAdmin: ctx.IsUserSiteAdmin(),
Keyword: strings.Trim(ctx.Query("q"), " "),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strings.TrimSpace

OrgID: ctx.Org.Organization.ID,
IncludeDesc: (ctx.Query("inclDesc") == "" || ctx.QueryBool("inclDesc")),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe include_desc is better.

Limit: ctx.QueryInt("limit"),
}

teams, _, err := models.SearchTeam(opts)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"ok": false,
"error": err.Error(),
lafriks marked this conversation as resolved.
Show resolved Hide resolved
})
return
}

apiTeams := make([]*api.Team, len(teams))
for i := range teams {
if err := teams[i].GetUnits(); err != nil {
ctx.Error(500, "GetUnits", err)
lafriks marked this conversation as resolved.
Show resolved Hide resolved
return
}
apiTeams[i] = convert.ToTeam(teams[i])
}

ctx.JSON(200, map[string]interface{}{
"ok": true,
"data": apiTeams,
})

}
59 changes: 59 additions & 0 deletions templates/swagger/v1_json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,65 @@
}
}
},
"/orgs/{org}/teams/search": {
"get": {
"produces": [
"application/json"
],
"tags": [
"organization"
],
"summary": "Search for teams within organization",
"operationId": "teamSearch",
"parameters": [
{
"type": "string",
"description": "name of the organization",
"name": "org",
"in": "path",
"required": true
},
{
"type": "string",
"description": "keywords to search",
"name": "q",
"in": "query",
"required": true
},
{
"type": "boolean",
"description": "include search within team description (defaults to true)",
"name": "inclDesc",
"in": "query"
},
{
"type": "integer",
"description": "limit size of results",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "SearchResults of a successful search",
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/Team"
}
},
"ok": {
"type": "boolean"
}
}
}
}
}
}
},
"/repos/migrate": {
"post": {
"consumes": [
Expand Down