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: 26 additions & 0 deletions docs/src/content/docs/configuration/issue-section.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ title: Issue Sections
Defines a section in the dashboard's issues view.

- Every section must define a [`title`] and [`filters`].
- When you define [`host`] for a section, that section fetches data from that GitHub host.
- When you define [`limit`] for a section, that value overrides the
[`defaults.issuesLimit`] setting.
- When you define [`layout`] for a section, that value overrides the
[`defaults.layout.issue`] setting.

[`title`]: #issues-title-title
[`filters`]: #issues-filters-filters
[`host`]: #github-host-host
[`limit`]: #issues-fetch-limit-limit
[`layout`]: #issues-section-layout-layout
[`defaults.issuesLimit`]: /configuration/defaults/#issue-fetch-limit-issueslimit
Expand Down Expand Up @@ -50,6 +52,30 @@ For more information about writing filters for searching GitHub, see [Searching]

[Searching]: /configuration/searching

## GitHub Host (`host`)

| Type | Default |
| :----- | :------ |
| String | (empty) |

This setting specifies the GitHub host for this section. Use this when you need to fetch
issues from a GitHub Enterprise instance or a different GitHub host.

When not specified or empty, the section uses the default GitHub host (github.com or the
host configured in your `gh` CLI).

### Example

```yaml
issuesSections:
- title: Enterprise Issues
filters: is:open author:@me
host: github.enterprise.com
- title: GitHub.com Issues
filters: is:open author:@me
# no host = default (github.com)
```

## Issues Section Layout (`layout`)

You can define how a Issues section displays items in its table by setting options for the
Expand Down
26 changes: 26 additions & 0 deletions docs/src/content/docs/configuration/notification-section.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ title: Notification Sections
Defines sections in the dashboard's Notifications view.

- Every section must define a [`title`] and [`filters`].
- When you define [`host`] for a section, that section fetches data from that GitHub host.
- When you define [`limit`] for a section, that value overrides the
[`defaults.notificationsLimit`] setting.

[`title`]: #notification-title-title
[`filters`]: #notification-filters-filters
[`host`]: #github-host-host
[`limit`]: #notification-fetch-limit-limit
[`defaults.notificationsLimit`]: /configuration/defaults/#notifications-fetch-limit-notificationslimit

Expand Down Expand Up @@ -116,6 +118,30 @@ This setting defines the filters for notifications in the section's table. Notif
- **Explicit `is:unread`**: Shows only unread notifications, excluding bookmarked read notifications
- **Reason filters**: Applied client-side after fetching from GitHub's API

## GitHub Host (`host`)

| Type | Default |
| :----- | :------ |
| String | (empty) |

This setting specifies the GitHub host for this section. Use this when you need to fetch
notifications from a GitHub Enterprise instance or a different GitHub host.

When not specified or empty, the section uses the default GitHub host (github.com or the
host configured in your `gh` CLI).

### Example

```yaml
notificationsSections:
- title: Enterprise Notifications
filters: ""
host: github.enterprise.com
- title: GitHub.com Notifications
filters: ""
# no host = default (github.com)
```

## Notification Fetch Limit (`limit`)

| Type | Minimum | Default |
Expand Down
26 changes: 26 additions & 0 deletions docs/src/content/docs/configuration/pr-section.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ title: PR Sections
Defines a section in the dashboard's PRs view.

- Every section must define a [`title`] and [`filters`].
- When you define [`host`] for a section, that section fetches data from that GitHub host.
- When you define [`limit`] for a section, that value overrides the
[`defaults.prsLimit`] setting.
- When you define [`layout`] for a section, that value overrides the
[`defaults.layout.pr`] setting.

[`title`]: #pr-title-title
[`filters`]: #pr-filters-filters
[`host`]: #github-host-host
[`limit`]: #pr-fetch-limit-limit
[`layout`]: #pr-section-layout-layout
[`defaults.prsLimit`]: /configuration/defaults/#pr-fetch-limit
Expand Down Expand Up @@ -50,6 +52,30 @@ For more information about writing filters for searching GitHub, see [Searching]

[Searching]: /configuration/searching

## GitHub Host (`host`)

| Type | Default |
| :----- | :------ |
| String | (empty) |

This setting specifies the GitHub host for this section. Use this when you need to fetch
PRs from a GitHub Enterprise instance or a different GitHub host.

When not specified or empty, the section uses the default GitHub host (github.com or the
host configured in your `gh` CLI).

### Example

```yaml
prSections:
- title: Enterprise PRs
filters: is:open author:@me
host: github.enterprise.com
- title: GitHub.com PRs
filters: is:open author:@me
# no host = default (github.com)
```

## PR Section Layout (`layout`)

You can define how a PR section displays items in its table by setting options for the
Expand Down
6 changes: 5 additions & 1 deletion internal/config/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,15 @@ const (
type SectionConfig struct {
Title string
Filters string
Host string `yaml:"host,omitempty"`
Limit *int `yaml:"limit,omitempty"`
Type *ViewType `yaml:"type,omitempty"`
}

type PrsSectionConfig struct {
Title string
Filters string
Host string `yaml:"host,omitempty"`
Limit *int `yaml:"limit,omitempty"`
Layout PrsLayoutConfig `yaml:"layout,omitempty"`
Type *ViewType `yaml:"type,omitempty"`
Expand All @@ -95,14 +97,16 @@ type PrsSectionConfig struct {
type IssuesSectionConfig struct {
Title string
Filters string
Host string `yaml:"host,omitempty"`
Limit *int `yaml:"limit,omitempty"`
Layout IssuesLayoutConfig `yaml:"layout,omitempty"`
}

type NotificationsSectionConfig struct {
Title string
Filters string
Limit *int `yaml:"limit,omitempty"`
Host string `yaml:"host,omitempty"`
Limit *int `yaml:"limit,omitempty"`
}

type PreviewConfig struct {
Expand Down
54 changes: 54 additions & 0 deletions internal/config/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,57 @@ func setupConfigEnvVar(t *testing.T) func() {
os.Unsetenv("GH_DASH_CONFIG")
}
}

func TestHostFieldParsing(t *testing.T) {
t.Run("Should parse host field in section configs", func(t *testing.T) {
clearEnv := setXDGConfigHomeEnvVar(t, "testdata")
defer clearEnv()

cwd := Testwd(t)
parsed, err := ParseConfig(Location{
ConfigFlag: path.Join(cwd, "./testdata/host-config.yml"),
})
testutils.AssertNoError(t, err)

// Check PR sections
require.Len(t, parsed.PRSections, 2)
assert.Equal(t, "github.enterprise.com", parsed.PRSections[0].Host)
assert.Equal(t, "", parsed.PRSections[1].Host) // no host = empty string

// Check Issues sections
require.Len(t, parsed.IssuesSections, 2)
assert.Equal(t, "github.enterprise.com", parsed.IssuesSections[0].Host)
assert.Equal(t, "", parsed.IssuesSections[1].Host)

// Check Notifications sections
require.Len(t, parsed.NotificationsSections, 2)
assert.Equal(t, "github.enterprise.com", parsed.NotificationsSections[0].Host)
assert.Equal(t, "", parsed.NotificationsSections[1].Host)
})

t.Run("ToSectionConfig should include host field", func(t *testing.T) {
prCfg := PrsSectionConfig{
Title: "Test",
Filters: "is:open",
Host: "github.enterprise.com",
}
sectionCfg := prCfg.ToSectionConfig()
assert.Equal(t, "github.enterprise.com", sectionCfg.Host)

issuesCfg := IssuesSectionConfig{
Title: "Test",
Filters: "is:open",
Host: "github.enterprise.com",
}
sectionCfg = issuesCfg.ToSectionConfig()
assert.Equal(t, "github.enterprise.com", sectionCfg.Host)

notifCfg := NotificationsSectionConfig{
Title: "Test",
Filters: "",
Host: "github.enterprise.com",
}
sectionCfg = notifCfg.ToSectionConfig()
assert.Equal(t, "github.enterprise.com", sectionCfg.Host)
})
}
22 changes: 22 additions & 0 deletions internal/config/testdata/host-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Test config for per-section host field
prSections:
- title: Enterprise PRs
filters: is:open author:@me
host: github.enterprise.com
- title: GitHub.com PRs
filters: is:open author:@me
# no host = default

issuesSections:
- title: Enterprise Issues
filters: is:open author:@me
host: github.enterprise.com
- title: GitHub.com Issues
filters: is:open author:@me

notificationsSections:
- title: Enterprise Notifications
filters: ""
host: github.enterprise.com
- title: GitHub.com Notifications
filters: ""
3 changes: 3 additions & 0 deletions internal/config/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func (cfg PrsSectionConfig) ToSectionConfig() SectionConfig {
return SectionConfig{
Title: cfg.Title,
Filters: cfg.Filters,
Host: cfg.Host,
Limit: cfg.Limit,
Type: cfg.Type,
}
Expand All @@ -41,6 +42,7 @@ func (cfg IssuesSectionConfig) ToSectionConfig() SectionConfig {
return SectionConfig{
Title: cfg.Title,
Filters: cfg.Filters,
Host: cfg.Host,
Limit: cfg.Limit,
}
}
Expand All @@ -49,6 +51,7 @@ func (cfg NotificationsSectionConfig) ToSectionConfig() SectionConfig {
return SectionConfig{
Title: cfg.Title,
Filters: cfg.Filters,
Host: cfg.Host,
Limit: cfg.Limit,
}
}
Expand Down
13 changes: 10 additions & 3 deletions internal/data/issueapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,17 @@ func makeIssuesQuery(query string) string {
return fmt.Sprintf("is:issue %s sort:updated", query)
}

func FetchIssues(query string, limit int, pageInfo *PageInfo) (IssuesResponse, error) {
func FetchIssues(query string, limit int, pageInfo *PageInfo, host string) (IssuesResponse, error) {
var err error
if client == nil {
var c *gh.GraphQLClient

if host != "" {
c, err = gh.NewGraphQLClient(gh.ClientOptions{Host: host})
} else if client == nil {
client, err = gh.DefaultGraphQLClient()
c = client
} else {
c = client
}

if err != nil {
Expand All @@ -123,7 +130,7 @@ func FetchIssues(query string, limit int, pageInfo *PageInfo) (IssuesResponse, e
"endCursor": (*graphql.String)(endCursor),
}
log.Debug("Fetching issues", "query", query, "limit", limit, "endCursor", endCursor)
err = client.Query("SearchIssues", &queryResult, variables)
err = c.Query("SearchIssues", &queryResult, variables)
if err != nil {
return IssuesResponse{}, err
}
Expand Down
11 changes: 9 additions & 2 deletions internal/data/notificationapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ func getRESTClient() (*gh.RESTClient, error) {
return restClient, err
}

func getRESTClientForHost(host string) (*gh.RESTClient, error) {
if host == "" {
return getRESTClient()
}
return gh.NewRESTClient(gh.ClientOptions{Host: host})
}

// NotificationReadState represents the read state filter for notifications
type NotificationReadState string

Expand All @@ -117,8 +124,8 @@ const (
NotificationStateAll NotificationReadState = "all" // Both read and unread
)

func FetchNotifications(limit int, repoFilters []string, readState NotificationReadState, pageInfo *PageInfo) (NotificationsResponse, error) {
client, err := getRESTClient()
func FetchNotifications(limit int, repoFilters []string, readState NotificationReadState, pageInfo *PageInfo, host string) (NotificationsResponse, error) {
client, err := getRESTClientForHost(host)
if err != nil {
return NotificationsResponse{}, err
}
Expand Down
13 changes: 10 additions & 3 deletions internal/data/prapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,16 +448,23 @@ func SetClient(c *gh.GraphQLClient) {
cachedClient = c
}

func FetchPullRequests(query string, limit int, pageInfo *PageInfo) (PullRequestsResponse, error) {
func FetchPullRequests(query string, limit int, pageInfo *PageInfo, host string) (PullRequestsResponse, error) {
var err error
if client == nil {
var c *gh.GraphQLClient

if host != "" {
c, err = gh.NewGraphQLClient(gh.ClientOptions{Host: host})
} else if client == nil {
if config.IsFeatureEnabled(config.FF_MOCK_DATA) {
log.Info("using mock data", "server", "https://localhost:3000")
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
client, err = gh.NewGraphQLClient(gh.ClientOptions{Host: "localhost:3000", AuthToken: "fake-token"})
} else {
client, err = gh.DefaultGraphQLClient()
}
c = client
} else {
c = client
}

if err != nil {
Expand All @@ -483,7 +490,7 @@ func FetchPullRequests(query string, limit int, pageInfo *PageInfo) (PullRequest
"endCursor": (*graphql.String)(endCursor),
}
log.Debug("Fetching PRs", "query", query, "limit", limit, "endCursor", endCursor)
err = client.Query("SearchPullRequests", &queryResult, variables)
err = c.Query("SearchPullRequests", &queryResult, variables)
if err != nil {
return PullRequestsResponse{}, err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/tui/components/issuessection/issuessection.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ func (m *Model) FetchNextPageSectionRows() []tea.Cmd {
if limit == nil {
limit = &m.Ctx.Config.Defaults.IssuesLimit
}
res, err := data.FetchIssues(m.GetFilters(), *limit, m.PageInfo)
res, err := data.FetchIssues(m.GetFilters(), *limit, m.PageInfo, m.Config.Host)
if err != nil {
return constants.TaskFinishedMsg{
SectionId: m.Id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,9 @@ func (m *Model) FetchNextPageSectionRows() []tea.Cmd {
// Capture config limit for the closure
limit := m.Ctx.Config.Defaults.NotificationsLimit

// Capture host for the closure
host := m.Config.Host

// Build reason filter map for O(1) lookup
reasonFilterMap := make(map[string]bool, len(filters.ReasonFilters))
for _, reason := range filters.ReasonFilters {
Expand Down Expand Up @@ -613,7 +616,7 @@ func (m *Model) FetchNextPageSectionRows() []tea.Cmd {
var lastPageInfo data.PageInfo
isFirstPage := pageInfo == nil
for {
res, err := data.FetchNotifications(limit, filters.RepoFilters, readState, currentPageInfo)
res, err := data.FetchNotifications(limit, filters.RepoFilters, readState, currentPageInfo, host)
if err != nil {
return constants.TaskFinishedMsg{
SectionId: m.Id,
Expand Down
Loading