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

294 lines
6.9 KiB
Go

package clone
import (
"fmt"
"strings"
"sync"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/ankitpokhrel/jira-cli/api"
"github.com/ankitpokhrel/jira-cli/internal/cmdutil"
"github.com/ankitpokhrel/jira-cli/internal/query"
"github.com/ankitpokhrel/jira-cli/pkg/adf"
"github.com/ankitpokhrel/jira-cli/pkg/jira"
)
const (
helpText = `Clone duplicates an issue and also allow you to override some of the metadata when doing so.`
examples = `$ jira issue clone ISSUE-1
# Clone issue and modify the summary, priority and assignee
$ jira issue clone ISSUE-1 -s"Modified summary" -yHigh -a$(jira me)
# Clone issue and replace text from summary and description
$ jira issue clone ISSUE-1 -H"find me:replace with me"`
)
// NewCmdClone is a clone command.
func NewCmdClone() *cobra.Command {
cmd := cobra.Command{
Use: "clone ISSUE-KEY",
Short: "Clone duplicates an issue",
Long: helpText,
Example: examples,
Annotations: map[string]string{
"help:args": "ISSUE-KEY\tKey of the issue to clone, eg: ISSUE-1",
},
Args: cobra.MinimumNArgs(1),
Run: clone,
}
setFlags(&cmd)
return &cmd
}
func clone(cmd *cobra.Command, args []string) {
server := viper.GetString("server")
project := viper.GetString("project.key")
projectType := viper.GetString("project.type")
params := parseFlags(cmd.Flags())
client := api.DefaultClient(params.debug)
cc := cloneCmd{
client: client,
params: params,
}
key := cmdutil.GetJiraIssueKey(project, args[0])
issue, err := func() (*jira.Issue, error) {
s := cmdutil.Info("Fetching issue details...")
defer s.Stop()
issue, err := api.ProxyGetIssue(client, key)
if err != nil {
return nil, err
}
return issue, nil
}()
cmdutil.ExitIfError(err)
cp := cc.getActualCreateParams(project, issue)
clonedIssueKey, err := func() (string, error) {
s := cmdutil.Info(fmt.Sprintf("Cloning %s...", key))
defer s.Stop()
cr := jira.CreateRequest{
Project: project,
IssueType: issue.Fields.IssueType.Name,
ParentIssueKey: cp.parent,
Summary: cp.summary,
Body: cp.body,
Priority: cp.priority,
Labels: cp.labels,
Components: cp.components,
}
cr.ForProjectType(projectType)
resp, err := api.ProxyCreate(client, &cr)
if err != nil {
return "", err
}
return resp.Key, nil
}()
cmdutil.ExitIfError(err)
cmdutil.Success("Issue cloned\n%s", cmdutil.GenerateServerBrowseURL(server, clonedIssueKey))
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
if err := client.LinkIssue(key, clonedIssueKey, "Cloners"); err != nil {
fmt.Println()
cmdutil.Failed("Unable to link cloned issue")
}
}()
if cp.assignee != "" {
wg.Add(1)
go func() {
defer wg.Done()
user, err := api.ProxyUserSearch(client, &jira.UserSearchOptions{
Query: cp.assignee,
Project: project,
})
if err != nil || len(user) == 0 {
fmt.Println()
cmdutil.Failed("Unable to find assignee")
}
if err = api.ProxyAssignIssue(client, clonedIssueKey, user[0], jira.AssigneeDefault); err != nil {
fmt.Println()
cmdutil.Failed("Unable to set assignee: %s", err.Error())
}
}()
}
s := cmdutil.Info("Updating metadata...")
defer s.Stop()
if web, _ := cmd.Flags().GetBool("web"); web {
err := cmdutil.Navigate(server, clonedIssueKey)
cmdutil.Fail(err.Error())
}
wg.Wait()
}
type createParams struct {
parent string
summary string
body interface{}
priority string
assignee string
labels []string
components []string
}
type cloneCmd struct {
client *jira.Client
params *cloneParams
}
func (cc *cloneCmd) getActualCreateParams(project string, issue *jira.Issue) *createParams {
cp := createParams{}
if cc.params.parent != "" {
cp.parent = cmdutil.GetJiraIssueKey(project, cc.params.parent)
} else if issue.Fields.Parent != nil {
cp.parent = issue.Fields.Parent.Key
}
cp.summary = issue.Fields.Summary
if cc.params.summary != "" {
cp.summary = cc.params.summary
}
cp.priority = issue.Fields.Priority.Name
if cc.params.priority != "" {
cp.priority = cc.params.priority
}
cp.assignee = issue.Fields.Assignee.Name
if cc.params.assignee != "" {
cp.assignee = cc.params.assignee
}
cp.labels = issue.Fields.Labels
if len(cc.params.labels) > 0 {
cp.labels = cc.params.labels
}
components := make([]string, 0, len(issue.Fields.Components))
for _, v := range issue.Fields.Components {
components = append(components, v.Name)
}
cp.components = components
if len(cc.params.components) > 0 {
cp.components = cc.params.components
}
var (
body interface{}
isADF bool
)
if issue.Fields.Description != nil {
body, isADF = issue.Fields.Description.(*adf.ADF)
if !isADF {
body = issue.Fields.Description.(string)
}
} else {
body = ""
}
if cc.params.replace != "" {
pieces := strings.Split(cc.params.replace, ":")
if len(pieces) != 2 {
fmt.Println()
cmdutil.Fail("Invalid replace string, must be in format <find>:<replace>. Skipping replacement...")
} else {
from, to := pieces[0], pieces[1]
cp.summary = strings.ReplaceAll(cp.summary, from, to)
if isADF {
body.(*adf.ADF).ReplaceAll(from, to)
} else {
body = strings.ReplaceAll(body.(string), from, to)
}
}
}
cp.body = body
return &cp
}
type cloneParams struct {
parent string
summary string
priority string
assignee string
labels []string
components []string
replace string
debug bool
}
func parseFlags(flags query.FlagParser) *cloneParams {
parent, err := flags.GetString("parent")
cmdutil.ExitIfError(err)
summary, err := flags.GetString("summary")
cmdutil.ExitIfError(err)
priority, err := flags.GetString("priority")
cmdutil.ExitIfError(err)
assignee, err := flags.GetString("assignee")
cmdutil.ExitIfError(err)
labels, err := flags.GetStringArray("label")
cmdutil.ExitIfError(err)
components, err := flags.GetStringArray("component")
cmdutil.ExitIfError(err)
replace, err := flags.GetString("replace")
cmdutil.ExitIfError(err)
debug, err := flags.GetBool("debug")
cmdutil.ExitIfError(err)
return &cloneParams{
parent: parent,
summary: summary,
priority: priority,
assignee: assignee,
labels: labels,
components: components,
replace: replace,
debug: debug,
}
}
func setFlags(cmd *cobra.Command) {
cmd.Flags().SortFlags = false
cmd.Flags().StringP("parent", "P", "", "Parent issue key")
cmd.Flags().StringP("summary", "s", "", "Issue summary or title")
cmd.Flags().StringP("priority", "y", "", "Issue priority")
cmd.Flags().StringP("assignee", "a", "", "Issue assignee (email or display name)")
cmd.Flags().StringArrayP("label", "l", []string{}, "Issue labels")
cmd.Flags().StringArrayP("component", "C", []string{}, "Issue components")
cmd.Flags().StringP("replace", "H", "", "Replace strings in summary and body. Format <search>:<replace>, eg: \"find me:replace with me\"")
cmd.Flags().Bool("web", false, "Open in web browser after successful cloning")
}