feat: render Markdown in rich parameter descriptions (#6098)

This commit is contained in:
Marcin Tojek
2023-02-08 12:57:12 +01:00
committed by GitHub
parent f24547ecb1
commit 1dc477819e
15 changed files with 280 additions and 71 deletions

3
coderd/apidoc/docs.go generated
View File

@ -7555,6 +7555,9 @@ const docTemplate = `{
"description": {
"type": "string"
},
"description_plaintext": {
"type": "string"
},
"icon": {
"type": "string"
},

View File

@ -6793,6 +6793,9 @@
"description": {
"type": "string"
},
"description_plaintext": {
"type": "string"
},
"icon": {
"type": "string"
},

View File

@ -0,0 +1,97 @@
package parameter
import (
"strings"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/glamour/ansi"
"golang.org/x/xerrors"
)
var plaintextStyle = ansi.StyleConfig{
Document: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{},
},
BlockQuote: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{},
},
Paragraph: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{},
},
List: ansi.StyleList{
StyleBlock: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{},
},
LevelIndent: 4,
},
Heading: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{},
},
H1: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{},
},
H2: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{},
},
H3: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{},
},
H4: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{},
},
H5: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{},
},
H6: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{},
},
Strikethrough: ansi.StylePrimitive{},
Emph: ansi.StylePrimitive{},
Strong: ansi.StylePrimitive{},
HorizontalRule: ansi.StylePrimitive{},
Item: ansi.StylePrimitive{},
Enumeration: ansi.StylePrimitive{
BlockPrefix: ". ",
}, Task: ansi.StyleTask{},
Link: ansi.StylePrimitive{
Format: "({{.text}})",
},
LinkText: ansi.StylePrimitive{
Format: "{{.text}}",
},
ImageText: ansi.StylePrimitive{
Format: "{{.text}}",
},
Image: ansi.StylePrimitive{
Format: "({{.text}})",
},
Code: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{},
},
CodeBlock: ansi.StyleCodeBlock{
StyleBlock: ansi.StyleBlock{},
},
Table: ansi.StyleTable{},
DefinitionDescription: ansi.StylePrimitive{},
}
// Plaintext function converts the description with optional Markdown tags
// to the plaintext form.
func Plaintext(markdown string) (string, error) {
renderer, err := glamour.NewTermRenderer(
glamour.WithStandardStyle("ascii"),
glamour.WithWordWrap(0), // don't need to add spaces in the end of line
glamour.WithStyles(plaintextStyle),
)
if err != nil {
return "", xerrors.Errorf("can't initialize the Markdown renderer: %w", err)
}
output, err := renderer.Render(markdown)
if err != nil {
return "", xerrors.Errorf("can't render description to plaintext: %w", err)
}
defer renderer.Close()
return strings.TrimSpace(output), nil
}

View File

@ -0,0 +1,49 @@
package parameter_test
import (
"testing"
"github.com/coder/coder/coderd/parameter"
"github.com/stretchr/testify/require"
)
func TestPlaintext(t *testing.T) {
t.Parallel()
t.Run("Simple", func(t *testing.T) {
t.Parallel()
mdDescription := `# Provide the machine image
See the [registry](https://container.registry.blah/namespace) for options.
![Minion](https://octodex.github.com/images/minion.png)
**This is bold text.**
__This is bold text.__
*This is italic text.*
> Blockquotes can also be nested.
~~Strikethrough.~~
1. Lorem ipsum dolor sit amet.
2. Consectetur adipiscing elit.
3. Integer molestie lorem at massa.
` + "`There are also code tags!`"
expected := "Provide the machine image\nSee the registry (https://container.registry.blah/namespace) for options.\n\nMinion (https://octodex.github.com/images/minion.png)\n\nThis is bold text.\nThis is bold text.\nThis is italic text.\n\nBlockquotes can also be nested.\nStrikethrough.\n\n1. Lorem ipsum dolor sit amet.\n2. Consectetur adipiscing elit.\n3. Integer molestie lorem at massa.\n\nThere are also code tags!"
stripped, err := parameter.Plaintext(mdDescription)
require.NoError(t, err)
require.Equal(t, expected, stripped)
})
t.Run("Nothing changes", func(t *testing.T) {
t.Parallel()
nothingChanges := "This is a simple description, so nothing changes."
stripped, err := parameter.Plaintext(nothingChanges)
require.NoError(t, err)
require.Equal(t, nothingChanges, stripped)
})
}

View File

@ -1458,19 +1458,25 @@ func convertTemplateVersionParameter(param database.TemplateVersionParameter) (c
Icon: option.Icon,
})
}
descriptionPlaintext, err := parameter.Plaintext(param.Description)
if err != nil {
return codersdk.TemplateVersionParameter{}, err
}
return codersdk.TemplateVersionParameter{
Name: param.Name,
Description: param.Description,
Type: param.Type,
Mutable: param.Mutable,
DefaultValue: param.DefaultValue,
Icon: param.Icon,
Options: options,
ValidationRegex: param.ValidationRegex,
ValidationMin: param.ValidationMin,
ValidationMax: param.ValidationMax,
ValidationError: param.ValidationError,
ValidationMonotonic: codersdk.ValidationMonotonicOrder(param.ValidationMonotonic),
Name: param.Name,
Description: param.Description,
DescriptionPlaintext: descriptionPlaintext,
Type: param.Type,
Mutable: param.Mutable,
DefaultValue: param.DefaultValue,
Icon: param.Icon,
Options: options,
ValidationRegex: param.ValidationRegex,
ValidationMin: param.ValidationMin,
ValidationMax: param.ValidationMax,
ValidationError: param.ValidationError,
ValidationMonotonic: codersdk.ValidationMonotonicOrder(param.ValidationMonotonic),
}, nil
}

View File

@ -20,6 +20,7 @@ import (
"github.com/coder/coder/coderd/autobuild/schedule"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/parameter"
"github.com/coder/coder/coderd/rbac"
"github.com/coder/coder/coderd/util/ptr"
"github.com/coder/coder/codersdk"
@ -1788,12 +1789,12 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
const (
firstParameterName = "first_parameter"
firstParameterType = "string"
firstParameterDescription = "This is first parameter"
firstParameterDescription = "This is _first_ *parameter*"
firstParameterValue = "1"
secondParameterName = "second_parameter"
secondParameterType = "number"
secondParameterDescription = "This is second parameter"
secondParameterDescription = "_This_ is second *parameter*"
secondParameterValue = "2"
secondParameterValidationMonotonic = codersdk.MonotonicOrderIncreasing
)
@ -1835,16 +1836,24 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
firstParameterDescriptionPlaintext, err := parameter.Plaintext(firstParameterDescription)
require.NoError(t, err)
secondParameterDescriptionPlaintext, err := parameter.Plaintext(secondParameterDescription)
require.NoError(t, err)
templateRichParameters, err := client.TemplateVersionRichParameters(ctx, version.ID)
require.NoError(t, err)
require.Len(t, templateRichParameters, 2)
require.Equal(t, templateRichParameters[0].Name, firstParameterName)
require.Equal(t, templateRichParameters[0].Type, firstParameterType)
require.Equal(t, templateRichParameters[0].ValidationMonotonic, codersdk.ValidationMonotonicOrder("")) // no validation for string
require.Equal(t, templateRichParameters[1].Name, secondParameterName)
require.Equal(t, templateRichParameters[1].Type, secondParameterType)
require.Equal(t, templateRichParameters[1].ValidationMonotonic, secondParameterValidationMonotonic)
require.Equal(t, firstParameterName, templateRichParameters[0].Name)
require.Equal(t, firstParameterType, templateRichParameters[0].Type)
require.Equal(t, firstParameterDescription, templateRichParameters[0].Description)
require.Equal(t, firstParameterDescriptionPlaintext, templateRichParameters[0].DescriptionPlaintext)
require.Equal(t, codersdk.ValidationMonotonicOrder(""), templateRichParameters[0].ValidationMonotonic) // no validation for string
require.Equal(t, secondParameterName, templateRichParameters[1].Name)
require.Equal(t, secondParameterType, templateRichParameters[1].Type)
require.Equal(t, secondParameterDescription, templateRichParameters[1].Description)
require.Equal(t, secondParameterDescriptionPlaintext, templateRichParameters[1].DescriptionPlaintext)
require.Equal(t, secondParameterValidationMonotonic, templateRichParameters[1].ValidationMonotonic)
expectedBuildParameters := []codersdk.WorkspaceBuildParameter{
{Name: firstParameterName, Value: firstParameterValue},