Skip to content

Add automated project board #27835

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

Closed
wants to merge 1 commit 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
77 changes: 77 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,83 @@ LEVEL = Info
;PROJECT_BOARD_BASIC_KANBAN_TYPE = To Do, In Progress, Done
;PROJECT_BOARD_BUG_TRIAGE_TYPE = Needs Triage, High Priority, Low Priority, Closed

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[project.automation]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;ENABLED = false
;MAX_RULES_PER_PROJECT = 25

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[project.automation.kanban]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Default rules when creating an automated Kanban project.
;; Each rule can specify a list of key/value pairs:
;; - trigger (required) see commands below
;; - action (required) see commands below
;; - target (optional) the rule is only active for these issue types
;; one of: issue, pr, default
;; can be comma-separated list (e.g. issue,pr)
;; the 'default' depends on the action, but most often
;; it is the same as issue,pr (notable exceptions are
;; commands 'xref', 'approve' and 'assign_reviewer')
;; (default value is target=default)
;; - context (optional) the rule is only active in this context
;; one of: column:{column}, project
;; where {column} is a column title
;; (default value is context=project)
;;
;; Commands can have arguments using the command:argument syntax.
;; They are available both as a trigger and as an action.
;; Available commands:
;; - move:{column} where {column} is a column title
Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately, we need something else before that:
board titles are not unique (per project) at the moment, so we need the UNIQUE INDEX for it first.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, having board titles here were a necessary evil I guess. I initially only supported board IDs, but then I added support for titles when I figured that we need some way to configure this before being able to add automation rules via the web interface.

Then, in the end I basically only kept support for titles as to simplify the configuration interface.

While there is no guarantee of unique board titles, I still think being able to address it a board by title in the configuration is a useful thing to do. (and I don't necessarily think that having duplicate board titles within one project is a likely use-case)

Not quite sure how to proceed with this particular issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should perhaps clarify that the board/column titles are only used at project creation time and are inherently tied to the column labels in the PROJECT_BOARD_BASIC_KANBAN_TYPE template. At project creation time, after the boards/columns have been created and have real IDs, the labels here are only used to lookup the IDs which are then stored in the database. Renaming the columns after creation does not "break" the rules. The same goes for labels, which also must exist at project creation time.

While I see the value of not letting the user "shoot themselves in the foot" by supplying duplicate titles in the default configuration, I kind of see that as a separate issue that could be fixed separately instead of growing this PR even further.

;; - status:{status} where {status} is one of: closed, reopened
;; - assign_project assigns the current project to the issue/pr
;; - assign_reviewer adds the current user as a requested reviewer
;; - assign assigns the current user to the issue/pr
;; when used as a trigger, it triggers when issue/pr is no longer unassigned
;; - unassign unassign the current user from the issue/pr
;; when used as a trigger, it triggers when issue/pr becomes unassigned
;; - approve approves a pull request (default target=pr)
;; - unapprove unapproves a pull request (default target=pr)
;; - xref:{action} a cross-reference is made between issue and pull request
;; where {action} is one of: none, closes, reopens, neutered
;; When xref is used as a trigger, target=pr can target a
;; pull request which will close an issue in the current project,
;; even if that pull request is not currently assigned to the project.
;; (default target=issue)
Comment on lines +1189 to +1194
Copy link
Member

Choose a reason for hiding this comment

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

Hmm… This does not sound like a good idea.
It seems like we have different ideas what xrefs are.
An xref is for me an external reference, so i.e. I mention PR #1 in issue #2.
How does opens/closes/… fit into this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Side note: I thought the x was for "cross", as in cross-reference.

A PR can do a cross-reference which closes an issue when the PR is merged (XRefActionCloses).

I figured that triggering an automation whenever a PR is opened which closes an issue in the project could be a useful thing to have.

In the default configuration I added some rules for:

  1. labeling an issue with Status/Blocked whenever a PR is opened for that issue (as it is blocked by the PR requiring review)
  2. automatically adding the PR as a "review ticket" to the project whenever it closes an issue in the project

I guess we could rename this command to something else, and also not consider the other XRefActions which are internally supported if it simplifies things.

Or remove it completely and make the available commands a bit more slim as a start.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would renaming this to pr:closes or something like that (and only keeping closes as an action) be better? Because as I said I think that is the only use-case which might be interesting right now.

;; - label:{label} adds a label to the issue/pr, where {label} is a label accessible in the current project
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
;; - label:{label} adds a label to the issue/pr, where {label} is a label accessible in the current project
;; - label:{labelname} adds the label with {labelname} to the issue/pr, assuming {labelname} is a label accessible in the current project

;; - unlabel:{label} remove label from the issue/pr
;RULE1 = trigger=status:closed, action=move:Done
;RULE2 = trigger=status:reopened, action=move:To Do
;RULE3 = trigger=approve, action=move:Done, target=pr, context=column:In Progress
;RULE4 = trigger=xref:closes, action=assign_project, target=pr
;RULE5 = trigger=assign_project, action=move:To Do, target=pr
;RULE6 = trigger=move:To Do, action=unassign
;RULE7 = trigger=move:To Do, action=status:reopened
;RULE8 = trigger=move:To Do, action=clear_reviewers
;RULE9 = trigger=move:In Progress, action=assign
;RULE10 = trigger=move:In Progress, action=status:reopened
;RULE11 = trigger=move:In Progress, action=assign_reviewer
;RULE12 = trigger=move:Done, action=status:closed, target=issue
;RULE13 = trigger=move:Done, action=approve, target=pr
;RULE14 = trigger=assign, action=move:In Progress, target=issue, context=column:To Do
;RULE15 = trigger=assign_reviewer, action=move:In Progress, target=pr, context=column:To Do
;RULE16 = trigger=xref:closes, action=label:Status/Blocked, target=issue, context=column:In Progress
;RULE17 = trigger=approve, action=unlabel:Status/Blocked, target=issue, context=column:In Progress
;RULE18 =
;RULE19 =
;RULE20 =
;RULE21 =
;RULE22 =
;RULE23 =
;RULE24 =
;RULE25 =
Comment on lines +1214 to +1221
Copy link
Member

Choose a reason for hiding this comment

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

As I think I've already mentioned, do not create a static amount of rules.
Instead, iterate over all keys in the section.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You might have mentioned it, albeit encoded into a single ? character in a previous comment. Thus it might have flown over my head a bit and thank you for taking the time to write it out in words here.

I guess we could do that, but could not see that we did a similar thing elsewhere (iterating over dynamic keys in the config). Thus I decided to kick it off with the "stupidly simple" approach first.


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[cors]
Expand Down
5 changes: 5 additions & 0 deletions docs/content/administration/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,11 @@ Default templates for project boards:
- `PROJECT_BOARD_BASIC_KANBAN_TYPE`: **To Do, In Progress, Done**
- `PROJECT_BOARD_BUG_TRIAGE_TYPE`: **Needs Triage, High Priority, Low Priority, Closed**

## Project Automation (`project.automation`)

- `ENABLED`: **false**: Whether automation is enabled for projects.
- `MAX_RULE_PER_PROJECT`: **25**: Maximum number of automation rules per project.
Copy link
Member

Choose a reason for hiding this comment

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

Do we even need such a setting?
I think it is much easier if we implement it at first without any max amount of rules.

Copy link
Member

Choose a reason for hiding this comment

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

Especially as you then wouldn't need to hardcode the number of rules.
You should instead loop over all keys in this section instead.

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 intent of this setting was to limit the maximum amount of rules considered at runtime, but I understand that this causes confusion at this stage.

I agree that this can be removed for the time being, as it can probably be reintroduced at a later time when automation rules can be added via the project settings in the GUI.


## Issue and pull request attachments (`attachment`)

- `ENABLED`: **true**: Whether issue and pull request attachments are enabled.
Expand Down
11 changes: 11 additions & 0 deletions models/issues/issue_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ func (issue *Issue) ProjectBoardID(ctx context.Context) int64 {
return ip.ProjectBoardID
}

func (issue *Issue) IsOnProjectBoard(ctx context.Context, board *project_model.Board) bool {
var ip project_model.ProjectIssue
has, err := db.GetEngine(ctx).Table(project_model.ProjectIssue{}).
Where("issue_id=? AND project_board_id=?", issue.ID, board.ID).
Get(&ip)
if err != nil || !has {
return false
}
return true
Comment on lines +50 to +58
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
func (issue *Issue) IsOnProjectBoard(ctx context.Context, board *project_model.Board) bool {
var ip project_model.ProjectIssue
has, err := db.GetEngine(ctx).Table(project_model.ProjectIssue{}).
Where("issue_id=? AND project_board_id=?", issue.ID, board.ID).
Get(&ip)
if err != nil || !has {
return false
}
return true
func (issue *Issue) IsOnProjectBoard(ctx context.Context, board *project_model.Board) (bool, error) {
return db.GetEngine(ctx).Exist(&project_model.ProjectIssue{IssueID: issue.ID, ProjectBoardID: board.ID})

}

// LoadIssuesFromBoard load issues assigned to this board
func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList, error) {
issueList := make(IssueList, 0, 10)
Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,8 @@ var migrations = []Migration{
NewMigration("Rename user themes", v1_22.RenameUserThemes),
// v281 -> v282
NewMigration("Add auth_token table", v1_22.CreateAuthTokenTable),
// v282 -> v283
NewMigration("Add project_automation table", v1_22.AddProjectAutomationTable),
}

// GetCurrentDBVersion returns the current db version
Expand Down
36 changes: 36 additions & 0 deletions models/migrations/v1_22/v282.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_22 //nolint

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

"xorm.io/xorm"
)

func AddProjectAutomationTable(x *xorm.Engine) error {
type (
AutomationTriggerType uint8
AutomationActionType uint8
AutomationActionTargetType uint8
)

type ProjectAutomation struct {
ID int64 `xorm:"pk autoincr"`
Enabled bool `xorm:"INDEX NOT NULL DEFAULT true"`
ProjectID int64 `xorm:"INDEX NOT NULL"`
ProjectBoardID int64 `xorm:"INDEX NOT NULL"`
TriggerType AutomationTriggerType `xorm:"INDEX NOT NULL"`
TriggerData int64 `xorm:"INDEX NOT NULL DEFAULT 0"`
ActionType AutomationActionType `xorm:"NOT NULL DEFAULT 0"`
ActionData int64 `xorm:"NOT NULL DEFAULT 0"`
ActionTarget AutomationActionTargetType `xorm:"NOT NULL DEFAULT 0"`
Sorting int64 `xorm:"NOT NULL DEFAULT 0"`

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

return x.Sync(new(ProjectAutomation))
}
Loading