From b7c25d1f1443610999d04116fed18dc035d43be5 Mon Sep 17 00:00:00 2001 From: Dhruv Thakur <13575379+dhth@users.noreply.github.com> Date: Mon, 20 Jan 2025 00:01:52 +0530 Subject: [PATCH] feat: allow opening failed workflows (#12) --- .github/workflows/build.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/vulncheck.yml | 2 +- README.md | 14 ++++++++++ cmd/help.go | 24 +++++++++++++---- cmd/open.go | 33 +++++++++++++++++++++++ cmd/render.go | 38 ++------------------------- cmd/results.go | 46 +++++++++++++++++++++++++++++++++ cmd/root.go | 23 +++++++++++------ docker-compose.yml | 2 +- go.mod | 10 +++---- go.sum | 16 ++++++------ internal/utils/browser.go | 17 ++++++++++++ 14 files changed, 164 insertions(+), 67 deletions(-) create mode 100644 cmd/open.go create mode 100644 cmd/results.go create mode 100644 internal/utils/browser.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1eff645..185a3f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ permissions: contents: read env: - GO_VERSION: '1.23.4' + GO_VERSION: '1.23.5' jobs: build: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2bfc139..8d75d9f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,7 +13,7 @@ permissions: contents: read env: - GO_VERSION: '1.23.4' + GO_VERSION: '1.23.5' jobs: lint: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b8bd9f..2dbf3fd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ permissions: id-token: write env: - GO_VERSION: '1.23.4' + GO_VERSION: '1.23.5' jobs: release: diff --git a/.github/workflows/vulncheck.yml b/.github/workflows/vulncheck.yml index 0252142..f840b53 100644 --- a/.github/workflows/vulncheck.yml +++ b/.github/workflows/vulncheck.yml @@ -14,7 +14,7 @@ permissions: contents: read env: - GO_VERSION: '1.23.4' + GO_VERSION: '1.23.5' jobs: vulncheck: diff --git a/README.md b/README.md index 65d7104..69736c1 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,20 @@ of two ways: ### Basic Usage +```text +Usage: + act3 [flags] + +Flags: + -c string path of the config file (default "/Users/user/.config/act3/act3.yml") + -f string output format to use; possible values: default, table, html (default "default") + -t string path of the HTML template file to use + -r string repo to fetch worflows for, in the format "owner/repo" + -g bool whether to use workflows defined globally via the config file (default false) + -o bool whether to open failed workflows (via your OS's "open" command) (default false) + -h, --help help for act3 +``` + By default, `act3` will show results for the repository associated with the current directory. Simply run `act3` from the project root. diff --git a/cmd/help.go b/cmd/help.go index 48661b3..266701f 100644 --- a/cmd/help.go +++ b/cmd/help.go @@ -1,7 +1,8 @@ package cmd -var ( - configSampleFormat = ` +import "fmt" + +var configSampleFormat = ` workflows: - id: W_kwDOLafHJ84FQglU repo: dhth/outtasync @@ -16,7 +17,20 @@ workflows: name: release key: cueitup:release ` - helpText = `Glance at the last 3 runs of your Github Actions. -Usage: act3 [flags]` -) +func getHelp(configFilePath string) string { + return fmt.Sprintf(`Glance at the last 3 runs of your Github Actions. + +Usage: + act3 [flags] + +Flags: + -c string path of the config file (default "%s") + -f string output format to use; possible values: default, table, html (default "default") + -t string path of the HTML template file to use + -r string repo to fetch worflows for, in the format "owner/repo" + -g bool whether to use workflows defined globally via the config file (default false) + -o bool whether to open failed workflows (via your OS's "open" command) (default false) + -h, --help help for act3 +`, configFilePath) +} diff --git a/cmd/open.go b/cmd/open.go new file mode 100644 index 0000000..f2f1c8c --- /dev/null +++ b/cmd/open.go @@ -0,0 +1,33 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/dhth/act3/internal/gh" + "github.com/dhth/act3/internal/utils" +) + +func openFailedWorkflows(results []gh.ResultData, goos string) { + var urls []string + for _, r := range results { + if r.Err != nil { + continue + } + + for _, rr := range r.Result.Workflow.Runs.Nodes { + if rr.CheckSuite.IsAFailure() { + urls = append(urls, rr.URL) + } + } + } + + if len(urls) == 0 { + return + } + + err := utils.OpenURLsInBrowser(urls, goos) + if err != nil { + fmt.Fprintf(os.Stderr, "error opening URLs: %s", err.Error()) + } +} diff --git a/cmd/render.go b/cmd/render.go index 7d0db15..ab43d2c 100644 --- a/cmd/render.go +++ b/cmd/render.go @@ -2,52 +2,18 @@ package cmd import ( "fmt" - "sort" "github.com/dhth/act3/internal/gh" "github.com/dhth/act3/internal/types" "github.com/dhth/act3/internal/ui" ) -func render(workflows []types.Workflow, config types.Config) error { - resultsMap := make(map[string]gh.ResultData) - resultChannel := make(chan gh.ResultData) - var results []gh.ResultData - - for _, wf := range workflows { - go func(workflow types.Workflow) { - resultChannel <- gh.GetWorkflowRuns(config.GHClient, workflow) - }(wf) - } - - for range workflows { - r := <-resultChannel - resultsMap[r.Workflow.ID] = r - } - - if config.CurrentRepo != nil { - var resultsList []gh.ResultData - for _, r := range resultsMap { - resultsList = append(resultsList, r) - } - // sort workflows alphabetically - sort.Slice(resultsList, func(i, j int) bool { - return resultsList[i].Workflow.Name < resultsList[j].Workflow.Name - }) - results = resultsList - } else { - // sort workflows in the sequence of the config file - resultsInConfigDefinedOrder := make([]gh.ResultData, len(workflows)) - for i, w := range workflows { - resultsInConfigDefinedOrder[i] = resultsMap[w.ID] - } - results = resultsInConfigDefinedOrder - } - +func render(results []gh.ResultData, config types.Config) error { output, err := ui.GetOutput(config, results) if err != nil { return err } + fmt.Print(output) return nil } diff --git a/cmd/results.go b/cmd/results.go new file mode 100644 index 0000000..e6918a2 --- /dev/null +++ b/cmd/results.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "sort" + + "github.com/dhth/act3/internal/gh" + "github.com/dhth/act3/internal/types" +) + +func getResults(workflows []types.Workflow, config types.Config) []gh.ResultData { + resultsMap := make(map[string]gh.ResultData) + resultChannel := make(chan gh.ResultData) + var results []gh.ResultData + + for _, wf := range workflows { + go func(workflow types.Workflow) { + resultChannel <- gh.GetWorkflowRuns(config.GHClient, workflow) + }(wf) + } + + for range workflows { + r := <-resultChannel + resultsMap[r.Workflow.ID] = r + } + + if config.CurrentRepo != nil { + var resultsList []gh.ResultData + for _, r := range resultsMap { + resultsList = append(resultsList, r) + } + // sort workflows alphabetically + sort.Slice(resultsList, func(i, j int) bool { + return resultsList[i].Workflow.Name < resultsList[j].Workflow.Name + }) + results = resultsList + } else { + // sort workflows in the sequence of the config file + resultsInConfigDefinedOrder := make([]gh.ResultData, len(workflows)) + for i, w := range workflows { + resultsInConfigDefinedOrder[i] = resultsMap[w.ID] + } + results = resultsInConfigDefinedOrder + } + + return results +} diff --git a/cmd/root.go b/cmd/root.go index 9f28f64..f9f6366 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -37,16 +37,18 @@ var ( ) var ( - format = flag.String("f", "", "output format to use; possible values: default, table, html") - htmlTemplateFile = flag.String("t", "", "path of the HTML template file to use") - global = flag.Bool("g", false, "whether to use workflows defined globally via the config file") - repo = flag.String("r", "", "repo to fetch worflows for, in the format \"owner/repo\"") + format = flag.String("f", "", "") + htmlTemplateFile = flag.String("t", "", "") + global = flag.Bool("g", false, "") + repo = flag.String("r", "", "") + openFailed = flag.Bool("o", false, "") ) func Execute() error { var defaultConfigDir string var configErr error - switch runtime.GOOS { + goos := runtime.GOOS + switch goos { case "linux", "windows": defaultConfigDir, configErr = os.UserConfigDir() default: @@ -67,8 +69,7 @@ Let %s know about this via %s. configFilePath := flag.String("c", defaultConfigFilePath, "path of the config file") flag.Usage = func() { - fmt.Fprintf(os.Stderr, "%s\n\nFlags:\n", helpText) - flag.PrintDefaults() + fmt.Fprint(os.Stderr, getHelp(defaultConfigFilePath)) } flag.Parse() @@ -179,9 +180,15 @@ Let %s know about this via %s. HTMLTemplate: htmlTemplate, } - err = render(workflows, config) + results := getResults(workflows, config) + + err = render(results, config) if err != nil { return err } + + if *openFailed { + openFailedWorkflows(results, goos) + } return nil } diff --git a/docker-compose.yml b/docker-compose.yml index 55ff8ae..3e85257 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ version: '3.8' # to test operations on linux services: act3-dev: - image: golang:1.23.4-alpine + image: golang:1.23.5-alpine volumes: - .:/go/src/app working_dir: /go/src/app diff --git a/go.mod b/go.mod index f4220eb..b5cf839 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/dhth/act3 -go 1.23.4 +go 1.23.5 require ( github.com/charmbracelet/lipgloss v1.0.0 @@ -17,16 +17,16 @@ require ( require ( dario.cat/mergo v1.0.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.1.4 // indirect + github.com/ProtonMail/go-crypto v1.1.5 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/x/ansi v0.6.0 // indirect + github.com/charmbracelet/x/ansi v0.7.0 // indirect github.com/cli/safeexec v1.0.1 // indirect github.com/cloudflare/circl v1.5.0 // indirect - github.com/cyphar/filepath-securejoin v0.3.6 // indirect + github.com/cyphar/filepath-securejoin v0.4.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.1 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/henvic/httpretty v0.1.4 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect diff --git a/go.sum b/go.sum index e083af7..48c8235 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.1.4 h1:G5U5asvD5N/6/36oIw3k2bOfBn5XVcZrb7PBjzzKKoE= -github.com/ProtonMail/go-crypto v1.1.4/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= +github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -13,8 +13,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= -github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= -github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= +github.com/charmbracelet/x/ansi v0.7.0 h1:/QfFmiXOGGwN6fRbzvQaYp7fu1pkxpZ3qFBZWBsP404= +github.com/charmbracelet/x/ansi v0.7.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= github.com/cli/go-gh/v2 v2.11.2 h1:oad1+sESTPNTiTvh3I3t8UmxuovNDxhwLzeMHk45Q9w= github.com/cli/go-gh/v2 v2.11.2/go.mod h1:vVFhi3TfjseIW26ED9itAR8gQK0aVThTm8sYrsZ5QTI= github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= @@ -23,8 +23,8 @@ github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJ github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk= github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= -github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cyphar/filepath-securejoin v0.4.0 h1:PioTG9TBRSApBpYGnDU8HC+miIsX8vitBH9LGNNMoLQ= +github.com/cyphar/filepath-securejoin v0.4.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -38,8 +38,8 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= -github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= diff --git a/internal/utils/browser.go b/internal/utils/browser.go new file mode 100644 index 0000000..cac8f09 --- /dev/null +++ b/internal/utils/browser.go @@ -0,0 +1,17 @@ +package utils + +import ( + "os/exec" +) + +func OpenURLsInBrowser(urls []string, goos string) error { + var openCmd string + switch goos { + case "darwin": + openCmd = "open" + default: + openCmd = "xdg-open" + } + c := exec.Command(openCmd, urls...) + return c.Run() +}