Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Projects enhancements #12506

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions models/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
api "code.gitea.io/gitea/modules/structs"
Expand Down Expand Up @@ -63,6 +64,7 @@ type Issue struct {
Reactions ReactionList `xorm:"-"`
TotalTrackedTime int64 `xorm:"-"`
Assignees []*User `xorm:"-"`
ProjectIssueID ProjectIssue
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ProjectIssueID ProjectIssue
ProjectIssueID ProjectIssue `xorm:"-"`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@6543 I can't do this because otherwise my query will stop working here https://github.com/patcito/gitea/blob/projects-enhancements/models/project_board.go#L206 as I use a field called project_issue_id in the join query and xorm will remove this field if I apply your suggestion of adding xorm:"-"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But ID should not be ProjectIssue type. Also this will generate columns to issue table just like ExcludeProjectID


// IsLocked limits commenting abilities to users on an issue
// with write access
Expand Down Expand Up @@ -1100,8 +1102,10 @@ type IssuesOptions struct {
ExcludedLabelNames []string
SortType string
IssueIDs []int64
ExcludeProjectID int64
// prioritize issues from this repo
PriorityRepoID int64
PriorityRepoID int64
RenderEmojiTitle util.OptionalBool
}

// sortIssuesSession sort an issues-related session based on the provided
Expand Down Expand Up @@ -1180,17 +1184,21 @@ func (opts *IssuesOptions) setupSession(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)
And("project_issue.project_id=?", opts.ProjectID).OrderBy("`project_issue`.priority")
}
lafriks marked this conversation as resolved.
Show resolved Hide resolved

lafriks marked this conversation as resolved.
Show resolved Hide resolved
if opts.ProjectBoardID != 0 {
if opts.ProjectBoardID > 0 {
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": opts.ProjectBoardID}))
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": opts.ProjectBoardID}).OrderBy("`project_issue`.priority"))
} else {
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0}))
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0}).OrderBy("`project_issue`.priority"))
zeripath marked this conversation as resolved.
Show resolved Hide resolved
}
}

if opts.ExcludeProjectID != 0 {
sess.NotIn("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_id": opts.ExcludeProjectID}).OrderBy("`project_issue`.priority"))
zeripath marked this conversation as resolved.
Show resolved Hide resolved
}

switch opts.IsPull {
case util.OptionalBoolTrue:
sess.And("issue.is_pull=?", true)
Expand Down Expand Up @@ -1280,7 +1288,15 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
if err := IssueList(issues).LoadAttributes(); err != nil {
return nil, fmt.Errorf("LoadAttributes: %v", err)
}

if opts.RenderEmojiTitle == util.OptionalBoolTrue {
var issuesWithEmojis []*Issue
for _, issue := range issues {
title := string(markup.RenderEmoji(issue.Title))
issue.Title = title
issuesWithEmojis = append(issuesWithEmojis, issue)
}
return issuesWithEmojis, nil
}
Comment on lines +1291 to +1299
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rendering of the emoji should be done elsewhere.

Copy link
Member

@silverwind silverwind Sep 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was it made an option? I thought emoji rendering to be a non-destructive operation that should always be performed where possible and generally we do it through the template helper.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the search API is being used to display issue in the board search and you requested it shows emojis. Where else could this code go? In the JavaScript? Sounds not practical to me.

Copy link
Member

@silverwind silverwind Sep 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably use markup.RenderEmoji, probably in routers/api. Might need HTML escaping. Or alternatively, move RenderEmoji in templates/helper.go to modules/emoji and export it there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@silverwind this would require every function in api that queries a list of issues. It makes more sense to leave it the central Issues function so that all can benefit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should move elsewhere or use one of the shared functions. Thematically, a issue has nothing to do with emoji rendering, we're doing that all over the place, not just in issues.

return issues, nil
}

Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ var migrations = []Migration{
NewMigration("code comment replies should have the commitID of the review they are replying to", updateCodeCommentReplies),
// v159 -> v160
NewMigration("update reactions constraint", updateReactionConstraint),
// v160 -> v161
NewMigration("Add projects boards and issues priorities", addProjectsIssuesBoardsPriority),
}

// GetCurrentDBVersion returns the current db version
Expand Down
26 changes: 26 additions & 0 deletions models/migrations/v160.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

import (
"xorm.io/xorm"
)

func addProjectsIssuesBoardsPriority(x *xorm.Engine) error {
// ProjectIssue saves relation from issue to a project
type ProjectIssue struct {
Priority int `xorm:"NOT NULL DEFAULT 0"`
}

if err := x.Sync2(new(ProjectIssue)); err != nil {
return err
}

type ProjectBoard struct {
Priority int `xorm:"NOT NULL DEFAULT 0"`
}

return x.Sync2(new(ProjectBoard))
}
23 changes: 23 additions & 0 deletions models/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"errors"
"fmt"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
Expand Down Expand Up @@ -47,6 +48,7 @@ type Project struct {
IsClosed bool `xorm:"INDEX"`
BoardType ProjectBoardType
Type ProjectType
Repo *Repository `xorm:"-"`

RenderedContent string `xorm:"-"`

Expand Down Expand Up @@ -305,3 +307,24 @@ func deleteProjectByID(e Engine, id int64) error {

return updateRepositoryProjectCount(e, p.RepoID)
}

// LoadRepository loads repository of a given project
func (p *Project) LoadRepository() error {
patcito marked this conversation as resolved.
Show resolved Hide resolved
return p.loadRepository(x)
}

// loadRepository loads repository of a given project
func (p *Project) loadRepository(e Engine) error {
if p.Repo != nil {
return nil
}
if p.Type == ProjectTypeRepository {
repo := &Repository{}
if _, err := e.ID(p.RepoID).Get(repo); err != nil {
log.Info("failed getting repo %v", err)
return err
}
p.Repo = repo
}
return nil
}
74 changes: 53 additions & 21 deletions models/project_board.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package models

import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"

Expand Down Expand Up @@ -32,17 +33,19 @@ const (

// ProjectBoard is used to represent boards on a project
type ProjectBoard struct {
ID int64 `xorm:"pk autoincr"`
Title string
Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board
ID int64 `xorm:"pk autoincr"`
Title string
Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board
Priority int `xorm:"NOT NULL DEFAULT 0"`

ProjectID int64 `xorm:"INDEX NOT NULL"`
CreatorID int64 `xorm:"NOT NULL"`

CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`

Issues []*Issue `xorm:"-"`
Issues []*Issue `xorm:"-"`
ProjectIssues []*ProjectIssue `xorm:"-"`
}

// IsProjectBoardTypeValid checks if the project board type is valid
Expand Down Expand Up @@ -174,7 +177,7 @@ func GetProjectBoards(projectID int64) ([]*ProjectBoard, error) {

var boards = make([]*ProjectBoard, 0, 5)

sess := x.Where("project_id=?", projectID)
sess := x.Where("project_id=?", projectID).OrderBy("priority")
return boards, sess.Find(&boards)
}

Expand All @@ -187,34 +190,63 @@ func GetUncategorizedBoard(projectID int64) (*ProjectBoard, error) {
}, nil
}

// LoadIssues load issues assigned to this board
func (b *ProjectBoard) LoadIssues() (IssueList, error) {
// LoadProjectIssues load project issues assigned to this board
func (b *ProjectBoard) LoadProjectIssues() ([]*ProjectIssue, error) {
zeripath marked this conversation as resolved.
Show resolved Hide resolved
var boardID int64
if !b.Default {
boardID = b.ID

} else {
// Issues without ProjectBoardID
boardID = -1
boardID = 0
}
issues, err := Issues(&IssuesOptions{
ProjectBoardID: boardID,
ProjectID: b.ProjectID,
})
b.Issues = issues
return issues, err
var projectIssues []*ProjectIssue
var issues []*Issue
if err := x.Table("issue").
Cols("issue.id, issue.repo_id, issue.index, issue.poster_id,issue.name,issue.milestone_id,issue.is_closed,issue.is_pull,issue.created_unix,issue.updated_unix, project_issue.id as project_issue_id, project_issue.project_id as project_issue_project_id, project_issue.project_board_id as project_issue_project_board_id").
Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
Where("project_board_id = ? and project_id =?",
boardID, b.ProjectID).
OrderBy("`project_issue`.priority").Find(&issues); err != nil {
log.Error("LoadAttributes: %v", err)
}

if err := IssueList(issues).LoadAttributes(); err != nil {
log.Error("LoadAttributes: %v", err)
}

for _, issue := range issues {
projectIssue := issue.ProjectIssueID
projectIssue.Issue = issue
projectIssues = append(projectIssues, &projectIssue)
}
return projectIssues, nil
}

// LoadIssues load issues assigned to the boards
func (bs ProjectBoardList) LoadIssues() (IssueList, error) {
issues := make(IssueList, 0, len(bs)*10)
func (bs ProjectBoardList) LoadIssues() error {
for i := range bs {
il, err := bs[i].LoadIssues()
il, err := bs[i].LoadProjectIssues()
if err != nil {
return nil, err
return err
}
bs[i].ProjectIssues = il
}
return nil
}

// UpdateBoardsPriority updates boards priority for a project
func UpdateBoardsPriority(boards []ProjectBoard) error {
sess := x.NewSession()
if err := sess.Begin(); err != nil {
return err
}
defer sess.Close()
for _, board := range boards {
if _, err := sess.ID(board.ID).Cols("priority").Update(&board); err != nil {
log.Info("failed updating board priorities %s", err)
return err
}
bs[i].Issues = il
issues = append(issues, il...)
}
return issues, nil
return sess.Commit()
}
73 changes: 59 additions & 14 deletions models/project_issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ package models
import (
"fmt"

"code.gitea.io/gitea/modules/log"
zeripath marked this conversation as resolved.
Show resolved Hide resolved

"xorm.io/xorm"
)

// ProjectIssue saves relation from issue to a project
type ProjectIssue struct {
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX"`
ProjectID int64 `xorm:"INDEX"`
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX"`
ProjectID int64 `xorm:"INDEX"`
Priority int `xorm:"NOT NULL DEFAULT 0"`
Issue *Issue `xorm:"-"`

// If 0, then it has not been added to a specific board in the project
ProjectBoardID int64 `xorm:"INDEX"`
Expand Down Expand Up @@ -64,12 +68,12 @@ func (i *Issue) projectID(e Engine) int64 {
return ip.ProjectID
}

// ProjectBoardID return project board id if issue was assigned to one
func (i *Issue) ProjectBoardID() int64 {
return i.projectBoardID(x)
// LoadProjectBoardID return project board id if issue was assigned to one
func (i *Issue) LoadProjectBoardID() int64 {
return i.loadProjectBoardID(x)
}

func (i *Issue) projectBoardID(e Engine) int64 {
func (i *Issue) loadProjectBoardID(e Engine) int64 {
var ip ProjectIssue
has, err := e.Where("issue_id=?", i.ID).Get(&ip)
if err != nil || !has {
Expand Down Expand Up @@ -139,9 +143,7 @@ func ChangeProjectAssign(issue *Issue, doer *User, newProjectID int64) error {
}

func addUpdateIssueProject(e *xorm.Session, issue *Issue, doer *User, newProjectID int64) error {

oldProjectID := issue.projectID(e)

if _, err := e.Where("project_issue.issue_id=?", issue.ID).Delete(&ProjectIssue{}); err != nil {
return err
}
Expand All @@ -163,11 +165,21 @@ func addUpdateIssueProject(e *xorm.Session, issue *Issue, doer *User, newProject
}
}

_, err := e.Insert(&ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
})
return err
var projectIssues []ProjectIssue
if newProjectID != 0 {
err := e.Where("issue_id = ? and project_id = ?", issue.ID, newProjectID).Find(&projectIssues)
if err != nil {
return err
}
if len(projectIssues) == 0 {
_, err := e.Insert(&ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
})
return err
}
}
return nil
}

// ____ _ _ ____ _
Expand Down Expand Up @@ -208,3 +220,36 @@ func (pb *ProjectBoard) removeIssues(e Engine) error {
_, err := e.Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", pb.ID)
return err
}

// UpdateBoardIssuesPriority update given board issue priority
func UpdateBoardIssuesPriority(issues []ProjectIssue) ([]ProjectIssue, error) {
sess := x.NewSession()
if err := sess.Begin(); err != nil {
var updatedIssues []ProjectIssue
return updatedIssues, err
}
defer sess.Close()
var updatedIssues []ProjectIssue
for _, pissue := range issues {
if pissue.ID != 0 {
if _, err := sess.ID(pissue.ID).Cols("priority", "project_board_id").Update(&pissue); err != nil {
log.Info("failed updating cards priorities %s", err)
return updatedIssues, err
}
updatedIssues = append(updatedIssues, pissue)
} else {
var existingIssue ProjectIssue
if found, err := sess.Where("issue_id = ? and project_id = ?", pissue.IssueID, pissue.ProjectID).
Get(&existingIssue); err != nil {
log.Error("failed finding issue %s", err)
} else if !found {
if _, err := sess.Insert(&pissue); err != nil {
log.Info("failed inserting cards priorities %s", err)
return updatedIssues, err
}
updatedIssues = append(updatedIssues, pissue)
}
}
}
return updatedIssues, sess.Commit()
}
Loading