df9305758f
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Generic: Project CI / Lint & Validate (pull_request) Successful in 46s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m16s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
4 built-in presets: default, software-development, support-tickets, bug-tracking. API endpoints to list presets, apply to org, and copy statuses between orgs. Web UI dropdown on org settings page. Claude-Session: https://claude.ai/code/session_011AAFzotGMf3ayvXhEmStCd
144 lines
4.5 KiB
Go
144 lines
4.5 KiB
Go
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package org
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
|
)
|
|
|
|
const tplOrgIssueStatuses templates.TplName = "org/settings/issue_statuses"
|
|
|
|
// SettingsIssueStatuses shows the org-level issue statuses management page.
|
|
func SettingsIssueStatuses(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("org.settings.issue_statuses")
|
|
ctx.Data["PageIsOrgSettings"] = true
|
|
ctx.Data["PageIsSettingsIssueStatuses"] = true
|
|
|
|
defs, err := issues_model.GetAllIssueStatusDefsByOrg(ctx, ctx.Org.Organization.ID)
|
|
if err != nil {
|
|
ctx.ServerError("GetAllIssueStatusDefsByOrg", err)
|
|
return
|
|
}
|
|
ctx.Data["IssueStatuses"] = defs
|
|
|
|
// Load preset names for the preset selector
|
|
presetNames := issues_model.StatusPresetNames()
|
|
type presetInfo struct {
|
|
Name string
|
|
Description string
|
|
}
|
|
presets := make([]presetInfo, 0, len(presetNames))
|
|
for _, name := range presetNames {
|
|
p := issues_model.StatusPresets[name]
|
|
presets = append(presets, presetInfo{Name: p.Name, Description: p.Description})
|
|
}
|
|
ctx.Data["StatusPresets"] = presets
|
|
|
|
ctx.HTML(http.StatusOK, tplOrgIssueStatuses)
|
|
}
|
|
|
|
// SettingsIssueStatusesApplyPresetPost applies a status preset to the org.
|
|
func SettingsIssueStatusesApplyPresetPost(ctx *context.Context) {
|
|
presetName := ctx.FormString("preset")
|
|
if err := issues_model.ApplyStatusPreset(ctx, ctx.Org.Organization.ID, presetName); err != nil {
|
|
ctx.Flash.Error("Unknown preset: " + presetName)
|
|
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("org.settings.issue_status_preset_applied"))
|
|
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
|
|
}
|
|
|
|
// SettingsIssueStatusesCreatePost creates a new org-level issue status.
|
|
func SettingsIssueStatusesCreatePost(ctx *context.Context) {
|
|
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
|
|
|
|
def := &issues_model.IssueStatusDef{
|
|
OrgID: ctx.Org.Organization.ID,
|
|
Name: ctx.FormString("name"),
|
|
Color: ctx.FormString("color"),
|
|
Description: ctx.FormString("description"),
|
|
ClosesIssue: ctx.FormString("closes_issue") == "on",
|
|
SortOrder: sortOrder,
|
|
IsActive: true,
|
|
}
|
|
|
|
if def.Name == "" {
|
|
ctx.Flash.Error("Status name is required")
|
|
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
|
|
return
|
|
}
|
|
|
|
if err := issues_model.CreateIssueStatusDef(ctx, def); err != nil {
|
|
ctx.ServerError("CreateIssueStatusDef", err)
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("org.settings.issue_status_created"))
|
|
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
|
|
}
|
|
|
|
// SettingsIssueStatusesEditPost updates an org-level issue status.
|
|
func SettingsIssueStatusesEditPost(ctx *context.Context) {
|
|
id := ctx.PathParamInt64("id")
|
|
def, err := issues_model.GetIssueStatusDefByID(ctx, id)
|
|
if err != nil {
|
|
ctx.ServerError("GetIssueStatusDefByID", err)
|
|
return
|
|
}
|
|
if def.OrgID != ctx.Org.Organization.ID {
|
|
ctx.NotFound(nil)
|
|
return
|
|
}
|
|
|
|
def.Name = ctx.FormString("name")
|
|
def.Color = ctx.FormString("color")
|
|
def.Description = ctx.FormString("description")
|
|
def.ClosesIssue = ctx.FormString("closes_issue") == "on"
|
|
def.IsActive = ctx.FormString("is_active") == "on"
|
|
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
|
|
def.SortOrder = sortOrder
|
|
|
|
if err := issues_model.UpdateIssueStatusDef(ctx, def); err != nil {
|
|
ctx.ServerError("UpdateIssueStatusDef", err)
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("org.settings.issue_status_updated"))
|
|
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
|
|
}
|
|
|
|
// SettingsIssueStatusesDeletePost deletes an org-level issue status.
|
|
func SettingsIssueStatusesDeletePost(ctx *context.Context) {
|
|
id := ctx.PathParamInt64("id")
|
|
def, err := issues_model.GetIssueStatusDefByID(ctx, id)
|
|
if err != nil {
|
|
ctx.ServerError("GetIssueStatusDefByID", err)
|
|
return
|
|
}
|
|
if def.OrgID != ctx.Org.Organization.ID {
|
|
ctx.NotFound(nil)
|
|
return
|
|
}
|
|
|
|
if err := issues_model.DeleteIssueStatusDef(ctx, id); err != nil {
|
|
if issues_model.IsErrStatusRequired(err) {
|
|
ctx.Flash.Error("Cannot delete required status: " + def.Name)
|
|
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
|
|
return
|
|
}
|
|
ctx.ServerError("DeleteIssueStatusDef", err)
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("org.settings.issue_status_deleted"))
|
|
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
|
|
}
|