Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions internal/services/planning_iterations.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ func (s *PlanningService) FindIterationByName(workspaceID int, name string) (*It

// ListIterations retrieves iterations with pagination and filtering.
func (s *PlanningService) ListIterations(params IterationListParams) ([]IterationResult, int, error) {
params.Status = normalizePlanningStatus(params.Status)
list := newPlanningListQuery(iterationSelectQuery+"\nWHERE 1=1", "SELECT COUNT(*) FROM iterations i WHERE 1=1")
list.addWorkspaceScope("i.workspace_id", "i.is_global", params.WorkspaceID, params.WorkspaceIDs, params.IncludeGlobal)
list.addNullableIDFilter("i.type_id", params.TypeID)
Expand Down Expand Up @@ -245,6 +246,7 @@ func (s *PlanningService) CreateIteration(params CreateIterationParams) (*Iterat
if params.Status == "" {
params.Status = "planned"
}
params.Status = normalizePlanningStatus(params.Status)
if err := s.validateIterationMutation(params); err != nil {
return nil, err
}
Expand Down Expand Up @@ -287,6 +289,7 @@ type UpdateIterationParams struct {

// UpdateIteration updates an existing iteration within its declared scope.
func (s *PlanningService) UpdateIteration(params UpdateIterationParams) (*IterationResult, error) {
params.Status = normalizePlanningStatus(params.Status)
if err := s.validateIterationMutation(CreateIterationParams{
Name: params.Name,
Description: params.Description,
Expand Down
4 changes: 4 additions & 0 deletions internal/services/planning_milestones.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ type MilestoneListParams struct {

// ListMilestones retrieves milestones with pagination and filtering.
func (s *PlanningService) ListMilestones(params MilestoneListParams) ([]MilestoneResult, int, error) {
params.Status = normalizePlanningStatus(params.Status)
list := newPlanningListQuery(milestoneSelectQuery+"\nWHERE 1=1", "SELECT COUNT(*) FROM milestones m WHERE 1=1")
list.addWorkspaceScope("m.workspace_id", "m.is_global", params.WorkspaceID, params.WorkspaceIDs, params.IncludeGlobal)
list.addNullableIDFilter("m.category_id", params.CategoryID)
Expand Down Expand Up @@ -445,6 +446,7 @@ func (s *PlanningService) CreateMilestone(params CreateMilestoneParams) (*Milest
if params.Status == "" {
params.Status = "planning"
}
params.Status = normalizePlanningStatus(params.Status)
if err := s.validateMilestoneMutation(params); err != nil {
return nil, err
}
Expand Down Expand Up @@ -522,6 +524,7 @@ func (s *PlanningService) FindMilestoneByName(workspaceID int, name string) (*Mi
// milestone to "in-progress" or "completed" without disturbing the other
// fields. Returns a "not found" error when no row matches the scope.
func (s *PlanningService) SetMilestoneStatus(milestoneID, workspaceID int, status string) error {
status = normalizePlanningStatus(status)
if !validMilestoneStatus(status) {
return planningValidationError("status", milestoneStatusValidationMessage)
}
Expand Down Expand Up @@ -589,6 +592,7 @@ type UpdateMilestoneParams struct {
// UpdateMilestone updates an existing milestone within its declared scope.
// is_global / workspace_id cannot be changed via this method.
func (s *PlanningService) UpdateMilestone(params UpdateMilestoneParams) (*MilestoneResult, error) {
params.Status = normalizePlanningStatus(params.Status)
if err := s.validateMilestoneMutation(CreateMilestoneParams{
Name: params.Name,
Description: params.Description,
Expand Down
11 changes: 11 additions & 0 deletions internal/services/planning_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ func planningValidationErrorWithCause(field, message string, cause error) error

const cancelledPlanningStatus = "cancelled" //nolint:misspell // Persisted API status.

// normalizePlanningStatus maps the US spelling "canceled" onto the canonical
// persisted planning status. Clients send either spelling; validation,
// filters, and storage all use the canonical one. Any other value passes
// through so validation can reject it.
func normalizePlanningStatus(status string) string {
if strings.EqualFold(strings.TrimSpace(status), "canceled") {
return cancelledPlanningStatus
}
return status
}

func validMilestoneStatus(status string) bool {
switch status {
case "planning", "in-progress", "completed", cancelledPlanningStatus:
Expand Down
10 changes: 7 additions & 3 deletions internal/wscli/milestone.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import (
"github.com/spf13/cobra"
)

// milestoneCancelledStatus is the canonical persisted milestone status. The
// API also accepts the US spelling "canceled" and normalizes it.
const milestoneCancelledStatus = "cancelled" //nolint:misspell // Persisted API status.

var milestoneCmd = &cobra.Command{
Use: "milestone",
Short: "Manage milestones",
Expand Down Expand Up @@ -236,7 +240,7 @@ func init() {
milestoneCmd.AddCommand(milestoneUpdateCmd)

// List filters
milestoneListCmd.Flags().StringVarP(&milestoneStatusFilter, "status", "s", "", "filter by status (planning, in-progress, completed, canceled)")
milestoneListCmd.Flags().StringVarP(&milestoneStatusFilter, "status", "s", "", "filter by status (planning, in-progress, completed, "+milestoneCancelledStatus+")")
milestoneListCmd.Flags().BoolVar(&milestoneGlobalOnly, "global", false, "show only global milestones")

// Get flags
Expand All @@ -246,11 +250,11 @@ func init() {
milestoneCreateCmd.Flags().StringVarP(&milestoneCreateName, "name", "n", "", "milestone name (required)")
milestoneCreateCmd.Flags().StringVarP(&milestoneCreateDesc, "description", "d", "", "milestone description")
milestoneCreateCmd.Flags().StringVar(&milestoneCreateTarget, "target", "", "target date (YYYY-MM-DD)")
milestoneCreateCmd.Flags().StringVar(&milestoneCreateStatus, "status", "", "initial status (default: planning)")
milestoneCreateCmd.Flags().StringVar(&milestoneCreateStatus, "status", "", "initial status: planning (default), in-progress, completed, "+milestoneCancelledStatus)

// Update flags
milestoneUpdateCmd.Flags().StringVarP(&milestoneUpdateName, "name", "n", "", "new milestone name")
milestoneUpdateCmd.Flags().StringVarP(&milestoneUpdateDesc, "description", "d", "", "new description")
milestoneUpdateCmd.Flags().StringVar(&milestoneUpdateTarget, "target", "", "new target date (YYYY-MM-DD)")
milestoneUpdateCmd.Flags().StringVar(&milestoneUpdateStatus, "status", "", "new status")
milestoneUpdateCmd.Flags().StringVar(&milestoneUpdateStatus, "status", "", "new status: planning, in-progress, completed, "+milestoneCancelledStatus)
}
Loading