mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09: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 { | ||||
| 		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 { | ||||
| @@ -1312,13 +1314,17 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) { | ||||
| 		sess.And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()}) | ||||
| 	} | ||||
|  | ||||
| 	if opts.LabelIDs != nil { | ||||
| 		for i, labelID := range opts.LabelIDs { | ||||
| 			if labelID > 0 { | ||||
| 				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 { | ||||
| 				sess.Where("issue.id not in (select issue_id from issue_label where label_id = ?)", -labelID) | ||||
| 	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 { | ||||
| 				if labelID > 0 { | ||||
| 					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) | ||||
| 		} | ||||
|  | ||||
| 		if len(opts.Labels) > 0 && opts.Labels != "0" { | ||||
| 		if len(opts.Labels) > 0 { | ||||
| 			labelIDs, err := base.StringsToInt64s(strings.Split(opts.Labels, ",")) | ||||
| 			if err != nil { | ||||
| 				log.Warn("Malformed Labels argument: %s", opts.Labels) | ||||
| 			} else { | ||||
| 				for i, labelID := range labelIDs { | ||||
| 					if labelID > 0 { | ||||
| 						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 { | ||||
| 						sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label WHERE label_id = ?)", -labelID) | ||||
| 				if labelIDs[0] == 0 { | ||||
| 					sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label)") | ||||
| 				} else { | ||||
| 					for i, labelID := range labelIDs { | ||||
| 						if labelID > 0 { | ||||
| 							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 { | ||||
| 			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 { | ||||
|   | ||||
| @@ -1361,6 +1361,7 @@ issues.delete_branch_at = `deleted branch <b>%s</b> %s` | ||||
| issues.filter_label = Label | ||||
| 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_select_no_label = No Label | ||||
| issues.filter_milestone = Milestone | ||||
| issues.filter_milestone_all = All 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_assignee = Assignee | ||||
| issues.filter_assginee_no_select = All assignees | ||||
| issues.filter_assginee_no_assignee = No assignee | ||||
| issues.filter_poster = Author | ||||
| issues.filter_poster_no_select = All authors | ||||
| issues.filter_type = Type | ||||
|   | ||||
| @@ -170,8 +170,11 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti | ||||
|  | ||||
| 	repo := ctx.Repo.Repository | ||||
| 	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") | ||||
| 	if len(selectLabels) > 0 && selectLabels != "0" { | ||||
| 	if len(selectLabels) > 0 { | ||||
| 		labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ",")) | ||||
| 		if err != nil { | ||||
| 			ctx.ServerError("StringsToInt64s", err) | ||||
|   | ||||
| @@ -38,6 +38,7 @@ | ||||
| 								<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_label"}}"> | ||||
| 							</div> | ||||
| 							<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> | ||||
| 							{{$previousExclusiveScope := "_no_scope"}} | ||||
| 							{{range .Labels}} | ||||
| @@ -156,6 +157,7 @@ | ||||
| 								<i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i> | ||||
| 								<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_assignee"}}"> | ||||
| 							</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> | ||||
| 							{{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}}"> | ||||
| @@ -226,6 +228,9 @@ | ||||
| 							{{svg "octicon-triangle-down" 14 "dropdown icon"}} | ||||
| 						</span> | ||||
| 						<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"}} | ||||
| 							{{range .Labels}} | ||||
| 								{{$exclusiveScope := .ExclusiveScope}} | ||||
| @@ -313,6 +318,9 @@ | ||||
| 							{{svg "octicon-triangle-down" 14 "dropdown icon"}} | ||||
| 						</span> | ||||
| 						<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"> | ||||
| 								{{.locale.Tr "repo.issues.action_assignee_no_select"}} | ||||
| 							</div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user