Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/dlvhdr/gh-dash/v4/internal/config"
"github.com/dlvhdr/gh-dash/v4/internal/git"
"github.com/dlvhdr/gh-dash/v4/internal/provider"
"github.com/dlvhdr/gh-dash/v4/internal/tui"
"github.com/dlvhdr/gh-dash/v4/internal/tui/constants"
dctx "github.com/dlvhdr/gh-dash/v4/internal/tui/context"
Expand All @@ -35,16 +36,17 @@ var (
)

var (
cfgFlag string
cfgFlag string
gitlabHost string

logo = lipgloss.NewStyle().Foreground(dctx.LogoColor).MarginBottom(1).SetString(constants.Logo)

rootCmd = &cobra.Command{
Use: "gh dash",
Long: lipgloss.JoinVertical(lipgloss.Left, logo.Render(),
"A rich terminal UI for GitHub that doesn't break your flow.",
"A rich terminal UI for GitHub/GitLab that doesn't break your flow.",
"Visit https://gh-dash.dev for the docs."),
Short: "A rich terminal UI for GitHub that doesn't break your flow.",
Short: "A rich terminal UI for GitHub/GitLab that doesn't break your flow.",
Version: "",
Example: `
# Running without arguments will either:
Expand All @@ -58,6 +60,9 @@ gh dash --config /path/to/configuration/file.yml
# Run with debug logging to debug.log
gh dash --debug

# Connect to a GitLab instance
gh dash --gitlab gitlab.example.com

# Print version
gh dash -v
`,
Expand Down Expand Up @@ -182,7 +187,22 @@ func init() {
"help for gh-dash",
)

rootCmd.Flags().StringVar(
&gitlabHost,
"gitlab",
"",
"GitLab hostname (e.g., gitlab.example.com) - enables GitLab mode",
)

rootCmd.Run = func(_ *cobra.Command, args []string) {
// Set up the provider based on flags
gitlabHostFlag, _ := rootCmd.Flags().GetString("gitlab")
if gitlabHostFlag != "" {
log.Info("Using GitLab provider", "host", gitlabHostFlag)
provider.SetProvider(provider.NewGitLabProvider(gitlabHostFlag))
} else {
provider.SetProvider(provider.NewGitHubProvider())
}
var repo string
repos := config.IsFeatureEnabled(config.FF_REPO_VIEW)
if repos && len(args) > 0 {
Expand Down
45 changes: 45 additions & 0 deletions gitlab-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# GitLab configuration for gh-dash
# Use with: ./gh-dash --gitlab gitlab.krone.at --config ./gitlab-config.yml
#
# Note: GitLab requires specifying a repo in the filters using 'repo:GROUP/PROJECT'

keybindings:
prs:
- key: tab
builtin: nextSidebarTab
- key: shift+tab
builtin: prevSidebarTab

prSections:
- title: Test MRs
filters: repo:hjanuschka/hjlab
- title: My Merge Requests
filters: repo:KRN/MGMT author:@me
- title: All Open MRs
filters: repo:KRN/MGMT

issuesSections:
- title: Test Issues
filters: repo:hjanuschka/hjlab
- title: My Issues
filters: repo:KRN/MGMT author:@me
- title: Assigned to Me
filters: repo:KRN/MGMT assignee:@me

defaults:
preview:
open: true
width: 50
prsLimit: 20
issuesLimit: 20
view: prs
refetchIntervalMinutes: 30

theme:
ui:
sectionsShowCount: true
table:
showSeparator: true
compact: false

confirmQuit: false
6 changes: 2 additions & 4 deletions internal/config/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ func (cfg Config) GetFullScreenDiffPagerEnv() []string {
env = append(
env,
"LESS=CRX",
fmt.Sprintf(
"GH_PAGER=%s",
diff,
),
fmt.Sprintf("GH_PAGER=%s", diff),
fmt.Sprintf("GLAB_PAGER=%s", diff), // For GitLab CLI
)

return env
Expand Down
103 changes: 103 additions & 0 deletions internal/data/issueapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
graphql "github.com/cli/shurcooL-graphql"
"github.com/shurcooL/githubv4"

"github.com/dlvhdr/gh-dash/v4/internal/provider"
"github.com/dlvhdr/gh-dash/v4/internal/tui/theme"
)

Expand Down Expand Up @@ -100,6 +101,11 @@ func makeIssuesQuery(query string) string {
}

func FetchIssues(query string, limit int, pageInfo *PageInfo) (IssuesResponse, error) {
// Use GitLab provider if configured
if provider.IsGitLab() {
return fetchIssuesFromGitLab(query, limit, pageInfo)
}

var err error
if client == nil {
client, err = gh.DefaultGraphQLClient()
Expand Down Expand Up @@ -146,6 +152,79 @@ func FetchIssues(query string, limit int, pageInfo *PageInfo) (IssuesResponse, e
}, nil
}

// fetchIssuesFromGitLab fetches issues from GitLab and converts them to the internal format
func fetchIssuesFromGitLab(query string, limit int, pageInfo *PageInfo) (IssuesResponse, error) {
p := provider.GetProvider()

var providerPageInfo *provider.PageInfo
if pageInfo != nil {
providerPageInfo = &provider.PageInfo{
HasNextPage: pageInfo.HasNextPage,
StartCursor: pageInfo.StartCursor,
EndCursor: pageInfo.EndCursor,
}
}

resp, err := p.FetchIssues(query, limit, providerPageInfo)
if err != nil {
return IssuesResponse{}, err
}

issues := make([]IssueData, len(resp.Issues))
for i, issue := range resp.Issues {
issues[i] = convertProviderIssueToData(issue)
}

return IssuesResponse{
Issues: issues,
TotalCount: resp.TotalCount,
PageInfo: PageInfo{
HasNextPage: resp.PageInfo.HasNextPage,
StartCursor: resp.PageInfo.StartCursor,
EndCursor: resp.PageInfo.EndCursor,
},
}, nil
}

// convertProviderIssueToData converts provider.IssueData to data.IssueData
func convertProviderIssueToData(issue provider.IssueData) IssueData {
assignees := make([]Assignee, len(issue.Assignees.Nodes))
for i, a := range issue.Assignees.Nodes {
assignees[i] = Assignee{Login: a.Login}
}

labels := make([]Label, len(issue.Labels.Nodes))
for i, l := range issue.Labels.Nodes {
labels[i] = Label{Name: l.Name, Color: l.Color}
}

comments := make([]IssueComment, len(issue.Comments.Nodes))
for i, c := range issue.Comments.Nodes {
comments[i] = IssueComment{
Author: struct{ Login string }{Login: c.Author.Login},
Body: c.Body,
UpdatedAt: c.UpdatedAt,
}
}

return IssueData{
Number: issue.Number,
Title: issue.Title,
Body: issue.Body,
State: issue.State,
Author: struct{ Login string }{Login: issue.Author.Login},
AuthorAssociation: issue.AuthorAssociation,
UpdatedAt: issue.UpdatedAt,
CreatedAt: issue.CreatedAt,
Url: issue.Url,
Repository: Repository{NameWithOwner: issue.Repository.NameWithOwner, IsArchived: issue.Repository.IsArchived},
Assignees: Assignees{Nodes: assignees},
Comments: IssueComments{TotalCount: issue.Comments.TotalCount, Nodes: comments},
Reactions: IssueReactions{TotalCount: issue.Reactions.TotalCount},
Labels: IssueLabels{Nodes: labels},
}
}

type IssuesResponse struct {
Issues []IssueData
TotalCount int
Expand Down Expand Up @@ -183,3 +262,27 @@ func FetchIssue(issueUrl string) (IssueData, error) {

return queryResult.Resource.Issue, nil
}

// FetchIssueComments fetches comments for a single issue (GitLab only)
func FetchIssueComments(issueUrl string) ([]IssueComment, error) {
if !provider.IsGitLab() {
return nil, nil // GitHub fetches comments in the main query
}

p := provider.GetProvider()
providerComments, err := p.FetchIssueComments(issueUrl)
if err != nil {
return nil, err
}

comments := make([]IssueComment, len(providerComments))
for i, c := range providerComments {
comments[i] = IssueComment{
Author: struct{ Login string }{Login: c.Author.Login},
Body: c.Body,
UpdatedAt: c.UpdatedAt,
}
}

return comments, nil
}
Loading