Merge pull request 'feat: allow tags passing when creating/editing an issue' (#31) from feat/issue-27/labels_for_issue_creation_and_edition into main
Reviewed-on: https://codeberg.org/romaintb/fgj/pulls/31
This commit is contained in:
commit
527f6bf15f
3 changed files with 327 additions and 7 deletions
90
cmd/issue.go
90
cmd/issue.go
|
|
@ -84,6 +84,7 @@ func init() {
|
||||||
issueCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
issueCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||||
issueCreateCmd.Flags().StringP("title", "t", "", "Title for the issue")
|
issueCreateCmd.Flags().StringP("title", "t", "", "Title for the issue")
|
||||||
issueCreateCmd.Flags().StringP("body", "b", "", "Body for the issue")
|
issueCreateCmd.Flags().StringP("body", "b", "", "Body for the issue")
|
||||||
|
issueCreateCmd.Flags().StringSliceP("label", "l", nil, "Labels to add (can be specified multiple times)")
|
||||||
|
|
||||||
issueCommentCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
issueCommentCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||||
issueCommentCmd.Flags().StringP("body", "b", "", "Comment body")
|
issueCommentCmd.Flags().StringP("body", "b", "", "Comment body")
|
||||||
|
|
@ -95,6 +96,8 @@ func init() {
|
||||||
issueEditCmd.Flags().StringP("title", "t", "", "New title for the issue")
|
issueEditCmd.Flags().StringP("title", "t", "", "New title for the issue")
|
||||||
issueEditCmd.Flags().StringP("body", "b", "", "New body for the issue")
|
issueEditCmd.Flags().StringP("body", "b", "", "New body for the issue")
|
||||||
issueEditCmd.Flags().StringP("state", "s", "", "New state for the issue (open or closed)")
|
issueEditCmd.Flags().StringP("state", "s", "", "New state for the issue (open or closed)")
|
||||||
|
issueEditCmd.Flags().StringSlice("add-label", nil, "Labels to add (can be specified multiple times)")
|
||||||
|
issueEditCmd.Flags().StringSlice("remove-label", nil, "Labels to remove (can be specified multiple times)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIssueList(cmd *cobra.Command, args []string) error {
|
func runIssueList(cmd *cobra.Command, args []string) error {
|
||||||
|
|
@ -233,6 +236,7 @@ func runIssueCreate(cmd *cobra.Command, args []string) error {
|
||||||
repo, _ := cmd.Flags().GetString("repo")
|
repo, _ := cmd.Flags().GetString("repo")
|
||||||
title, _ := cmd.Flags().GetString("title")
|
title, _ := cmd.Flags().GetString("title")
|
||||||
body, _ := cmd.Flags().GetString("body")
|
body, _ := cmd.Flags().GetString("body")
|
||||||
|
labelNames, _ := cmd.Flags().GetStringSlice("label")
|
||||||
|
|
||||||
owner, name, err := parseRepo(repo)
|
owner, name, err := parseRepo(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -253,9 +257,18 @@ func runIssueCreate(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var labelIDs []int64
|
||||||
|
if len(labelNames) > 0 {
|
||||||
|
labelIDs, err = resolveLabelIDs(client, owner, name, labelNames)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
issue, _, err := client.CreateIssue(owner, name, gitea.CreateIssueOption{
|
issue, _, err := client.CreateIssue(owner, name, gitea.CreateIssueOption{
|
||||||
Title: title,
|
Title: title,
|
||||||
Body: body,
|
Body: body,
|
||||||
|
Labels: labelIDs,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create issue: %w", err)
|
return fmt.Errorf("failed to create issue: %w", err)
|
||||||
|
|
@ -357,6 +370,8 @@ func runIssueEdit(cmd *cobra.Command, args []string) error {
|
||||||
title, _ := cmd.Flags().GetString("title")
|
title, _ := cmd.Flags().GetString("title")
|
||||||
body, _ := cmd.Flags().GetString("body")
|
body, _ := cmd.Flags().GetString("body")
|
||||||
stateStr, _ := cmd.Flags().GetString("state")
|
stateStr, _ := cmd.Flags().GetString("state")
|
||||||
|
addLabelNames, _ := cmd.Flags().GetStringSlice("add-label")
|
||||||
|
removeLabelNames, _ := cmd.Flags().GetStringSlice("remove-label")
|
||||||
|
|
||||||
issueNumber, err := strconv.ParseInt(args[0], 10, 64)
|
issueNumber, err := strconv.ParseInt(args[0], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -368,8 +383,8 @@ func runIssueEdit(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if title == "" && body == "" && stateStr == "" {
|
if title == "" && body == "" && stateStr == "" && len(addLabelNames) == 0 && len(removeLabelNames) == 0 {
|
||||||
return fmt.Errorf("at least one of --title, --body, or --state must be provided")
|
return fmt.Errorf("at least one of --title, --body, --state, --add-label, or --remove-label must be provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := config.Load()
|
cfg, err := config.Load()
|
||||||
|
|
@ -405,12 +420,73 @@ func runIssueEdit(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = client.EditIssue(owner, name, issueNumber, editOpt)
|
if title != "" || body != "" || stateStr != "" {
|
||||||
if err != nil {
|
_, _, err = client.EditIssue(owner, name, issueNumber, editOpt)
|
||||||
return fmt.Errorf("failed to edit issue: %w", err)
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to edit issue: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addLabelNames) > 0 {
|
||||||
|
labelIDs, err := resolveLabelIDs(client, owner, name, addLabelNames)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _, err = client.AddIssueLabels(owner, name, issueNumber, gitea.IssueLabelsOption{
|
||||||
|
Labels: labelIDs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add labels: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(removeLabelNames) > 0 {
|
||||||
|
labelIDs, err := resolveLabelIDs(client, owner, name, removeLabelNames)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, labelID := range labelIDs {
|
||||||
|
_, err = client.DeleteIssueLabel(owner, name, issueNumber, labelID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to remove label %d: %w", labelID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Issue #%d updated\n", issueNumber)
|
fmt.Printf("Issue #%d updated\n", issueNumber)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveLabelIDs(client *api.Client, owner, name string, labelNames []string) ([]int64, error) {
|
||||||
|
if len(labelNames) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
labels, _, err := client.ListRepoLabels(owner, name, gitea.ListLabelsOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list labels: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
labelNameToID := make(map[string]int64)
|
||||||
|
for _, label := range labels {
|
||||||
|
labelNameToID[label.Name] = label.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
var labelIDs []int64
|
||||||
|
var missingLabels []string
|
||||||
|
for _, labelName := range labelNames {
|
||||||
|
labelID, exists := labelNameToID[labelName]
|
||||||
|
if !exists {
|
||||||
|
missingLabels = append(missingLabels, labelName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
labelIDs = append(labelIDs, labelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missingLabels) > 0 {
|
||||||
|
return nil, fmt.Errorf("labels not found: %s", strings.Join(missingLabels, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return labelIDs, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,74 @@ func (env *TestEnv) VerifyAPIConnection() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnsureTestLabels creates test labels if they don't exist
|
||||||
|
func (env *TestEnv) EnsureTestLabels() {
|
||||||
|
labelNames := []string{"bug", "enhancement", "help-wanted"}
|
||||||
|
|
||||||
|
for _, name := range labelNames {
|
||||||
|
labels, _, err := env.Client.ListRepoLabels(env.Owner, env.RepoName, gitea.ListLabelsOptions{})
|
||||||
|
if err != nil {
|
||||||
|
env.T.Fatalf("failed to list labels: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exists := false
|
||||||
|
for _, label := range labels {
|
||||||
|
if label.Name == name {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
color := "#00aabb"
|
||||||
|
if name == "bug" {
|
||||||
|
color = "#ff0000"
|
||||||
|
} else if name == "enhancement" {
|
||||||
|
color = "#00ff00"
|
||||||
|
}
|
||||||
|
_, _, err = env.Client.CreateLabel(env.Owner, env.RepoName, gitea.CreateLabelOption{
|
||||||
|
Name: name,
|
||||||
|
Color: color,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
env.T.Logf("warning: failed to create label '%s': %v", name, err)
|
||||||
|
} else {
|
||||||
|
env.T.Logf("Created test label: %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIssueLabels gets the labels on an issue
|
||||||
|
func (env *TestEnv) GetIssueLabels(issueNumber int64) ([]*gitea.Label, error) {
|
||||||
|
labels, _, err := env.Client.GetIssueLabels(env.Owner, env.RepoName, issueNumber, gitea.ListLabelsOptions{})
|
||||||
|
return labels, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabelIDs converts label names to label IDs
|
||||||
|
func (env *TestEnv) GetLabelIDs(labelNames []string) []int64 {
|
||||||
|
labels, _, err := env.Client.ListRepoLabels(env.Owner, env.RepoName, gitea.ListLabelsOptions{})
|
||||||
|
if err != nil {
|
||||||
|
env.T.Fatalf("failed to list labels: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nameToID := make(map[string]int64)
|
||||||
|
for _, label := range labels {
|
||||||
|
nameToID[label.Name] = label.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
var ids []int64
|
||||||
|
for _, name := range labelNames {
|
||||||
|
id, exists := nameToID[name]
|
||||||
|
if !exists {
|
||||||
|
env.T.Fatalf("label '%s' not found", name)
|
||||||
|
}
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
// GetBinaryPath returns the path to the built fgj binary
|
// GetBinaryPath returns the path to the built fgj binary
|
||||||
func (env *TestEnv) GetBinaryPath() string {
|
func (env *TestEnv) GetBinaryPath() string {
|
||||||
binaryPath := os.Getenv("FGJ_BINARY_PATH")
|
binaryPath := os.Getenv("FGJ_BINARY_PATH")
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,182 @@ func TestCreateAndListIssues(t *testing.T) {
|
||||||
t.Logf("Successfully created and listed issue #%d", issueNum)
|
t.Logf("Successfully created and listed issue #%d", issueNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestIssueCreateWithLabels verifies we can create issues with labels via CLI
|
||||||
|
func TestIssueCreateWithLabels(t *testing.T) {
|
||||||
|
env := NewTestEnv(t)
|
||||||
|
|
||||||
|
// Ensure test labels exist
|
||||||
|
env.EnsureTestLabels()
|
||||||
|
|
||||||
|
// Create an issue with labels via CLI
|
||||||
|
result := env.RunCLI(
|
||||||
|
"--hostname", env.Hostname,
|
||||||
|
"issue", "create",
|
||||||
|
"-t", "[FGJ E2E Test] Issue with Labels",
|
||||||
|
"-b", "This issue was created with labels",
|
||||||
|
"-l", "bug",
|
||||||
|
"-l", "enhancement",
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.ExitCode != 0 {
|
||||||
|
t.Fatalf("issue create with labels failed with exit code %d: %s", result.ExitCode, result.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract issue number from output (format: "Issue created: #<number>")
|
||||||
|
var issueNum int64
|
||||||
|
_, err := fmt.Sscanf(result.Stdout, "Issue created: #%d", &issueNum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse issue number from output: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer env.CleanupIssue(issueNum)
|
||||||
|
|
||||||
|
// Verify labels were applied
|
||||||
|
labels, err := env.GetIssueLabels(issueNum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get issue labels: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(labels) != 2 {
|
||||||
|
t.Fatalf("expected 2 labels, got %d", len(labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
labelNames := make(map[string]bool)
|
||||||
|
for _, label := range labels {
|
||||||
|
labelNames[label.Name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !labelNames["bug"] || !labelNames["enhancement"] {
|
||||||
|
t.Fatalf("expected labels 'bug' and 'enhancement', got %v", labelNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Successfully created issue #%d with labels: bug, enhancement", issueNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIssueEditAddLabels verifies we can add labels to an issue via CLI
|
||||||
|
func TestIssueEditAddLabels(t *testing.T) {
|
||||||
|
env := NewTestEnv(t)
|
||||||
|
|
||||||
|
// Ensure test labels exist
|
||||||
|
env.EnsureTestLabels()
|
||||||
|
|
||||||
|
// Create an issue without labels
|
||||||
|
issueNum := env.CreateTestIssue(
|
||||||
|
"[FGJ E2E Test] Add Labels Test",
|
||||||
|
"This issue will have labels added",
|
||||||
|
)
|
||||||
|
defer env.CleanupIssue(issueNum)
|
||||||
|
|
||||||
|
// Verify no labels initially
|
||||||
|
labels, err := env.GetIssueLabels(issueNum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get initial labels: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(labels) != 0 {
|
||||||
|
t.Fatalf("expected 0 labels initially, got %d", len(labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add labels via CLI
|
||||||
|
result := env.RunCLI(
|
||||||
|
"--hostname", env.Hostname,
|
||||||
|
"issue", "edit",
|
||||||
|
fmt.Sprintf("%d", issueNum),
|
||||||
|
"--add-label", "bug",
|
||||||
|
"--add-label", "help-wanted",
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.ExitCode != 0 {
|
||||||
|
t.Fatalf("issue edit add-label failed with exit code %d: %s", result.ExitCode, result.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify labels were added
|
||||||
|
labels, err = env.GetIssueLabels(issueNum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get labels after edit: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(labels) != 2 {
|
||||||
|
t.Fatalf("expected 2 labels after edit, got %d", len(labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
labelNames := make(map[string]bool)
|
||||||
|
for _, label := range labels {
|
||||||
|
labelNames[label.Name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !labelNames["bug"] || !labelNames["help-wanted"] {
|
||||||
|
t.Fatalf("expected labels 'bug' and 'help-wanted', got %v", labelNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Successfully added labels to issue #%d", issueNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIssueEditRemoveLabels verifies we can remove labels from an issue via CLI
|
||||||
|
func TestIssueEditRemoveLabels(t *testing.T) {
|
||||||
|
env := NewTestEnv(t)
|
||||||
|
|
||||||
|
// Ensure test labels exist
|
||||||
|
env.EnsureTestLabels()
|
||||||
|
|
||||||
|
// Create an issue with labels
|
||||||
|
issue, _, err := env.Client.CreateIssue(env.Owner, env.RepoName, gitea.CreateIssueOption{
|
||||||
|
Title: "[FGJ E2E Test] Remove Labels Test",
|
||||||
|
Body: "This issue will have labels removed",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create issue: %v", err)
|
||||||
|
}
|
||||||
|
defer env.CleanupIssue(issue.Index)
|
||||||
|
|
||||||
|
// Add labels via API
|
||||||
|
labelIDs := env.GetLabelIDs([]string{"bug", "enhancement"})
|
||||||
|
_, _, err = env.Client.AddIssueLabels(env.Owner, env.RepoName, issue.Index, gitea.IssueLabelsOption{
|
||||||
|
Labels: labelIDs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to add initial labels: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify initial labels
|
||||||
|
labels, err := env.GetIssueLabels(issue.Index)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get initial labels: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(labels) != 2 {
|
||||||
|
t.Fatalf("expected 2 labels initially, got %d", len(labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove one label via CLI
|
||||||
|
result := env.RunCLI(
|
||||||
|
"--hostname", env.Hostname,
|
||||||
|
"issue", "edit",
|
||||||
|
fmt.Sprintf("%d", issue.Index),
|
||||||
|
"--remove-label", "bug",
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.ExitCode != 0 {
|
||||||
|
t.Fatalf("issue edit remove-label failed with exit code %d: %s", result.ExitCode, result.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify label was removed
|
||||||
|
labels, err = env.GetIssueLabels(issue.Index)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get labels after edit: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(labels) != 1 {
|
||||||
|
t.Fatalf("expected 1 label after removal, got %d", len(labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
if labels[0].Name != "enhancement" {
|
||||||
|
t.Fatalf("expected remaining label 'enhancement', got '%s'", labels[0].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Successfully removed label from issue #%d", issue.Index)
|
||||||
|
}
|
||||||
|
|
||||||
// TestGetIssue verifies we can get issue details
|
// TestGetIssue verifies we can get issue details
|
||||||
func TestGetIssue(t *testing.T) {
|
func TestGetIssue(t *testing.T) {
|
||||||
env := NewTestEnv(t)
|
env := NewTestEnv(t)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue