-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
adam
committed
Dec 21, 2022
1 parent
98ef4a2
commit db3556e
Showing
17 changed files
with
517 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package messages | ||
|
||
type AuthenticationErrorMsg struct { | ||
Err error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package messages | ||
|
||
import "github.com/admcpr/hub-bub/structs" | ||
|
||
type AuthenticationMsg struct { | ||
User structs.User | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package messages | ||
|
||
type ErrMsg struct{ Err error } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package messages | ||
|
||
import "github.com/admcpr/hub-bub/structs" | ||
|
||
type OrgListMsg struct { | ||
Organisations []structs.Organisation | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package messages | ||
|
||
import "github.com/admcpr/hub-bub/queries" | ||
|
||
type RepositoryListMsg struct { | ||
OrganizationQuery queries.OrganizationQuery | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package models | ||
|
||
import tea "github.com/charmbracelet/bubbletea" | ||
|
||
/* | ||
Model management | ||
Need to replace this with the a MainModel, see nested models youtube | ||
*/ | ||
type modelName int | ||
|
||
var MainModel []tea.Model | ||
|
||
const ( | ||
UserModelName modelName = iota | ||
OrganisationModelName | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package models | ||
|
||
import ( | ||
"log" | ||
|
||
"github.com/admcpr/hub-bub/messages" | ||
"github.com/admcpr/hub-bub/queries" | ||
"github.com/admcpr/hub-bub/structs" | ||
"github.com/admcpr/hub-bub/utils" | ||
"github.com/charmbracelet/bubbles/table" | ||
tea "github.com/charmbracelet/bubbletea" | ||
"github.com/charmbracelet/lipgloss" | ||
"github.com/cli/go-gh" | ||
graphql "github.com/cli/shurcooL-graphql" | ||
) | ||
|
||
/* Repository model */ | ||
type OrganisationModel struct { | ||
Title string | ||
Url string | ||
RepositoryTable table.Model | ||
} | ||
|
||
func (m OrganisationModel) Init() tea.Cmd { | ||
return nil | ||
} | ||
|
||
func (m OrganisationModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | ||
var cmd tea.Cmd | ||
|
||
switch msg := msg.(type) { | ||
|
||
case messages.RepositoryListMsg: | ||
// m.RepositoryTable = buildRepositoryTable(msg.Repositories) | ||
m.RepositoryTable = buildRepositoryTable(msg.OrganizationQuery) | ||
return m, nil | ||
|
||
case tea.KeyMsg: | ||
switch msg.String() { | ||
case "ctrl+c", "q": | ||
return m, tea.Quit | ||
case "enter", " ": | ||
return m, tea.Batch( | ||
tea.Printf("Let's go to %s!", m.RepositoryTable.SelectedRow()[1]), | ||
) | ||
case "esc": | ||
return MainModel[UserModelName], nil | ||
} | ||
} | ||
|
||
m.RepositoryTable, cmd = m.RepositoryTable.Update(msg) | ||
|
||
return m, cmd | ||
} | ||
|
||
// View implements tea.Model | ||
func (m OrganisationModel) View() string { | ||
return utils.BaseStyle.Render(m.RepositoryTable.View()) + "\n" | ||
} | ||
|
||
func (m OrganisationModel) GetRepositories() tea.Msg { | ||
client, err := gh.GQLClient(nil) | ||
if err != nil { | ||
return messages.AuthenticationErrorMsg{Err: err} | ||
} | ||
|
||
var organizationQuery = queries.OrganizationQuery{} | ||
|
||
variables := map[string]interface{}{ | ||
"login": graphql.String(m.Title), | ||
"first": graphql.Int(30), | ||
} | ||
err = client.Query("OrganizationRepositories", &organizationQuery, variables) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
return messages.RepositoryListMsg{OrganizationQuery: organizationQuery} | ||
} | ||
|
||
func buildOrganisationTable(organisations []structs.Organisation) table.Model { | ||
columns := []table.Column{ | ||
{Title: "Login", Width: 20}, | ||
{Title: "Url", Width: 80}, | ||
} | ||
|
||
rows := make([]table.Row, len(organisations)) | ||
for i, org := range organisations { | ||
rows[i] = table.Row{org.Login, org.Url} | ||
} | ||
|
||
t := table.New( | ||
table.WithColumns(columns), | ||
table.WithRows(rows), | ||
table.WithFocused(true), | ||
table.WithHeight(len(organisations)), | ||
) | ||
|
||
s := table.DefaultStyles() | ||
s.Header = s.Header. | ||
BorderStyle(lipgloss.NormalBorder()). | ||
BorderForeground(lipgloss.Color("240")). | ||
BorderBottom(true). | ||
Bold(false) | ||
s.Selected = s.Selected. | ||
Foreground(lipgloss.Color("229")). | ||
Background(lipgloss.Color("57")). | ||
Bold(false) | ||
t.SetStyles(s) | ||
|
||
return t | ||
} | ||
|
||
func buildRepositoryTable(organizationQuery queries.OrganizationQuery) table.Model { | ||
columns := []table.Column{ | ||
{Title: "Name", Width: 20}, | ||
{Title: "Issues", Width: 10}, | ||
{Title: "Wiki", Width: 10}, | ||
{Title: "Projects", Width: 10}, | ||
{Title: "Rebase Merge", Width: 10}, | ||
{Title: "Auto Merge", Width: 10}, | ||
{Title: "Delete Branch On Merge", Width: 10}, | ||
} | ||
|
||
edges := organizationQuery.Organization.Repositories.Edges | ||
|
||
rows := make([]table.Row, len(edges)) | ||
for i, repo := range edges { | ||
rows[i] = table.Row{ | ||
repo.Node.Name, | ||
utils.YesNo(repo.Node.HasIssuesEnabled), | ||
utils.YesNo(repo.Node.HasWikiEnabled), | ||
utils.YesNo(repo.Node.HasProjectsEnabled), | ||
utils.YesNo(repo.Node.RebaseMergeAllowed), | ||
utils.YesNo(repo.Node.AutoMergeAllowed), | ||
utils.YesNo(repo.Node.DeleteBranchOnMerge), | ||
} | ||
} | ||
|
||
t := table.New( | ||
table.WithColumns(columns), | ||
table.WithRows(rows), | ||
table.WithFocused(true), | ||
table.WithHeight(len(edges)), | ||
) | ||
|
||
s := table.DefaultStyles() | ||
s.Header = s.Header. | ||
BorderStyle(lipgloss.NormalBorder()). | ||
BorderForeground(lipgloss.Color("240")). | ||
BorderBottom(true). | ||
Bold(false) | ||
s.Selected = s.Selected. | ||
Foreground(lipgloss.Color("229")). | ||
Background(lipgloss.Color("57")). | ||
Bold(false) | ||
t.SetStyles(s) | ||
|
||
return t | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package models | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
tea "github.com/charmbracelet/bubbletea" | ||
) | ||
|
||
func TestOrganisationModel_Update(t *testing.T) { | ||
type args struct { | ||
msg tea.Msg | ||
} | ||
tests := []struct { | ||
name string | ||
m OrganisationModel | ||
args args | ||
wantModel tea.Model | ||
wantCmd tea.Cmd | ||
}{ | ||
// TODO: Add more test cases. | ||
{"Quit KeyMsg", OrganisationModel{}, args{tea.KeyMsg{Type: tea.KeyCtrlC}}, OrganisationModel{}, tea.Quit}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
gotModel, gotCmd := tt.m.Update(tt.args.msg) | ||
if !reflect.DeepEqual(gotModel, tt.wantModel) { | ||
t.Errorf("OrganisationModel.Update() gotModel = %v, want %v", gotModel, tt.wantModel) | ||
} | ||
if reflect.ValueOf(gotCmd) != reflect.ValueOf(tt.wantCmd) { | ||
t.Errorf("OrganisationModel.Update() gotCmd = %v, want %v", gotCmd, tt.wantCmd) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package models | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/admcpr/hub-bub/messages" | ||
"github.com/admcpr/hub-bub/structs" | ||
"github.com/admcpr/hub-bub/utils" | ||
"github.com/cli/go-gh" | ||
|
||
"github.com/charmbracelet/bubbles/table" | ||
tea "github.com/charmbracelet/bubbletea" | ||
) | ||
|
||
type UserModel struct { | ||
Authenticated bool | ||
User structs.User | ||
SelectedOrgUrl string | ||
OrganisationTable table.Model | ||
} | ||
|
||
func (m UserModel) Init() tea.Cmd { | ||
return checkLoginStatus | ||
} | ||
|
||
func (m UserModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | ||
var cmd tea.Cmd | ||
|
||
switch msg := msg.(type) { | ||
|
||
case messages.AuthenticationMsg: | ||
m.Authenticated = true | ||
m.User = msg.User | ||
return m, getOrganisations | ||
|
||
case messages.AuthenticationErrorMsg: | ||
m.Authenticated = false | ||
return m, nil | ||
|
||
case messages.OrgListMsg: | ||
m.OrganisationTable = buildOrganisationTable(msg.Organisations) | ||
return m, nil | ||
|
||
case tea.KeyMsg: | ||
switch msg.String() { | ||
case "ctrl+c", "q": | ||
return m, tea.Quit | ||
case "enter", " ": | ||
MainModel[UserModelName] = m | ||
orgModel := &OrganisationModel{ | ||
Title: m.OrganisationTable.SelectedRow()[0], | ||
Url: m.OrganisationTable.SelectedRow()[1], | ||
} | ||
|
||
MainModel[OrganisationModelName] = orgModel | ||
|
||
return orgModel, orgModel.GetRepositories | ||
} | ||
} | ||
|
||
m.OrganisationTable, cmd = m.OrganisationTable.Update(msg) | ||
|
||
return m, cmd | ||
} | ||
|
||
func (m UserModel) View() string { | ||
s := fmt.Sprintln("Press q to quit.") | ||
|
||
if !m.Authenticated { | ||
return fmt.Sprintln("You are not authenticated try running `gh auth login`") | ||
} | ||
|
||
s += fmt.Sprintf("Hello %s, press Enter to select an organisation.\n", m.User.Name) | ||
s += utils.BaseStyle.Render(m.OrganisationTable.View()) + "\n" | ||
|
||
return s | ||
} | ||
|
||
func checkLoginStatus() tea.Msg { | ||
// Use an API helper to grab repository tags | ||
client, err := gh.RESTClient(nil) | ||
if err != nil { | ||
return messages.AuthenticationErrorMsg{Err: err} | ||
} | ||
response := structs.User{} | ||
|
||
err = client.Get("user", &response) | ||
if err != nil { | ||
fmt.Println(err) | ||
return messages.AuthenticationErrorMsg{Err: err} | ||
} | ||
|
||
return messages.AuthenticationMsg{User: response} | ||
} | ||
|
||
func getOrganisations() tea.Msg { | ||
client, err := gh.RESTClient(nil) | ||
if err != nil { | ||
return messages.AuthenticationErrorMsg{Err: err} | ||
} | ||
response := []structs.Organisation{} | ||
|
||
err = client.Get("user/orgs", &response) | ||
if err != nil { | ||
fmt.Println(err) | ||
return messages.ErrMsg{Err: err} | ||
} | ||
|
||
return messages.OrgListMsg{Organisations: response} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package models | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
messages "github.com/admcpr/hub-bub/messages" | ||
structs "github.com/admcpr/hub-bub/structs" | ||
tea "github.com/charmbracelet/bubbletea" | ||
) | ||
|
||
func TestUserModel_Update(t *testing.T) { | ||
testUser := structs.User{Login: "test"} | ||
type args struct { | ||
msg tea.Msg | ||
} | ||
tests := []struct { | ||
name string | ||
m UserModel | ||
args args | ||
wantModel tea.Model | ||
wantCmd tea.Cmd | ||
}{ | ||
// TODO: Add test cases. | ||
{"Authentication Success", UserModel{}, args{messages.AuthenticationMsg{User: testUser}}, UserModel{Authenticated: true, User: testUser}, getOrganisations}, | ||
{"Authentication Failure", UserModel{}, args{messages.AuthenticationErrorMsg{}}, UserModel{Authenticated: false}, nil}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
gotModel, gotCmd := tt.m.Update(tt.args.msg) | ||
if !reflect.DeepEqual(gotModel, tt.wantModel) { | ||
t.Errorf("UserModel.Update() gotModel = %v, want %v", gotModel, tt.wantModel) | ||
} | ||
if reflect.ValueOf(gotCmd) != reflect.ValueOf(tt.wantCmd) { | ||
t.Errorf("UserModel.Update() gotCmd = %v, want %v", gotCmd, tt.wantCmd) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.