diff --git a/app/github.go b/app/github.go index b489045..839dc26 100644 --- a/app/github.go +++ b/app/github.go @@ -154,3 +154,45 @@ func (a *App) GithubSync() error { return a.Save() } + +func (a *App) ListLabels() ([]string, error) { + owner, repo := a.Campaign.RepoComponents() + opts := &github.ListOptions{Page: 0, PerPage: 100} + labels, _, err := a.GithubClient.Issues.ListLabels(context.Background(), owner, repo, opts) + if err != nil { + return nil, err + } + + strLabels := make([]string, len(labels)) + for i, label := range labels { + strLabels[i] = *label.Name + } + + return strLabels, nil +} + +func (a *App) CheckLabels(labels []string) (bool, []string, error) { + ghLabels, err := a.ListLabels() + if err != nil { + return false, nil, err + } + + badLabels := []string{} + for _, label := range labels { + exists := false + for _, ghLabel := range ghLabels { + if label == ghLabel { + exists = true + } + } + + if !exists { + badLabels = append(badLabels, label) + } + } + + if len(badLabels) == 0 { + return true, nil, nil + } + return false, badLabels, nil +} diff --git a/cmd/label.go b/cmd/label.go new file mode 100644 index 0000000..af9b478 --- /dev/null +++ b/cmd/label.go @@ -0,0 +1,141 @@ +package cmd + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "strings" + + "github.com/fatih/color" + "github.com/spf13/cobra" + + "git.ctrlz.es/mgdelacroix/campaigner/app" +) + +const defaultEditor = "vim" + +func ListLabelCmd() *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "List the local campaign labels", + Args: cobra.NoArgs, + Run: withApp(listLabelCmdF), + } +} + +func RemoteLabelCmd() *cobra.Command { + return &cobra.Command{ + Use: "remote", + Short: "List all the GitHub repository labels", + Args: cobra.NoArgs, + Run: withApp(remoteLabelCmdF), + } +} + +func UpdateLabelCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "update", + Short: "Updates the campaign GitHub labels", + Args: cobra.NoArgs, + Run: withApp(updateLabelCmdF), + } + + cmd.Flags().BoolP("skip-check", "s", false, "do not check if the labels exist in the remote repository") + + return cmd +} + +func LabelCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "label", + Short: "Commands to manage GitHub labels", + } + + cmd.AddCommand( + ListLabelCmd(), + RemoteLabelCmd(), + UpdateLabelCmd(), + ) + + return cmd +} + +func listLabelCmdF(a *app.App, cmd *cobra.Command, _ []string) { + for _, label := range a.Campaign.Github.Labels { + fmt.Println(label) + } +} + +func remoteLabelCmdF(a *app.App, cmd *cobra.Command, _ []string) { + labels, err := a.ListLabels() + if err != nil { + ErrorAndExit(cmd, fmt.Errorf("cannot retrieve labels list: %w", err)) + } + + fmt.Printf("Labels for repository %s:\n\n", color.GreenString(a.Campaign.Github.Repo)) + for _, label := range labels { + fmt.Println(label) + } +} + +func updateLabelCmdF(a *app.App, cmd *cobra.Command, _ []string) { + skipCheck, _ := cmd.Flags().GetBool("skip-check") + + file, err := ioutil.TempFile(os.TempDir(), "campaigner-") + if err != nil { + ErrorAndExit(cmd, fmt.Errorf("cannot create temp file: %w", err)) + } + defer func() { + file.Close() + os.Remove(file.Name()) + }() + labelBytes := []byte(strings.Join(a.Campaign.Github.Labels, "\n")) + if _, err := file.Write(labelBytes); err != nil { + ErrorAndExit(cmd, fmt.Errorf("cannot write labels to temp file: %w", err)) + } + + editor := os.Getenv("EDITOR") + if editor == "" { + editor = defaultEditor + } + + editorCmd := exec.Command(editor, file.Name()) + editorCmd.Stdout = cmd.OutOrStdout() + editorCmd.Stdin = cmd.InOrStdin() + editorCmd.Stderr = cmd.ErrOrStderr() + + if err := editorCmd.Run(); err != nil { + ErrorAndExit(cmd, fmt.Errorf("cannot run editor command: %w", err)) + } + + newLabelBytes, err := ioutil.ReadFile(file.Name()) + if err != nil { + ErrorAndExit(cmd, fmt.Errorf("cannot read file: %w", err)) + } + + newLabels := []string{} + for _, label := range strings.Split(string(newLabelBytes), "\n") { + if label != "" { + newLabels = append(newLabels, strings.TrimSpace(label)) + } + } + + if !skipCheck { + ok, badLabels, err := a.CheckLabels(newLabels) + if err != nil { + ErrorAndExit(cmd, fmt.Errorf("cannot check new labels list: %w", err)) + } + + if !ok { + ErrorAndExit(cmd, fmt.Errorf("these labels doesn't exist in the repository:\n\n%s", strings.Join(badLabels, "\n"))) + } + } + + a.Campaign.Github.Labels = newLabels + if err := a.Save(); err != nil { + ErrorAndExit(cmd, fmt.Errorf("cannot save campaign: %w", err)) + } + + cmd.Println("Labels successfully updated") +} diff --git a/cmd/root.go b/cmd/root.go index 5775ddf..5a7f861 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -47,6 +47,7 @@ func RootCmd() *cobra.Command { AddCmd(), // FilterCmd(), InitCmd(), + LabelCmd(), StatusCmd(), PublishCmd(), PullCmd(), diff --git a/model/campaign.go b/model/campaign.go index 75946c4..8c5f29e 100644 --- a/model/campaign.go +++ b/model/campaign.go @@ -80,10 +80,10 @@ func (c *Campaign) PrintStatus() { w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.AlignRight) fmt.Fprintf(w, " %d\t-\ttotal tickets\t\n", totalTickets) - fmt.Fprintf(w, " %d\t%d%%\tpublished in Jira\t\n", totalPublishedJira, totalPublishedJira * 100 / totalTickets) - fmt.Fprintf(w, " %d\t%d%%\tpublished in Github\t\n", totalPublishedGithub, totalPublishedGithub * 100 / totalTickets) - fmt.Fprintf(w, " %d\t%d%%\tassigned\t\n", totalAssigned, totalAssigned * 100 / totalTickets) - fmt.Fprintf(w, " %d\t%d%%\tclosed\t\n\n", totalClosed, totalClosed * 100 / totalTickets) + fmt.Fprintf(w, " %d\t%d%%\tpublished in Jira\t\n", totalPublishedJira, totalPublishedJira*100/totalTickets) + fmt.Fprintf(w, " %d\t%d%%\tpublished in Github\t\n", totalPublishedGithub, totalPublishedGithub*100/totalTickets) + fmt.Fprintf(w, " %d\t%d%%\tassigned\t\n", totalAssigned, totalAssigned*100/totalTickets) + fmt.Fprintf(w, " %d\t%d%%\tclosed\t\n\n", totalClosed, totalClosed*100/totalTickets) w.Flush() }