diff --git a/_site/content/posts/modules/travisci.md b/_site/content/posts/modules/travisci.md index e162c9a6..437a4d79 100644 --- a/_site/content/posts/modules/travisci.md +++ b/_site/content/posts/modules/travisci.md @@ -18,7 +18,23 @@ wtf/travisci/ ## Keyboard Commands -None. +Key: `[return]`
+Action: Open the selected build in the browser. + +Key: `j`
+Action: Select the next build in the list. + +Key: `k`
+Action: Select the previous build in the list. + +Key: `r`
+Action: Refresh the data. + +Key: `↓`
+Action: Select the next build in the list. + +Key: `↑`
+Action: Select the previous build in the list. ## Configuration diff --git a/docs/index.xml b/docs/index.xml index 9444cc49..900a47b4 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -50,10 +50,12 @@ Configuration zendesk:apiKey:"3276d7155dd9ee27b8b14f8743a408a9"e https://wtfutil.com/posts/modules/travisci/ Added in v0.0.12. Displays build information for your Travis CI account. -Source Code wtf/travisci/ Keyboard Commands None. -Configuration travisci:apiKey:"3276d7155dd9ee27b8b14f8743a408a9"enabled:trueposition:top:4left:1height:1width:2pro:falserefreshInterval:900 Attributes apiKey Value: Your Travis CI API access token. -enabled Determines whether or not this module is executed and if its data displayed onscreen. Values: true, false. -position Defines where in the grid this module’s widget will be displayed. refreshInterval How often, in seconds, this module will update its data. Values: A positive integer, 0. +Source Code wtf/travisci/ Keyboard Commands Key: [return] Action: Open the selected build in the browser. +Key: j Action: Select the next build in the list. +Key: k Action: Select the previous build in the list. +Key: r Action: Refresh the data. +Key: ↓ Action: Select the next build in the list. +Key: ↑ Action: Select the previous build in the list. diff --git a/docs/posts/index.xml b/docs/posts/index.xml index 35b49c9d..701ace3a 100644 --- a/docs/posts/index.xml +++ b/docs/posts/index.xml @@ -50,10 +50,12 @@ Configuration zendesk:apiKey:"3276d7155dd9ee27b8b14f8743a408a9"e https://wtfutil.com/posts/modules/travisci/ Added in v0.0.12. Displays build information for your Travis CI account. -Source Code wtf/travisci/ Keyboard Commands None. -Configuration travisci:apiKey:"3276d7155dd9ee27b8b14f8743a408a9"enabled:trueposition:top:4left:1height:1width:2pro:falserefreshInterval:900 Attributes apiKey Value: Your Travis CI API access token. -enabled Determines whether or not this module is executed and if its data displayed onscreen. Values: true, false. -position Defines where in the grid this module’s widget will be displayed. refreshInterval How often, in seconds, this module will update its data. Values: A positive integer, 0. +Source Code wtf/travisci/ Keyboard Commands Key: [return] Action: Open the selected build in the browser. +Key: j Action: Select the next build in the list. +Key: k Action: Select the previous build in the list. +Key: r Action: Refresh the data. +Key: ↓ Action: Select the next build in the list. +Key: ↑ Action: Select the previous build in the list. diff --git a/docs/posts/modules/travisci/index.html b/docs/posts/modules/travisci/index.html index 80fb84ba..00b2f85f 100644 --- a/docs/posts/modules/travisci/index.html +++ b/docs/posts/modules/travisci/index.html @@ -147,7 +147,23 @@ height="0" width="0" style="display:none;visibility:hidden">
wtf/travisci/

Keyboard Commands

-

None.

+

Key: [return]
+Action: Open the selected build in the browser.

+ +

Key: j
+Action: Select the next build in the list.

+ +

Key: k
+Action: Select the previous build in the list.

+ +

Key: r
+Action: Refresh the data.

+ +

Key:
+Action: Select the next build in the list.

+ +

Key:
+Action: Select the previous build in the list.

Configuration

travisci:
diff --git a/gerrit/display.go b/gerrit/display.go
index a7ff3b1f..24645197 100644
--- a/gerrit/display.go
+++ b/gerrit/display.go
@@ -66,10 +66,7 @@ func (widget *Widget) displayStats(project *GerritProject) string {
 
 func (widget *Widget) rowColor(index int) string {
 	if widget.View.HasFocus() && (index == widget.selected) {
-		foreColor := wtf.Config.UString("wtf.colors.highlight.fore", "black")
-		backColor := wtf.Config.UString("wtf.colors.highlight.back", "orange")
-
-		return fmt.Sprintf("%s:%s", foreColor, backColor)
+		return wtf.DefaultFocussedRowColor()
 	}
 	return wtf.RowColor("gerrit", index)
 }
diff --git a/jira/widget.go b/jira/widget.go
index 29d536d1..4fcac8ab 100644
--- a/jira/widget.go
+++ b/jira/widget.go
@@ -130,10 +130,7 @@ func (widget *Widget) contentFrom(searchResult *SearchResult) string {
 
 func (widget *Widget) rowColor(idx int) string {
 	if widget.View.HasFocus() && (idx == widget.selected) {
-		foreColor := wtf.Config.UString("wtf.colors.highlight.fore", "black")
-		backColor := wtf.Config.UString("wtf.colors.highlight.back", "orange")
-
-		return fmt.Sprintf("%s:%s", foreColor, backColor)
+		return wtf.DefaultFocussedRowColor()
 	}
 	return wtf.RowColor("jira", idx)
 }
diff --git a/main.go b/main.go
index 54a1ce5f..32bbe493 100644
--- a/main.go
+++ b/main.go
@@ -235,7 +235,7 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) {
 	case "todoist":
 		widgets = append(widgets, todoist.NewWidget(app, pages))
 	case "travisci":
-		widgets = append(widgets, travisci.NewWidget())
+		widgets = append(widgets, travisci.NewWidget(app, pages))
 	case "trello":
 		widgets = append(widgets, trello.NewWidget())
 	case "twitter":
diff --git a/travisci/client.go b/travisci/client.go
index 2b1530ed..dbd906fc 100644
--- a/travisci/client.go
+++ b/travisci/client.go
@@ -13,11 +13,16 @@ import (
 	"github.com/senorprogrammer/wtf/wtf"
 )
 
+var TRAVIS_HOSTS = map[bool]string{
+	false: "travis-ci.org",
+	true:  "travis-ci.com",
+}
+
 func BuildsFor() (*Builds, error) {
 	builds := &Builds{}
 
 	pro := wtf.Config.UBool("wtf.mods.travisci.pro", false)
-	travisAPIURL.Host = hosts[pro]
+	travisAPIURL.Host = "api." + TRAVIS_HOSTS[pro]
 
 	resp, err := travisRequest("builds")
 	if err != nil {
@@ -33,19 +38,15 @@ func BuildsFor() (*Builds, error) {
 
 var (
 	travisAPIURL = &url.URL{Scheme: "https", Path: "/"}
-	hosts        = map[bool]string{
-		false: "api.travis-ci.org",
-		true:  "api.travis-ci.com",
-	}
 )
 
 func travisRequest(path string) (*http.Response, error) {
 	params := url.Values{}
 	params.Add("limit", "10")
 
-	url := travisAPIURL.ResolveReference(&url.URL{Path: path, RawQuery: params.Encode()})
+	requestUrl := travisAPIURL.ResolveReference(&url.URL{Path: path, RawQuery: params.Encode()})
 
-	req, err := http.NewRequest("GET", url.String(), nil)
+	req, err := http.NewRequest("GET", requestUrl.String(), nil)
 	req.Header.Add("Accept", "application/json")
 	req.Header.Add("Content-Type", "application/json")
 	req.Header.Add("Travis-API-Version", "3")
diff --git a/travisci/travis.go b/travisci/travis.go
index 47f619e6..aca03616 100644
--- a/travisci/travis.go
+++ b/travisci/travis.go
@@ -5,10 +5,12 @@ type Builds struct {
 }
 
 type Build struct {
+	ID         int        `json:"id"`
 	CreatedBy  Owner      `json:"created_by"`
 	Branch     Branch     `json:"branch"`
 	Number     string     `json:"number"`
 	Repository Repository `json:"repository"`
+	Commit     Commit     `json:"commit"`
 	State      string     `json:"state"`
 }
 
@@ -22,4 +24,9 @@ type Branch struct {
 
 type Repository struct {
 	Name string `json:"name"`
+	Slug string `json:"slug"`
+}
+
+type Commit struct {
+	Message string `json:"message"`
 }
diff --git a/travisci/widget.go b/travisci/widget.go
index a22bdf3c..170e548b 100644
--- a/travisci/widget.go
+++ b/travisci/widget.go
@@ -2,18 +2,45 @@ package travisci
 
 import (
 	"fmt"
+	"github.com/gdamore/tcell"
+	"github.com/rivo/tview"
 	"github.com/senorprogrammer/wtf/wtf"
+	"strings"
 )
 
+const HelpText = `
+ Keyboard commands for Travis CI:
+
+   /: Show/hide this help window
+   j: Select the next build in the list
+   k: Select the previous build in the list
+   r: Refresh the data
+
+   arrow down: Select the next build in the list
+   arrow up:   Select the previous build in the list
+
+   return: Open the selected build in a browser
+`
+
 type Widget struct {
+	wtf.HelpfulWidget
 	wtf.TextWidget
+
+	builds   *Builds
+	selected int
 }
 
-func NewWidget() *Widget {
+func NewWidget(app *tview.Application, pages *tview.Pages) *Widget {
 	widget := Widget{
-		TextWidget: wtf.NewTextWidget("TravisCI", "travisci", false),
+		HelpfulWidget: wtf.NewHelpfulWidget(app, pages, HelpText),
+		TextWidget:    wtf.NewTextWidget("TravisCI", "travisci", true),
 	}
 
+	widget.HelpfulWidget.SetView(widget.View)
+	widget.unselect()
+
+	widget.View.SetInputCapture(widget.keyboardIntercept)
+
 	return &widget
 }
 
@@ -28,31 +55,43 @@ func (widget *Widget) Refresh() {
 
 	widget.UpdateRefreshedAt()
 
-	widget.View.SetTitle(fmt.Sprintf("%s - Builds", widget.Name))
-
-	var content string
 	if err != nil {
 		widget.View.SetWrap(true)
-		content = err.Error()
+		widget.View.SetTitle(widget.Name)
+		widget.View.SetText(err.Error())
 	} else {
-		widget.View.SetWrap(false)
-		content = widget.contentFrom(builds)
+		widget.builds = builds
 	}
 
-	widget.View.SetText(content)
+	widget.display()
 }
 
 /* -------------------- Unexported Functions -------------------- */
 
+func (widget *Widget) display() {
+	if widget.builds == nil {
+		return
+	}
+
+	widget.View.SetWrap(false)
+
+	widget.View.SetTitle(widget.ContextualTitle(fmt.Sprintf("%s - Builds", widget.Name)))
+	widget.View.SetText(widget.contentFrom(widget.builds))
+}
+
 func (widget *Widget) contentFrom(builds *Builds) string {
 	var str string
-	for _, build := range builds.Builds {
+	for idx, build := range builds.Builds {
+
 		str = str + fmt.Sprintf(
-			"[%s] %s-%s (%s) [white]%s\n",
+			"[%s] [%s] %s-%s (%s) [%s]%s - [blue]%s\n",
+			widget.rowColor(idx),
 			buildColor(&build),
 			build.Repository.Name,
 			build.Number,
 			build.Branch.Name,
+			widget.rowColor(idx),
+			strings.Split(build.Commit.Message, "\n")[0],
 			build.CreatedBy.Login,
 		)
 	}
@@ -60,6 +99,13 @@ func (widget *Widget) contentFrom(builds *Builds) string {
 	return str
 }
 
+func (widget *Widget) rowColor(idx int) string {
+	if widget.View.HasFocus() && (idx == widget.selected) {
+		return wtf.DefaultFocussedRowColor()
+	}
+	return "White"
+}
+
 func buildColor(build *Build) string {
 	switch build.State {
 	case "broken":
@@ -80,3 +126,69 @@ func buildColor(build *Build) string {
 		return "white"
 	}
 }
+
+func (widget *Widget) next() {
+	widget.selected++
+	if widget.builds != nil && widget.selected >= len(widget.builds.Builds) {
+		widget.selected = 0
+	}
+
+	widget.display()
+}
+
+func (widget *Widget) prev() {
+	widget.selected--
+	if widget.selected < 0 && widget.builds != nil {
+		widget.selected = len(widget.builds.Builds) - 1
+	}
+
+	widget.display()
+}
+
+func (widget *Widget) openBuild() {
+	sel := widget.selected
+	if sel >= 0 && widget.builds != nil && sel < len(widget.builds.Builds) {
+		build := &widget.builds.Builds[widget.selected]
+		travisHost := TRAVIS_HOSTS[wtf.Config.UBool("wtf.mods.travisci.pro", false)]
+		wtf.OpenFile(fmt.Sprintf("https://%s/%s/%s/%d", travisHost, build.Repository.Slug, "builds", build.ID))
+	}
+}
+
+func (widget *Widget) unselect() {
+	widget.selected = -1
+	widget.display()
+}
+
+func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey {
+	switch string(event.Rune()) {
+	case "/":
+		widget.ShowHelp()
+	case "j":
+		widget.next()
+		return nil
+	case "k":
+		widget.prev()
+		return nil
+	case "r":
+		widget.Refresh()
+		return nil
+	}
+
+	switch event.Key() {
+	case tcell.KeyDown:
+		widget.next()
+		return nil
+	case tcell.KeyEnter:
+		widget.openBuild()
+		return nil
+	case tcell.KeyEsc:
+		widget.unselect()
+		return event
+	case tcell.KeyUp:
+		widget.prev()
+		widget.display()
+		return nil
+	default:
+		return event
+	}
+}
diff --git a/wtf/utils.go b/wtf/utils.go
index 01f351f3..a2bb778e 100644
--- a/wtf/utils.go
+++ b/wtf/utils.go
@@ -113,6 +113,13 @@ func RightAlignFormat(view *tview.TextView) string {
 	return fmt.Sprintf("%%%ds", w-1)
 }
 
+func DefaultFocussedRowColor() string {
+	foreColor := Config.UString("wtf.colors.highlight.fore", "black")
+	backColor := Config.UString("wtf.colors.highlight.back", "orange")
+
+	return fmt.Sprintf("%s:%s", foreColor, backColor)
+}
+
 func RowColor(module string, idx int) string {
 	evenKey := fmt.Sprintf("wtf.mods.%s.colors.row.even", module)
 	oddKey := fmt.Sprintf("wtf.mods.%s.colors.row.odd", module)