Skip to content

Commit 05d9810

Browse files
authored
Merge branch 'main' into 31666-project-columns-height-by-content
2 parents cfac52d + 74550a9 commit 05d9810

File tree

23 files changed

+360
-43
lines changed

23 files changed

+360
-43
lines changed

models/issues/issue_project.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ func (issue *Issue) ProjectColumnID(ctx context.Context) int64 {
4848
}
4949

5050
// LoadIssuesFromColumn load issues assigned to this column
51-
func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column) (IssueList, error) {
52-
issueList, err := Issues(ctx, &IssuesOptions{
53-
ProjectColumnID: b.ID,
54-
ProjectID: b.ProjectID,
55-
SortType: "project-column-sorting",
56-
})
51+
func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *IssuesOptions) (IssueList, error) {
52+
issueList, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) {
53+
o.ProjectColumnID = b.ID
54+
o.ProjectID = b.ProjectID
55+
o.SortType = "project-column-sorting"
56+
}))
5757
if err != nil {
5858
return nil, err
5959
}
@@ -78,10 +78,10 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column) (IssueLi
7878
}
7979

8080
// LoadIssuesFromColumnList load issues assigned to the columns
81-
func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList) (map[int64]IssueList, error) {
81+
func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList, opts *IssuesOptions) (map[int64]IssueList, error) {
8282
issuesMap := make(map[int64]IssueList, len(bs))
8383
for i := range bs {
84-
il, err := LoadIssuesFromColumn(ctx, bs[i])
84+
il, err := LoadIssuesFromColumn(ctx, bs[i], opts)
8585
if err != nil {
8686
return nil, err
8787
}

models/issues/issue_search.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ type IssuesOptions struct { //nolint
5454
User *user_model.User // issues permission scope
5555
}
5656

57+
// Copy returns a copy of the options.
58+
// Be careful, it's not a deep copy, so `IssuesOptions.RepoIDs = {...}` is OK while `IssuesOptions.RepoIDs[0] = ...` is not.
59+
func (o *IssuesOptions) Copy(edit ...func(options *IssuesOptions)) *IssuesOptions {
60+
if o == nil {
61+
return nil
62+
}
63+
v := *o
64+
for _, e := range edit {
65+
e(&v)
66+
}
67+
return &v
68+
}
69+
5770
// applySorts sort an issues-related session based on the provided
5871
// sortType string
5972
func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {

models/organization/org_user.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import (
99

1010
"code.gitea.io/gitea/models/db"
1111
"code.gitea.io/gitea/models/perm"
12+
"code.gitea.io/gitea/models/unit"
1213
user_model "code.gitea.io/gitea/models/user"
14+
"code.gitea.io/gitea/modules/container"
1315
"code.gitea.io/gitea/modules/log"
1416

1517
"xorm.io/builder"
@@ -112,6 +114,49 @@ func IsUserOrgOwner(ctx context.Context, users user_model.UserList, orgID int64)
112114
return results
113115
}
114116

117+
// GetOrgAssignees returns all users that have write access and can be assigned to issues
118+
// of the any repository in the organization.
119+
func GetOrgAssignees(ctx context.Context, orgID int64) (_ []*user_model.User, err error) {
120+
e := db.GetEngine(ctx)
121+
userIDs := make([]int64, 0, 10)
122+
if err = e.Table("access").
123+
Join("INNER", "repository", "`repository`.id = `access`.repo_id").
124+
Where("`repository`.owner_id = ? AND `access`.mode >= ?", orgID, perm.AccessModeWrite).
125+
Select("user_id").
126+
Find(&userIDs); err != nil {
127+
return nil, err
128+
}
129+
130+
additionalUserIDs := make([]int64, 0, 10)
131+
if err = e.Table("team_user").
132+
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
133+
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
134+
Join("INNER", "repository", "`repository`.id = `team_repo`.repo_id").
135+
Where("`repository`.owner_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
136+
orgID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
137+
Distinct("`team_user`.uid").
138+
Select("`team_user`.uid").
139+
Find(&additionalUserIDs); err != nil {
140+
return nil, err
141+
}
142+
143+
uniqueUserIDs := make(container.Set[int64])
144+
uniqueUserIDs.AddMultiple(userIDs...)
145+
uniqueUserIDs.AddMultiple(additionalUserIDs...)
146+
147+
users := make([]*user_model.User, 0, len(uniqueUserIDs))
148+
if len(userIDs) > 0 {
149+
if err = e.In("id", uniqueUserIDs.Values()).
150+
Where(builder.Eq{"`user`.is_active": true}).
151+
OrderBy(user_model.GetOrderByName()).
152+
Find(&users); err != nil {
153+
return nil, err
154+
}
155+
}
156+
157+
return users, nil
158+
}
159+
115160
func loadOrganizationOwners(ctx context.Context, users user_model.UserList, orgID int64) (map[int64]*TeamUser, error) {
116161
if len(users) == 0 {
117162
return nil, nil

options/locale/locale_en-US.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ filter.public = Public
159159
filter.private = Private
160160

161161
no_results_found = No results found.
162+
internal_error_skipped = Internal error occurred but is skipped: %s
162163

163164
[search]
164165
search = Search...
@@ -1279,7 +1280,6 @@ commit_graph.color = Color
12791280
commit.contained_in = This commit is contained in:
12801281
commit.contained_in_default_branch = This commit is part of the default branch
12811282
commit.load_referencing_branches_and_tags = Load branches and tags referencing this commit
1282-
commit.load_tags_failed = Load tags failed because of internal error
12831283
blame = Blame
12841284
download_file = Download file
12851285
normal_view = Normal View

options/locale/locale_fr-FR.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1254,7 +1254,6 @@ commit_graph.color=Couleur
12541254
commit.contained_in=Cette révision appartient à :
12551255
commit.contained_in_default_branch=Cette révision appartient à la branche par défaut
12561256
commit.load_referencing_branches_and_tags=Charger les branches et étiquettes référençant cette révision
1257-
commit.load_tags_failed=Le chargement des étiquettes a échoué à cause d’une erreur interne
12581257
blame=Annotations
12591258
download_file=Télécharger le fichier
12601259
normal_view=Vue normale

options/locale/locale_ja-JP.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1263,7 +1263,6 @@ commit_graph.color=カラー
12631263
commit.contained_in=このコミットが含まれているのは:
12641264
commit.contained_in_default_branch=このコミットはデフォルトブランチに含まれています
12651265
commit.load_referencing_branches_and_tags=このコミットを参照しているブランチやタグを取得
1266-
commit.load_tags_failed=内部エラーによりタグの読み込みに失敗しました
12671266
blame=Blame
12681267
download_file=ファイルをダウンロード
12691268
normal_view=通常表示

options/locale/locale_pt-PT.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ filter.public=Público
159159
filter.private=Privado
160160

161161
no_results_found=Não foram encontrados quaisquer resultados.
162+
internal_error_skipped=Ocorreu um erro interno mas foi ignorado: %s
162163

163164
[search]
164165
search=Pesquisar...
@@ -1279,7 +1280,6 @@ commit_graph.color=Colorido
12791280
commit.contained_in=Este cometimento está contido em:
12801281
commit.contained_in_default_branch=Este cometimento é parte do ramo principal
12811282
commit.load_referencing_branches_and_tags=Carregar ramos e etiquetas que referenciem este cometimento
1282-
commit.load_tags_failed=O carregamento das etiquetas falhou por causa de um erro interno
12831283
blame=Responsabilidade
12841284
download_file=Descarregar ficheiro
12851285
normal_view=Vista normal

routers/api/v1/repo/issue.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -833,10 +833,16 @@ func EditIssue(ctx *context.APIContext) {
833833
if (form.Deadline != nil || form.RemoveDeadline != nil) && canWrite {
834834
var deadlineUnix timeutil.TimeStamp
835835

836-
if (form.RemoveDeadline == nil || !*form.RemoveDeadline) && !form.Deadline.IsZero() {
837-
deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
838-
23, 59, 59, 0, form.Deadline.Location())
839-
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
836+
if form.RemoveDeadline == nil || !*form.RemoveDeadline {
837+
if form.Deadline == nil {
838+
ctx.Error(http.StatusBadRequest, "", "The due_date cannot be empty")
839+
return
840+
}
841+
if !form.Deadline.IsZero() {
842+
deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
843+
23, 59, 59, 0, form.Deadline.Location())
844+
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
845+
}
840846
}
841847

842848
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {

routers/api/v1/repo/release.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"code.gitea.io/gitea/models/perm"
1313
repo_model "code.gitea.io/gitea/models/repo"
1414
"code.gitea.io/gitea/models/unit"
15+
"code.gitea.io/gitea/modules/git"
1516
api "code.gitea.io/gitea/modules/structs"
1617
"code.gitea.io/gitea/modules/web"
1718
"code.gitea.io/gitea/routers/api/v1/utils"
@@ -251,6 +252,8 @@ func CreateRelease(ctx *context.APIContext) {
251252
ctx.Error(http.StatusConflict, "ReleaseAlreadyExist", err)
252253
} else if models.IsErrProtectedTagName(err) {
253254
ctx.Error(http.StatusUnprocessableEntity, "ProtectedTagName", err)
255+
} else if git.IsErrNotExist(err) {
256+
ctx.Error(http.StatusNotFound, "ErrNotExist", fmt.Errorf("target \"%v\" not found: %w", rel.Target, err))
254257
} else {
255258
ctx.Error(http.StatusInternalServerError, "CreateRelease", err)
256259
}

routers/web/org/projects.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"code.gitea.io/gitea/models/db"
1313
issues_model "code.gitea.io/gitea/models/issues"
14+
org_model "code.gitea.io/gitea/models/organization"
1415
project_model "code.gitea.io/gitea/models/project"
1516
attachment_model "code.gitea.io/gitea/models/repo"
1617
"code.gitea.io/gitea/models/unit"
@@ -333,7 +334,29 @@ func ViewProject(ctx *context.Context) {
333334
return
334335
}
335336

336-
issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, columns)
337+
var labelIDs []int64
338+
// 1,-2 means including label 1 and excluding label 2
339+
// 0 means issues with no label
340+
// blank means labels will not be filtered for issues
341+
selectLabels := ctx.FormString("labels")
342+
if selectLabels == "" {
343+
ctx.Data["AllLabels"] = true
344+
} else if selectLabels == "0" {
345+
ctx.Data["NoLabel"] = true
346+
}
347+
if len(selectLabels) > 0 {
348+
labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ","))
349+
if err != nil {
350+
ctx.Flash.Error(ctx.Tr("invalid_data", selectLabels), true)
351+
}
352+
}
353+
354+
assigneeID := ctx.FormInt64("assignee")
355+
356+
issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, columns, &issues_model.IssuesOptions{
357+
LabelIDs: labelIDs,
358+
AssigneeID: assigneeID,
359+
})
337360
if err != nil {
338361
ctx.ServerError("LoadIssuesOfColumns", err)
339362
return
@@ -372,6 +395,46 @@ func ViewProject(ctx *context.Context) {
372395
}
373396
}
374397

398+
// TODO: Add option to filter also by repository specific labels
399+
labels, err := issues_model.GetLabelsByOrgID(ctx, project.OwnerID, "", db.ListOptions{})
400+
if err != nil {
401+
ctx.ServerError("GetLabelsByOrgID", err)
402+
return
403+
}
404+
405+
// Get the exclusive scope for every label ID
406+
labelExclusiveScopes := make([]string, 0, len(labelIDs))
407+
for _, labelID := range labelIDs {
408+
foundExclusiveScope := false
409+
for _, label := range labels {
410+
if label.ID == labelID || label.ID == -labelID {
411+
labelExclusiveScopes = append(labelExclusiveScopes, label.ExclusiveScope())
412+
foundExclusiveScope = true
413+
break
414+
}
415+
}
416+
if !foundExclusiveScope {
417+
labelExclusiveScopes = append(labelExclusiveScopes, "")
418+
}
419+
}
420+
421+
for _, l := range labels {
422+
l.LoadSelectedLabelsAfterClick(labelIDs, labelExclusiveScopes)
423+
}
424+
ctx.Data["Labels"] = labels
425+
ctx.Data["NumLabels"] = len(labels)
426+
427+
// Get assignees.
428+
assigneeUsers, err := org_model.GetOrgAssignees(ctx, project.OwnerID)
429+
if err != nil {
430+
ctx.ServerError("GetRepoAssignees", err)
431+
return
432+
}
433+
ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers)
434+
435+
ctx.Data["SelectLabels"] = selectLabels
436+
ctx.Data["AssigneeID"] = assigneeID
437+
375438
project.RenderedContent = templates.RenderMarkdownToHtml(ctx, project.Description)
376439
ctx.Data["LinkedPRs"] = linkedPrsMap
377440
ctx.Data["PageIsViewProjects"] = true

0 commit comments

Comments
 (0)