Merge pull request '[FEAT] Add label filters in organization issues dashboard' (#2944) from iminfinity/forgejo:add/label-filters into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2944 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
commit
4ccb8c8b1f
|
@ -538,6 +538,36 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if org != nil {
|
||||||
|
// Get Org Labels
|
||||||
|
labels, err := issues_model.GetLabelsByOrgID(ctx, ctx.Org.Organization.ID, ctx.FormString("sort"), db.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetLabelsByOrgID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the exclusive scope for every label ID
|
||||||
|
labelExclusiveScopes := make([]string, 0, len(opts.LabelIDs))
|
||||||
|
for _, labelID := range opts.LabelIDs {
|
||||||
|
foundExclusiveScope := false
|
||||||
|
for _, label := range labels {
|
||||||
|
if label.ID == labelID || label.ID == -labelID {
|
||||||
|
labelExclusiveScopes = append(labelExclusiveScopes, label.ExclusiveScope())
|
||||||
|
foundExclusiveScope = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundExclusiveScope {
|
||||||
|
labelExclusiveScopes = append(labelExclusiveScopes, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range labels {
|
||||||
|
l.LoadSelectedLabelsAfterClick(opts.LabelIDs, labelExclusiveScopes)
|
||||||
|
}
|
||||||
|
ctx.Data["Labels"] = labels
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
// Get issues as defined by opts.
|
// Get issues as defined by opts.
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
|
@ -621,6 +651,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||||
ctx.Data["SortType"] = sortType
|
ctx.Data["SortType"] = sortType
|
||||||
ctx.Data["IsShowClosed"] = isShowClosed
|
ctx.Data["IsShowClosed"] = isShowClosed
|
||||||
ctx.Data["SelectLabels"] = selectedLabels
|
ctx.Data["SelectLabels"] = selectedLabels
|
||||||
|
ctx.Data["PageIsOrgIssues"] = org != nil
|
||||||
|
|
||||||
if isShowClosed {
|
if isShowClosed {
|
||||||
ctx.Data["State"] = "closed"
|
ctx.Data["State"] = "closed"
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -130,3 +131,36 @@ func TestDashboardPagination(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, out, `<a class=" item navigation" href="/?page=2">`)
|
assert.Contains(t, out, `<a class=" item navigation" href="/?page=2">`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOrgLabels(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
|
|
||||||
|
ctx, _ := contexttest.MockContext(t, "org/org3/issues")
|
||||||
|
contexttest.LoadUser(t, ctx, 2)
|
||||||
|
contexttest.LoadOrganization(t, ctx, 3)
|
||||||
|
Issues(ctx)
|
||||||
|
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
|
||||||
|
|
||||||
|
assert.True(t, ctx.Data["PageIsOrgIssues"].(bool))
|
||||||
|
|
||||||
|
orgLabels := []struct {
|
||||||
|
ID int64
|
||||||
|
OrgID int64
|
||||||
|
Name string
|
||||||
|
}{
|
||||||
|
{3, 3, "orglabel3"},
|
||||||
|
{4, 3, "orglabel4"},
|
||||||
|
}
|
||||||
|
|
||||||
|
labels, ok := ctx.Data["Labels"].([]*issues_model.Label)
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
if assert.Len(t, labels, len(orgLabels)) {
|
||||||
|
for i, label := range labels {
|
||||||
|
assert.EqualValues(t, orgLabels[i].OrgID, label.OrgID)
|
||||||
|
assert.EqualValues(t, orgLabels[i].ID, label.ID)
|
||||||
|
assert.EqualValues(t, orgLabels[i].Name, label.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
org_model "code.gitea.io/gitea/models/organization"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
@ -146,6 +147,19 @@ func LoadUser(t *testing.T, ctx gocontext.Context, userID int64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadOrganization load an org into a test context
|
||||||
|
func LoadOrganization(t *testing.T, ctx gocontext.Context, orgID int64) {
|
||||||
|
org := unittest.AssertExistsAndLoadBean(t, &org_model.Organization{ID: orgID})
|
||||||
|
switch ctx := ctx.(type) {
|
||||||
|
case *context.Context:
|
||||||
|
ctx.Org.Organization = org
|
||||||
|
case *context.APIContext:
|
||||||
|
ctx.Org.Organization = org
|
||||||
|
default:
|
||||||
|
assert.FailNow(t, "context is not *context.Context or *context.APIContext")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LoadGitRepo load a git repo into a test context. Requires that ctx.Repo has
|
// LoadGitRepo load a git repo into a test context. Requires that ctx.Repo has
|
||||||
// already been populated.
|
// already been populated.
|
||||||
func LoadGitRepo(t *testing.T, ctx *context.Context) {
|
func LoadGitRepo(t *testing.T, ctx *context.Context) {
|
||||||
|
|
|
@ -1,53 +1,5 @@
|
||||||
<!-- Label -->
|
<!-- Label -->
|
||||||
<div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item label-filter">
|
{{template "shared/label_filter" .}}
|
||||||
<span class="text">
|
|
||||||
{{ctx.Locale.Tr "repo.issues.filter_label"}}
|
|
||||||
</span>
|
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
|
||||||
<div class="menu">
|
|
||||||
<div class="ui icon search input">
|
|
||||||
<i class="icon">{{svg "octicon-search" 16}}</i>
|
|
||||||
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_label"}}">
|
|
||||||
</div>
|
|
||||||
<div class="ui checkbox compact archived-label-filter">
|
|
||||||
<input name="archived" type="checkbox"
|
|
||||||
id="archived-filter-checkbox"
|
|
||||||
{{if .ShowArchivedLabels}}checked{{end}}
|
|
||||||
>
|
|
||||||
<label for="archived-filter-checkbox">
|
|
||||||
{{ctx.Locale.Tr "repo.issues.label_archived_filter"}}
|
|
||||||
<i class="tw-ml-1" data-tooltip-content={{ctx.Locale.Tr "repo.issues.label_archive_tooltip"}}>
|
|
||||||
{{svg "octicon-info"}}
|
|
||||||
</i>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<span class="info">{{ctx.Locale.Tr "repo.issues.filter_label_exclude"}}</span>
|
|
||||||
<div class="divider"></div>
|
|
||||||
<a rel="nofollow" class="{{if .AllLabels}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_label_no_select"}}</a>
|
|
||||||
<a rel="nofollow" class="{{if .NoLabel}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=0&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_label_select_no_label"}}</a>
|
|
||||||
{{$previousExclusiveScope := "_no_scope"}}
|
|
||||||
{{range .Labels}}
|
|
||||||
{{$exclusiveScope := .ExclusiveScope}}
|
|
||||||
{{if and (ne $previousExclusiveScope $exclusiveScope)}}
|
|
||||||
<div class="divider"></div>
|
|
||||||
{{end}}
|
|
||||||
{{$previousExclusiveScope = $exclusiveScope}}
|
|
||||||
<a rel="nofollow" class="item label-filter-item tw-flex tw-items-center" {{if .IsArchived}}data-is-archived{{end}} href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}" data-label-id="{{.ID}}">
|
|
||||||
{{if .IsExcluded}}
|
|
||||||
{{svg "octicon-circle-slash"}}
|
|
||||||
{{else if .IsSelected}}
|
|
||||||
{{if $exclusiveScope}}
|
|
||||||
{{svg "octicon-dot-fill"}}
|
|
||||||
{{else}}
|
|
||||||
{{svg "octicon-check"}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
{{RenderLabel $.Context ctx.Locale .}}
|
|
||||||
<p class="tw-ml-auto">{{template "repo/issue/labels/label_archived" .}}</p>
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{if not .Milestone}}
|
{{if not .Milestone}}
|
||||||
<!-- Milestone -->
|
<!-- Milestone -->
|
||||||
|
|
50
templates/shared/label_filter.tmpl
Normal file
50
templates/shared/label_filter.tmpl
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<!-- Label -->
|
||||||
|
<div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item label-filter">
|
||||||
|
<span class="text">
|
||||||
|
{{ctx.Locale.Tr "repo.issues.filter_label"}}
|
||||||
|
</span>
|
||||||
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
|
<div class="menu">
|
||||||
|
<div class="ui icon search input">
|
||||||
|
<i class="icon">{{svg "octicon-search" 16}}</i>
|
||||||
|
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_label"}}">
|
||||||
|
</div>
|
||||||
|
<div class="ui checkbox compact archived-label-filter">
|
||||||
|
<input name="archived" type="checkbox"
|
||||||
|
id="archived-filter-checkbox"
|
||||||
|
{{if .ShowArchivedLabels}}checked{{end}}
|
||||||
|
>
|
||||||
|
<label for="archived-filter-checkbox">
|
||||||
|
{{ctx.Locale.Tr "repo.issues.label_archived_filter"}}
|
||||||
|
<i class="tw-ml-1" data-tooltip-content={{ctx.Locale.Tr "repo.issues.label_archive_tooltip"}}>
|
||||||
|
{{svg "octicon-info"}}
|
||||||
|
</i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<span class="info">{{ctx.Locale.Tr "repo.issues.filter_label_exclude"}}</span>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<a rel="nofollow" class="{{if .AllLabels}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_label_no_select"}}</a>
|
||||||
|
<a rel="nofollow" class="{{if .NoLabel}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=0&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_label_select_no_label"}}</a>
|
||||||
|
{{$previousExclusiveScope := "_no_scope"}}
|
||||||
|
{{range .Labels}}
|
||||||
|
{{$exclusiveScope := .ExclusiveScope}}
|
||||||
|
{{if and (ne $previousExclusiveScope $exclusiveScope)}}
|
||||||
|
<div class="divider"></div>
|
||||||
|
{{end}}
|
||||||
|
{{$previousExclusiveScope = $exclusiveScope}}
|
||||||
|
<a rel="nofollow" class="item label-filter-item tw-flex tw-items-center" {{if .IsArchived}}data-is-archived{{end}} href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&labels={{.QueryString}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}" data-label-id="{{.ID}}">
|
||||||
|
{{if .IsExcluded}}
|
||||||
|
{{svg "octicon-circle-slash"}}
|
||||||
|
{{else if .IsSelected}}
|
||||||
|
{{if $exclusiveScope}}
|
||||||
|
{{svg "octicon-dot-fill"}}
|
||||||
|
{{else}}
|
||||||
|
{{svg "octicon-check"}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{RenderLabel $.Context ctx.Locale .}}
|
||||||
|
<p class="tw-ml-auto">{{template "repo/issue/labels/label_archived" .}}</p>
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -37,11 +37,11 @@
|
||||||
<div class="flex-container-main content">
|
<div class="flex-container-main content">
|
||||||
<div class="list-header">
|
<div class="list-header">
|
||||||
<div class="small-menu-items ui compact tiny menu list-header-toggle">
|
<div class="small-menu-items ui compact tiny menu list-header-toggle">
|
||||||
<a class="item{{if not .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=open&q={{$.Keyword}}">
|
<a class="item{{if not .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=open&labels={{.SelectLabels}}&q={{$.Keyword}}">
|
||||||
{{svg "octicon-issue-opened" 16 "tw-mr-2"}}
|
{{svg "octicon-issue-opened" 16 "tw-mr-2"}}
|
||||||
{{ctx.Locale.PrettyNumber .IssueStats.OpenCount}} {{ctx.Locale.Tr "repo.issues.open_title"}}
|
{{ctx.Locale.PrettyNumber .IssueStats.OpenCount}} {{ctx.Locale.Tr "repo.issues.open_title"}}
|
||||||
</a>
|
</a>
|
||||||
<a class="item{{if .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=closed&q={{$.Keyword}}">
|
<a class="item{{if .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=closed&labels={{.SelectLabels}}&q={{$.Keyword}}">
|
||||||
{{svg "octicon-issue-closed" 16 "tw-mr-2"}}
|
{{svg "octicon-issue-closed" 16 "tw-mr-2"}}
|
||||||
{{ctx.Locale.PrettyNumber .IssueStats.ClosedCount}} {{ctx.Locale.Tr "repo.issues.closed_title"}}
|
{{ctx.Locale.PrettyNumber .IssueStats.ClosedCount}} {{ctx.Locale.Tr "repo.issues.closed_title"}}
|
||||||
</a>
|
</a>
|
||||||
|
@ -56,6 +56,10 @@
|
||||||
{{template "shared/search/button"}}
|
{{template "shared/search/button"}}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<!-- Label -->
|
||||||
|
{{if .PageIsOrgIssues}}
|
||||||
|
{{template "shared/label_filter" .}}
|
||||||
|
{{end}}
|
||||||
<!-- Sort -->
|
<!-- Sort -->
|
||||||
<div class="list-header-sort ui small dropdown type jump item">
|
<div class="list-header-sort ui small dropdown type jump item">
|
||||||
<span class="text tw-whitespace-nowrap">
|
<span class="text tw-whitespace-nowrap">
|
||||||
|
@ -63,14 +67,14 @@
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
</span>
|
</span>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=recentupdate&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>
|
<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=recentupdate&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>
|
||||||
<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=leastupdate&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}</a>
|
<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=leastupdate&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}</a>
|
||||||
<a class="{{if or (eq .SortType "latest") (not .SortType)}}active {{end}}item" href="?type={{$.ViewType}}&sort=latest&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</a>
|
<a class="{{if or (eq .SortType "latest") (not .SortType)}}active {{end}}item" href="?type={{$.ViewType}}&sort=latest&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</a>
|
||||||
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="?type={{$.ViewType}}&sort=oldest&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</a>
|
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="?type={{$.ViewType}}&sort=oldest&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</a>
|
||||||
<a class="{{if eq .SortType "mostcomment"}}active {{end}}item" href="?type={{$.ViewType}}&sort=mostcomment&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.mostcomment"}}</a>
|
<a class="{{if eq .SortType "mostcomment"}}active {{end}}item" href="?type={{$.ViewType}}&sort=mostcomment&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.mostcomment"}}</a>
|
||||||
<a class="{{if eq .SortType "leastcomment"}}active {{end}}item" href="?type={{$.ViewType}}&sort=leastcomment&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastcomment"}}</a>
|
<a class="{{if eq .SortType "leastcomment"}}active {{end}}item" href="?type={{$.ViewType}}&sort=leastcomment&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastcomment"}}</a>
|
||||||
<a class="{{if eq .SortType "nearduedate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=nearduedate&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.nearduedate"}}</a>
|
<a class="{{if eq .SortType "nearduedate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=nearduedate&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.nearduedate"}}</a>
|
||||||
<a class="{{if eq .SortType "farduedate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=farduedate&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.farduedate"}}</a>
|
<a class="{{if eq .SortType "farduedate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=farduedate&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.farduedate"}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -222,3 +222,28 @@ func TestTeamSearch(t *testing.T) {
|
||||||
req.Header.Add("X-Csrf-Token", csrf)
|
req.Header.Add("X-Csrf-Token", csrf)
|
||||||
session.MakeRequest(t, req, http.StatusNotFound)
|
session.MakeRequest(t, req, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOrgDashboardLabels(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||||
|
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization})
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
|
||||||
|
req := NewRequestf(t, "GET", "/org/%s/issues?labels=3,4", org.Name)
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
|
labelFilterHref, ok := htmlDoc.Find(".list-header-sort a").Attr("href")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Contains(t, labelFilterHref, "labels=3%2c4")
|
||||||
|
|
||||||
|
// Exclude label
|
||||||
|
req = NewRequestf(t, "GET", "/org/%s/issues?labels=3,-4", org.Name)
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
|
labelFilterHref, ok = htmlDoc.Find(".list-header-sort a").Attr("href")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Contains(t, labelFilterHref, "labels=3%2c-4")
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue