Skip to content

Commit

Permalink
feat: add reviewer/author mode (#1)
Browse files Browse the repository at this point in the history
These modes are catered towards browsing PRs that are to be
reviewed or are authored by the logged in user.
  • Loading branch information
dhth authored May 23, 2024
1 parent 51e91ca commit 2d5fe56
Show file tree
Hide file tree
Showing 12 changed files with 543 additions and 170 deletions.
81 changes: 49 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ sources:
```bash
prs
prs -config-file /path/to/config.yml
prs -mode=reviewer # to only see PRs requesting your review
prs -mode=author # to only see PRs authored by you
```

Screenshots
Expand All @@ -80,38 +82,53 @@ Reference Manual
---

```
prs Reference Manual
prs has 5 views:
- PR List View
- PR Timeline List View
- PR Review Comments View
- Repo List View
- Help View (this one)
Keyboard Shortcuts:
General
<tab> Switch focus between PR List and PR Timeline Pane
1 Switch focus to PR List View
2 Switch focus to PR Timeline List View
3 Switch focus to PR Review Comments View
<ctrl+s> Switch focus to Repo List View
? Switch focus to Help View
PR List/Timeline List View
<ctrl+v> Show PR details
<ctrl+d> Show PR diff
PR List View
<ctrl+b> Open PR in the browser
<ctrl+r> Reload PR list
<enter> Switch focus to PR Timeline View for currently selected PR
PR Timeline View
<ctrl+b> Open timeline item in browser
<ctrl+r> Reload timeline list
<enter> Switch focus to Review Comments View for currently selected item
prs Reference Manual
(scroll line by line with j/k/arrow keys or by half a page with <c-d>/<c-u>)
prs has 5 views:
- PR List View
- PR Timeline List View
- PR Review Comments View
- Repo List View (only applicable when -mode=repos)
- Help View (this one)
Keyboard Shortcuts
General
<tab> Switch focus between PR List and PR Timeline Pane
1 Switch focus to PR List View
2 Switch focus to PR Timeline List View
3 Switch focus to PR Review Comments View
<ctrl+s> Switch focus to Repo List View
? Switch focus to Help View
PR List/Timeline List View
<ctrl+v> Show PR details
<ctrl+d> Show PR diff
PR List View
Indicators for current review
decision:
± implies CHANGES_REQUESTED
🟡 implies REVIEW_REQUIRED
✅ implies APPROVED
<ctrl+b> Open PR in the browser
<ctrl+r> Reload PR list
<enter> Switch focus to PR Timeline View for currently selected PR
<enter> Show commit/revision range
PR Timeline View
<ctrl+b> Open timeline item in browser
<ctrl+r> Reload timeline list
<enter> Switch focus to Review Comments View for currently selected item
```

Acknowledgements
Expand Down
18 changes: 17 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import (
"github.com/dhth/prs/ui"
)

var (
modeFlag = flag.String("mode", "repos", "mode to run prs in; values: repos, reviewer, author")
)

func die(msg string, args ...any) {
fmt.Fprintf(os.Stderr, msg+"\n", args...)
os.Exit(1)
Expand Down Expand Up @@ -50,5 +54,17 @@ func Execute() {
die(cfgErrSuggestion("Error: no repos found in config file"))
}

ui.RenderUI(config)
var mode ui.Mode
switch *modeFlag {
case "repos":
mode = ui.RepoMode
case "reviewer":
mode = ui.ReviewerMode
case "author":
mode = ui.AuthorMode
default:
die("unknown mode provided; possible values: repos, reviewer, author")
}

ui.RenderUI(config, mode)
}
49 changes: 35 additions & 14 deletions ui/cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import (

func chooseRepo(repo string) tea.Cmd {
return func() tea.Msg {
return RepoChosenMsg{repo}
return repoChosenMsg{repo}
}
}

func choosePR(prNumberStr string) tea.Cmd {
return func() tea.Msg {
prNumber, err := strconv.Atoi(prNumberStr)
return PRChosenMsg{prNumber, err}
return prChosenMsg{prNumber, err}
}
}

Expand All @@ -35,9 +35,9 @@ func openURLInBrowser(url string) tea.Cmd {
c := exec.Command(openCmd, url)
return tea.ExecProcess(c, func(err error) tea.Msg {
if err != nil {
return URLOpenedinBrowserMsg{url: url, err: err}
return urlOpenedinBrowserMsg{url: url, err: err}
}
return tea.Msg(URLOpenedinBrowserMsg{url: url})
return tea.Msg(urlOpenedinBrowserMsg{url: url})
})
}

Expand All @@ -56,9 +56,9 @@ func showDiff(repoOwner, repoName string, prNumber int, pager *string) tea.Cmd {
))
return tea.ExecProcess(c, func(err error) tea.Msg {
if err != nil {
return PRDiffDoneMsg{err: err}
return prDiffDoneMsg{err: err}
}
return tea.Msg(PRDiffDoneMsg{})
return tea.Msg(prDiffDoneMsg{})
})
}

Expand All @@ -71,28 +71,49 @@ func showPR(repoOwner, repoName string, prNumber int) tea.Cmd {
))
return tea.ExecProcess(c, func(err error) tea.Msg {
if err != nil {
return PRDiffDoneMsg{err: err}
return prDiffDoneMsg{err: err}
}
return tea.Msg(PRDiffDoneMsg{})
return tea.Msg(prDiffDoneMsg{})
})
}

func hideHelp(interval time.Duration) tea.Cmd {
return tea.Tick(interval, func(time.Time) tea.Msg {
return HideHelpMsg{}
return hideHelpMsg{}
})
}

func fetchPRS(ghClient *ghapi.GraphQLClient, repoOwner string, repoName string, prCount int) tea.Cmd {
return func() tea.Msg {
prs, err := GetPRs(ghClient, repoOwner, repoName, prCount)
return PRsFetchedMsg{prs, err}
prs, err := getPRs(ghClient, repoOwner, repoName, prCount)
return prsFetchedMsg{prs, err}
}
}

func fetchPRTLItems(ghClient *ghapi.GraphQLClient, repoOwner string, repoName string, prNumber int, tlItemsCount int, commentsCount int) tea.Cmd {
func fetchViewerLogin(ghClient *ghapi.GraphQLClient) tea.Cmd {
return func() tea.Msg {
prTLItems, err := GetPRTL(ghClient, repoOwner, repoName, prNumber, tlItemsCount, commentsCount)
return PRTLFetchedMsg{prNumber, prTLItems, err}
login, err := getViewerLogin(ghClient)
return viewerLoginFetched{login, err}
}
}

func fetchPRsToReview(ghClient *ghapi.GraphQLClient, authorLogin string) tea.Cmd {
return func() tea.Msg {
prs, err := getPRsToReview(ghClient, authorLogin)
return reviewPRsFetchedMsg{prs, err}
}
}

func fetchAuthoredPRs(ghClient *ghapi.GraphQLClient, authorLogin string) tea.Cmd {
return func() tea.Msg {
prs, err := getAuthoredPRs(ghClient, authorLogin)
return authoredPRsFetchedMsg{prs, err}
}
}

func fetchPRTLItems(ghClient *ghapi.GraphQLClient, repoOwner string, repoName string, prNumber int, tlItemsCount int) tea.Cmd {
return func() tea.Msg {
prTLItems, err := getPRTL(ghClient, repoOwner, repoName, prNumber, tlItemsCount)
return prTLFetchedMsg{repoOwner, repoName, prNumber, prTLItems, err}
}
}
49 changes: 47 additions & 2 deletions ui/gh.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package ui

import (
"fmt"
ghapi "github.com/cli/go-gh/v2/pkg/api"
ghgql "github.com/cli/shurcooL-graphql"
)

func GetPRs(ghClient *ghapi.GraphQLClient, repoOwner string, repoName string, prCount int) ([]pr, error) {
func getPRs(ghClient *ghapi.GraphQLClient, repoOwner string, repoName string, prCount int) ([]pr, error) {
var query prsQuery

variables := map[string]interface{}{
Expand All @@ -20,7 +21,51 @@ func GetPRs(ghClient *ghapi.GraphQLClient, repoOwner string, repoName string, pr
return query.RepositoryOwner.Repository.PullRequests.Nodes, nil
}

func GetPRTL(ghClient *ghapi.GraphQLClient, repoOwner string, repoName string, prNumber int, tlItemsCount int, commentsCount int) ([]prTLItem, error) {
func getViewerLogin(ghClient *ghapi.GraphQLClient) (string, error) {
var query userLoginQuery

err := ghClient.Query("PullRequests", &query, nil)
if err != nil {
return "", err
}
return query.Viewer.Login, nil
}

func getPRsToReview(ghClient *ghapi.GraphQLClient, authorLogin string) ([]pr, error) {
var query prSearchQuery

variables := map[string]interface{}{
"query": ghgql.String(fmt.Sprintf("type:pr state:open review-requested:%s sort:updated-desc", authorLogin)),
}
err := ghClient.Query("ReviewPullRequests", &query, variables)
if err != nil {
return nil, err
}
var prs []pr
for _, edge := range query.Search.Edges {
prs = append(prs, edge.Node.pr)
}
return prs, nil
}

func getAuthoredPRs(ghClient *ghapi.GraphQLClient, authorLogin string) ([]pr, error) {
var query prSearchQuery

variables := map[string]interface{}{
"query": ghgql.String(fmt.Sprintf("is:pr is:open author:%s sort:updated-desc", authorLogin)),
}
err := ghClient.Query("AuthoredPullRequests", &query, variables)
if err != nil {
return nil, err
}
var prs []pr
for _, edge := range query.Search.Edges {
prs = append(prs, edge.Node.pr)
}
return prs, nil
}

func getPRTL(ghClient *ghapi.GraphQLClient, repoOwner string, repoName string, prNumber int, tlItemsCount int) ([]prTLItem, error) {
var query prTLQuery

variables := map[string]interface{}{
Expand Down
39 changes: 23 additions & 16 deletions ui/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,36 +25,43 @@ var (
- PR List View
- PR Timeline List View
- PR Review Comments View
- Repo List View
- Repo List View (only applicable when -mode=repos)
- Help View (this one)
`),
helpHeaderStyle.Render("Keyboard Shortcuts"),
helpHeaderStyle.Render("General"),
helpSectionStyle.Render(`
<tab> Switch focus between PR List and PR Timeline Pane
1 Switch focus to PR List View
2 Switch focus to PR Timeline List View
3 Switch focus to PR Review Comments View
<ctrl+s> Switch focus to Repo List View
? Switch focus to Help View
<tab> Switch focus between PR List and PR Timeline Pane
1 Switch focus to PR List View
2 Switch focus to PR Timeline List View
3 Switch focus to PR Review Comments View
<ctrl+s> Switch focus to Repo List View
? Switch focus to Help View
`),
helpHeaderStyle.Render("PR List/Timeline List View"),
helpSectionStyle.Render(`
<ctrl+v> Show PR details
<ctrl+d> Show PR diff
<ctrl+v> Show PR details
<ctrl+d> Show PR diff
`),
helpHeaderStyle.Render("PR List View"),
helpSectionStyle.Render(`
<ctrl+b> Open PR in the browser
<ctrl+r> Reload PR list
<enter> Switch focus to PR Timeline View for currently selected PR
<enter> Show commit/revision range
Indicators for current review
decision:
± implies CHANGES_REQUESTED
🟡 implies REVIEW_REQUIRED
✅ implies APPROVED
<ctrl+b> Open PR in the browser
<ctrl+r> Reload PR list
<enter> Switch focus to PR Timeline View for currently selected PR
<enter> Show commit/revision range
`),
helpHeaderStyle.Render("PR Timeline View"),
helpSectionStyle.Render(`
<ctrl+b> Open timeline item in browser
<ctrl+r> Reload timeline list
<enter> Switch focus to Review Comments View for currently selected item
<ctrl+b> Open timeline item in browser
<ctrl+r> Reload timeline list
<enter> Switch focus to Review Comments View for currently selected item
`),
)
)
Loading

0 comments on commit 2d5fe56

Please sign in to comment.