mirror of
https://github.com/taigrr/wtf
synced 2025-01-18 04:03:14 -08:00
Add Buildkite widget
This commit is contained in:
parent
f5e8011906
commit
f56cc483b4
@ -6,6 +6,7 @@ import (
|
||||
"github.com/wtfutil/wtf/modules/azuredevops"
|
||||
"github.com/wtfutil/wtf/modules/bamboohr"
|
||||
"github.com/wtfutil/wtf/modules/bargraph"
|
||||
"github.com/wtfutil/wtf/modules/buildkite"
|
||||
"github.com/wtfutil/wtf/modules/circleci"
|
||||
"github.com/wtfutil/wtf/modules/clocks"
|
||||
"github.com/wtfutil/wtf/modules/cmdrunner"
|
||||
@ -96,6 +97,9 @@ func MakeWidget(
|
||||
case "blockfolio":
|
||||
settings := blockfolio.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = blockfolio.NewWidget(app, settings)
|
||||
case "buildkite":
|
||||
settings := buildkite.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = buildkite.NewWidget(app, pages, settings)
|
||||
case "circleci":
|
||||
settings := circleci.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = circleci.NewWidget(app, settings)
|
||||
|
105
modules/buildkite/client.go
Normal file
105
modules/buildkite/client.go
Normal file
@ -0,0 +1,105 @@
|
||||
package buildkite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wtfutil/wtf/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Pipeline struct {
|
||||
Slug string `json:"slug"`
|
||||
}
|
||||
|
||||
type Build struct {
|
||||
State string `json:"state"`
|
||||
Pipeline Pipeline `json:"pipeline"`
|
||||
Branch string `json:"branch"`
|
||||
WebUrl string `json:"web_url"`
|
||||
}
|
||||
|
||||
func (widget *Widget) getBuilds() ([]Build, error) {
|
||||
builds := []Build{}
|
||||
|
||||
for _, pipeline := range widget.settings.pipelines {
|
||||
buildsForPipeline, err := widget.recentBuilds(pipeline)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mostRecent := mostRecentBuildForBranches(buildsForPipeline, pipeline.branches)
|
||||
builds = append(builds, mostRecent...)
|
||||
}
|
||||
|
||||
return builds, nil
|
||||
}
|
||||
|
||||
func (widget *Widget) recentBuilds(pipeline PipelineSettings) ([]Build, error) {
|
||||
url := fmt.Sprintf(
|
||||
"https://api.buildkite.com/v2/organizations/%s/pipelines/%s/builds%s",
|
||||
widget.settings.orgSlug,
|
||||
pipeline.slug,
|
||||
branchesQuery(pipeline.branches),
|
||||
)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", widget.settings.token))
|
||||
|
||||
httpClient := &http.Client{Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf(resp.Status)
|
||||
}
|
||||
|
||||
builds := []Build{}
|
||||
err = utils.ParseJson(&builds, resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return builds, nil
|
||||
}
|
||||
|
||||
func branchesQuery(branches []string) string {
|
||||
if len(branches) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(branches) == 1 {
|
||||
return fmt.Sprintf("?branch=%s", branches[0])
|
||||
}
|
||||
|
||||
queryString := fmt.Sprintf("?branch[]=%s", branches[0])
|
||||
for _, branch := range branches[1:] {
|
||||
queryString += fmt.Sprintf("&branch[]=%s", branch)
|
||||
}
|
||||
|
||||
return queryString
|
||||
}
|
||||
|
||||
func mostRecentBuildForBranches(builds []Build, branches []string) []Build {
|
||||
recentBuilds := []Build{}
|
||||
|
||||
haveMostRecentBuildForBranch := map[string]bool{}
|
||||
for _, branch := range branches {
|
||||
haveMostRecentBuildForBranch[branch] = false
|
||||
}
|
||||
|
||||
for _, build := range builds {
|
||||
if !haveMostRecentBuildForBranch[build.Branch] {
|
||||
haveMostRecentBuildForBranch[build.Branch] = true
|
||||
recentBuilds = append(recentBuilds, build)
|
||||
}
|
||||
}
|
||||
|
||||
return recentBuilds
|
||||
}
|
5
modules/buildkite/keyboard.go
Normal file
5
modules/buildkite/keyboard.go
Normal file
@ -0,0 +1,5 @@
|
||||
package buildkite
|
||||
|
||||
func (widget *Widget) initializeKeyboardControls() {
|
||||
widget.InitializeCommonControls(widget.Refresh)
|
||||
}
|
54
modules/buildkite/settings.go
Normal file
54
modules/buildkite/settings.go
Normal file
@ -0,0 +1,54 @@
|
||||
package buildkite
|
||||
|
||||
import (
|
||||
"github.com/olebedev/config"
|
||||
"github.com/wtfutil/wtf/cfg"
|
||||
"github.com/wtfutil/wtf/utils"
|
||||
"os"
|
||||
)
|
||||
|
||||
type PipelineSettings struct {
|
||||
slug string
|
||||
branches []string
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
common *cfg.Common
|
||||
token string `help:"Your Buildkite API Token"`
|
||||
orgSlug string `help:"Organization Slug"`
|
||||
pipelines []PipelineSettings `help:"An array of pipelines to get data from"`
|
||||
}
|
||||
|
||||
const defaultTitle = "Buildkite"
|
||||
const defaultFocusable = true
|
||||
|
||||
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
|
||||
settings := Settings{
|
||||
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
|
||||
token: ymlConfig.UString("token", os.Getenv("WTF_BUILDKITE_TOKEN")),
|
||||
orgSlug: ymlConfig.UString("organizationSlug"),
|
||||
pipelines: buildPipelineSettings(ymlConfig),
|
||||
}
|
||||
|
||||
return &settings
|
||||
}
|
||||
|
||||
func buildPipelineSettings(ymlConfig *config.Config) []PipelineSettings {
|
||||
pipelines := []PipelineSettings{}
|
||||
|
||||
for slug, _ := range ymlConfig.UMap("pipelines") {
|
||||
branches := utils.ToStrs(ymlConfig.UList("pipelines." + slug + ".branches"))
|
||||
if len(branches) == 0 {
|
||||
branches = []string{"master"}
|
||||
}
|
||||
|
||||
pipeline := PipelineSettings{
|
||||
slug: slug,
|
||||
branches: branches,
|
||||
}
|
||||
|
||||
pipelines = append(pipelines, pipeline)
|
||||
}
|
||||
|
||||
return pipelines
|
||||
}
|
129
modules/buildkite/widget.go
Normal file
129
modules/buildkite/widget.go
Normal file
@ -0,0 +1,129 @@
|
||||
package buildkite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/wtfutil/wtf/view"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const HelpText = `
|
||||
Keyboard commands for Buildkite:
|
||||
`
|
||||
|
||||
type Widget struct {
|
||||
view.KeyboardWidget
|
||||
view.TextWidget
|
||||
settings *Settings
|
||||
|
||||
builds []Build
|
||||
err error
|
||||
}
|
||||
|
||||
func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *Widget {
|
||||
widget := Widget{
|
||||
KeyboardWidget: view.NewKeyboardWidget(app, pages, settings.common),
|
||||
TextWidget: view.NewTextWidget(app, settings.common),
|
||||
settings: settings,
|
||||
}
|
||||
|
||||
widget.initializeKeyboardControls()
|
||||
widget.View.SetInputCapture(widget.InputCapture)
|
||||
widget.View.SetScrollable(true)
|
||||
widget.KeyboardWidget.SetView(widget.View)
|
||||
|
||||
return &widget
|
||||
}
|
||||
|
||||
/* -------------------- Exported Functions -------------------- */
|
||||
|
||||
func (widget *Widget) Refresh() {
|
||||
builds, err := widget.getBuilds()
|
||||
|
||||
if err != nil {
|
||||
widget.err = err
|
||||
widget.builds = nil
|
||||
} else {
|
||||
widget.builds = builds
|
||||
widget.err = nil
|
||||
}
|
||||
|
||||
// The last call should always be to the display function
|
||||
widget.display()
|
||||
}
|
||||
|
||||
/* -------------------- Unexported Functions -------------------- */
|
||||
|
||||
func (widget *Widget) display() {
|
||||
widget.Redraw(widget.content)
|
||||
}
|
||||
|
||||
func (widget *Widget) content() (string, string, bool) {
|
||||
title := fmt.Sprintf("%s - [green]%s", widget.CommonSettings().Title, widget.settings.orgSlug)
|
||||
|
||||
if widget.err != nil {
|
||||
return title, widget.err.Error(), true
|
||||
}
|
||||
|
||||
pipelineData := groupByPipeline(widget.builds)
|
||||
maxPipelineLength := getLongestPipelineLength(widget.builds)
|
||||
|
||||
str := ""
|
||||
for pipeline, builds := range pipelineData {
|
||||
str += fmt.Sprintf("[white]%s", padRight(pipeline, maxPipelineLength))
|
||||
for _, build := range builds {
|
||||
str += fmt.Sprintf(" [%s]%s[white]", buildColor(build.State), build.Branch)
|
||||
}
|
||||
str += "\n"
|
||||
}
|
||||
|
||||
return title, str, false
|
||||
}
|
||||
|
||||
func groupByPipeline(builds []Build) map[string][]Build {
|
||||
grouped := make(map[string][]Build)
|
||||
|
||||
for _, build := range builds {
|
||||
if _, ok := grouped[build.Pipeline.Slug]; ok {
|
||||
grouped[build.Pipeline.Slug] = append(grouped[build.Pipeline.Slug], build)
|
||||
} else {
|
||||
grouped[build.Pipeline.Slug] = []Build{}
|
||||
grouped[build.Pipeline.Slug] = append(grouped[build.Pipeline.Slug], build)
|
||||
}
|
||||
}
|
||||
|
||||
return grouped
|
||||
}
|
||||
|
||||
func getLongestPipelineLength(builds []Build) int {
|
||||
maxPipelineLength := 0
|
||||
|
||||
for _, build := range builds {
|
||||
if len(build.Pipeline.Slug) > maxPipelineLength {
|
||||
maxPipelineLength = len(build.Pipeline.Slug)
|
||||
}
|
||||
}
|
||||
|
||||
return maxPipelineLength
|
||||
}
|
||||
|
||||
func padRight(text string, length int) string {
|
||||
padLength := length - len(text)
|
||||
|
||||
if padLength <= 0 {
|
||||
return text[:length]
|
||||
}
|
||||
|
||||
return text + strings.Repeat(" ", padLength)
|
||||
}
|
||||
|
||||
func buildColor(state string) string {
|
||||
switch state {
|
||||
case "passed":
|
||||
return "green"
|
||||
case "failed":
|
||||
return "red"
|
||||
default:
|
||||
return "yellow"
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user