Skip to content

Commit

Permalink
Add ability to set step summaries (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
sethvargo authored May 17, 2022
1 parent af63bd2 commit 9185bae
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 18 deletions.
47 changes: 41 additions & 6 deletions actions.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit 9185bae

Please sign in to comment.