mirror of https://gitlab.com/gitlab-org/cli.git
182 lines
4.5 KiB
Go
182 lines
4.5 KiB
Go
package issueutils
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gitlab.com/gitlab-org/cli/pkg/iostreams"
|
|
|
|
"gitlab.com/gitlab-org/cli/api"
|
|
"gitlab.com/gitlab-org/cli/internal/config"
|
|
"gitlab.com/gitlab-org/cli/internal/glrepo"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"gitlab.com/gitlab-org/cli/pkg/tableprinter"
|
|
"gitlab.com/gitlab-org/cli/pkg/utils"
|
|
|
|
"github.com/xanzy/go-gitlab"
|
|
)
|
|
|
|
func DisplayIssueList(streams *iostreams.IOStreams, issues []*gitlab.Issue, projectID string) string {
|
|
c := streams.Color()
|
|
table := tableprinter.NewTablePrinter()
|
|
table.SetIsTTY(streams.IsOutputTTY())
|
|
for _, issue := range issues {
|
|
table.AddCell(streams.Hyperlink(IssueState(c, issue), issue.WebURL))
|
|
table.AddCell(issue.References.Full)
|
|
table.AddCell(issue.Title)
|
|
|
|
if len(issue.Labels) > 0 {
|
|
table.AddCellf("(%s)", c.Cyan(strings.Trim(strings.Join(issue.Labels, ", "), ",")))
|
|
} else {
|
|
table.AddCell("")
|
|
}
|
|
|
|
table.AddCell(c.Gray(utils.TimeToPrettyTimeAgo(*issue.CreatedAt)))
|
|
table.EndRow()
|
|
}
|
|
|
|
return table.Render()
|
|
}
|
|
|
|
func DisplayIssue(c *iostreams.ColorPalette, i *gitlab.Issue, isTTY bool) string {
|
|
duration := utils.TimeToPrettyTimeAgo(*i.CreatedAt)
|
|
issueID := IssueState(c, i)
|
|
|
|
if isTTY {
|
|
return fmt.Sprintf("%s %s (%s)\n %s\n", issueID, i.Title, duration, i.WebURL)
|
|
} else {
|
|
return i.WebURL
|
|
}
|
|
}
|
|
|
|
func IssueState(c *iostreams.ColorPalette, i *gitlab.Issue) (issueID string) {
|
|
if i.State == "opened" {
|
|
issueID = c.Green(fmt.Sprintf("#%d", i.IID))
|
|
} else {
|
|
issueID = c.Red(fmt.Sprintf("#%d", i.IID))
|
|
}
|
|
return
|
|
}
|
|
|
|
func IssuesFromArgs(apiClient *gitlab.Client, baseRepoFn func() (glrepo.Interface, error), args []string) ([]*gitlab.Issue, glrepo.Interface, error) {
|
|
baseRepo, err := baseRepoFn()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if len(args) <= 1 {
|
|
if len(args) == 1 {
|
|
args = strings.Split(args[0], ",")
|
|
}
|
|
if len(args) <= 1 {
|
|
issue, repo, err := IssueFromArg(apiClient, baseRepoFn, args[0])
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
baseRepo = repo
|
|
return []*gitlab.Issue{issue}, baseRepo, err
|
|
}
|
|
}
|
|
|
|
errGroup, _ := errgroup.WithContext(context.Background())
|
|
issues := make([]*gitlab.Issue, len(args))
|
|
for i, arg := range args {
|
|
i, arg := i, arg
|
|
errGroup.Go(func() error {
|
|
issue, repo, err := IssueFromArg(apiClient, baseRepoFn, arg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
baseRepo = repo
|
|
issues[i] = issue
|
|
return nil
|
|
})
|
|
}
|
|
if err := errGroup.Wait(); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return issues, baseRepo, nil
|
|
}
|
|
|
|
func IssueFromArg(apiClient *gitlab.Client, baseRepoFn func() (glrepo.Interface, error), arg string) (*gitlab.Issue, glrepo.Interface, error) {
|
|
issueIID, baseRepo := issueMetadataFromURL(arg)
|
|
if issueIID == 0 {
|
|
var err error
|
|
issueIID, err = strconv.Atoi(strings.TrimPrefix(arg, "#"))
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("invalid issue format: %q", arg)
|
|
}
|
|
}
|
|
|
|
if baseRepo == nil {
|
|
var err error
|
|
baseRepo, err = baseRepoFn()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("could not determine base repo: %w", err)
|
|
}
|
|
} else {
|
|
// initialize a new HTTP Client with the new host
|
|
// TODO: avoid reinitializing the config, get the config as a parameter
|
|
|
|
cfg, _ := config.Init()
|
|
a, err := api.NewClientWithCfg(baseRepo.RepoHost(), cfg, false)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
apiClient = a.Lab()
|
|
}
|
|
|
|
issue, err := issueFromIID(apiClient, baseRepo, issueIID)
|
|
return issue, baseRepo, err
|
|
}
|
|
|
|
// FIXME: have a single regex to match either of the following
|
|
//
|
|
// OWNER/REPO/issues/id
|
|
// GROUP/NAMESPACE/REPO/issues/id
|
|
var (
|
|
issueURLPersonalRE = regexp.MustCompile(`^/([^/]+)/([^/]+)/issues/(\d+)`)
|
|
issueURLGroupRE = regexp.MustCompile(`^/([^/]+)/([^/]+)/([^/]+)/issues/(\d+)`)
|
|
)
|
|
|
|
func issueMetadataFromURL(s string) (int, glrepo.Interface) {
|
|
u, err := url.Parse(s)
|
|
if err != nil {
|
|
return 0, nil
|
|
}
|
|
|
|
if u.Scheme != "https" && u.Scheme != "http" {
|
|
return 0, nil
|
|
}
|
|
|
|
u.Path = strings.Replace(u.Path, "/-/issues", "/issues", 1)
|
|
|
|
m := issueURLPersonalRE.FindStringSubmatch(u.Path)
|
|
if m == nil {
|
|
m = issueURLGroupRE.FindStringSubmatch(u.Path)
|
|
if m == nil {
|
|
return 0, nil
|
|
}
|
|
}
|
|
var issueIID int
|
|
if len(m) > 0 {
|
|
issueIID, _ = strconv.Atoi(m[len(m)-1])
|
|
}
|
|
|
|
u.Path = strings.Replace(u.Path, fmt.Sprintf("/issues/%d", issueIID), "", 1)
|
|
|
|
repo, err := glrepo.FromURL(u)
|
|
if err != nil {
|
|
return 0, nil
|
|
}
|
|
return issueIID, repo
|
|
}
|
|
|
|
func issueFromIID(apiClient *gitlab.Client, repo glrepo.Interface, issueIID int) (*gitlab.Issue, error) {
|
|
return api.GetIssue(apiClient, repo.FullName(), issueIID)
|
|
}
|