Skip to content

Commit

Permalink
add githuballrepos
Browse files Browse the repository at this point in the history
  • Loading branch information
k2589 committed Oct 10, 2024
1 parent 6a02b97 commit ce5d1b0
Show file tree
Hide file tree
Showing 8 changed files with 868 additions and 0 deletions.
4 changes: 4 additions & 0 deletions app/widget_maker.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/wtfutil/wtf/modules/gerrit"
"github.com/wtfutil/wtf/modules/git"
"github.com/wtfutil/wtf/modules/github"
"github.com/wtfutil/wtf/modules/githuballrepos"
"github.com/wtfutil/wtf/modules/gitlab"
"github.com/wtfutil/wtf/modules/gitlabtodo"
"github.com/wtfutil/wtf/modules/gitter"
Expand Down Expand Up @@ -198,6 +199,9 @@ func MakeWidget(
case "github":
settings := github.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = github.NewWidget(tviewApp, redrawChan, pages, settings)
case "githuballrepos":
settings := githuballrepos.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = githuballrepos.NewWidget(tviewApp, redrawChan, pages, settings)
case "gitlab":
settings := gitlab.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = gitlab.NewWidget(tviewApp, redrawChan, pages, settings)
Expand Down
107 changes: 107 additions & 0 deletions modules/githuballrepos/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package githuballrepos

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"

"io/fs"

"github.com/wtfutil/wtf/logger"
)

type Cacher interface {
Get() *WidgetData
Set(data *WidgetData)
IsValid() bool
}

const cacheDuration = 5 * time.Minute

// Cache stores the widget data and its expiration time
type Cache struct {
data *WidgetData
expires time.Time
configPath string
}

// NewCache creates a new Cache instance
func NewCache(configPath string) *Cache {
cache := &Cache{
configPath: configPath,
}

// Ensure the cache directory exists
cacheDir := filepath.Dir(cache.configPath)
logger.Log(fmt.Sprintf("Cache directory: %s\n", cacheDir))
if err := os.MkdirAll(cacheDir, 0755); err != nil {
logger.Log(fmt.Sprintf("Error creating cache directory: %s\n", err))
}

cache.load()
return cache
}

// Set updates the cache with new data
func (c *Cache) Set(data *WidgetData) {
c.data = data
c.expires = time.Now().Add(cacheDuration)
c.save()
}

// Get retrieves the cached data
func (c *Cache) Get() *WidgetData {
return c.data
}

// IsValid checks if the cache is still valid
func (c *Cache) IsValid() bool {
return c.data != nil && time.Now().Before(c.expires)
}

// save writes the cache data to disk
func (c *Cache) save() {
cacheData := struct {
Data *WidgetData `json:"data"`
Expires time.Time `json:"expires"`
}{
Data: c.data,
Expires: c.expires,
}

jsonData, err := json.Marshal(cacheData)
if err != nil {
logger.Log(fmt.Sprintf("Error marshaling cache data: %s\n", err))
return
}

if err := os.WriteFile(c.configPath, jsonData, fs.FileMode(0644)); err != nil {
logger.Log(fmt.Sprintf("Error writing cache file: %s\n", err))
}
}

// load reads the cache data from disk
func (c *Cache) load() {
jsonData, err := os.ReadFile(c.configPath)
if err != nil {
if !os.IsNotExist(err) {
logger.Log(fmt.Sprintf("Error reading cache file: %s\n", err))
}
return
}

var cacheData struct {
Data *WidgetData `json:"data"`
Expires time.Time `json:"expires"`
}

if err := json.Unmarshal(jsonData, &cacheData); err != nil {
logger.Log(fmt.Sprintf("Error unmarshaling cache data: %s\n", err))
return
}

c.data = cacheData.Data
c.expires = cacheData.Expires
}
146 changes: 146 additions & 0 deletions modules/githuballrepos/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package githuballrepos

import (
"context"
"fmt"

"github.com/shurcooL/githubv4"
"golang.org/x/oauth2"
)

type GitHubFetcher interface {
FetchData(orgs []string, username string) *WidgetData
}

// GitHubClient handles communication with the GitHub API
type GitHubClient struct {
client *githubv4.Client
}

// NewGitHubClient creates a new GitHubClient
func NewGitHubClient(token string) *GitHubClient {
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
httpClient := oauth2.NewClient(context.Background(), src)

return &GitHubClient{
client: githubv4.NewClient(httpClient),
}
}

// FetchData retrieves all required data from GitHub
func (c *GitHubClient) FetchData(orgs []string, username string) *WidgetData {
data := &WidgetData{
MyPRs: make([]PR, 0),
PRReviewRequests: make([]PR, 0),
WatchedPRs: make([]PR, 0),
}

for _, org := range orgs {
data.PRsOpenedByMe += c.fetchPRCount(org, username, "author")
data.PRReviewRequestsCount += c.fetchPRCount(org, username, "review-requested")
data.OpenIssuesCount += c.fetchIssueCount(org)

data.MyPRs = append(data.MyPRs, c.fetchPRs(org, username, "author")...)
data.PRReviewRequests = append(data.PRReviewRequests, c.fetchPRs(org, username, "review-requested")...)
data.WatchedPRs = append(data.WatchedPRs, c.fetchPRs(org, username, "involves")...)
}

return data
}

func (c *GitHubClient) fetchPRCount(org, username, filter string) int {
var query struct {
Search struct {
IssueCount int
} `graphql:"search(query: $query, type: ISSUE, first: 0)"`
}

variables := map[string]interface{}{
"query": githubv4.String(fmt.Sprintf("org:%s is:pr is:open %s:%s", org, filter, username)),
}

err := c.client.Query(context.Background(), &query, variables)
if err != nil {
// Handle error (log it, etc.)
return 0
}

return query.Search.IssueCount
}

func (c *GitHubClient) fetchIssueCount(org string) int {
var query struct {
Search struct {
IssueCount int
} `graphql:"search(query: $query, type: ISSUE, first: 0)"`
}

variables := map[string]interface{}{
"query": githubv4.String(fmt.Sprintf("org:%s is:issue is:open", org)),
}

err := c.client.Query(context.Background(), &query, variables)
if err != nil {
// Handle error (log it, etc.)
return 0
}

return query.Search.IssueCount
}

func (c *GitHubClient) fetchPRs(org, username, filter string) []PR {
var query struct {
Search struct {
Nodes []struct {
PullRequest struct {
Title string
URL string
Author struct {
Login string
}
Repository struct {
Name string
}
} `graphql:"... on PullRequest"`
}
PageInfo struct {
EndCursor githubv4.String
HasNextPage bool
}
} `graphql:"search(query: $query, type: ISSUE, first: 100, after: $cursor)"`
}

variables := map[string]interface{}{
"query": githubv4.String(fmt.Sprintf("org:%s is:pr is:open %s:%s", org, filter, username)),
"cursor": (*githubv4.String)(nil), // Null for first request
}

var allPRs []PR

for {
err := c.client.Query(context.Background(), &query, variables)
if err != nil {
// Handle error (log it, etc.)
break
}

for _, node := range query.Search.Nodes {
pr := node.PullRequest
allPRs = append(allPRs, PR{
Title: pr.Title,
URL: pr.URL,
Author: pr.Author.Login,
Repository: pr.Repository.Name,
})
}

if !query.Search.PageInfo.HasNextPage {
break
}
variables["cursor"] = githubv4.NewString(query.Search.PageInfo.EndCursor)
}

return allPRs
}
57 changes: 57 additions & 0 deletions modules/githuballrepos/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package githuballrepos

import (
"fmt"
"strings"
)

// WidgetData holds all the data for the widget
type WidgetData struct {
PRsOpenedByMe int
PRReviewRequestsCount int
OpenIssuesCount int

MyPRs []PR
PRReviewRequests []PR
WatchedPRs []PR
}

// PR represents a single pull request
type PR struct {
Title string
URL string
Author string
Repository string
}

// FormatCounters returns a formatted string of counters
func (d *WidgetData) FormatCounters() string {
return fmt.Sprintf(
"PRs opened by me: %d\nPR review requests: %d\nOpen issues: %d\n",
d.PRsOpenedByMe,
d.PRReviewRequestsCount,
d.OpenIssuesCount,
)
}

// FormatPRs returns a formatted string of PRs
func (d *WidgetData) FormatPRs() string {
var sb strings.Builder

sb.WriteString("[green]My PRs:[white]\n")
for _, pr := range d.MyPRs {
sb.WriteString(fmt.Sprintf("- %s (%s)\n", pr.Title, pr.Repository))
}

sb.WriteString("\n[yellow]PR Review Requests:[white]\n")
for _, pr := range d.PRReviewRequests {
sb.WriteString(fmt.Sprintf("- %s (%s)\n", pr.Title, pr.Repository))
}

sb.WriteString("\n[blue]Watched PRs:[white]\n")
for _, pr := range d.WatchedPRs {
sb.WriteString(fmt.Sprintf("- %s (%s by %s)\n", pr.Title, pr.Repository, pr.Author))
}

return sb.String()
}
Loading

0 comments on commit ce5d1b0

Please sign in to comment.