jira-cli/internal/cmd/issue/link/link.go

233 lines
5.4 KiB
Go

package link
import (
"fmt"
"os"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/ankitpokhrel/jira-cli/api"
"github.com/ankitpokhrel/jira-cli/internal/cmd/issue/link/remote"
"github.com/ankitpokhrel/jira-cli/internal/cmdutil"
"github.com/ankitpokhrel/jira-cli/internal/query"
"github.com/ankitpokhrel/jira-cli/pkg/jira"
)
const (
helpText = `Link connects two issues to a given link type.`
examples = `$ jira issue link ISSUE-1 ISSUE-2 Duplicate`
optionCancel = "Cancel"
)
// NewCmdLink is a link command.
func NewCmdLink() *cobra.Command {
cmd := cobra.Command{
Use: "link INWARD_ISSUE_KEY OUTWARD_ISSUE_KEY ISSUE_LINK_TYPE",
Short: "Link connects two issues",
Long: helpText,
Example: examples,
Aliases: []string{"ln"},
Annotations: map[string]string{
"help:args": "INWARD_ISSUE_KEY\tIssue key of the source issue, eg: ISSUE-1\n" +
"OUTWARD_ISSUE_KEY\tIssue key of the target issue, eg: ISSUE-2\n" +
"ISSUE_LINK_TYPE\tRelationship between two issues, eg: Duplicates, Blocks etc.",
},
Run: link,
}
cmd.AddCommand(remote.NewCmdRemoteLink())
cmd.PersistentFlags().Bool("web", false, "Open issue in web browser after successful linking")
return &cmd
}
func link(cmd *cobra.Command, args []string) {
project := viper.GetString("project.key")
params := parseArgsAndFlags(cmd.Flags(), args, project)
client := api.DefaultClient(params.debug)
lc := linkCmd{
client: client,
linkTypes: nil,
params: params,
}
cmdutil.ExitIfError(lc.setInwardIssueKey(project))
cmdutil.ExitIfError(lc.setOutwardIssueKey(project))
cmdutil.ExitIfError(lc.setLinkTypes())
cmdutil.ExitIfError(lc.setDesiredLinkType())
if lc.params.linkType == optionCancel {
cmdutil.Fail("Action aborted")
os.Exit(0)
}
lt, err := lc.verifyIssueLinkType()
if err != nil {
fmt.Println()
cmdutil.Failed("Error: %s", err.Error())
return
}
err = func() error {
s := cmdutil.Info("Linking issues")
defer s.Stop()
return client.LinkIssue(lc.params.inwardIssueKey, lc.params.outwardIssueKey, lt.Name)
}()
cmdutil.ExitIfError(err)
server := viper.GetString("server")
cmdutil.Success("Issues linked as %q", lc.params.linkType)
fmt.Printf("%s\n", cmdutil.GenerateServerBrowseURL(server, lc.params.inwardIssueKey))
if web, _ := cmd.Flags().GetBool("web"); web {
err := cmdutil.Navigate(server, lc.params.inwardIssueKey)
cmdutil.ExitIfError(err)
}
}
type linkParams struct {
inwardIssueKey string
outwardIssueKey string
linkType string
debug bool
}
func parseArgsAndFlags(flags query.FlagParser, args []string, project string) *linkParams {
var inwardIssueKey, outwardIssueKey, linkType string
nargs := len(args)
if nargs >= 1 {
inwardIssueKey = cmdutil.GetJiraIssueKey(project, args[0])
}
if nargs >= 2 {
outwardIssueKey = cmdutil.GetJiraIssueKey(project, args[1])
}
if nargs >= 3 {
linkType = args[2]
}
debug, err := flags.GetBool("debug")
cmdutil.ExitIfError(err)
return &linkParams{
inwardIssueKey: inwardIssueKey,
outwardIssueKey: outwardIssueKey,
linkType: linkType,
debug: debug,
}
}
type linkCmd struct {
client *jira.Client
linkTypes []*jira.IssueLinkType
params *linkParams
}
func (lc *linkCmd) setInwardIssueKey(project string) error {
if lc.params.inwardIssueKey != "" {
return nil
}
var ans string
qs := &survey.Question{
Name: "inwardIssueKey",
Prompt: &survey.Input{Message: "Inward issue key"},
Validate: survey.Required,
}
if err := survey.Ask([]*survey.Question{qs}, &ans); err != nil {
return err
}
lc.params.inwardIssueKey = cmdutil.GetJiraIssueKey(project, ans)
return nil
}
func (lc *linkCmd) setOutwardIssueKey(project string) error {
if lc.params.outwardIssueKey != "" {
return nil
}
var ans string
qs := &survey.Question{
Name: "outwardIssueKey",
Prompt: &survey.Input{Message: "Outward issue key"},
Validate: survey.Required,
}
if err := survey.Ask([]*survey.Question{qs}, &ans); err != nil {
return err
}
lc.params.outwardIssueKey = cmdutil.GetJiraIssueKey(project, ans)
return nil
}
func (lc *linkCmd) setLinkTypes() error {
s := cmdutil.Info("Fetching link types. Please wait...")
defer s.Stop()
lt, err := lc.client.GetIssueLinkTypes()
if err != nil {
return err
}
lc.linkTypes = lt
return nil
}
func (lc *linkCmd) setDesiredLinkType() error {
if lc.params.linkType != "" {
return nil
}
options := make([]string, 0, len(lc.linkTypes))
for _, t := range lc.linkTypes {
options = append(options, fmt.Sprintf("%s: %s", t.Name, t.Inward))
}
options = append(options, optionCancel)
var ans string
qs := &survey.Question{
Name: "linkType",
Prompt: &survey.Select{
Message: "Link type:",
Options: options,
},
Validate: survey.Required,
}
if err := survey.Ask([]*survey.Question{qs}, &ans); err != nil {
return err
}
lc.params.linkType = strings.Split(ans, ":")[0]
return nil
}
func (lc *linkCmd) verifyIssueLinkType() (*jira.IssueLinkType, error) {
var lt *jira.IssueLinkType
st := strings.ToLower(lc.params.linkType)
all := make([]string, 0, len(lc.linkTypes))
for _, t := range lc.linkTypes {
if strings.ToLower(t.Name) == st {
lt = t
}
all = append(all, fmt.Sprintf("'%s'", t.Name))
}
if lt == nil {
return nil, fmt.Errorf(
"invalid issue link type %q\nAvailable issue link types are: %s",
lc.params.linkType, strings.Join(all, ", "),
)
}
return lt, nil
}