Add daemon support for label commands and populate labels in issue queries
- Updated label CLI commands to support both daemon and direct modes - Added label fetching to GetIssue() and scanIssues() methods - All label operations (add, remove, list, list-all) work with daemon - Closed bd-162 (label CLI commands), bd-166 (duplicate), bd-141 (daemon support) Amp-Thread-ID: https://ampcode.com/threads/T-4858f62e-ad06-4cc7-ad05-17ee76861f86 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
122
cmd/bd/label.go
122
cmd/bd/label.go
@@ -3,6 +3,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/steveyegge/beads/internal/rpc"
|
||||||
"github.com/steveyegge/beads/internal/types"
|
"github.com/steveyegge/beads/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,13 +23,36 @@ var labelCmd = &cobra.Command{
|
|||||||
// executeLabelCommand executes a label operation and handles output
|
// executeLabelCommand executes a label operation and handles output
|
||||||
func executeLabelCommand(issueID, label, operation string, operationFunc func(context.Context, string, string, string) error) {
|
func executeLabelCommand(issueID, label, operation string, operationFunc func(context.Context, string, string, string) error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := operationFunc(ctx, issueID, label, actor); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
// Use daemon if available
|
||||||
os.Exit(1)
|
if daemonClient != nil {
|
||||||
}
|
var err error
|
||||||
|
if operation == "added" {
|
||||||
|
_, err = daemonClient.AddLabel(&rpc.LabelAddArgs{
|
||||||
|
ID: issueID,
|
||||||
|
Label: label,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
_, err = daemonClient.RemoveLabel(&rpc.LabelRemoveArgs{
|
||||||
|
ID: issueID,
|
||||||
|
Label: label,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Direct mode
|
||||||
|
if err := operationFunc(ctx, issueID, label, actor); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// Schedule auto-flush
|
// Schedule auto-flush
|
||||||
markDirtyAndScheduleFlush()
|
markDirtyAndScheduleFlush()
|
||||||
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
outputJSON(map[string]interface{}{
|
outputJSON(map[string]interface{}{
|
||||||
@@ -49,7 +74,9 @@ var labelAddCmd = &cobra.Command{
|
|||||||
Short: "Add a label to an issue",
|
Short: "Add a label to an issue",
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
executeLabelCommand(args[0], args[1], "added", store.AddLabel)
|
executeLabelCommand(args[0], args[1], "added", func(ctx context.Context, issueID, label, actor string) error {
|
||||||
|
return store.AddLabel(ctx, issueID, label, actor)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +85,9 @@ var labelRemoveCmd = &cobra.Command{
|
|||||||
Short: "Remove a label from an issue",
|
Short: "Remove a label from an issue",
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
executeLabelCommand(args[0], args[1], "removed", store.RemoveLabel)
|
executeLabelCommand(args[0], args[1], "removed", func(ctx context.Context, issueID, label, actor string) error {
|
||||||
|
return store.RemoveLabel(ctx, issueID, label, actor)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,10 +99,30 @@ var labelListCmd = &cobra.Command{
|
|||||||
issueID := args[0]
|
issueID := args[0]
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
labels, err := store.GetLabels(ctx, issueID)
|
var labels []string
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
// Use daemon if available
|
||||||
os.Exit(1)
|
if daemonClient != nil {
|
||||||
|
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: issueID})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var issue types.Issue
|
||||||
|
if err := json.Unmarshal(resp.Data, &issue); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
labels = issue.Labels
|
||||||
|
} else {
|
||||||
|
// Direct mode
|
||||||
|
var err error
|
||||||
|
labels, err = store.GetLabels(ctx, issueID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
@@ -105,23 +154,48 @@ var labelListAllCmd = &cobra.Command{
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Get all issues to collect labels
|
var issues []*types.Issue
|
||||||
issues, err := store.SearchIssues(ctx, "", types.IssueFilter{})
|
var err error
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
// Use daemon if available
|
||||||
os.Exit(1)
|
if daemonClient != nil {
|
||||||
|
resp, err := daemonClient.List(&rpc.ListArgs{})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(resp.Data, &issues); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Direct mode
|
||||||
|
issues, err = store.SearchIssues(ctx, "", types.IssueFilter{})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect unique labels with counts
|
// Collect unique labels with counts
|
||||||
labelCounts := make(map[string]int)
|
labelCounts := make(map[string]int)
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
labels, err := store.GetLabels(ctx, issue.ID)
|
if daemonClient != nil {
|
||||||
if err != nil {
|
// Labels are already in the issue from daemon
|
||||||
fmt.Fprintf(os.Stderr, "Error getting labels for %s: %v\n", issue.ID, err)
|
for _, label := range issue.Labels {
|
||||||
os.Exit(1)
|
labelCounts[label]++
|
||||||
}
|
}
|
||||||
for _, label := range labels {
|
} else {
|
||||||
labelCounts[label]++
|
// Direct mode - need to fetch labels
|
||||||
|
labels, err := store.GetLabels(ctx, issue.ID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error getting labels for %s: %v\n", issue.ID, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
for _, label := range labels {
|
||||||
|
labelCounts[label]++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ func (s *SQLiteStorage) GetDependencies(ctx context.Context, issueID string) ([]
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
return scanIssues(rows)
|
return s.scanIssues(ctx, rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDependents returns issues that depend on this issue
|
// GetDependents returns issues that depend on this issue
|
||||||
@@ -382,7 +382,7 @@ func (s *SQLiteStorage) GetDependents(ctx context.Context, issueID string) ([]*t
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
return scanIssues(rows)
|
return s.scanIssues(ctx, rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDependencyRecords returns raw dependency records for an issue
|
// GetDependencyRecords returns raw dependency records for an issue
|
||||||
@@ -644,7 +644,7 @@ func (s *SQLiteStorage) DetectCycles(ctx context.Context) ([][]*types.Issue, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to scan issues from rows
|
// Helper function to scan issues from rows
|
||||||
func scanIssues(rows *sql.Rows) ([]*types.Issue, error) {
|
func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*types.Issue, error) {
|
||||||
var issues []*types.Issue
|
var issues []*types.Issue
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var issue types.Issue
|
var issue types.Issue
|
||||||
@@ -677,6 +677,13 @@ func scanIssues(rows *sql.Rows) ([]*types.Issue, error) {
|
|||||||
issue.ExternalRef = &externalRef.String
|
issue.ExternalRef = &externalRef.String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch labels for this issue
|
||||||
|
labels, err := s.GetLabels(ctx, issue.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get labels for issue %s: %w", issue.ID, err)
|
||||||
|
}
|
||||||
|
issue.Labels = labels
|
||||||
|
|
||||||
issues = append(issues, &issue)
|
issues = append(issues, &issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,5 +112,5 @@ func (s *SQLiteStorage) GetIssuesByLabel(ctx context.Context, label string) ([]*
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
return scanIssues(rows)
|
return s.scanIssues(ctx, rows)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func (s *SQLiteStorage) GetReadyWork(ctx context.Context, filter types.WorkFilte
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
return scanIssues(rows)
|
return s.scanIssues(ctx, rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlockedIssues returns issues that are blocked by dependencies
|
// GetBlockedIssues returns issues that are blocked by dependencies
|
||||||
|
|||||||
@@ -925,6 +925,13 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
|
|||||||
issue.OriginalSize = int(originalSize.Int64)
|
issue.OriginalSize = int(originalSize.Int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch labels for this issue
|
||||||
|
labels, err := s.GetLabels(ctx, issue.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get labels: %w", err)
|
||||||
|
}
|
||||||
|
issue.Labels = labels
|
||||||
|
|
||||||
return &issue, nil
|
return &issue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1693,7 +1700,7 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
return scanIssues(rows)
|
return s.scanIssues(ctx, rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetConfig sets a configuration value
|
// SetConfig sets a configuration value
|
||||||
|
|||||||
Reference in New Issue
Block a user