@@ -203,13 +203,26 @@ func GetLabelInRepoByName(repoID int64, labelName string) (*Label, error) { | |||||
return getLabelInRepoByName(x, repoID, labelName) | return getLabelInRepoByName(x, repoID, labelName) | ||||
} | } | ||||
// GetLabelIDsInRepoByNames returns a list of labelIDs by names in a given | |||||
// repository. | |||||
// it silently ignores label names that do not belong to the repository. | |||||
func GetLabelIDsInRepoByNames(repoID int64, labelNames []string) ([]int64, error) { | |||||
labelIDs := make([]int64, 0, len(labelNames)) | |||||
return labelIDs, x.Table("label"). | |||||
Where("repo_id = ?", repoID). | |||||
In("name", labelNames). | |||||
Asc("name"). | |||||
Cols("id"). | |||||
Find(&labelIDs) | |||||
} | |||||
// GetLabelInRepoByID returns a label by ID in given repository. | // GetLabelInRepoByID returns a label by ID in given repository. | ||||
func GetLabelInRepoByID(repoID, labelID int64) (*Label, error) { | func GetLabelInRepoByID(repoID, labelID int64) (*Label, error) { | ||||
return getLabelInRepoByID(x, repoID, labelID) | return getLabelInRepoByID(x, repoID, labelID) | ||||
} | } | ||||
// GetLabelsInRepoByIDs returns a list of labels by IDs in given repository, | // GetLabelsInRepoByIDs returns a list of labels by IDs in given repository, | ||||
// it silently ignores label IDs that are not belong to the repository. | |||||
// it silently ignores label IDs that do not belong to the repository. | |||||
func GetLabelsInRepoByIDs(repoID int64, labelIDs []int64) ([]*Label, error) { | func GetLabelsInRepoByIDs(repoID int64, labelIDs []int64) ([]*Label, error) { | ||||
labels := make([]*Label, 0, len(labelIDs)) | labels := make([]*Label, 0, len(labelIDs)) | ||||
return labels, x. | return labels, x. | ||||
@@ -81,6 +81,30 @@ func TestGetLabelInRepoByName(t *testing.T) { | |||||
assert.True(t, IsErrLabelNotExist(err)) | assert.True(t, IsErrLabelNotExist(err)) | ||||
} | } | ||||
func TestGetLabelInRepoByNames(t *testing.T) { | |||||
assert.NoError(t, PrepareTestDatabase()) | |||||
labelIDs, err := GetLabelIDsInRepoByNames(1, []string{"label1", "label2"}) | |||||
assert.NoError(t, err) | |||||
assert.Len(t, labelIDs, 2) | |||||
assert.Equal(t, int64(1), labelIDs[0]) | |||||
assert.Equal(t, int64(2), labelIDs[1]) | |||||
} | |||||
func TestGetLabelInRepoByNamesDiscardsNonExistentLabels(t *testing.T) { | |||||
assert.NoError(t, PrepareTestDatabase()) | |||||
// label3 doesn't exists.. See labels.yml | |||||
labelIDs, err := GetLabelIDsInRepoByNames(1, []string{"label1", "label2", "label3"}) | |||||
assert.NoError(t, err) | |||||
assert.Len(t, labelIDs, 2) | |||||
assert.Equal(t, int64(1), labelIDs[0]) | |||||
assert.Equal(t, int64(2), labelIDs[1]) | |||||
assert.NoError(t, err) | |||||
} | |||||
func TestGetLabelInRepoByID(t *testing.T) { | func TestGetLabelInRepoByID(t *testing.T) { | ||||
assert.NoError(t, PrepareTestDatabase()) | assert.NoError(t, PrepareTestDatabase()) | ||||
label, err := GetLabelInRepoByID(1, 1) | label, err := GetLabelInRepoByID(1, 1) | ||||
@@ -43,6 +43,10 @@ func ListIssues(ctx *context.APIContext) { | |||||
// in: query | // in: query | ||||
// description: whether issue is open or closed | // description: whether issue is open or closed | ||||
// type: string | // type: string | ||||
// - name: labels | |||||
// in: query | |||||
// description: comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded | |||||
// type: string | |||||
// - name: page | // - name: page | ||||
// in: query | // in: query | ||||
// description: page number of requested issues | // description: page number of requested issues | ||||
@@ -71,20 +75,30 @@ func ListIssues(ctx *context.APIContext) { | |||||
keyword = "" | keyword = "" | ||||
} | } | ||||
var issueIDs []int64 | var issueIDs []int64 | ||||
var labelIDs []int64 | |||||
var err error | var err error | ||||
if len(keyword) > 0 { | if len(keyword) > 0 { | ||||
issueIDs, err = indexer.SearchIssuesByKeyword(ctx.Repo.Repository.ID, keyword) | issueIDs, err = indexer.SearchIssuesByKeyword(ctx.Repo.Repository.ID, keyword) | ||||
} | } | ||||
if splitted := strings.Split(ctx.Query("labels"), ","); len(splitted) > 0 { | |||||
labelIDs, err = models.GetLabelIDsInRepoByNames(ctx.Repo.Repository.ID, splitted) | |||||
if err != nil { | |||||
ctx.Error(500, "GetLabelIDsInRepoByNames", err) | |||||
return | |||||
} | |||||
} | |||||
// Only fetch the issues if we either don't have a keyword or the search returned issues | // Only fetch the issues if we either don't have a keyword or the search returned issues | ||||
// This would otherwise return all issues if no issues were found by the search. | // This would otherwise return all issues if no issues were found by the search. | ||||
if len(keyword) == 0 || len(issueIDs) > 0 { | |||||
if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 { | |||||
issues, err = models.Issues(&models.IssuesOptions{ | issues, err = models.Issues(&models.IssuesOptions{ | ||||
RepoIDs: []int64{ctx.Repo.Repository.ID}, | RepoIDs: []int64{ctx.Repo.Repository.ID}, | ||||
Page: ctx.QueryInt("page"), | Page: ctx.QueryInt("page"), | ||||
PageSize: setting.UI.IssuePagingNum, | PageSize: setting.UI.IssuePagingNum, | ||||
IsClosed: isClosed, | IsClosed: isClosed, | ||||
IssueIDs: issueIDs, | IssueIDs: issueIDs, | ||||
LabelIDs: labelIDs, | |||||
}) | }) | ||||
} | } | ||||
@@ -2060,6 +2060,12 @@ | |||||
"in": "query" | "in": "query" | ||||
}, | }, | ||||
{ | { | ||||
"type": "string", | |||||
"description": "comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded", | |||||
"name": "labels", | |||||
"in": "query" | |||||
}, | |||||
{ | |||||
"type": "integer", | "type": "integer", | ||||
"description": "page number of requested issues", | "description": "page number of requested issues", | ||||
"name": "page", | "name": "page", | ||||