fix: move required custom field validation before issue creation
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Branch Policy Check / Verify merge target (pull_request) Failing after 2s
Universal: PR Check / Branch Policy (pull_request) Failing after 3s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 51s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m22s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 4m19s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
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
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled

Validation now runs before NewIssue() to prevent orphaned issues
when a required field is missing. Reuses the defs query for both
validation and saving.
This commit is contained in:
Jonathan Miller
2026-06-11 21:56:14 -05:00
parent 245b5a8e6a
commit c67e7373fb
+27 -28
View File
@@ -722,6 +722,22 @@ func CreateIssue(ctx *context.APIContext) {
form.Labels = make([]int64, 0)
}
// Validate required custom fields BEFORE creating the issue to avoid
// leaving orphaned issues when validation fails.
customFieldDefs, defErr := issues_model.GetCustomFieldsByOwner(ctx, ctx.Repo.Repository.OwnerID, issues_model.CustomFieldScopeIssue)
if defErr != nil {
ctx.APIErrorInternal(defErr)
return
}
for _, def := range customFieldDefs {
if def.Required {
if v, ok := form.CustomFields[def.Name]; !ok || strings.TrimSpace(v) == "" {
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("custom field %q is required", def.Name))
return
}
}
}
if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs, form.Projects); err != nil {
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.APIError(http.StatusForbidden, err)
@@ -733,35 +749,18 @@ func CreateIssue(ctx *context.APIContext) {
return
}
// Save custom field values if provided (resolve field names to IDs).
// Validate required fields are present.
{
defs, defErr := issues_model.GetCustomFieldsByOwner(ctx, ctx.Repo.Repository.OwnerID, issues_model.CustomFieldScopeIssue)
if defErr != nil {
ctx.APIErrorInternal(defErr)
return
// Save custom field values (reuse defs from validation above).
if len(customFieldDefs) > 0 && len(form.CustomFields) > 0 {
vals := make(map[int64]string)
for _, def := range customFieldDefs {
if v, ok := form.CustomFields[def.Name]; ok {
vals[def.ID] = v
}
}
if len(defs) > 0 {
// Check required fields.
for _, def := range defs {
if def.Required {
if v, ok := form.CustomFields[def.Name]; !ok || strings.TrimSpace(v) == "" {
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("custom field %q is required", def.Name))
return
}
}
}
vals := make(map[int64]string)
for _, def := range defs {
if v, ok := form.CustomFields[def.Name]; ok {
vals[def.ID] = v
}
}
if len(vals) > 0 {
if setErr := issues_model.SetCustomFieldValues(ctx, issue.ID, vals); setErr != nil {
ctx.APIErrorInternal(setErr)
return
}
if len(vals) > 0 {
if setErr := issues_model.SetCustomFieldValues(ctx, issue.ID, vals); setErr != nil {
ctx.APIErrorInternal(setErr)
return
}
}
}