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 all 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
29 changes: 29 additions & 0 deletions integrations/api_team_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,32 @@ 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)

user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*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)

}
62 changes: 62 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,67 @@ type Team struct {
Units []*TeamUnit `xorm:"-"`
}

// SearchTeamOptions holds the search options
type SearchTeamOptions struct {
UserID int64
Keyword string
OrgID int64
IncludeDesc bool
PageSize int
Page int
}

// SearchTeam search for teams. Caller is responsible to check permissions.
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
if opts.Page <= 0 {
opts.Page = 1
}
if opts.PageSize == 0 {
// Default limit
opts.PageSize = 10
}

var cond = builder.NewCond()

if len(opts.Keyword) > 0 {
lowerKeyword := strings.ToLower(opts.Keyword)
var keywordCond builder.Cond = builder.Like{"lower_name", lowerKeyword}
if opts.IncludeDesc {
keywordCond = keywordCond.Or(builder.Like{"LOWER(description)", 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, err
}

sess = sess.Where(cond)
if opts.PageSize == -1 {
lafriks marked this conversation as resolved.
Show resolved Hide resolved
opts.PageSize = int(count)
} else {
sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
}

teams := make([]*Team, 0, opts.PageSize)
if err = sess.
OrderBy("lower_name").
Find(&teams); err != nil {
return nil, 0, err
}

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
4 changes: 2 additions & 2 deletions public/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1766,11 +1766,11 @@ function searchTeams() {
$searchTeamBox.search({
minCharacters: 2,
apiSettings: {
url: suburl + '/api/v1/orgs/' + $searchTeamBox.data('org') + '/teams',
url: suburl + '/api/v1/orgs/' + $searchTeamBox.data('org') + '/teams/search?q={query}',
headers: {"X-Csrf-Token": csrf},
onResponse: function(response) {
const items = [];
$.each(response, function (_i, item) {
$.each(response.data, function (_i, item) {
const title = item.name + ' (' + item.permission + ' access)';
items.push({
title: title,
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
83 changes: 83 additions & 0 deletions routers/api/v1/org/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
package org

import (
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/convert"
"code.gitea.io/gitea/routers/api/v1/user"
Expand Down Expand Up @@ -504,3 +507,83 @@ 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 an organization
// 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
// type: string
// - name: include_desc
// in: query
// description: include search within team description (defaults to true)
// type: boolean
// - name: limit
// in: query
// description: limit size of results
// type: integer
// - name: page
// in: query
// description: page number of results to return (1-based)
// 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.User.ID,
Keyword: strings.TrimSpace(ctx.Query("q")),
OrgID: ctx.Org.Organization.ID,
IncludeDesc: (ctx.Query("include_desc") == "" || ctx.QueryBool("include_desc")),
PageSize: ctx.QueryInt("limit"),
Page: ctx.QueryInt("page"),
}

teams, _, err := models.SearchTeam(opts)
if err != nil {
log.Error("SearchTeam failed: %v", err)
ctx.JSON(500, map[string]interface{}{
"ok": false,
"error": "SearchTeam internal failure",
})
return
}

apiTeams := make([]*api.Team, len(teams))
for i := range teams {
if err := teams[i].GetUnits(); err != nil {
log.Error("Team GetUnits failed: %v", err)
ctx.JSON(500, map[string]interface{}{
"ok": false,
"error": "SearchTeam failed to get units",
})
return
}
apiTeams[i] = convert.ToTeam(teams[i])
}

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

}
2 changes: 1 addition & 1 deletion templates/repo/settings/collaboration.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
<form class="ui form" id="repo-collab-team-form" action="{{.Link}}/team" method="post">
{{.CsrfTokenHtml}}
<div class="inline field ui left">
<div id="search-team-box" class="ui search" data-org="{{.OrgID}}">
<div id="search-team-box" class="ui search" data-org="{{.OrgName}}">
<div class="ui input">
<input class="prompt" name="team" placeholder="Search teams..." autocomplete="off" autofocus required>
</div>
Expand Down
64 changes: 64 additions & 0 deletions templates/swagger/v1_json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,70 @@
}
}
},
"/orgs/{org}/teams/search": {
"get": {
"produces": [
"application/json"
],
"tags": [
"organization"
],
"summary": "Search for teams within an 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"
},
{
"type": "boolean",
"description": "include search within team description (defaults to true)",
"name": "include_desc",
"in": "query"
},
{
"type": "integer",
"description": "limit size of results",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"description": "page number of results to return (1-based)",
"name": "page",
"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