diff --git a/_site/content/modules/jenkins.md b/_site/content/modules/jenkins.md index 055fbd58..46198dd7 100644 --- a/_site/content/modules/jenkins.md +++ b/_site/content/modules/jenkins.md @@ -17,6 +17,26 @@ Displays jenkins status of given builds in a project or view wtf/jenkins/ ``` +## Keyboard Commands + +Key: `[return]`
+Action: Open the selected job in the browser. + +Key: `j`
+Action: Select the next job in the list. + +Key: `k`
+Action: Select the previous job in the list. + +Key: `r`
+Action: Refresh the data. + +Key: `↓`
+Action: Select the next job in the list. + +Key: `↑`
+Action: Select the previous job in the list. + ## Configuration ```yaml diff --git a/docs/index.json b/docs/index.json index 85fc0502..3805d4d3 100644 --- a/docs/index.json +++ b/docs/index.json @@ -200,7 +200,7 @@ "title": "Jenkins", "tags": [], "description": "", - "content": " Added in v0.0.8.\nDisplays jenkins status of given builds in a project or view\nSource Code wtf/jenkins/ Configuration jenkins:apiKey:\u0026#34;3276d7155dd9ee27b8b14f8743a408a9\u0026#34;enabled:trueposition:top:2left:3height:2width:3refreshInterval:300url:\u0026#34;https://jenkins.domain.com/jenkins/view_url\u0026#34;user:\u0026#34;username\u0026#34;verifyServerCertificate:true Attributes apiKey Value: Your Jenkins API key.\nenabled Determines whether or not this module is executed and if its data displayed onscreen. Values: true, false.\nposition Defines where in the grid this module\u0026rsquo;s widget will be displayed.\nrefreshInterval How often, in seconds, this module will update its data. Values: A positive integer, 0..n.\nuser Your Jenkins username. url The url to your Jenkins project or view. Values: A valid URI.\nverifyServerCertificate Optional Determines whether or not the server\u0026rsquo;s certificate chain and host name are verified. Values: true, false.\n" + "content": " Added in v0.0.8.\nDisplays jenkins status of given builds in a project or view\nSource Code wtf/jenkins/ Keyboard Commands Key: [return] Action: Open the selected job in the browser.\nKey: j Action: Select the next job in the list.\nKey: k Action: Select the previous job in the list.\nKey: r Action: Refresh the data.\nKey: ↓ Action: Select the next job in the list.\nKey: ↑ Action: Select the previous job in the list.\nConfiguration jenkins:apiKey:\u0026#34;3276d7155dd9ee27b8b14f8743a408a9\u0026#34;enabled:trueposition:top:2left:3height:2width:3refreshInterval:300url:\u0026#34;https://jenkins.domain.com/jenkins/view_url\u0026#34;user:\u0026#34;username\u0026#34;verifyServerCertificate:true Attributes apiKey Value: Your Jenkins API key.\nenabled Determines whether or not this module is executed and if its data displayed onscreen. Values: true, false.\nposition Defines where in the grid this module\u0026rsquo;s widget will be displayed.\nrefreshInterval How often, in seconds, this module will update its data. Values: A positive integer, 0..n.\nuser Your Jenkins username. url The url to your Jenkins project or view. Values: A valid URI.\nverifyServerCertificate Optional Determines whether or not the server\u0026rsquo;s certificate chain and host name are verified. Values: true, false.\n" }, { "uri": "https://wtfutil.com/modules/jira/", @@ -341,4 +341,4 @@ "tags": [], "description": "", "content": "" -}] \ No newline at end of file +}] diff --git a/docs/index.xml b/docs/index.xml index 37664915..0ee622b4 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -328,10 +328,12 @@ Key: ↑ Action: Select the previous story in the list. https://wtfutil.com/modules/jenkins/ Added in v0.0.8. Displays jenkins status of given builds in a project or view -Source Code wtf/jenkins/ Configuration jenkins:apiKey:"3276d7155dd9ee27b8b14f8743a408a9"enabled:trueposition:top:2left:3height:2width:3refreshInterval:300url:"https://jenkins.domain.com/jenkins/view_url"user:"username"verifyServerCertificate:true Attributes apiKey Value: Your Jenkins API key. -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/jenkins/ Keyboard Commands Key: [return] Action: Open the selected job in the browser. +Key: j Action: Select the next job in the list. +Key: k Action: Select the previous job in the list. +Key: r Action: Refresh the data. +Key: ↓ Action: Select the next job in the list. +Key: ↑ Action: Select the previous job in the list. diff --git a/docs/modules/index.xml b/docs/modules/index.xml index e9e73307..aa15a26e 100644 --- a/docs/modules/index.xml +++ b/docs/modules/index.xml @@ -178,10 +178,12 @@ Key: ↑ Action: Select the previous story in the list. https://wtfutil.com/modules/jenkins/ Added in v0.0.8. Displays jenkins status of given builds in a project or view -Source Code wtf/jenkins/ Configuration jenkins:apiKey:"3276d7155dd9ee27b8b14f8743a408a9"enabled:trueposition:top:2left:3height:2width:3refreshInterval:300url:"https://jenkins.domain.com/jenkins/view_url"user:"username"verifyServerCertificate:true Attributes apiKey Value: Your Jenkins API key. -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/jenkins/ Keyboard Commands Key: [return] Action: Open the selected job in the browser. +Key: j Action: Select the next job in the list. +Key: k Action: Select the previous job in the list. +Key: r Action: Refresh the data. +Key: ↓ Action: Select the next job in the list. +Key: ↑ Action: Select the previous job in the list. diff --git a/docs/modules/jenkins/index.html b/docs/modules/jenkins/index.html index 09d00663..82f728d0 100644 --- a/docs/modules/jenkins/index.html +++ b/docs/modules/jenkins/index.html @@ -480,6 +480,26 @@

Source Code

wtf/jenkins/
+

Keyboard Commands

+ +

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

+ +

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

+ +

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

+ +

Key: r
+Action: Refresh the data.

+ +

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

+ +

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

+

Configuration

jenkins:
   apiKey: "3276d7155dd9ee27b8b14f8743a408a9"
diff --git a/jenkins/widget.go b/jenkins/widget.go
index cf12609f..b8371d8b 100644
--- a/jenkins/widget.go
+++ b/jenkins/widget.go
@@ -2,19 +2,48 @@ package jenkins
 
 import (
 	"fmt"
+	"github.com/gdamore/tcell"
+	"github.com/rivo/tview"
 	"github.com/senorprogrammer/wtf/wtf"
 	"os"
+	"strconv"
 )
 
+const HelpText = `
+ Keyboard commands for Jenkins:
+
+   /: Show/hide this help window
+   j: Select the next job in the list
+   k: Select the previous job in the list
+   r: Refresh the data
+
+   arrow down: Select the next job in the list
+   arrow up:   Select the previous job in the list
+
+   return: Open the selected job in a browser
+`
+
 type Widget struct {
+	wtf.HelpfulWidget
 	wtf.TextWidget
+
+	view     *View
+	selected int
 }
 
-func NewWidget() *Widget {
+func NewWidget(app *tview.Application, pages *tview.Pages) *Widget {
 	widget := Widget{
-		TextWidget: wtf.NewTextWidget("Jenkins", "jenkins", false),
+		HelpfulWidget: wtf.NewHelpfulWidget(app, pages, HelpText),
+		TextWidget:    wtf.NewTextWidget("Jenkins", "jenkins", true),
 	}
 
+	widget.HelpfulWidget.SetView(widget.View)
+	widget.unselect()
+
+	widget.View.SetScrollable(true)
+	widget.View.SetRegions(true)
+	widget.View.SetInputCapture(widget.keyboardIntercept)
+
 	return &widget
 }
 
@@ -30,25 +59,34 @@ func (widget *Widget) Refresh() {
 		wtf.Config.UString("wtf.mods.jenkins.user"),
 		widget.apiKey(),
 	)
+	widget.view = view
 
 	widget.UpdateRefreshedAt()
 
-	var content string
 	if err != nil {
 		widget.View.SetWrap(true)
-		widget.View.SetTitle(fmt.Sprintf(" %s ", widget.Name))
-		content = err.Error()
-	} else {
-		widget.View.SetWrap(false)
-		widget.View.SetTitle(fmt.Sprintf(" %s: [green] ", widget.Name))
-		content = widget.contentFrom(view)
+		widget.View.SetTitle(widget.ContextualTitle(widget.Name))
+		widget.View.SetText(err.Error())
 	}
 
-	widget.View.SetText(content)
+	widget.display()
 }
 
 /* -------------------- Unexported Functions -------------------- */
 
+func (widget *Widget) display() {
+	if widget.view == nil {
+		return
+	}
+
+	widget.View.SetWrap(false)
+
+	widget.View.Clear()
+	widget.View.SetTitle(widget.ContextualTitle(fmt.Sprintf("%s: [red]%s", widget.Name, widget.view.Name)))
+	widget.View.SetText(widget.contentFrom(widget.view))
+	widget.View.Highlight(strconv.Itoa(widget.selected)).ScrollToHighlight()
+}
+
 func (widget *Widget) apiKey() string {
 	return wtf.Config.UString(
 		"wtf.mods.jenkins.apiKey",
@@ -57,19 +95,30 @@ func (widget *Widget) apiKey() string {
 }
 
 func (widget *Widget) contentFrom(view *View) string {
-	str := fmt.Sprintf(" [red]%s[white]\n", view.Name)
-
-	for _, job := range view.Jobs {
+	var str string
+	for idx, job := range view.Jobs {
 		str = str + fmt.Sprintf(
-			" [%s]%-6s[white]\n",
+			`["%d"][""][%s] [%s]%-6s[white]`,
+			idx,
+			widget.rowColor(idx),
 			widget.jobColor(&job),
 			job.Name,
 		)
+
+		str = str + "\n"
 	}
 
 	return str
 }
 
+func (widget *Widget) rowColor(idx int) string {
+	if widget.View.HasFocus() && (idx == widget.selected) {
+		return wtf.DefaultFocussedRowColor()
+	}
+
+	return wtf.DefaultRowColor()
+}
+
 func (widget *Widget) jobColor(job *Job) string {
 	switch job.Color {
 	case "blue":
@@ -80,3 +129,67 @@ func (widget *Widget) jobColor(job *Job) string {
 		return "white"
 	}
 }
+
+func (widget *Widget) next() {
+	widget.selected++
+	if widget.view != nil && widget.selected >= len(widget.view.Jobs) {
+		widget.selected = 0
+	}
+
+	widget.display()
+}
+
+func (widget *Widget) prev() {
+	widget.selected--
+	if widget.selected < 0 && widget.view != nil {
+		widget.selected = len(widget.view.Jobs) - 1
+	}
+
+	widget.display()
+}
+
+func (widget *Widget) openJob() {
+	sel := widget.selected
+	if sel >= 0 && widget.view != nil && sel < len(widget.view.Jobs) {
+		job := &widget.view.Jobs[widget.selected]
+		wtf.OpenFile(job.Url)
+	}
+}
+
+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.openJob()
+		return nil
+	case tcell.KeyEsc:
+		widget.unselect()
+		return event
+	case tcell.KeyUp:
+		widget.prev()
+		return nil
+	default:
+		return event
+	}
+}
diff --git a/main.go b/main.go
index 09297f65..bc8e8522 100644
--- a/main.go
+++ b/main.go
@@ -219,7 +219,7 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) {
 	case "ipinfo":
 		widgets = append(widgets, ipinfo.NewWidget())
 	case "jenkins":
-		widgets = append(widgets, jenkins.NewWidget())
+		widgets = append(widgets, jenkins.NewWidget(app, pages))
 	case "jira":
 		widgets = append(widgets, jira.NewWidget(app, pages))
 	case "logger":
diff --git a/wtf/utils.go b/wtf/utils.go
index 7921a60b..05e00183 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 DefaultRowColor() string {
+	foreColor := Config.UString("wtf.colors.foreground", "white")
+	backColor := Config.UString("wtf.colors.background", "black")
+
+	return fmt.Sprintf("%s:%s", foreColor, backColor)
+}
+
 func DefaultFocussedRowColor() string {
 	foreColor := Config.UString("wtf.colors.highlight.fore", "black")
 	backColor := Config.UString("wtf.colors.highlight.back", "orange")