Allow multiple projects per issue and pull requests#36784
Allow multiple projects per issue and pull requests#36784wxiaoguang merged 162 commits intogo-gitea:mainfrom
Conversation
b180da7 to
a5dbb63
Compare
icyavocado
left a comment
There was a problem hiding this comment.
Addressing previous concerns in #34386
There was a problem hiding this comment.
Pull request overview
This PR implements the ability to assign multiple projects to an issue or pull request, resolving issue #12974. It upgrades the data model throughout the stack — from the database layer (Issue.Projects [] replacing Issue.Project), the search/indexer models (ProjectIDs []int64 replacing ProjectID int64), through the service layer, web routers, templates, and tests.
Changes:
- Core model changes:
Issue.Project(singular) replaced withIssue.Projects(slice) across models, list loading, indexers, and search options - Service/router changes:
IssueAssignOrRemoveProjectnow accepts[]int64and is diff-based (adds new, removes old), form and validation logic updated for multi-select projects - Frontend/template changes:
data-selection-mode="multiple"on project sidebar,project_idsfield,project-listCSS class rename inlist.tmpl
Reviewed changes
Copilot reviewed 32 out of 32 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
models/issues/issue.go |
Replace Project field with Projects []*Project + loaded flag |
models/issues/issue_project.go |
Replace LoadProject/single-project logic with LoadProjects, diff-based IssueAssignOrRemoveProject |
models/issues/issue_list.go |
Update LoadProjects for multiple projects per issue |
models/issues/issue_search.go |
ProjectID int64 → ProjectIDs []int64; INNER JOIN for project filter |
models/issues/issue_project_multi_test.go |
New multi-project integration tests |
modules/util/util.go |
New DiffSlice generic utility function |
modules/indexer/issues/internal/model.go |
ProjectID → ProjectIDs []int64 in indexer data model |
modules/indexer/issues/bleve/bleve.go |
Multi-project search in Bleve |
modules/indexer/issues/elasticsearch/elasticsearch.go |
Multi-project search in Elasticsearch |
modules/indexer/issues/meilisearch/meilisearch.go |
Multi-project search in Meilisearch |
modules/indexer/issues/dboptions.go |
ProjectID → ProjectIDs conversion |
routers/web/repo/projects.go |
UpdateIssueProject updated for multi-project |
routers/web/repo/issue_new.go |
Form validation for ProjectIDs string |
routers/web/repo/pull.go |
PR creation project assignment updated |
routers/web/repo/issue_list.go |
Project filter params updated |
services/issue/issue.go |
NewIssue signature change to projectIDs []int64 |
services/pull/pull.go |
PR options updated for ProjectIDs []int64 |
services/forms/repo_form.go |
ProjectID int64 → ProjectIDs string |
templates/shared/issuelist.tmpl |
Render multiple projects per issue |
templates/repo/issue/sidebar/project_list.tmpl |
Multi-select sidebar |
templates/projects/list.tmpl |
CSS class rename milestone-list → project-list |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
69f8e06 to
b244390
Compare
|
This comment was written by Claude. Some issues I found during review:
|
If you like, you can add a actual playwright e2e test, just ensure that it's absolutely stable and not flaky please. |
In commit 1e9b8ac, tests was written for this behaviour, and used
In commit 5972dc2, test was written to confirm the behaviour of assigning default column automatically, and then remove the
In commit a5cd0f8, reintroduce no ProjectID test, mimic the original test functionality.
In commit 20279aa, return 400 since it was user input error.
In commit 437ef65, using a new util to split string to present data on the template level instead, this should provide []int64 data straight from template level down.
In commit 935154d, a test was added to recereate the double filtering issue, and a small patch was added to
In commit eb2308d, test was added to go through the API end points setup, and also recreate the swagger template data so actions can pass properly.
In commit 3154a0e, added test to recreate the LoadProjects issue when projectIDs lingered. The fix is to reset the flag and
In commit 740bfce, added e2e tests for projects. The test was using a mixed of api calls, and clicking around the interface. When writing the e2e tests, a commit 87644e8 was introduced so user can filter multiple projects. using |
silverwind
left a comment
There was a problem hiding this comment.
This review was written by Claude.
Nice work on this — the approach is clean, no DB migration needed, and the test coverage is solid. A few concerns:
ProjectColumnID() is still singular
issue.ProjectColumnID() uses .Get(&ip) which returns an arbitrary project_issue row. With multiple projects per issue, this is nondeterministic. getIssueIndexerData() in modules/indexer/issues/util.go calls it to index the ProjectColumnID field — that indexed value is now meaningless when an issue belongs to multiple projects with different columns.
SearchIssues parameter precedence
In routers/web/repo/issue_list.go, if both project and projects query params are provided, the single project value gets appended to includedProjectIDs first (line ~164), then the projects param completely replaces includedProjectIDs (line ~173), silently dropping the project value. Should either skip appending project when projects is present, or document the precedence.
Silent project validation change
In ValidateRepoMetasForNewIssue, the old code returned 404 when a project ID wasn't in candidateProjects. The new code silently drops invalid IDs. A user submitting project_ids=999 would succeed with no project assigned instead of getting an error.
N+1 queries in IssueAssignOrRemoveProject
For each project to add: GetProjectByID + MustDefaultColumn + SELECT max(sorting) = 3 queries per project inside the transaction. Could batch-load the projects upfront.
Removed "no project" indexer test case
The internal test suite (modules/indexer/issues/internal/tests/tests.go) had a dedicated "no ProjectID" test case validating ProjectID = optional.Some(int64(0)) across all indexer engines (bleve/elasticsearch/meilisearch). It was removed without a per-engine replacement — the new test in indexer_test.go covers it at a higher level but doesn't exercise each engine's implementation.
|
Some more minor concerns, and please fix the merge conflict. Once done, I will look at the UI. |
|
This comment was written by Claude. Good progress addressing the previous round of feedback. A few remaining concerns: API validation logic is duplicated
|
|
Just wanted to say we're following this with interest. We're evaluating a move from Forgejo to Gitea and multi-project support plus a projects API would be a deciding factor. The work here looks solid. Happy to help test when it's ready. |
|
@icyavocado updates? please lets try to move this into 1.26 |
Im actively working on this issue. Will have a better update in a day. |
437ef65 to
5979d49
Compare
|
Much appreciated for reviewing my PR! I addressed the merge conflict and then worked on the updates below. Update to comment by @silverwind
In commit 1c0d99a, introduced a new
In commit f064b5b, in an API call,
In commit 9aea92b, we now return a 400 Bad Request for invalid IDs. Added tests to check the error responses.
In commit 0f76785, I introduced two helper functions,
In commit a7589dc, I added Update to comment by @bircni
In commit 65f57ee, I created the helper function
In commit a036265, I added the comment before the redirect:
In commit 1c0d99a, I introduced a new
In commit a036265, I added the note above the bug fix. Sorry in advance if this is not what you wanted. |
|
You need to solve some conflicts |
5979d49 to
e8e4d95
Compare
Change the issue/PR API response from `project` (singular object) to `projects` (array of objects). Today this array has 0 or 1 entries. When multi-project support lands (go-gitea#36784), the API contract won't need to change. Co-Authored-By: Claude Opus 4 (claude-opus-4-6)
f4c03fc to
1d6b6aa
Compare
1d6b6aa to
4a0c9a1
Compare
|
Cleaned up a lot for AI slop, still WIP. TBH, I don't understand why such UI/UX can look good to you @silverwind since you always keep focusing on and arguing about many trivial details. But this one obviously isn't trivial. UI is wrong, UX is wrong, search logic is wrong. |
| } finally { | ||
| await apiDeleteRepo(page.request, user, repoName); | ||
| } |
There was a problem hiding this comment.
@silverwind I think you have refactored the tests and removed all "try-finally-apiDeleteRepo", what do you think?
There was a problem hiding this comment.
Yes, these should be removed for the sake of performance.
57b19ed to
4d2ad28
Compare
4d2ad28 to
8ef4591
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 57 out of 58 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
wxiaoguang
left a comment
There was a problem hiding this comment.
Cleaned up 1000 lines of AI slop
| await apiCreateRepo(request, {name: repoName}); | ||
| await Promise.all([ | ||
| apiCreateIssue(request, owner, repoName, {title: 'Reaction test'}), | ||
| apiCreateIssue(request, {owner, repo: repoName, title: 'Reaction test'}), |
There was a problem hiding this comment.
The design of these helpers is that required args are positional and optional ones are in the object. This violates it.
There was a problem hiding this comment.
Need a complete fix for the e2e code (including #36784 (comment))
Just not worth to introduce more AI slop in this one.
* origin/main: Refactor CI workflows (go-gitea#37487) Allow multiple projects per issue and pull requests (go-gitea#36784) [skip ci] Updated translations via Crowdin Refactor compare diff/pull page (1) (go-gitea#37481) Fix review submission from single-commit PR view (go-gitea#37475) Refactor integration tests infrastructure (go-gitea#37462) Fix allow maintainer edit permission check (go-gitea#37479) Serve OpenAPI 3.0 spec at /openapi.v1.json (go-gitea#37038) Batch-load related data in actions run, job, and task API endpoints (go-gitea#37032) Add DEFAULT_TITLE_SOURCE setting for pull request title default behavior (go-gitea#37465) Fix compare dropdown for branches without common history (go-gitea#37470) FIX: URL sanitization to handle schemeless credentials (go-gitea#37440) Refactor pull request view (4) (go-gitea#37451) # Conflicts: # modules/indexer/issues/elasticsearch/elasticsearch.go

Add ability to add and remove multiple projects per issue and pull request.
E2E test video: https://github.com/user-attachments/assets/5807944d-f524-4280-b61f-0cadcb4aa904 stiched together from 740bfce
No breaking change expected.
Copilot was used to review changes, syntax and help with commit messages.
Resolve #12974
Updated progress:
Add support for column picker
Styling reverted to use class .milestone-list