Skip to content

Commit 290ecde

Browse files
committed
add/remove label on kanban column change
1 parent f4ea8d9 commit 290ecde

File tree

12 files changed

+210
-59
lines changed

12 files changed

+210
-59
lines changed

models/project/board.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type Board struct {
5555
Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board
5656
Sorting int8 `xorm:"NOT NULL DEFAULT 0"`
5757
Color string `xorm:"VARCHAR(7)"`
58+
LabelID int64 `xorm:"DEFAULT 0"`
5859

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

models/project/issue.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ type ProjectIssue struct { //revive:disable-line:exported
2222

2323
// the sorting order on the board
2424
Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
25+
26+
// label that is added when a issue is moved to this column
27+
LabelID int64 `xorm:"DEFAULT 0"`
2528
}
2629

2730
func init() {

routers/web/org/projects.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"code.gitea.io/gitea/modules/web"
2525
shared_user "code.gitea.io/gitea/routers/web/shared/user"
2626
"code.gitea.io/gitea/services/forms"
27+
issue_service "code.gitea.io/gitea/services/issue"
2728
)
2829

2930
const (
@@ -545,6 +546,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
545546
ProjectID: project.ID,
546547
Title: form.Title,
547548
Color: form.Color,
549+
LabelID: form.LabelID,
548550
CreatorID: ctx.Doer.ID,
549551
}); err != nil {
550552
ctx.ServerError("NewProjectBoard", err)
@@ -742,10 +744,27 @@ func MoveIssues(ctx *context.Context) {
742744
}
743745
}
744746

747+
fromColumnLabelID, err := strconv.ParseInt(ctx.FormString("fromColumnLabelID"), 10, 64)
748+
if err != nil {
749+
ctx.ServerError("fromColumnLableId is required", errors.New("fromColumnLableId is required"))
750+
return
751+
}
752+
753+
issueID, err := strconv.ParseInt(ctx.FormString("issueID"), 10, 64)
754+
if err != nil {
755+
ctx.ServerError("moved issueID is required", errors.New("moved issueID is required"))
756+
return
757+
}
758+
745759
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil {
746760
ctx.ServerError("MoveIssuesOnProjectBoard", err)
747761
return
748762
}
749763

764+
if err = issue_service.AddAndOrRemoveLabelFromIssue(ctx, issueID, fromColumnLabelID, board); err != nil {
765+
ctx.ServerError("failed adding/removing label in addAndOrRemoveLabel", err)
766+
return
767+
}
768+
750769
ctx.JSONOK()
751770
}

routers/web/repo/issue_label.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ func RetrieveLabels(ctx *context.Context) {
9999
ctx.Data["SortType"] = ctx.FormString("sort")
100100
}
101101

102+
// RetrieveLabelsOrg clone of RetrieveLabels but without repo
103+
func RetrieveLabelsOrg(ctx *context.Context) {
104+
orgLabels, err := issues_model.GetLabelsByOrgID(ctx, ctx.Org.Organization.ID, ctx.FormString("sort"), db.ListOptions{})
105+
if err != nil {
106+
ctx.ServerError("GetLabelsByOrgID", err)
107+
return
108+
}
109+
110+
ctx.Data["Labels"] = orgLabels
111+
}
112+
102113
// NewLabel create new label for repository
103114
func NewLabel(ctx *context.Context) {
104115
form := web.GetForm(ctx).(*forms.CreateLabelForm)

routers/web/repo/projects.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"net/http"
1010
"net/url"
11+
"strconv"
1112
"strings"
1213

1314
"code.gitea.io/gitea/models/db"
@@ -25,6 +26,7 @@ import (
2526
"code.gitea.io/gitea/modules/util"
2627
"code.gitea.io/gitea/modules/web"
2728
"code.gitea.io/gitea/services/forms"
29+
issue_service "code.gitea.io/gitea/services/issue"
2830
)
2931

3032
const (
@@ -483,6 +485,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
483485
Title: form.Title,
484486
Color: form.Color,
485487
CreatorID: ctx.Doer.ID,
488+
LabelID: form.LabelID,
486489
}); err != nil {
487490
ctx.ServerError("NewProjectBoard", err)
488491
return
@@ -687,10 +690,27 @@ func MoveIssues(ctx *context.Context) {
687690
}
688691
}
689692

693+
fromColumnLabelID, err := strconv.ParseInt(ctx.FormString("fromColumnLabelID"), 10, 64)
694+
if err != nil {
695+
ctx.ServerError("fromColumnLableId is required", errors.New("fromColumnLableId is required"))
696+
return
697+
}
698+
699+
issueID, err := strconv.ParseInt(ctx.FormString("issueID"), 10, 64)
700+
if err != nil {
701+
ctx.ServerError("moved issueID is required", errors.New("moved issueID is required"))
702+
return
703+
}
704+
690705
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil {
691706
ctx.ServerError("MoveIssuesOnProjectBoard", err)
692707
return
693708
}
694709

710+
if err = issue_service.AddAndOrRemoveLabelFromIssue(ctx, issueID, fromColumnLabelID, board); err != nil {
711+
ctx.ServerError("failed adding/removing label in addAndOrRemoveLabel", err)
712+
return
713+
}
714+
695715
ctx.JSONOK()
696716
}

routers/web/web.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@ func registerRoutes(m *web.Route) {
981981
m.Group("/projects", func() {
982982
m.Group("", func() {
983983
m.Get("", org.Projects)
984-
m.Get("/{id}", org.ViewProject)
984+
m.Get("/{id}", repo.RetrieveLabelsOrg, org.ViewProject)
985985
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true))
986986
m.Group("", func() { //nolint:dupl
987987
m.Get("/new", org.RenderNewProject)
@@ -1320,7 +1320,7 @@ func registerRoutes(m *web.Route) {
13201320

13211321
m.Group("/projects", func() {
13221322
m.Get("", repo.Projects)
1323-
m.Get("/{id}", repo.ViewProject)
1323+
m.Get("/{id}", repo.RetrieveLabels, repo.ViewProject)
13241324
m.Group("", func() { //nolint:dupl
13251325
m.Get("/new", repo.RenderNewProject)
13261326
m.Post("/new", web.Bind(forms.CreateProjectForm{}), repo.NewProjectPost)

services/forms/repo_form.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ type EditProjectBoardForm struct {
538538
Title string `binding:"Required;MaxSize(100)"`
539539
Sorting int8
540540
Color string `binding:"MaxSize(7)"`
541+
LabelID int64
541542
}
542543

543544
// _____ .__.__ __

services/issue/project.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package issue
5+
6+
import (
7+
"errors"
8+
9+
issues_model "code.gitea.io/gitea/models/issues"
10+
project_model "code.gitea.io/gitea/models/project"
11+
"code.gitea.io/gitea/modules/context"
12+
)
13+
14+
// AddAndOrRemoveLabelFromIssue updates issue label according to board LabelID
15+
func AddAndOrRemoveLabelFromIssue(ctx *context.Context, currentIssueId int64, fromColumnLabelID int64, board *project_model.Board) error {
16+
issue, err := issues_model.GetIssueByID(ctx, currentIssueId)
17+
if err != nil {
18+
return errors.New("failed getting issue")
19+
}
20+
var addedLabel *issues_model.Label
21+
if board.LabelID != 0 {
22+
addedLabel, err = issues_model.GetLabelByID(ctx, board.LabelID)
23+
if err != nil {
24+
return errors.New("failed getting add label")
25+
}
26+
}
27+
var removedLabel *issues_model.Label
28+
if fromColumnLabelID != 0 {
29+
30+
removedLabel, err = issues_model.GetLabelByID(ctx, fromColumnLabelID)
31+
if err != nil {
32+
return errors.New("failed getting remove label")
33+
}
34+
}
35+
36+
// Delete old label from current issue
37+
if fromColumnLabelID != 0 {
38+
if err := RemoveLabel(ctx, issue, ctx.Doer, removedLabel); err != nil {
39+
return err
40+
}
41+
}
42+
43+
// Add New Label to current issue
44+
if board.LabelID != 0 {
45+
if err := AddLabel(ctx, issue, ctx.Doer, addedLabel); err != nil {
46+
return err
47+
}
48+
}
49+
50+
return nil
51+
}

templates/projects/view.tmpl

Lines changed: 78 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,91 @@
11
{{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}}
22

3-
<div class="ui container">
4-
<div class="gt-df gt-sb gt-ac gt-mb-4">
5-
<h2 class="gt-mb-0">{{.Project.Title}}</h2>
6-
{{if $canWriteProject}}
7-
<div class="ui compact mini menu">
8-
<a class="item" href="{{.Link}}/edit?redirect=project">
9-
{{svg "octicon-pencil"}}
10-
{{ctx.Locale.Tr "repo.issues.label_edit"}}
11-
</a>
12-
{{if .Project.IsClosed}}
13-
<button class="item btn link-action" data-url="{{.Link}}/open">
14-
{{svg "octicon-check"}}
15-
{{ctx.Locale.Tr "repo.projects.open"}}
16-
</button>
17-
{{else}}
18-
<button class="item btn link-action" data-url="{{.Link}}/close">
19-
{{svg "octicon-skip"}}
20-
{{ctx.Locale.Tr "repo.projects.close"}}
21-
</button>
22-
{{end}}
23-
<button class="item btn delete-button" data-url="{{.Link}}/delete" data-id="{{.Project.ID}}">
24-
{{svg "octicon-trash"}}
25-
{{ctx.Locale.Tr "repo.issues.label_delete"}}
3+
<div class="gt-df gt-sb gt-ac gt-mb-4">
4+
<h2 class="gt-mb-0">{{.Project.Title}}</h2>
5+
{{if $canWriteProject}}
6+
<div class="ui compact mini menu">
7+
<a class="item" href="{{.Link}}/edit?redirect=project">
8+
{{svg "octicon-pencil"}}
9+
{{ctx.Locale.Tr "repo.issues.label_edit"}}
10+
</a>
11+
{{if .Project.IsClosed}}
12+
<button class="item btn link-action" data-url="{{.Link}}/open">
13+
{{svg "octicon-check"}}
14+
{{ctx.Locale.Tr "repo.projects.open"}}
2615
</button>
27-
<button class="item btn show-modal" data-modal="#new-project-column-item">
28-
{{svg "octicon-plus"}}
29-
{{ctx.Locale.Tr "new_project_column"}}
16+
{{else}}
17+
<button class="item btn link-action" data-url="{{.Link}}/close">
18+
{{svg "octicon-skip"}}
19+
{{ctx.Locale.Tr "repo.projects.close"}}
3020
</button>
21+
{{end}}
22+
<button class="item btn delete-button" data-url="{{.Link}}/delete" data-id="{{.Project.ID}}">
23+
{{svg "octicon-trash"}}
24+
{{ctx.Locale.Tr "repo.issues.label_delete"}}
25+
</button>
26+
<button class="item btn show-modal" data-modal="#new-project-column-item">
27+
{{svg "octicon-plus"}}
28+
{{ctx.Locale.Tr "new_project_column"}}
29+
</button>
30+
</div>
31+
<div class="ui small modal new-project-column-modal" id="new-project-column-item">
32+
<div class="header">
33+
{{ctx.Locale.Tr "repo.projects.column.new"}}
3134
</div>
32-
<div class="ui small modal new-project-column-modal" id="new-project-column-item">
33-
<div class="header">
34-
{{ctx.Locale.Tr "repo.projects.column.new"}}
35-
</div>
36-
<div class="content">
37-
<form class="ui form">
38-
<div class="required field">
39-
<label for="new_project_column">{{ctx.Locale.Tr "repo.projects.column.new_title"}}</label>
40-
<input class="new-project-column" id="new_project_column" name="title" required>
41-
</div>
35+
<div class="content">
36+
<form class="ui form">
37+
<div class="required field">
38+
<label for="new_project_column">{{ctx.Locale.Tr "repo.projects.column.new_title"}}</label>
39+
<input class="new-project-column" id="new_project_column" name="title" required>
40+
</div>
4241

43-
<div class="field color-field">
44-
<label for="new_project_column_color">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
45-
<div class="color picker column">
46-
<input class="color-picker" maxlength="7" placeholder="#c320f6" id="new_project_column_color_picker" name="color">
47-
{{template "repo/issue/label_precolors"}}
48-
</div>
42+
<div class="field color-field">
43+
<label for="new_project_column_color">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
44+
<div class="color picker column">
45+
<input class="color-picker" maxlength="7" placeholder="#c320f6" id="new_project_column_color_picker" name="color">
46+
{{template "repo/issue/label_precolors"}}
4947
</div>
48+
</div>
5049

51-
<div class="text right actions">
52-
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
53-
<button data-url="{{$.Link}}" class="ui primary button" id="new_project_column_submit">{{ctx.Locale.Tr "repo.projects.column.new_submit"}}</button>
50+
{{if .Labels }}
51+
<!-- Label -->
52+
<div class="ui dropdown jump item label-filter" >
53+
<span class="text">
54+
Choose label
55+
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
56+
</span>
57+
<div class="menu" style="max-height:300px;overflow-y: scroll;">
58+
<div class="ui icon search input">
59+
<i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i>
60+
<input type="text" id="new_project_column_label" name="label" placeholder="{{.locale.Tr "repo.issues.filter_label"}}">
61+
</div>
62+
{{$previousExclusiveScope := "_no_scope"}}
63+
{{range .Labels}}
64+
{{$exclusiveScope := .ExclusiveScope}}
65+
{{if and (ne $previousExclusiveScope $exclusiveScope)}}
66+
<div class="divider"></div>
67+
{{end}}
68+
{{$previousExclusiveScope = $exclusiveScope}}
69+
<div class="item label-filter-item labels-for-project-creation" data-label-id="{{.ID}}" >{{RenderLabel $.Context .}}</div>
70+
{{end}}
71+
</div>
5472
</div>
55-
</form>
56-
</div>
73+
<input name="label" id="new_project_column_project_label" class="gt-mb-4">
74+
<input name="labelId" id="new_project_column_project_label_id" type="number" style="display: none;">
75+
{{end}}
76+
<div class="text right actions">
77+
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
78+
<button data-url="{{$.Link}}" class="ui primary button" id="new_project_column_submit">{{ctx.Locale.Tr "repo.projects.column.new_submit"}}</button>
79+
</div>
80+
</form>
5781
</div>
58-
{{end}}
59-
</div>
82+
</div>
83+
{{end}}
84+
</div>
6085

61-
<div class="content">{{$.Project.RenderedContent|Str2html}}</div>
86+
<div class="content">{{$.Project.RenderedContent|Str2html}}</div>
6287

63-
<div class="divider"></div>
64-
</div>
88+
<div class="divider"></div>
6589

6690
<div id="project-board">
6791
<div class="board {{if .CanWriteProjects}}sortable{{end}}">
@@ -165,9 +189,10 @@
165189

166190
<div class="divider"></div>
167191

192+
{{- $currentColumnLabelID := .LabelID }}
168193
<div class="ui cards {{if and $canWriteProject (ne .ID 0)}}{{/* ID 0 is default column which cannot be moved */}}gt-cursor-grab{{end}}" data-url="{{$.Link}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}">
169194
{{range (index $.IssuesMap .ID)}}
170-
<div class="issue-card gt-word-break {{if $canWriteProject}}gt-cursor-grab{{end}}" data-issue="{{.ID}}">
195+
<div class="issue-card gt-word-break {{if $canWriteProject}}gt-cursor-grab{{end}}" data-issue="{{.ID}}" data-column-label-id="{{$currentColumnLabelID}}">
171196
{{template "repo/issue/card" (dict "Issue" . "Page" $)}}
172197
</div>
173198
{{end}}

web_src/js/features/project-label.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export function initNewProjectFormLabel() {
2+
if (!document.querySelectorAll('#new-project-column-item form input#new_project_column_project_label').length) return;
3+
4+
const labels = document.querySelectorAll('.labels-for-project-creation');
5+
const selectedLabel = document.querySelector('input#new_project_column_project_label');
6+
const selectedLabelId = document.querySelector('input#new_project_column_project_label_id');
7+
8+
if (!labels || !selectedLabel || !selectedLabelId) return;
9+
10+
for (const l of labels) {
11+
l.addEventListener('click', () => {
12+
selectedLabel.value = l.textContent;
13+
selectedLabelId.value = l.dataset.labelId;
14+
});
15+
}
16+
}

0 commit comments

Comments
 (0)