diff options
Diffstat (limited to 'routers')
-rw-r--r-- | routers/api/v1/api.go | 7 | ||||
-rw-r--r-- | routers/api/v1/org/label.go | 237 | ||||
-rw-r--r-- | routers/api/v1/repo/issue.go | 7 | ||||
-rw-r--r-- | routers/api/v1/repo/issue_label.go | 8 | ||||
-rw-r--r-- | routers/api/v1/repo/label.go | 4 | ||||
-rw-r--r-- | routers/org/org_labels.go | 106 | ||||
-rw-r--r-- | routers/org/setting.go | 12 | ||||
-rw-r--r-- | routers/repo/compare.go | 1 | ||||
-rw-r--r-- | routers/repo/issue.go | 36 | ||||
-rw-r--r-- | routers/repo/issue_label.go | 41 | ||||
-rw-r--r-- | routers/routes/routes.go | 8 |
11 files changed, 452 insertions, 15 deletions
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index eee9440574..e5bb98033b 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -861,6 +861,13 @@ func RegisterRoutes(m *macaron.Macaron) { Post(reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam) m.Get("/search", org.SearchTeam) }, reqOrgMembership()) + m.Group("/labels", func() { + m.Get("", org.ListLabels) + m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel) + m.Combo("/:id").Get(org.GetLabel). + Patch(reqToken(), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel). + Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel) + }) m.Group("/hooks", func() { m.Combo("").Get(org.ListHooks). Post(bind(api.CreateHookOption{}), org.CreateHook) diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go new file mode 100644 index 0000000000..c5fb262a30 --- /dev/null +++ b/routers/api/v1/org/label.go @@ -0,0 +1,237 @@ +// 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 org + +import ( + "fmt" + "net/http" + "strconv" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/routers/api/v1/utils" +) + +// ListLabels list all the labels of an organization +func ListLabels(ctx *context.APIContext) { + // swagger:operation GET /orgs/{org}/labels organization orgListLabels + // --- + // summary: List an organization's labels + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results, maximum page size is 50 + // type: integer + // responses: + // "200": + // "$ref": "#/responses/LabelList" + + labels, err := models.GetLabelsByOrgID(ctx.Org.Organization.ID, ctx.Query("sort"), utils.GetListOptions(ctx)) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetLabelsByOrgID", err) + return + } + + ctx.JSON(http.StatusOK, convert.ToLabelList(labels)) +} + +// CreateLabel create a label for a repository +func CreateLabel(ctx *context.APIContext, form api.CreateLabelOption) { + // swagger:operation POST /orgs/{org}/labels organization orgCreateLabel + // --- + // summary: Create a label for an organization + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/CreateLabelOption" + // responses: + // "201": + // "$ref": "#/responses/Label" + // "422": + // "$ref": "#/responses/validationError" + + form.Color = strings.Trim(form.Color, " ") + if len(form.Color) == 6 { + form.Color = "#" + form.Color + } + if !models.LabelColorPattern.MatchString(form.Color) { + ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", form.Color)) + return + } + + label := &models.Label{ + Name: form.Name, + Color: form.Color, + OrgID: ctx.Org.Organization.ID, + Description: form.Description, + } + if err := models.NewLabel(label); err != nil { + ctx.Error(http.StatusInternalServerError, "NewLabel", err) + return + } + ctx.JSON(http.StatusCreated, convert.ToLabel(label)) +} + +// GetLabel get label by organization and label id +func GetLabel(ctx *context.APIContext) { + // swagger:operation GET /orgs/{org}/labels/{id} organization orgGetLabel + // --- + // summary: Get a single label + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: id + // in: path + // description: id of the label to get + // type: integer + // format: int64 + // required: true + // responses: + // "200": + // "$ref": "#/responses/Label" + + var ( + label *models.Label + err error + ) + strID := ctx.Params(":id") + if intID, err2 := strconv.ParseInt(strID, 10, 64); err2 != nil { + label, err = models.GetLabelInOrgByName(ctx.Org.Organization.ID, strID) + } else { + label, err = models.GetLabelInOrgByID(ctx.Org.Organization.ID, intID) + } + if err != nil { + if models.IsErrOrgLabelNotExist(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "GetLabelByOrgID", err) + } + return + } + + ctx.JSON(http.StatusOK, convert.ToLabel(label)) +} + +// EditLabel modify a label for an Organization +func EditLabel(ctx *context.APIContext, form api.EditLabelOption) { + // swagger:operation PATCH /orgs/{org}/labels/{id} organization orgEditLabel + // --- + // summary: Update a label + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: id + // in: path + // description: id of the label to edit + // type: integer + // format: int64 + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/EditLabelOption" + // responses: + // "200": + // "$ref": "#/responses/Label" + // "422": + // "$ref": "#/responses/validationError" + + label, err := models.GetLabelInOrgByID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id")) + if err != nil { + if models.IsErrOrgLabelNotExist(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "GetLabelByRepoID", err) + } + return + } + + if form.Name != nil { + label.Name = *form.Name + } + if form.Color != nil { + label.Color = strings.Trim(*form.Color, " ") + if len(label.Color) == 6 { + label.Color = "#" + label.Color + } + if !models.LabelColorPattern.MatchString(label.Color) { + ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", label.Color)) + return + } + } + if form.Description != nil { + label.Description = *form.Description + } + if err := models.UpdateLabel(label); err != nil { + ctx.ServerError("UpdateLabel", err) + return + } + ctx.JSON(http.StatusOK, convert.ToLabel(label)) +} + +// DeleteLabel delete a label for an organization +func DeleteLabel(ctx *context.APIContext) { + // swagger:operation DELETE /orgs/{org}/labels/{id} organization orgDeleteLabel + // --- + // summary: Delete a label + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: id + // in: path + // description: id of the label to delete + // type: integer + // format: int64 + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + + if err := models.DeleteLabel(ctx.Org.Organization.ID, ctx.ParamsInt64(":id")); err != nil { + ctx.Error(http.StatusInternalServerError, "DeleteLabel", err) + return + } + + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 25664e45a9..217c97c69b 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -81,8 +81,10 @@ func SearchIssues(ctx *context.APIContext) { AllPublic: true, TopicOnly: false, Collaborate: util.OptionalBoolNone, - OrderBy: models.SearchOrderByRecentUpdated, - Actor: ctx.User, + // This needs to be a column that is not nil in fixtures or + // MySQL will return different results when sorting by null in some cases + OrderBy: models.SearchOrderByAlphabetically, + Actor: ctx.User, } if ctx.IsSigned { opts.Private = true @@ -152,6 +154,7 @@ func SearchIssues(ctx *context.APIContext) { Page: ctx.QueryInt("page"), PageSize: setting.UI.IssuePagingNum, }, + RepoIDs: repoIDs, IsClosed: isClosed, IssueIDs: issueIDs, diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go index 8089891265..8b2a1988fa 100644 --- a/routers/api/v1/repo/issue_label.go +++ b/routers/api/v1/repo/issue_label.go @@ -171,12 +171,12 @@ func DeleteIssueLabel(ctx *context.APIContext) { return } - label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) + label, err := models.GetLabelByID(ctx.ParamsInt64(":id")) if err != nil { if models.IsErrLabelNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "", err) } else { - ctx.Error(http.StatusInternalServerError, "GetLabelInRepoByID", err) + ctx.Error(http.StatusInternalServerError, "GetLabelByID", err) } return } @@ -308,9 +308,9 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) return } - labels, err = models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) + labels, err = models.GetLabelsByIDs(form.Labels) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetLabelsInRepoByIDs", err) + ctx.Error(http.StatusInternalServerError, "GetLabelsByIDs", err) return } diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go index 95dbbc9551..5f70e74407 100644 --- a/routers/api/v1/repo/label.go +++ b/routers/api/v1/repo/label.go @@ -96,7 +96,7 @@ func GetLabel(ctx *context.APIContext) { label, err = models.GetLabelInRepoByID(ctx.Repo.Repository.ID, intID) } if err != nil { - if models.IsErrLabelNotExist(err) { + if models.IsErrRepoLabelNotExist(err) { ctx.NotFound() } else { ctx.Error(http.StatusInternalServerError, "GetLabelByRepoID", err) @@ -197,7 +197,7 @@ func EditLabel(ctx *context.APIContext, form api.EditLabelOption) { label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) if err != nil { - if models.IsErrLabelNotExist(err) { + if models.IsErrRepoLabelNotExist(err) { ctx.NotFound() } else { ctx.Error(http.StatusInternalServerError, "GetLabelByRepoID", err) diff --git a/routers/org/org_labels.go b/routers/org/org_labels.go new file mode 100644 index 0000000000..e5b9d9ddee --- /dev/null +++ b/routers/org/org_labels.go @@ -0,0 +1,106 @@ +// 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 org + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" + "code.gitea.io/gitea/modules/context" +) + +// RetrieveLabels find all the labels of an organization +func RetrieveLabels(ctx *context.Context) { + labels, err := models.GetLabelsByOrgID(ctx.Org.Organization.ID, ctx.Query("sort"), models.ListOptions{}) + if err != nil { + ctx.ServerError("RetrieveLabels.GetLabels", err) + return + } + for _, l := range labels { + l.CalOpenIssues() + } + ctx.Data["Labels"] = labels + ctx.Data["NumLabels"] = len(labels) + ctx.Data["SortType"] = ctx.Query("sort") +} + +// NewLabel create new label for organization +func NewLabel(ctx *context.Context, form auth.CreateLabelForm) { + ctx.Data["Title"] = ctx.Tr("repo.labels") + ctx.Data["PageIsLabels"] = true + + if ctx.HasError() { + ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) + ctx.Redirect(ctx.Org.OrgLink + "/settings/labels") + return + } + + l := &models.Label{ + OrgID: ctx.Org.Organization.ID, + Name: form.Title, + Description: form.Description, + Color: form.Color, + } + if err := models.NewLabel(l); err != nil { + ctx.ServerError("NewLabel", err) + return + } + ctx.Redirect(ctx.Org.OrgLink + "/settings/labels") +} + +// UpdateLabel update a label's name and color +func UpdateLabel(ctx *context.Context, form auth.CreateLabelForm) { + l, err := models.GetLabelInOrgByID(ctx.Org.Organization.ID, form.ID) + if err != nil { + switch { + case models.IsErrOrgLabelNotExist(err): + ctx.Error(404) + default: + ctx.ServerError("UpdateLabel", err) + } + return + } + + l.Name = form.Title + l.Description = form.Description + l.Color = form.Color + if err := models.UpdateLabel(l); err != nil { + ctx.ServerError("UpdateLabel", err) + return + } + ctx.Redirect(ctx.Org.OrgLink + "/settings/labels") +} + +// DeleteLabel delete a label +func DeleteLabel(ctx *context.Context) { + if err := models.DeleteLabel(ctx.Org.Organization.ID, ctx.QueryInt64("id")); err != nil { + ctx.Flash.Error("DeleteLabel: " + err.Error()) + } else { + ctx.Flash.Success(ctx.Tr("repo.issues.label_deletion_success")) + } + + ctx.JSON(200, map[string]interface{}{ + "redirect": ctx.Org.OrgLink + "/settings/labels", + }) +} + +// InitializeLabels init labels for an organization +func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) { + if ctx.HasError() { + ctx.Redirect(ctx.Repo.RepoLink + "/labels") + return + } + + if err := models.InitializeLabels(models.DefaultDBContext(), ctx.Org.Organization.ID, form.TemplateName, true); err != nil { + if models.IsErrIssueLabelTemplateLoad(err) { + originalErr := err.(models.ErrIssueLabelTemplateLoad).OriginalError + ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) + ctx.Redirect(ctx.Org.OrgLink + "/settings/labels") + return + } + ctx.ServerError("InitializeLabels", err) + return + } + ctx.Redirect(ctx.Org.OrgLink + "/settings/labels") +} diff --git a/routers/org/setting.go b/routers/org/setting.go index 3b6e124587..348d8cc8d8 100644 --- a/routers/org/setting.go +++ b/routers/org/setting.go @@ -24,6 +24,8 @@ const ( tplSettingsDelete base.TplName = "org/settings/delete" // tplSettingsHooks template path for render hook settings tplSettingsHooks base.TplName = "org/settings/hooks" + // tplSettingsLabels template path for render labels settings + tplSettingsLabels base.TplName = "org/settings/labels" ) // Settings render the main settings page @@ -177,3 +179,13 @@ func DeleteWebhook(ctx *context.Context) { "redirect": ctx.Org.OrgLink + "/settings/hooks", }) } + +// Labels render organization labels page +func Labels(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.labels") + ctx.Data["PageIsOrgSettingsLabels"] = true + ctx.Data["RequireMinicolors"] = true + ctx.Data["RequireTribute"] = true + ctx.Data["LabelTemplates"] = models.LabelTemplates + ctx.HTML(200, tplSettingsLabels) +} diff --git a/routers/repo/compare.go b/routers/repo/compare.go index 815ec35650..87b66dc7fb 100644 --- a/routers/repo/compare.go +++ b/routers/repo/compare.go @@ -335,7 +335,6 @@ func PrepareCompareDiff( } else { title = headBranch } - ctx.Data["title"] = title ctx.Data["Username"] = headUser.Name ctx.Data["Reponame"] = headRepo.Name diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 9cc6ea1dfa..6dbf9cf5c8 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -259,6 +259,18 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB ctx.ServerError("GetLabelsByRepoID", err) return } + + if repo.Owner.IsOrganization() { + orgLabels, err := models.GetLabelsByOrgID(repo.Owner.ID, ctx.Query("sort"), models.ListOptions{}) + if err != nil { + ctx.ServerError("GetLabelsByOrgID", err) + return + } + + ctx.Data["OrgLabels"] = orgLabels + labels = append(labels, orgLabels...) + } + for _, l := range labels { l.LoadSelectedLabelsAfterClick(labelIDs) } @@ -377,6 +389,15 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository, isPull boo return nil } ctx.Data["Labels"] = labels + if repo.Owner.IsOrganization() { + orgLabels, err := models.GetLabelsByOrgID(repo.Owner.ID, ctx.Query("sort"), models.ListOptions{}) + if err != nil { + return nil + } + + ctx.Data["OrgLabels"] = orgLabels + labels = append(labels, orgLabels...) + } RetrieveRepoMilestonesAndAssignees(ctx, repo) if ctx.Written() { @@ -593,6 +614,7 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { Content: form.Content, Ref: form.Ref, } + if err := issue_service.NewIssue(repo, issue, labelIDs, attachments, assigneeIDs); err != nil { if models.IsErrUserDoesNotHaveAccessToRepo(err) { ctx.Error(400, "UserDoesNotHaveAccessToRepo", err.Error()) @@ -761,6 +783,19 @@ func ViewIssue(ctx *context.Context) { ctx.ServerError("GetLabelsByRepoID", err) return } + ctx.Data["Labels"] = labels + + if repo.Owner.IsOrganization() { + orgLabels, err := models.GetLabelsByOrgID(repo.Owner.ID, ctx.Query("sort"), models.ListOptions{}) + if err != nil { + ctx.ServerError("GetLabelsByOrgID", err) + return + } + ctx.Data["OrgLabels"] = orgLabels + + labels = append(labels, orgLabels...) + } + hasSelected := false for i := range labels { if labelIDMark[labels[i].ID] { @@ -769,7 +804,6 @@ func ViewIssue(ctx *context.Context) { } } ctx.Data["HasSelectedLabel"] = hasSelected - ctx.Data["Labels"] = labels // Check milestone and assignee. if ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { diff --git a/routers/repo/issue_label.go b/routers/repo/issue_label.go index 8ac9b8d336..16638404e3 100644 --- a/routers/repo/issue_label.go +++ b/routers/repo/issue_label.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" issue_service "code.gitea.io/gitea/services/issue" ) @@ -35,7 +36,7 @@ func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) { return } - if err := models.InitializeLabels(models.DefaultDBContext(), ctx.Repo.Repository.ID, form.TemplateName); err != nil { + if err := models.InitializeLabels(models.DefaultDBContext(), ctx.Repo.Repository.ID, form.TemplateName, false); err != nil { if models.IsErrIssueLabelTemplateLoad(err) { originalErr := err.(models.ErrIssueLabelTemplateLoad).OriginalError ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) @@ -48,17 +49,47 @@ func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) { ctx.Redirect(ctx.Repo.RepoLink + "/labels") } -// RetrieveLabels find all the labels of a repository +// RetrieveLabels find all the labels of a repository and organization func RetrieveLabels(ctx *context.Context) { labels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, ctx.Query("sort"), models.ListOptions{}) if err != nil { ctx.ServerError("RetrieveLabels.GetLabels", err) return } + for _, l := range labels { l.CalOpenIssues() } + ctx.Data["Labels"] = labels + + if ctx.Repo.Owner.IsOrganization() { + orgLabels, err := models.GetLabelsByOrgID(ctx.Repo.Owner.ID, ctx.Query("sort"), models.ListOptions{}) + if err != nil { + ctx.ServerError("GetLabelsByOrgID", err) + return + } + for _, l := range orgLabels { + l.CalOpenOrgIssues(ctx.Repo.Repository.ID, l.ID) + } + ctx.Data["OrgLabels"] = orgLabels + + org, err := models.GetOrgByName(ctx.Repo.Owner.LowerName) + if err != nil { + ctx.ServerError("GetOrgByName", err) + return + } + if ctx.User != nil { + ctx.Org.IsOwner, err = org.IsOwnedBy(ctx.User.ID) + if err != nil { + ctx.ServerError("org.IsOwnedBy", err) + return + } + ctx.Org.OrgLink = setting.AppSubURL + "/org/" + org.LowerName + ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner + ctx.Data["OrganizationLink"] = ctx.Org.OrgLink + } + } ctx.Data["NumLabels"] = len(labels) ctx.Data["SortType"] = ctx.Query("sort") } @@ -89,10 +120,10 @@ func NewLabel(ctx *context.Context, form auth.CreateLabelForm) { // UpdateLabel update a label's name and color func UpdateLabel(ctx *context.Context, form auth.CreateLabelForm) { - l, err := models.GetLabelByID(form.ID) + l, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, form.ID) if err != nil { switch { - case models.IsErrLabelNotExist(err): + case models.IsErrRepoLabelNotExist(err): ctx.Error(404) default: ctx.ServerError("UpdateLabel", err) @@ -141,7 +172,7 @@ func UpdateIssueLabel(ctx *context.Context) { case "attach", "detach", "toggle": label, err := models.GetLabelByID(ctx.QueryInt64("id")) if err != nil { - if models.IsErrLabelNotExist(err) { + if models.IsErrRepoLabelNotExist(err) { ctx.Error(404, "GetLabelByID") } else { ctx.ServerError("GetLabelByID", err) diff --git a/routers/routes/routes.go b/routers/routes/routes.go index e92f8a60b6..4409830dfe 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -594,6 +594,14 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/feishu/:id", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksEditPost) }) + m.Group("/labels", func() { + m.Get("", org.RetrieveLabels, org.Labels) + m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), org.NewLabel) + m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), org.UpdateLabel) + m.Post("/delete", org.DeleteLabel) + m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), org.InitializeLabels) + }) + m.Route("/delete", "GET,POST", org.SettingsDelete) }) }, context.OrgAssignment(true, true)) |