From 22586b7a06b1e629dc0d2ac816c6171033e77054 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 6 Jun 2026 13:40:53 -0500 Subject: [PATCH] fix(issues): auto-seed default statuses and priorities for orgs Status and priority are first-class fields, not custom fields. They must always show in the sidebar without requiring manual setup. When an org has no definitions, the standard presets are auto-created on first access. --- models/issues/issue_priority.go | 30 ++++++++++++++++++++++++++++-- models/issues/issue_status.go | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/models/issues/issue_priority.go b/models/issues/issue_priority.go index 3b6bcee322..8ec092a30e 100644 --- a/models/issues/issue_priority.go +++ b/models/issues/issue_priority.go @@ -33,12 +33,38 @@ func (IssuePriorityDef) TableName() string { } // GetIssuePriorityDefsByOrg returns active priority definitions for an org. +// If none exist, seeds the org with default priorities automatically. func GetIssuePriorityDefsByOrg(ctx context.Context, orgID int64) ([]*IssuePriorityDef, error) { defs := make([]*IssuePriorityDef, 0, 10) - return defs, db.GetEngine(ctx). + if err := db.GetEngine(ctx). Where("org_id = ? AND is_active = ?", orgID, true). OrderBy("sort_order ASC, id ASC"). - Find(&defs) + Find(&defs); err != nil { + return nil, err + } + if len(defs) == 0 && orgID > 0 { + if err := seedDefaultIssuePriorities(ctx, orgID); err != nil { + return defs, nil // non-fatal + } + return GetIssuePriorityDefsByOrg(ctx, orgID) + } + return defs, nil +} + +// seedDefaultIssuePriorities creates the standard priority presets for an org. +func seedDefaultIssuePriorities(ctx context.Context, orgID int64) error { + defaults := []*IssuePriorityDef{ + {OrgID: orgID, Name: "Critical", Color: "#dc2626", Description: "Requires immediate attention", SortOrder: 1, IsActive: true}, + {OrgID: orgID, Name: "High", Color: "#f97316", Description: "Should be addressed soon", SortOrder: 2, IsActive: true}, + {OrgID: orgID, Name: "Medium", Color: "#eab308", Description: "Normal priority", SortOrder: 3, IsDefault: true, IsActive: true}, + {OrgID: orgID, Name: "Low", Color: "#2563eb", Description: "Can wait", SortOrder: 4, IsActive: true}, + } + for _, d := range defaults { + if _, err := db.GetEngine(ctx).Insert(d); err != nil { + return err + } + } + return nil } // GetAllIssuePriorityDefsByOrg returns all priority definitions (including inactive). diff --git a/models/issues/issue_status.go b/models/issues/issue_status.go index 4f35cbd3f7..08da82186a 100644 --- a/models/issues/issue_status.go +++ b/models/issues/issue_status.go @@ -37,12 +37,40 @@ func (IssueStatusDef) TableName() string { // ────────────────────────────────────────────────────────────────────── // GetIssueStatusDefsByOrg returns active status definitions for an org. +// If none exist, seeds the org with default statuses automatically. func GetIssueStatusDefsByOrg(ctx context.Context, orgID int64) ([]*IssueStatusDef, error) { defs := make([]*IssueStatusDef, 0, 10) - return defs, db.GetEngine(ctx). + if err := db.GetEngine(ctx). Where("org_id = ? AND is_active = ?", orgID, true). OrderBy("sort_order ASC, id ASC"). - Find(&defs) + Find(&defs); err != nil { + return nil, err + } + if len(defs) == 0 && orgID > 0 { + if err := seedDefaultIssueStatuses(ctx, orgID); err != nil { + return defs, nil // non-fatal + } + return GetIssueStatusDefsByOrg(ctx, orgID) + } + return defs, nil +} + +// seedDefaultIssueStatuses creates the standard status presets for an org. +func seedDefaultIssueStatuses(ctx context.Context, orgID int64) error { + defaults := []*IssueStatusDef{ + {OrgID: orgID, Name: "In Progress", Color: "#2563eb", Description: "Work is actively being done", SortOrder: 1, IsActive: true}, + {OrgID: orgID, Name: "Needs Info", Color: "#f59e0b", Description: "Waiting for more information", SortOrder: 2, IsActive: true}, + {OrgID: orgID, Name: "Blocked", Color: "#dc2626", Description: "Cannot proceed due to dependency", SortOrder: 3, IsActive: true}, + {OrgID: orgID, Name: "Resolved", Color: "#16a34a", Description: "Fix implemented and verified", ClosesIssue: true, SortOrder: 4, IsActive: true}, + {OrgID: orgID, Name: "Won't Fix", Color: "#6b7280", Description: "Decided not to address", ClosesIssue: true, SortOrder: 5, IsActive: true}, + {OrgID: orgID, Name: "Duplicate", Color: "#8b5cf6", Description: "Already tracked elsewhere", ClosesIssue: true, SortOrder: 6, IsActive: true}, + } + for _, d := range defaults { + if _, err := db.GetEngine(ctx).Insert(d); err != nil { + return err + } + } + return nil } // GetAllIssueStatusDefsByOrg returns all status definitions (including inactive). -- 2.52.0