Skip to content

Commit

Permalink
feat: Add weblink to issue (#483)
Browse files Browse the repository at this point in the history
Fixes #446
  • Loading branch information
HamzaSayadi authored Oct 15, 2022
1 parent 045d65a commit 60f2a02
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 1 deletion.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,17 @@ $ jira issue link
$ jira issue link ISSUE-1 ISSUE-2 Blocks
```

##### Remote
The `remote` command lets you add a remote web link to an issue.

```sh
# Adds a remote web link using an interactive prompt
$ jira issue link remote

# Pass required parameters to skip prompt
$ jira issue link remote ISSUE-1 https://example.com "Example text"
```

#### Unlink
The `unlink` command lets you unlink two linked issues.

Expand Down
4 changes: 3 additions & 1 deletion internal/cmd/issue/link/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"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"
Expand Down Expand Up @@ -37,7 +38,8 @@ func NewCmdLink() *cobra.Command {
Run: link,
}

cmd.Flags().Bool("web", false, "Open inward issue in web browser after successful linking")
cmd.AddCommand(remote.NewCmdRemoteLink())
cmd.PersistentFlags().Bool("web", false, "Open issue in web browser after successful linking")

return &cmd
}
Expand Down
166 changes: 166 additions & 0 deletions internal/cmd/issue/link/remote/remote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package remote

import (
"fmt"

"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/cmdutil"
"github.com/ankitpokhrel/jira-cli/internal/query"
"github.com/ankitpokhrel/jira-cli/pkg/jira"
)

const (
helpText = `Adds a remote web link to an issue`
examples = `$ jira issue link remote ISSUE-1 http://weblink.com weblink-title`
)

// NewCmdRemoteLink is a link command.
func NewCmdRemoteLink() *cobra.Command {
cmd := cobra.Command{
Use: "remote ISSUE_KEY WEBLINK_URL WEBLINK_TITLE",
Short: "Adds a remote web link to an issue",
Long: helpText,
Example: examples,
Aliases: []string{"rmln"},
Annotations: map[string]string{
"help:args": "ISSUE_KEY\tIssue key, eg: ISSUE-1\n" +
"WEBLINK_URL\tUrl of the weblink\n" +
"WEBLINK_TITLE\tTitle of the weblink",
},
Run: remotelink,
}

return &cmd
}

func remotelink(cmd *cobra.Command, args []string) {
project := viper.GetString("project.key")
params := parseArgsAndFlags(cmd.Flags(), args, project)
client := api.Client(jira.Config{Debug: params.debug})
lc := linkCmd{
client: client,
params: params,
}

cmdutil.ExitIfError(lc.setIssueKey(project))
cmdutil.ExitIfError(lc.setRemoteLinkURL())
cmdutil.ExitIfError(lc.setRemoteLinkTitle())

err := func() error {
s := cmdutil.Info("Creating remote web link for issue")
defer s.Stop()

return client.RemoteLinkIssue(lc.params.issueKey, lc.params.title, lc.params.url)
}()
cmdutil.ExitIfError(err)

server := viper.GetString("server")

cmdutil.Success("Remote web link created for Issue %s", lc.params.issueKey)
fmt.Printf("%s/browse/%s\n", server, lc.params.issueKey)

if web, _ := cmd.Flags().GetBool("web"); web {
err := cmdutil.Navigate(server, lc.params.issueKey)
cmdutil.ExitIfError(err)
}
}

type linkParams struct {
issueKey string
url string
title string
debug bool
}

func parseArgsAndFlags(flags query.FlagParser, args []string, project string) *linkParams {
var issueKey, url, title string
nargs := len(args)
if nargs >= 1 {
issueKey = cmdutil.GetJiraIssueKey(project, args[0])
}
if nargs >= 2 {
url = args[1]
}
if nargs >= 3 {
title = args[2]
}

debug, err := flags.GetBool("debug")
cmdutil.ExitIfError(err)

return &linkParams{
issueKey: issueKey,
url: url,
title: title,
debug: debug,
}
}

type linkCmd struct {
client *jira.Client
params *linkParams
}

func (lc *linkCmd) setIssueKey(project string) error {
if lc.params.issueKey != "" {
return nil
}

var ans string

qs := &survey.Question{
Name: "issueKey",
Prompt: &survey.Input{Message: "Issue key"},
Validate: survey.Required,
}
if err := survey.Ask([]*survey.Question{qs}, &ans); err != nil {
return err
}
lc.params.issueKey = cmdutil.GetJiraIssueKey(project, ans)

return nil
}

func (lc *linkCmd) setRemoteLinkURL() error {
if lc.params.url != "" {
return nil
}

var ans string

qs := &survey.Question{
Name: "remoteLinkURL",
Prompt: &survey.Input{Message: "Remote link URL"},
Validate: survey.Required,
}
if err := survey.Ask([]*survey.Question{qs}, &ans); err != nil {
return err
}
lc.params.url = ans

return nil
}

func (lc *linkCmd) setRemoteLinkTitle() error {
if lc.params.title != "" {
return nil
}

var ans string

qs := &survey.Question{
Name: "remoteLinkTitle",
Prompt: &survey.Input{Message: "Remote link title"},
Validate: survey.Required,
}
if err := survey.Ask([]*survey.Question{qs}, &ans); err != nil {
return err
}
lc.params.title = ans

return nil
}
39 changes: 39 additions & 0 deletions pkg/jira/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,42 @@ func ifaceToADF(v interface{}) *adf.ADF {

return doc
}

type remotelinkRequest struct {
RemoteObject struct {
URL string `json:"url"`
Title string `json:"title"`
} `json:"object"`
}

// RemoteLinkIssue adds a remote link to an issue using POST /issue/{issueId}/remotelink endpoint.
func (c *Client) RemoteLinkIssue(issueID, title, url string) error {
body, err := json.Marshal(remotelinkRequest{
RemoteObject: struct {
URL string `json:"url"`
Title string `json:"title"`
}{Title: title, URL: url},
})
if err != nil {
return err
}

path := fmt.Sprintf("/issue/%s/remotelink", issueID)

res, err := c.PostV2(context.Background(), path, body, Header{
"Accept": "application/json",
"Content-Type": "application/json",
})
if err != nil {
return err
}
if res == nil {
return ErrEmptyResponse
}
defer func() { _ = res.Body.Close() }()

if res.StatusCode != http.StatusCreated {
return formatUnexpectedResponse(res)
}
return nil
}
28 changes: 28 additions & 0 deletions pkg/jira/issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -526,3 +526,31 @@ func TestGetField(t *testing.T) {
_, err = client.GetField()
assert.NotNil(t, err)
}

func TestRemoteLinkIssue(t *testing.T) {
var unexpectedStatusCode bool

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/rest/api/2/issue/TEST-1/remotelink", r.URL.Path)
assert.Equal(t, http.MethodPost, r.Method)
assert.Equal(t, "application/json", r.Header.Get("Accept"))
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))

if unexpectedStatusCode {
w.WriteHeader(400)
} else {
w.WriteHeader(201)
}
}))
defer server.Close()

client := NewClient(Config{Server: server.URL}, WithTimeout(3*time.Second))

err := client.RemoteLinkIssue("TEST-1", "weblink title", "http://weblink.com")
assert.NoError(t, err)

unexpectedStatusCode = true

err = client.RemoteLinkIssue("TEST-1", "weblink title", "https://weblink.com")
assert.Error(t, &ErrUnexpectedResponse{}, err)
}

0 comments on commit 60f2a02

Please sign in to comment.