mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-27 09:44:00 +01:00
Support no label/assignee filter and batch clearing labels/assignees (#24707)
Since milestones has been implemented, this PR will fix #3407 --------- Co-authored-by: Jason Song <i@wolfogre.com>
This commit is contained in:
parent
e7c2231dee
commit
b807d2f620
4 changed files with 40 additions and 15 deletions
|
@ -1251,6 +1251,8 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) {
|
||||||
|
|
||||||
if opts.AssigneeID > 0 {
|
if opts.AssigneeID > 0 {
|
||||||
applyAssigneeCondition(sess, opts.AssigneeID)
|
applyAssigneeCondition(sess, opts.AssigneeID)
|
||||||
|
} else if opts.AssigneeID == db.NoConditionID {
|
||||||
|
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_assignees)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.PosterID > 0 {
|
if opts.PosterID > 0 {
|
||||||
|
@ -1312,16 +1314,20 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) {
|
||||||
sess.And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()})
|
sess.And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()})
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.LabelIDs != nil {
|
if len(opts.LabelIDs) > 0 {
|
||||||
|
if opts.LabelIDs[0] == 0 {
|
||||||
|
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label)")
|
||||||
|
} else {
|
||||||
for i, labelID := range opts.LabelIDs {
|
for i, labelID := range opts.LabelIDs {
|
||||||
if labelID > 0 {
|
if labelID > 0 {
|
||||||
sess.Join("INNER", fmt.Sprintf("issue_label il%d", i),
|
sess.Join("INNER", fmt.Sprintf("issue_label il%d", i),
|
||||||
fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID))
|
fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID))
|
||||||
} else {
|
} else if labelID < 0 { // 0 is not supported here, so just ignore it
|
||||||
sess.Where("issue.id not in (select issue_id from issue_label where label_id = ?)", -labelID)
|
sess.Where("issue.id not in (select issue_id from issue_label where label_id = ?)", -labelID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(opts.IncludedLabelNames) > 0 {
|
if len(opts.IncludedLabelNames) > 0 {
|
||||||
sess.In("issue.id", BuildLabelNamesIssueIDsCondition(opts.IncludedLabelNames))
|
sess.In("issue.id", BuildLabelNamesIssueIDsCondition(opts.IncludedLabelNames))
|
||||||
|
@ -1705,21 +1711,25 @@ func getIssueStatsChunk(opts *IssueStatsOptions, issueIDs []int64) (*IssueStats,
|
||||||
sess.In("issue.id", issueIDs)
|
sess.In("issue.id", issueIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.Labels) > 0 && opts.Labels != "0" {
|
if len(opts.Labels) > 0 {
|
||||||
labelIDs, err := base.StringsToInt64s(strings.Split(opts.Labels, ","))
|
labelIDs, err := base.StringsToInt64s(strings.Split(opts.Labels, ","))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Malformed Labels argument: %s", opts.Labels)
|
log.Warn("Malformed Labels argument: %s", opts.Labels)
|
||||||
|
} else {
|
||||||
|
if labelIDs[0] == 0 {
|
||||||
|
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label)")
|
||||||
} else {
|
} else {
|
||||||
for i, labelID := range labelIDs {
|
for i, labelID := range labelIDs {
|
||||||
if labelID > 0 {
|
if labelID > 0 {
|
||||||
sess.Join("INNER", fmt.Sprintf("issue_label il%d", i),
|
sess.Join("INNER", fmt.Sprintf("issue_label il%d", i),
|
||||||
fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID))
|
fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID))
|
||||||
} else {
|
} else if labelID < 0 { // 0 is not supported here, so just ignore it
|
||||||
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label WHERE label_id = ?)", -labelID)
|
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label WHERE label_id = ?)", -labelID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if opts.MilestoneID > 0 {
|
if opts.MilestoneID > 0 {
|
||||||
sess.And("issue.milestone_id = ?", opts.MilestoneID)
|
sess.And("issue.milestone_id = ?", opts.MilestoneID)
|
||||||
|
@ -1734,6 +1744,8 @@ func getIssueStatsChunk(opts *IssueStatsOptions, issueIDs []int64) (*IssueStats,
|
||||||
|
|
||||||
if opts.AssigneeID > 0 {
|
if opts.AssigneeID > 0 {
|
||||||
applyAssigneeCondition(sess, opts.AssigneeID)
|
applyAssigneeCondition(sess, opts.AssigneeID)
|
||||||
|
} else if opts.AssigneeID == db.NoConditionID {
|
||||||
|
sess.Where("id NOT IN (SELECT issue_id FROM issue_assignees)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.PosterID > 0 {
|
if opts.PosterID > 0 {
|
||||||
|
|
|
@ -1361,6 +1361,7 @@ issues.delete_branch_at = `deleted branch <b>%s</b> %s`
|
||||||
issues.filter_label = Label
|
issues.filter_label = Label
|
||||||
issues.filter_label_exclude = `Use <code>alt</code> + <code>click/enter</code> to exclude labels`
|
issues.filter_label_exclude = `Use <code>alt</code> + <code>click/enter</code> to exclude labels`
|
||||||
issues.filter_label_no_select = All labels
|
issues.filter_label_no_select = All labels
|
||||||
|
issues.filter_label_select_no_label = No Label
|
||||||
issues.filter_milestone = Milestone
|
issues.filter_milestone = Milestone
|
||||||
issues.filter_milestone_all = All milestones
|
issues.filter_milestone_all = All milestones
|
||||||
issues.filter_milestone_none = No milestones
|
issues.filter_milestone_none = No milestones
|
||||||
|
@ -1371,6 +1372,7 @@ issues.filter_project_all = All projects
|
||||||
issues.filter_project_none = No project
|
issues.filter_project_none = No project
|
||||||
issues.filter_assignee = Assignee
|
issues.filter_assignee = Assignee
|
||||||
issues.filter_assginee_no_select = All assignees
|
issues.filter_assginee_no_select = All assignees
|
||||||
|
issues.filter_assginee_no_assignee = No assignee
|
||||||
issues.filter_poster = Author
|
issues.filter_poster = Author
|
||||||
issues.filter_poster_no_select = All authors
|
issues.filter_poster_no_select = All authors
|
||||||
issues.filter_type = Type
|
issues.filter_type = Type
|
||||||
|
|
|
@ -170,8 +170,11 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
|
||||||
|
|
||||||
repo := ctx.Repo.Repository
|
repo := ctx.Repo.Repository
|
||||||
var labelIDs []int64
|
var labelIDs []int64
|
||||||
|
// 1,-2 means including label 1 and excluding label 2
|
||||||
|
// 0 means issues with no label
|
||||||
|
// blank means labels will not be filtered for issues
|
||||||
selectLabels := ctx.FormString("labels")
|
selectLabels := ctx.FormString("labels")
|
||||||
if len(selectLabels) > 0 && selectLabels != "0" {
|
if len(selectLabels) > 0 {
|
||||||
labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ","))
|
labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ","))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("StringsToInt64s", err)
|
ctx.ServerError("StringsToInt64s", err)
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_label"}}">
|
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_label"}}">
|
||||||
</div>
|
</div>
|
||||||
<span class="info">{{.locale.Tr "repo.issues.filter_label_exclude" | Safe}}</span>
|
<span class="info">{{.locale.Tr "repo.issues.filter_label_exclude" | Safe}}</span>
|
||||||
|
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=0&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_select_no_label"}}</a>
|
||||||
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_no_select"}}</a>
|
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_no_select"}}</a>
|
||||||
{{$previousExclusiveScope := "_no_scope"}}
|
{{$previousExclusiveScope := "_no_scope"}}
|
||||||
{{range .Labels}}
|
{{range .Labels}}
|
||||||
|
@ -156,6 +157,7 @@
|
||||||
<i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i>
|
<i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i>
|
||||||
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_assignee"}}">
|
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_assignee"}}">
|
||||||
</div>
|
</div>
|
||||||
|
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee=-1&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_assginee_no_assignee"}}</a>
|
||||||
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_assginee_no_select"}}</a>
|
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_assginee_no_select"}}</a>
|
||||||
{{range .Assignees}}
|
{{range .Assignees}}
|
||||||
<a class="{{if eq $.AssigneeID .ID}}active selected{{end}} item gt-df" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{.ID}}&poster={{$.PosterID}}">
|
<a class="{{if eq $.AssigneeID .ID}}active selected{{end}} item gt-df" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{.ID}}&poster={{$.PosterID}}">
|
||||||
|
@ -226,6 +228,9 @@
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
</span>
|
</span>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
|
<div class="item issue-action" data-action="clear" data-url="{{$.RepoLink}}/issues/labels">
|
||||||
|
{{.locale.Tr "repo.issues.new.clear_labels"}}
|
||||||
|
</div>
|
||||||
{{$previousExclusiveScope := "_no_scope"}}
|
{{$previousExclusiveScope := "_no_scope"}}
|
||||||
{{range .Labels}}
|
{{range .Labels}}
|
||||||
{{$exclusiveScope := .ExclusiveScope}}
|
{{$exclusiveScope := .ExclusiveScope}}
|
||||||
|
@ -313,6 +318,9 @@
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
</span>
|
</span>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
|
<div class="item issue-action" data-action="clear" data-url="{{$.Link}}/assignee">
|
||||||
|
{{.locale.Tr "repo.issues.new.clear_assignees"}}
|
||||||
|
</div>
|
||||||
<div class="item issue-action" data-element-id="0" data-url="{{$.Link}}/assignee">
|
<div class="item issue-action" data-element-id="0" data-url="{{$.Link}}/assignee">
|
||||||
{{.locale.Tr "repo.issues.action_assignee_no_select"}}
|
{{.locale.Tr "repo.issues.action_assignee_no_select"}}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue