Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to set step summaries #43

Merged
merged 1 commit into from May 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 41 additions & 6 deletions actions.go
Expand Up @@ -22,6 +22,7 @@ import (
"context"
"encoding/json"
"fmt"
"html/template"
"io"
"net/http"
"net/url"
Expand Down Expand Up @@ -52,6 +53,8 @@ const (
groupCmd = "group"
endGroupCmd = "endgroup"

stepSummaryCmd = "step-summary"

debugCmd = "debug"
noticeCmd = "notice"
warningCmd = "warning"
Expand Down Expand Up @@ -230,6 +233,38 @@ func (c *Action) EndGroup() {
})
}

// AddStepSummary writes the given markdown to the job summary. If a job summary
// already exists, this value is appended.
//
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary
// https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/
func (c *Action) AddStepSummary(markdown string) {
c.IssueFileCommand(&Command{
Name: stepSummaryCmd,
Message: markdown,
})
}

// AddStepSummaryTemplate adds a summary template by parsing the given Go
// template using html/template with the given input data. See AddStepSummary
// for caveats.
//
// This primarily exists as a convenience function that renders a template.
func (c *Action) AddStepSummaryTemplate(tmpl string, data any) error {
t, err := template.New("").Parse(tmpl)
if err != nil {
return fmt.Errorf("failed to parse template: %w", err)
}

var b bytes.Buffer
if err := t.Execute(&b, data); err != nil {
return fmt.Errorf("failed to execute template: %w", err)
}

c.AddStepSummary(b.String())
return nil
}

// SetEnv sets an environment variable. It panics if it cannot write to the
// output file.
//
Expand Down Expand Up @@ -258,7 +293,7 @@ func (c *Action) SetOutput(k, v string) {
// Debugf prints a debug-level message. It follows the standard fmt.Printf
// arguments, appending an OS-specific line break to the end of the message. It
// panics if it cannot write to the output stream.
func (c *Action) Debugf(msg string, args ...interface{}) {
func (c *Action) Debugf(msg string, args ...any) {
// ::debug <c.fields>::<msg, args>
c.IssueCommand(&Command{
Name: debugCmd,
Expand All @@ -270,7 +305,7 @@ func (c *Action) Debugf(msg string, args ...interface{}) {
// Noticef prints a notice-level message. It follows the standard fmt.Printf
// arguments, appending an OS-specific line break to the end of the message. It
// panics if it cannot write to the output stream.
func (c *Action) Noticef(msg string, args ...interface{}) {
func (c *Action) Noticef(msg string, args ...any) {
// ::notice <c.fields>::<msg, args>
c.IssueCommand(&Command{
Name: noticeCmd,
Expand All @@ -282,7 +317,7 @@ func (c *Action) Noticef(msg string, args ...interface{}) {
// Warningf prints a warning-level message. It follows the standard fmt.Printf
// arguments, appending an OS-specific line break to the end of the message. It
// panics if it cannot write to the output stream.
func (c *Action) Warningf(msg string, args ...interface{}) {
func (c *Action) Warningf(msg string, args ...any) {
// ::warning <c.fields>::<msg, args>
c.IssueCommand(&Command{
Name: warningCmd,
Expand All @@ -294,7 +329,7 @@ func (c *Action) Warningf(msg string, args ...interface{}) {
// Errorf prints a error-level message. It follows the standard fmt.Printf
// arguments, appending an OS-specific line break to the end of the message. It
// panics if it cannot write to the output stream.
func (c *Action) Errorf(msg string, args ...interface{}) {
func (c *Action) Errorf(msg string, args ...any) {
// ::error <c.fields>::<msg, args>
c.IssueCommand(&Command{
Name: errorCmd,
Expand All @@ -305,15 +340,15 @@ func (c *Action) Errorf(msg string, args ...interface{}) {

// Fatalf prints a error-level message and exits. This is equivalent to Errorf
// followed by os.Exit(1).
func (c *Action) Fatalf(msg string, args ...interface{}) {
func (c *Action) Fatalf(msg string, args ...any) {
c.Errorf(msg, args...)
osExit(1)
}

// Infof prints message to stdout without any level annotations. It follows the
// standard fmt.Printf arguments, appending an OS-specific line break to the end
// of the message. It panics if it cannot write to the output stream.
func (c *Action) Infof(msg string, args ...interface{}) {
func (c *Action) Infof(msg string, args ...any) {
if _, err := fmt.Fprintf(c.w, msg+EOF, args...); err != nil {
panic(fmt.Errorf("failed to write info command: %w", err))
}
Expand Down
43 changes: 43 additions & 0 deletions actions_doc_test.go
Expand Up @@ -38,6 +38,49 @@ func ExampleAction_AddPath() {
a.AddPath("/tmp/myapp")
}

func ExampleAction_GetInput() {
a := githubactions.New()
a.GetInput("foo")
}

func ExampleAction_Group() {
a := githubactions.New()
a.Group("My group")
}

func ExampleAction_EndGroup() {
a := githubactions.New()
a.Group("My group")

// work

a.EndGroup()
}

func ExampleAction_AddStepSummary() {
a := githubactions.New()
a.AddStepSummary(`
## Heading

- :rocket:
- :moon:
`)
}

func ExampleAction_AddStepSummaryTemplate() {
a := githubactions.New()
if err := a.AddStepSummaryTemplate(`
## Heading

- {{.Input}}
- :moon:
`, map[string]string{
"Input": ":rocket:",
}); err != nil {
// handle error
}
}

func ExampleAction_Debugf() {
a := githubactions.New()
a.Debugf("a debug message")
Expand Down
29 changes: 23 additions & 6 deletions actions_root.go
Expand Up @@ -14,7 +14,9 @@

package githubactions

import "context"
import (
"context"
)

var (
defaultAction = New()
Expand Down Expand Up @@ -71,6 +73,21 @@ func EndGroup() {
defaultAction.EndGroup()
}

// AddStepSummary writes the given markdown to the job summary. If a job summary
// already exists, this value is appended.
func AddStepSummary(markdown string) {
defaultAction.AddStepSummary(markdown)
}

// AddStepSummaryTemplate adds a summary template by parsing the given Go
// template using html/template with the given input data. See AddStepSummary
// for caveats.
//
// This primarily exists as a convenience function that renders a template.
func AddStepSummaryTemplate(tmpl string, data any) error {
return defaultAction.AddStepSummaryTemplate(tmpl, data)
}

// SetEnv sets an environment variable.
func SetEnv(k, v string) {
defaultAction.SetEnv(k, v)
Expand All @@ -83,31 +100,31 @@ func SetOutput(k, v string) {

// Debugf prints a debug-level message. The arguments follow the standard Printf
// arguments.
func Debugf(msg string, args ...interface{}) {
func Debugf(msg string, args ...any) {
defaultAction.Debugf(msg, args...)
}

// Errorf prints a error-level message. The arguments follow the standard Printf
// arguments.
func Errorf(msg string, args ...interface{}) {
func Errorf(msg string, args ...any) {
defaultAction.Errorf(msg, args...)
}

// Fatalf prints a error-level message and exits. This is equivalent to Errorf
// followed by os.Exit(1).
func Fatalf(msg string, args ...interface{}) {
func Fatalf(msg string, args ...any) {
defaultAction.Fatalf(msg, args...)
}

// Infof prints a info-level message. The arguments follow the standard Printf
// arguments.
func Infof(msg string, args ...interface{}) {
func Infof(msg string, args ...any) {
defaultAction.Infof(msg, args...)
}

// Warningf prints a warning-level message. The arguments follow the standard
// Printf arguments.
func Warningf(msg string, args ...interface{}) {
func Warningf(msg string, args ...any) {
defaultAction.Warningf(msg, args...)
}

Expand Down
86 changes: 80 additions & 6 deletions actions_test.go
Expand Up @@ -144,16 +144,14 @@ func TestAction_RemoveMatcher(t *testing.T) {
func TestAction_AddPath(t *testing.T) {
t.Parallel()

const envGitHubPath = "GITHUB_PATH"

// expect a file command to be issued when env file is set.
file, err := os.CreateTemp("", "")
if err != nil {
t.Fatalf("unable to create a temp env file: %s", err)
}
defer os.Remove(file.Name())

fakeGetenvFunc := newFakeGetenvFunc(t, envGitHubPath, file.Name())
fakeGetenvFunc := newFakeGetenvFunc(t, "GITHUB_PATH", file.Name())
var b bytes.Buffer
a := New(WithWriter(&b), WithGetenv(fakeGetenvFunc))

Expand Down Expand Up @@ -227,10 +225,86 @@ func TestAction_EndGroup(t *testing.T) {
}
}

func TestAction_SetEnv(t *testing.T) {
func TestAction_AddStepSummary(t *testing.T) {
t.Parallel()

// expectations for env file env commands
var b bytes.Buffer
file, err := os.CreateTemp("", "")
if err != nil {
t.Fatalf("unable to create a temp env file: %s", err)
}

defer os.Remove(file.Name())
fakeGetenvFunc := newFakeGetenvFunc(t, "GITHUB_STEP_SUMMARY", file.Name())
a := New(WithWriter(&b), WithGetenv(fakeGetenvFunc))
a.AddStepSummary(`
## This is

some markdown
`)
a.AddStepSummary(`
- content
`)

// expect an empty stdout buffer
if got, want := b.String(), ""; got != want {
t.Errorf("expected %q to be %q", got, want)
}

// expect the command to be written to the file.
data, err := io.ReadAll(file)
if err != nil {
t.Errorf("unable to read temp summary file: %s", err)
}

want := "\n## This is\n\nsome markdown\n" + EOF + "\n- content\n" + EOF
if got := string(data); got != want {
t.Errorf("expected %q to be %q", got, want)
}
}

func TestAction_AddStepSummaryTemplate(t *testing.T) {
t.Parallel()

const envGitHubEnv = "GITHUB_ENV"
// expectations for env file env commands
var b bytes.Buffer
file, err := os.CreateTemp("", "")
if err != nil {
t.Fatalf("unable to create a temp env file: %s", err)
}

defer os.Remove(file.Name())
fakeGetenvFunc := newFakeGetenvFunc(t, "GITHUB_STEP_SUMMARY", file.Name())
a := New(WithWriter(&b), WithGetenv(fakeGetenvFunc))
a.AddStepSummaryTemplate(`
## This is

{{.Input}}
- content
`, map[string]string{
"Input": "some markdown",
})

// expect an empty stdout buffer
if got, want := b.String(), ""; got != want {
t.Errorf("expected %q to be %q", got, want)
}

// expect the command to be written to the file.
data, err := io.ReadAll(file)
if err != nil {
t.Errorf("unable to read temp summary file: %s", err)
}

want := "\n## This is\n\nsome markdown\n- content\n" + EOF
if got := string(data); got != want {
t.Errorf("expected %q to be %q", got, want)
}
}

func TestAction_SetEnv(t *testing.T) {
t.Parallel()

// expectations for env file env commands
var b bytes.Buffer
Expand All @@ -240,7 +314,7 @@ func TestAction_SetEnv(t *testing.T) {
}

defer os.Remove(file.Name())
fakeGetenvFunc := newFakeGetenvFunc(t, envGitHubEnv, file.Name())
fakeGetenvFunc := newFakeGetenvFunc(t, "GITHUB_ENV", file.Name())
a := New(WithWriter(&b), WithGetenv(fakeGetenvFunc))
a.SetEnv("key", "value")
a.SetEnv("key2", "value2")
Expand Down