From 8574a6433fab47b6f20997f024c176490dfad1c0 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Sat, 4 Feb 2023 22:35:08 +0800
Subject: [PATCH] Show all projects, not just repo projects and open/closed
 projects  (#22640)

This PR fixes two problems. One is when filter repository issues, only
repository level projects are listed. Another is if you list open
issues, only open projects will be displayed in filter options and if
you list closed issues, only closed projects will be displayed in filter
options.

In this PR, both repository level and org/user level projects will be
displayed in filter, and both open and closed projects will be listed as
filter items.

---------

Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: delvh <dev.lh@web.de>
---
 models/db/search.go             |  6 ++++
 models/issues/issue.go          |  2 ++
 options/locale/locale_en-US.ini |  3 +-
 routers/web/repo/issue.go       | 10 ++----
 templates/repo/issue/list.tmpl  | 64 +++++++++++++++++++++++++++------
 5 files changed, 65 insertions(+), 20 deletions(-)

diff --git a/models/db/search.go b/models/db/search.go
index f5273cb6f6b..26e082756a5 100644
--- a/models/db/search.go
+++ b/models/db/search.go
@@ -27,3 +27,9 @@ const (
 	SearchOrderByForks                 SearchOrderBy = "num_forks ASC"
 	SearchOrderByForksReverse          SearchOrderBy = "num_forks DESC"
 )
+
+const (
+	// Which means a condition to filter the records which don't match any id.
+	// It's different from zero which means the condition could be ignored.
+	NoneID = -1
+)
diff --git a/models/issues/issue.go b/models/issues/issue.go
index 78cac900523..3ddc7992709 100644
--- a/models/issues/issue.go
+++ b/models/issues/issue.go
@@ -1251,6 +1251,8 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) {
 	if opts.ProjectID > 0 {
 		sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
 			And("project_issue.project_id=?", opts.ProjectID)
+	} else if opts.ProjectID == db.NoneID { // show those that are in no project
+		sess.And(builder.NotIn("issue.id", builder.Select("issue_id").From("project_issue")))
 	}
 
 	if opts.ProjectBoardID != 0 {
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 26217293a5e..f384056613c 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1306,7 +1306,8 @@ issues.filter_label_no_select = All labels
 issues.filter_milestone = Milestone
 issues.filter_milestone_no_select = All milestones
 issues.filter_project = Project
-issues.filter_project_no_select = All projects
+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_poster = Author
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 5bff9e67f34..2193da5110e 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -363,16 +363,10 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
 		return 0
 	}
 
-	projects, _, err := project_model.FindProjects(ctx, project_model.SearchOptions{
-		RepoID:   repo.ID,
-		Type:     project_model.TypeRepository,
-		IsClosed: util.OptionalBoolOf(isShowClosed),
-	})
-	if err != nil {
-		ctx.ServerError("FindProjects", err)
+	retrieveProjects(ctx, repo)
+	if ctx.Written() {
 		return
 	}
-	ctx.Data["Projects"] = projects
 
 	ctx.Data["IssueStats"] = issueStats
 	ctx.Data["SelLabelIDs"] = labelIDs
diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl
index 23bcc60f94f..cf2a2a6bbab 100644
--- a/templates/repo/issue/list.tmpl
+++ b/templates/repo/issue/list.tmpl
@@ -75,19 +75,41 @@
 					</div>
 
 					<!-- Project -->
-					<div class="ui {{if not .Projects}}disabled{{end}} dropdown jump item">
+					<div class="ui{{if not (or .OpenProjects .ClosedProjects)}} disabled{{end}} dropdown jump item">
 						<span class="text">
-							{{.locale.Tr "repo.issues.filter_project"}}
+							{{.locale.Tr "repo.issues.filter_projects"}}
 							{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 						</span>
 						<div class="menu">
 							<div class="ui icon search input">
 								<i class="icon df ac jc">{{svg "octicon-search" 16}}</i>
-								<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_project"}}">
+								<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_projects"}}">
 							</div>
-							<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_project_no_select"}}</a>
-							{{range .Projects}}
-								<a class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.Title}}</a>
+							<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_project_all"}}</a>
+							<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&project=-1&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_project_none"}}</a>
+							{{if .OpenProjects}}
+								<div class="divider"></div>
+								<div class="header">
+									{{.locale.Tr "repo.issues.new.open_projects"}}
+								</div>
+								{{range .OpenProjects}}
+									<a class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">
+										{{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "mr-3"}}{{else}}{{svg "octicon-project" 18 "mr-3"}}{{end}}
+										{{.Title}}
+									</a>
+								{{end}}
+							{{end}}
+							{{if .ClosedProjects}}
+								<div class="divider"></div>
+								<div class="header">
+									{{.locale.Tr "repo.issues.new.closed_projects"}}
+								</div>
+								{{range .ClosedProjects}}
+									<a class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">
+										{{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "mr-3"}}{{else}}{{svg "octicon-project" 18 "mr-3"}}{{end}}
+										{{.Title}}
+									</a>
+								{{end}}
 							{{end}}
 						</div>
 					</div>
@@ -222,18 +244,38 @@
 					</div>
 
 					<!-- Projects -->
-					<div class="ui {{if not .Projects}}disabled{{end}} dropdown jump item">
+					<div class="ui{{if not (or .OpenProjects .ClosedProjects)}} disabled{{end}} dropdown jump item">
 						<span class="text">
 							{{.locale.Tr "repo.project_board"}}
 							{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 						</span>
 						<div class="menu">
 							<div class="item issue-action" data-element-id="0" data-url="{{$.Link}}/projects">
-							{{.locale.Tr "repo.issues.new.no_projects"}}
+							{{.locale.Tr "repo.issues.new.clear_projects"}}
 							</div>
-							{{range .Projects}}
-								<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects">
-									{{.Title}}
+							{{if .OpenProjects}}
+								<div class="divider"></div>
+								<div class="header">
+									{{.locale.Tr "repo.issues.new.open_projects"}}
+								</div>
+								{{range .OpenProjects}}
+									<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects">
+										{{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "mr-3"}}{{else}}{{svg "octicon-project" 18 "mr-3"}}{{end}}
+										{{.Title}}
+									</div>
+								{{end}}
+							{{end}}
+							{{if .ClosedProjects}}
+								<div class="divider"></div>
+								<div class="header">
+									{{.locale.Tr "repo.issues.new.closed_projects"}}
+								</div>
+								{{range .ClosedProjects}}
+									<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects">
+										{{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "mr-3"}}{{else}}{{svg "octicon-project" 18 "mr-3"}}{{end}}
+										{{.Title}}
+									</div>
+								{{end}}
 								</div>
 							{{end}}
 						</div>