Skip to content

Commit

Permalink
Added initial experimental support for Slack markdown in blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
manterfield committed Aug 18, 2024
1 parent 2b6985b commit dacad83
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 45 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ require (
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aws/aws-sdk-go v1.44.122 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/eritikass/githubmarkdownconvertergo v0.1.10 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
Expand All @@ -67,6 +68,7 @@ require (
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/teekennedy/goldmark-markdown v0.3.0 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
go.opencensus.io v0.23.0 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/eritikass/githubmarkdownconvertergo v0.1.10 h1:mL93ADvYMOeT15DcGtK9AaFFc+RcWcy6kQBC6yS/5f4=
github.com/eritikass/githubmarkdownconvertergo v0.1.10/go.mod h1:BdpHs6imOtzE5KorbUtKa6bZ0ZBh1yFcrTTAL8FwDKY=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU=
github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU=
Expand Down Expand Up @@ -462,13 +464,16 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/teekennedy/goldmark-markdown v0.3.0 h1:ik9/biVGCwGWFg8dQ3KVm2pQ/wiiG0whYiUcz9xH0W8=
github.com/teekennedy/goldmark-markdown v0.3.0/go.mod h1:kMhDz8La77A9UHvJGsxejd0QUflN9sS+QXCqnhmxmNo=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
Expand Down
58 changes: 43 additions & 15 deletions internal/runner/slackcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import (
"strconv"
"time"

mdconv "github.com/eritikass/githubmarkdownconvertergo"
"github.com/goccy/go-json"
"github.com/hiphops-io/hops/markdown"
"github.com/hiphops-io/hops/nats"
"github.com/manterfield/go-mapreader"
"github.com/slack-go/slack"

"github.com/hiphops-io/hops/markdown"
"github.com/hiphops-io/hops/nats"
)

type (
Expand Down Expand Up @@ -54,20 +56,23 @@ func SlackCommandRequest(flow *markdown.Flow, hopsMsg *nats.HopsMsg, matchError
}

if errors.Is(matchError, markdown.ErrCommandNotFound) {
sendErrorResponse(api, fmt.Sprintf("Sorry, `%s` didn't match any commands", mapreader.Str(hopsMsg.Data, "text")), responseURL)
return err
return sendErrorResponse(api, fmt.Sprintf("Sorry, `%s` didn't match any commands", mapreader.Str(hopsMsg.Data, "text")), responseURL)
// TODO: Log this
// return err
} else if matchError != nil {
sendErrorResponse(api, fmt.Sprintf("An error occurred - this could be due to a misconfiguration\n`%s`", matchError.Error()), responseURL)
return err
return sendErrorResponse(api, fmt.Sprintf("An error occurred - this could be due to a misconfiguration\n`%s`", matchError.Error()), responseURL)
// TODO: Log this
// return err
} else if flow == nil {
return sendErrorResponse(api, "Command conditions not met - _Perhaps the command is restricted to specific channels or users?_", responseURL)
}

// If we get here then we've got an actual command to present. Yay.
blocks, err := CommandToSlackBlocks(flow.Command)
if err != nil {
sendErrorResponse(api, "command failed", responseURL)
return fmt.Errorf("unable to render command as modal: %w", err)
return sendErrorResponse(api, "command failed", responseURL)
// TODO: Log this
// return fmt.Errorf("unable to render command as modal: %w", err)
}

privateMeta, err := json.Marshal(CommandPrivateMeta{
Expand All @@ -76,19 +81,29 @@ func SlackCommandRequest(flow *markdown.Flow, hopsMsg *nats.HopsMsg, matchError
})

if err != nil {
sendErrorResponse(api, "command failed", responseURL)
return fmt.Errorf("unable to marshal private metadata: %w", err)
return sendErrorResponse(api, "command failed", responseURL)
// TODO: Log this
// return fmt.Errorf("unable to marshal private metadata: %w", err)
}

headerBlock, err := FlowHeaderBlock(flow)
if err != nil {
return sendErrorResponse(api, "command failed", responseURL)
// TODO: Log this error
}

_, err = api.OpenView(
blocks = append(
[]slack.Block{headerBlock},
blocks...,
)

r, err := api.OpenView(
hopsMsg.Data["trigger_id"].(string),
slack.ModalViewRequest{
Type: slack.VTModal,
Title: slack.NewTextBlockObject("plain_text", flow.DisplayName(), false, false),
Blocks: slack.Blocks{
BlockSet: blocks,
// slack.NewTextBlockObject() // We want the rendered slack markdown here
// Maybe with the option to hide it?
},
PrivateMetadata: string(privateMeta),
CallbackID: "command",
Expand All @@ -97,8 +112,11 @@ func SlackCommandRequest(flow *markdown.Flow, hopsMsg *nats.HopsMsg, matchError
},
)
if err != nil {
sendErrorResponse(api, "command failed - unable to open form", responseURL)
return fmt.Errorf("unable to open slack view: %w", err)
fmt.Println("Got error opening form:", err.Error())
fmt.Printf("%v\n", r)
return sendErrorResponse(api, "command failed - unable to open form", responseURL)
// TODO: Log this
// return fmt.Errorf("unable to open slack view: %w", err)
}

return nil
Expand Down Expand Up @@ -142,6 +160,16 @@ func CommandToSlackBlocks(command markdown.Command) ([]slack.Block, error) {
return blocks, nil
}

func FlowHeaderBlock(flow *markdown.Flow) (slack.Block, error) {
md, err := flow.Markdown()
if err != nil {
return nil, err
}

slackMD := mdconv.Slack(md, mdconv.SlackConvertOptions{Headlines: true})
return slack.NewSectionBlock(slack.NewTextBlockObject("mrkdwn", slackMD, false, false), nil, nil), nil
}

func ParamBlockLabel(name string) *slack.TextBlockObject {
return &slack.TextBlockObject{
Type: slack.PlainTextType,
Expand Down
41 changes: 27 additions & 14 deletions markdown/flow.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package markdown

import (
"bytes"
"errors"
"fmt"
"io/fs"
Expand All @@ -11,7 +12,6 @@ import (

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/yuin/goldmark/ast"
"github.com/zclconf/go-cty/cty/gocty"
"go.abhg.dev/goldmark/frontmatter"
"golang.org/x/text/cases"
Expand All @@ -27,12 +27,13 @@ type (
On string `yaml:"on" validate:"required_without_all=Command Schedule"`
Schedule string `yaml:"schedule" validate:"required_without_all=On Command,omitempty,standard_cron"`
Worker string `yaml:"worker"`
// Calculated fields
// Computed fields
ID string
dirName string
fileName string
ifExpression hcl.Expression
markdown ast.Node
markdown []byte
md *Markdown
path string
}

Expand Down Expand Up @@ -70,7 +71,7 @@ func NewFlowReader(basePath string) *FlowReader {
return &FlowReader{
basePath: basePath,
index: NewFlowIndex(),
md: NewMarkdownHTML(),
md: NewMarkdown(),
}
}

Expand Down Expand Up @@ -140,12 +141,23 @@ func (fr *FlowReader) ReadAll() error {
}

func (fr *FlowReader) ReadFlow(path string) (*Flow, error) {
ast, pCtx, err := fr.md.ParseFile(path)
content, pCtx, err := fr.md.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("unable to load flow: %w", err)
}

f := &Flow{path: path}
flowDirName := filepath.Base(filepath.Dir(path))
fileName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))

f := &Flow{
ID: fmt.Sprintf("%s.%s", flowDirName, fileName),
path: path,
markdown: content,
md: fr.md,
dirName: flowDirName,
fileName: fileName,
}

fm := frontmatter.Get(pCtx)
if fm == nil {
return nil, fmt.Errorf("flow does not contain any fields")
Expand All @@ -155,14 +167,6 @@ func (fr *FlowReader) ReadFlow(path string) (*Flow, error) {
return nil, fmt.Errorf("unable to decode flow: %w", err)
}

flowDirName := filepath.Base(filepath.Dir(path))
fileName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))

f.dirName = flowDirName
f.fileName = fileName
f.markdown = ast
f.ID = fmt.Sprintf("%s.%s", flowDirName, fileName)

if f.Worker == "" {
f.Worker = f.ID
}
Expand Down Expand Up @@ -268,6 +272,15 @@ func (f *Flow) IfValue(evalCtx *hcl.EvalContext) (bool, error) {
return matches, nil
}

func (f *Flow) Markdown() (string, error) {
var b bytes.Buffer
if _, err := f.md.Markdown(f.markdown, &b); err != nil {
return "", err
}

return b.String(), nil
}

func (pi *ParamItem) DisplayName() string {
for name := range *pi {
return titleCase(name)
Expand Down
45 changes: 33 additions & 12 deletions markdown/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@ import (
"io"
"os"

mdrender "github.com/teekennedy/goldmark-markdown"
"github.com/yuin/goldmark"
emoji "github.com/yuin/goldmark-emoji"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
"go.abhg.dev/goldmark/frontmatter"
)

type Markdown struct {
md goldmark.Markdown
md goldmark.Markdown
htmlRenderer renderer.Renderer
markdownRenderer renderer.Renderer
}

func NewMarkdownHTML() *Markdown {
func NewMarkdown() *Markdown {
md := goldmark.New(
goldmark.WithExtensions(
emoji.Emoji,
Expand All @@ -28,32 +31,50 @@ func NewMarkdownHTML() *Markdown {
Formats: []frontmatter.Format{frontmatter.YAML},
},
),
goldmark.WithRendererOptions(
html.WithUnsafe(),
),
)

return &Markdown{md: md}
htmlRend := md.Renderer()
htmlRend.AddOptions(html.WithUnsafe())

mdRend := mdrender.NewRenderer()

return &Markdown{
md: md,
htmlRenderer: htmlRend,
markdownRenderer: mdRend,
}
}

func (m *Markdown) Convert(source []byte, w io.Writer) (parser.Context, error) {
func (m *Markdown) HTML(source []byte, w io.Writer) (parser.Context, error) {
ctx := parser.NewContext()
m.md.SetRenderer(m.htmlRenderer)

if err := m.md.Convert(source, w, parser.WithContext(ctx)); err != nil {
return nil, err
}

return ctx, nil
}

func (m *Markdown) ParseFile(path string) (ast.Node, parser.Context, error) {
func (m *Markdown) Markdown(source []byte, w io.Writer) (parser.Context, error) {
ctx := parser.NewContext()
m.md.SetRenderer(m.markdownRenderer)

if err := m.md.Convert(source, w, parser.WithContext(ctx)); err != nil {
return nil, err
}

return ctx, nil
}

func (m *Markdown) ReadFile(path string) ([]byte, parser.Context, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, nil, err
}

reader := text.NewReader(content)
pCtx := parser.NewContext()
ast := m.md.Parser().Parse(reader, parser.WithContext(pCtx))

return ast, pCtx, nil
m.md.Parser().Parse(reader, parser.WithContext(pCtx))
return content, pCtx, nil
}
4 changes: 2 additions & 2 deletions markdown/markdown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ title: A page
},
}

md := NewMarkdownHTML()
md := NewMarkdown()

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var b bytes.Buffer

_, err := md.Convert(tc.markdown, &b)
_, err := md.HTML(tc.markdown, &b)
require.NoError(t, err, "Markdown should convert without error")

// Compare as strings to make failure output more readable
Expand Down
4 changes: 2 additions & 2 deletions markdown/staticgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func NewStaticBuilder() (*StaticBuilder, error) {
}

s := &StaticBuilder{
md: NewMarkdownHTML(),
md: NewMarkdown(),
pageTemplate: pageTemplate,
}

Expand Down Expand Up @@ -96,7 +96,7 @@ func (s *StaticBuilder) BuildMarkdown(source, build string) error {
}

var mdOutput bytes.Buffer
pCtx, err := s.md.Convert(content, &mdOutput)
pCtx, err := s.md.HTML(content, &mdOutput)
if err != nil {
return err
}
Expand Down

0 comments on commit dacad83

Please sign in to comment.