mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08: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:
		@@ -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,13 +1314,17 @@ 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 {
 | 
				
			||||||
		for i, labelID := range opts.LabelIDs {
 | 
							if opts.LabelIDs[0] == 0 {
 | 
				
			||||||
			if labelID > 0 {
 | 
								sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label)")
 | 
				
			||||||
				sess.Join("INNER", fmt.Sprintf("issue_label il%d", i),
 | 
							} else {
 | 
				
			||||||
					fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID))
 | 
								for i, labelID := range opts.LabelIDs {
 | 
				
			||||||
			} else {
 | 
									if labelID > 0 {
 | 
				
			||||||
				sess.Where("issue.id not in (select issue_id from issue_label where label_id = ?)", -labelID)
 | 
										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))
 | 
				
			||||||
 | 
									} 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)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -1705,17 +1711,21 @@ 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 {
 | 
								} else {
 | 
				
			||||||
				for i, labelID := range labelIDs {
 | 
									if labelIDs[0] == 0 {
 | 
				
			||||||
					if labelID > 0 {
 | 
										sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label)")
 | 
				
			||||||
						sess.Join("INNER", fmt.Sprintf("issue_label il%d", i),
 | 
									} else {
 | 
				
			||||||
							fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID))
 | 
										for i, labelID := range labelIDs {
 | 
				
			||||||
					} else {
 | 
											if labelID > 0 {
 | 
				
			||||||
						sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label WHERE label_id = ?)", -labelID)
 | 
												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))
 | 
				
			||||||
 | 
											} 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)
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user