Skip to content

Commit

Permalink
feat(718): issue view: --raw option (#720)
Browse files Browse the repository at this point in the history
Co-authored-by: Mykhailo Palahuta <michaeller.2012@gmail.com>
  • Loading branch information
mpalahuta and Drofff committed Apr 6, 2024
1 parent df2a86c commit 53a3d56
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 38 deletions.
9 changes: 9 additions & 0 deletions api/client.go
Expand Up @@ -95,6 +95,15 @@ func ProxyCreate(c *jira.Client, cr *jira.CreateRequest) (*jira.CreateResponse,
return resp, err
}

// ProxyGetIssueRaw executes the same request as ProxyGetIssue but returns raw API response body string.
func ProxyGetIssueRaw(c *jira.Client, key string) (string, error) {
it := viper.GetString("installation")
if it == jira.InstallationTypeLocal {
return c.GetIssueV2Raw(key)
}
return c.GetIssueRaw(key)
}

// ProxyGetIssue uses either a v2 or v3 version of the Jira GET /issue/{key}
// endpoint to fetch the issue details based on configured installation type.
// Defaults to v3 if installation type is not defined in the config.
Expand Down
63 changes: 54 additions & 9 deletions internal/cmd/issue/view/view.go
@@ -1,6 +1,8 @@
package view

import (
"fmt"

"github.com/spf13/cobra"
"github.com/spf13/viper"

Expand All @@ -16,7 +18,20 @@ const (
examples = `$ jira issue view ISSUE-1
# Show 5 recent comments when viewing the issue
$ jira issue view ISSUE-1 --comments 5`
$ jira issue view ISSUE-1 --comments 5
# Get the raw JSON data
$ jira issue view ISSUE-1 --raw`

flagRaw = "raw"
flagDebug = "debug"
flagComments = "comments"
flagPlain = "plain"

configProject = "project.key"
configServer = "server"

messageFetchingData = "Fetching issue details..."
)

// NewCmdView is a view command.
Expand All @@ -34,34 +49,64 @@ func NewCmdView() *cobra.Command {
Run: view,
}

cmd.Flags().Uint("comments", 1, "Show N comments")
cmd.Flags().Bool("plain", false, "Display output in plain mode")
cmd.Flags().Uint(flagComments, 1, "Show N comments")
cmd.Flags().Bool(flagPlain, false, "Display output in plain mode")
cmd.Flags().Bool(flagRaw, false, "Print raw Jira API response. Set this flag if you want to process the issue contents using a program")

return &cmd
}

func view(cmd *cobra.Command, args []string) {
debug, err := cmd.Flags().GetBool("debug")
raw, err := cmd.Flags().GetBool(flagRaw)
cmdutil.ExitIfError(err)

if raw {
viewRaw(cmd, args)
return
}
viewPretty(cmd, args)
}

func viewRaw(cmd *cobra.Command, args []string) {
debug, err := cmd.Flags().GetBool(flagDebug)
cmdutil.ExitIfError(err)

key := cmdutil.GetJiraIssueKey(viper.GetString(configProject), args[0])

apiResp, err := func() (string, error) {
s := cmdutil.Info(messageFetchingData)
defer s.Stop()

client := api.DefaultClient(debug)
return api.ProxyGetIssueRaw(client, key)
}()
cmdutil.ExitIfError(err)

fmt.Println(apiResp)
}

func viewPretty(cmd *cobra.Command, args []string) {
debug, err := cmd.Flags().GetBool(flagDebug)
cmdutil.ExitIfError(err)

comments, err := cmd.Flags().GetUint("comments")
comments, err := cmd.Flags().GetUint(flagComments)
cmdutil.ExitIfError(err)

key := cmdutil.GetJiraIssueKey(viper.GetString("project.key"), args[0])
key := cmdutil.GetJiraIssueKey(viper.GetString(configProject), args[0])
iss, err := func() (*jira.Issue, error) {
s := cmdutil.Info("Fetching issue details...")
s := cmdutil.Info(messageFetchingData)
defer s.Stop()

client := api.DefaultClient(debug)
return api.ProxyGetIssue(client, key, issue.NewNumCommentsFilter(comments))
}()
cmdutil.ExitIfError(err)

plain, err := cmd.Flags().GetBool("plain")
plain, err := cmd.Flags().GetBool(flagPlain)
cmdutil.ExitIfError(err)

v := tuiView.Issue{
Server: viper.GetString("server"),
Server: viper.GetString(configServer),
Data: iss,
Display: tuiView.DisplayFormat{Plain: plain},
Options: tuiView.IssueOption{NumComments: comments},
Expand Down
80 changes: 54 additions & 26 deletions pkg/jira/issue.go
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"

"github.com/ankitpokhrel/jira-cli/pkg/jira/filter/issue"

Expand All @@ -26,15 +28,55 @@ const (

// GetIssue fetches issue details using GET /issue/{key} endpoint.
func (c *Client) GetIssue(key string, opts ...filter.Filter) (*Issue, error) {
return c.getIssue(key, apiVersion3, opts)
iss, err := c.getIssue(key, apiVersion3)
if err != nil {
return nil, err
}

iss.Fields.Description = ifaceToADF(iss.Fields.Description)

total := iss.Fields.Comment.Total
limit := filter.Collection(opts).GetInt(issue.KeyIssueNumComments)
if limit > total {
limit = total
}
for i := total - 1; i >= total-limit; i-- {
body := iss.Fields.Comment.Comments[i].Body
iss.Fields.Comment.Comments[i].Body = ifaceToADF(body)
}
return iss, nil
}

// GetIssueV2 fetches issue details using v2 version of Jira GET /issue/{key} endpoint.
func (c *Client) GetIssueV2(key string, opts ...filter.Filter) (*Issue, error) {
return c.getIssue(key, apiVersion2, opts)
func (c *Client) GetIssueV2(key string, _ ...filter.Filter) (*Issue, error) {
return c.getIssue(key, apiVersion2)
}

func (c *Client) getIssue(key, ver string) (*Issue, error) {
rawOut, err := c.getIssueRaw(key, ver)
if err != nil {
return nil, err
}

var iss Issue
err = json.Unmarshal([]byte(rawOut), &iss)
if err != nil {
return nil, err
}
return &iss, nil
}

// GetIssueRaw fetches issue details same as GetIssue but returns the raw API response body string.
func (c *Client) GetIssueRaw(key string) (string, error) {
return c.getIssueRaw(key, apiVersion3)
}

// GetIssueV2Raw fetches issue details same as GetIssueV2 but returns the raw API response body string.
func (c *Client) GetIssueV2Raw(key string) (string, error) {
return c.getIssueRaw(key, apiVersion2)
}

func (c *Client) getIssue(key, ver string, opts filter.Collection) (*Issue, error) {
func (c *Client) getIssueRaw(key, ver string) (string, error) {
path := fmt.Sprintf("/issue/%s", key)

var (
Expand All @@ -50,37 +92,23 @@ func (c *Client) getIssue(key, ver string, opts filter.Collection) (*Issue, erro
}

if err != nil {
return nil, err
return "", err
}
if res == nil {
return nil, ErrEmptyResponse
return "", ErrEmptyResponse
}
defer func() { _ = res.Body.Close() }()

if res.StatusCode != http.StatusOK {
return nil, formatUnexpectedResponse(res)
return "", formatUnexpectedResponse(res)
}

var out Issue
if err := json.NewDecoder(res.Body).Decode(&out); err != nil {
return nil, err
}

if ver == apiVersion3 {
out.Fields.Description = ifaceToADF(out.Fields.Description)

total := out.Fields.Comment.Total
limit := opts.GetInt(issue.KeyIssueNumComments)
if limit > total {
limit = total
}
for i := total - 1; i >= total-limit; i-- {
body := out.Fields.Comment.Comments[i].Body
out.Fields.Comment.Comments[i].Body = ifaceToADF(body)
}
var b strings.Builder
_, err = io.Copy(&b, res.Body)
if err != nil {
return "", err
}

return &out, nil
return b.String(), nil
}

// AssignIssue assigns issue to the user using v3 version of the PUT /issue/{key}/assignee endpoint.
Expand Down

0 comments on commit 53a3d56

Please sign in to comment.