Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1dc4778

Browse files
authored
feat: render Markdown in rich parameter descriptions (#6098)
1 parent f24547e commit 1dc4778

File tree

15 files changed

+280
-71
lines changed

15 files changed

+280
-71
lines changed

cli/cliui/parameter.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.ParameterSchem
6363

6464
func RichParameter(cmd *cobra.Command, templateVersionParameter codersdk.TemplateVersionParameter) (string, error) {
6565
_, _ = fmt.Fprintln(cmd.OutOrStdout(), Styles.Bold.Render(templateVersionParameter.Name))
66-
if templateVersionParameter.Description != "" {
67-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.TrimSpace(strings.Join(strings.Split(templateVersionParameter.Description, "\n"), "\n "))+"\n")
66+
if templateVersionParameter.DescriptionPlaintext != "" {
67+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.TrimSpace(strings.Join(strings.Split(templateVersionParameter.DescriptionPlaintext, "\n"), "\n "))+"\n")
6868
}
6969

7070
var err error

coderd/apidoc/docs.go

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/parameter/plaintext.go

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package parameter
2+
3+
import (
4+
"strings"
5+
6+
"github.com/charmbracelet/glamour"
7+
"github.com/charmbracelet/glamour/ansi"
8+
"golang.org/x/xerrors"
9+
)
10+
11+
var plaintextStyle = ansi.StyleConfig{
12+
Document: ansi.StyleBlock{
13+
StylePrimitive: ansi.StylePrimitive{},
14+
},
15+
BlockQuote: ansi.StyleBlock{
16+
StylePrimitive: ansi.StylePrimitive{},
17+
},
18+
Paragraph: ansi.StyleBlock{
19+
StylePrimitive: ansi.StylePrimitive{},
20+
},
21+
List: ansi.StyleList{
22+
StyleBlock: ansi.StyleBlock{
23+
StylePrimitive: ansi.StylePrimitive{},
24+
},
25+
LevelIndent: 4,
26+
},
27+
Heading: ansi.StyleBlock{
28+
StylePrimitive: ansi.StylePrimitive{},
29+
},
30+
H1: ansi.StyleBlock{
31+
StylePrimitive: ansi.StylePrimitive{},
32+
},
33+
H2: ansi.StyleBlock{
34+
StylePrimitive: ansi.StylePrimitive{},
35+
},
36+
H3: ansi.StyleBlock{
37+
StylePrimitive: ansi.StylePrimitive{},
38+
},
39+
H4: ansi.StyleBlock{
40+
StylePrimitive: ansi.StylePrimitive{},
41+
},
42+
H5: ansi.StyleBlock{
43+
StylePrimitive: ansi.StylePrimitive{},
44+
},
45+
H6: ansi.StyleBlock{
46+
StylePrimitive: ansi.StylePrimitive{},
47+
},
48+
Strikethrough: ansi.StylePrimitive{},
49+
Emph: ansi.StylePrimitive{},
50+
Strong: ansi.StylePrimitive{},
51+
HorizontalRule: ansi.StylePrimitive{},
52+
Item: ansi.StylePrimitive{},
53+
Enumeration: ansi.StylePrimitive{
54+
BlockPrefix: ". ",
55+
}, Task: ansi.StyleTask{},
56+
Link: ansi.StylePrimitive{
57+
Format: "({{.text}})",
58+
},
59+
LinkText: ansi.StylePrimitive{
60+
Format: "{{.text}}",
61+
},
62+
ImageText: ansi.StylePrimitive{
63+
Format: "{{.text}}",
64+
},
65+
Image: ansi.StylePrimitive{
66+
Format: "({{.text}})",
67+
},
68+
Code: ansi.StyleBlock{
69+
StylePrimitive: ansi.StylePrimitive{},
70+
},
71+
CodeBlock: ansi.StyleCodeBlock{
72+
StyleBlock: ansi.StyleBlock{},
73+
},
74+
Table: ansi.StyleTable{},
75+
DefinitionDescription: ansi.StylePrimitive{},
76+
}
77+
78+
// Plaintext function converts the description with optional Markdown tags
79+
// to the plaintext form.
80+
func Plaintext(markdown string) (string, error) {
81+
renderer, err := glamour.NewTermRenderer(
82+
glamour.WithStandardStyle("ascii"),
83+
glamour.WithWordWrap(0), // don't need to add spaces in the end of line
84+
glamour.WithStyles(plaintextStyle),
85+
)
86+
if err != nil {
87+
return "", xerrors.Errorf("can't initialize the Markdown renderer: %w", err)
88+
}
89+
90+
output, err := renderer.Render(markdown)
91+
if err != nil {
92+
return "", xerrors.Errorf("can't render description to plaintext: %w", err)
93+
}
94+
defer renderer.Close()
95+
96+
return strings.TrimSpace(output), nil
97+
}

coderd/parameter/plaintext_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package parameter_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/coder/coder/coderd/parameter"
7+
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestPlaintext(t *testing.T) {
12+
t.Parallel()
13+
t.Run("Simple", func(t *testing.T) {
14+
t.Parallel()
15+
16+
mdDescription := `# Provide the machine image
17+
See the [registry](https://container.registry.blah/namespace) for options.
18+
19+
![Minion](https://octodex.github.com/images/minion.png)
20+
21+
**This is bold text.**
22+
__This is bold text.__
23+
*This is italic text.*
24+
> Blockquotes can also be nested.
25+
~~Strikethrough.~~
26+
27+
1. Lorem ipsum dolor sit amet.
28+
2. Consectetur adipiscing elit.
29+
3. Integer molestie lorem at massa.
30+
31+
` + "`There are also code tags!`"
32+
33+
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!"
34+
35+
stripped, err := parameter.Plaintext(mdDescription)
36+
require.NoError(t, err)
37+
require.Equal(t, expected, stripped)
38+
})
39+
40+
t.Run("Nothing changes", func(t *testing.T) {
41+
t.Parallel()
42+
43+
nothingChanges := "This is a simple description, so nothing changes."
44+
45+
stripped, err := parameter.Plaintext(nothingChanges)
46+
require.NoError(t, err)
47+
require.Equal(t, nothingChanges, stripped)
48+
})
49+
}

coderd/templateversions.go

+18-12
Original file line numberDiff line numberDiff line change
@@ -1458,19 +1458,25 @@ func convertTemplateVersionParameter(param database.TemplateVersionParameter) (c
14581458
Icon: option.Icon,
14591459
})
14601460
}
1461+
1462+
descriptionPlaintext, err := parameter.Plaintext(param.Description)
1463+
if err != nil {
1464+
return codersdk.TemplateVersionParameter{}, err
1465+
}
14611466
return codersdk.TemplateVersionParameter{
1462-
Name: param.Name,
1463-
Description: param.Description,
1464-
Type: param.Type,
1465-
Mutable: param.Mutable,
1466-
DefaultValue: param.DefaultValue,
1467-
Icon: param.Icon,
1468-
Options: options,
1469-
ValidationRegex: param.ValidationRegex,
1470-
ValidationMin: param.ValidationMin,
1471-
ValidationMax: param.ValidationMax,
1472-
ValidationError: param.ValidationError,
1473-
ValidationMonotonic: codersdk.ValidationMonotonicOrder(param.ValidationMonotonic),
1467+
Name: param.Name,
1468+
Description: param.Description,
1469+
DescriptionPlaintext: descriptionPlaintext,
1470+
Type: param.Type,
1471+
Mutable: param.Mutable,
1472+
DefaultValue: param.DefaultValue,
1473+
Icon: param.Icon,
1474+
Options: options,
1475+
ValidationRegex: param.ValidationRegex,
1476+
ValidationMin: param.ValidationMin,
1477+
ValidationMax: param.ValidationMax,
1478+
ValidationError: param.ValidationError,
1479+
ValidationMonotonic: codersdk.ValidationMonotonicOrder(param.ValidationMonotonic),
14741480
}, nil
14751481
}
14761482

coderd/workspaces_test.go

+18-9
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/coder/coder/coderd/autobuild/schedule"
2121
"github.com/coder/coder/coderd/coderdtest"
2222
"github.com/coder/coder/coderd/database"
23+
"github.com/coder/coder/coderd/parameter"
2324
"github.com/coder/coder/coderd/rbac"
2425
"github.com/coder/coder/coderd/util/ptr"
2526
"github.com/coder/coder/codersdk"
@@ -1788,12 +1789,12 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
17881789
const (
17891790
firstParameterName = "first_parameter"
17901791
firstParameterType = "string"
1791-
firstParameterDescription = "This is first parameter"
1792+
firstParameterDescription = "This is _first_ *parameter*"
17921793
firstParameterValue = "1"
17931794

17941795
secondParameterName = "second_parameter"
17951796
secondParameterType = "number"
1796-
secondParameterDescription = "This is second parameter"
1797+
secondParameterDescription = "_This_ is second *parameter*"
17971798
secondParameterValue = "2"
17981799
secondParameterValidationMonotonic = codersdk.MonotonicOrderIncreasing
17991800
)
@@ -1835,16 +1836,24 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
18351836
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
18361837
defer cancel()
18371838

1839+
firstParameterDescriptionPlaintext, err := parameter.Plaintext(firstParameterDescription)
1840+
require.NoError(t, err)
1841+
secondParameterDescriptionPlaintext, err := parameter.Plaintext(secondParameterDescription)
1842+
require.NoError(t, err)
1843+
18381844
templateRichParameters, err := client.TemplateVersionRichParameters(ctx, version.ID)
18391845
require.NoError(t, err)
18401846
require.Len(t, templateRichParameters, 2)
1841-
require.Equal(t, templateRichParameters[0].Name, firstParameterName)
1842-
require.Equal(t, templateRichParameters[0].Type, firstParameterType)
1843-
require.Equal(t, templateRichParameters[0].ValidationMonotonic, codersdk.ValidationMonotonicOrder("")) // no validation for string
1844-
1845-
require.Equal(t, templateRichParameters[1].Name, secondParameterName)
1846-
require.Equal(t, templateRichParameters[1].Type, secondParameterType)
1847-
require.Equal(t, templateRichParameters[1].ValidationMonotonic, secondParameterValidationMonotonic)
1847+
require.Equal(t, firstParameterName, templateRichParameters[0].Name)
1848+
require.Equal(t, firstParameterType, templateRichParameters[0].Type)
1849+
require.Equal(t, firstParameterDescription, templateRichParameters[0].Description)
1850+
require.Equal(t, firstParameterDescriptionPlaintext, templateRichParameters[0].DescriptionPlaintext)
1851+
require.Equal(t, codersdk.ValidationMonotonicOrder(""), templateRichParameters[0].ValidationMonotonic) // no validation for string
1852+
require.Equal(t, secondParameterName, templateRichParameters[1].Name)
1853+
require.Equal(t, secondParameterType, templateRichParameters[1].Type)
1854+
require.Equal(t, secondParameterDescription, templateRichParameters[1].Description)
1855+
require.Equal(t, secondParameterDescriptionPlaintext, templateRichParameters[1].DescriptionPlaintext)
1856+
require.Equal(t, secondParameterValidationMonotonic, templateRichParameters[1].ValidationMonotonic)
18481857

18491858
expectedBuildParameters := []codersdk.WorkspaceBuildParameter{
18501859
{Name: firstParameterName, Value: firstParameterValue},

codersdk/templateversions.go

+13-12
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,19 @@ const (
3333

3434
// TemplateVersionParameter represents a parameter for a template version.
3535
type TemplateVersionParameter struct {
36-
Name string `json:"name"`
37-
Description string `json:"description"`
38-
Type string `json:"type" enums:"string,number,bool"`
39-
Mutable bool `json:"mutable"`
40-
DefaultValue string `json:"default_value"`
41-
Icon string `json:"icon"`
42-
Options []TemplateVersionParameterOption `json:"options"`
43-
ValidationError string `json:"validation_error,omitempty"`
44-
ValidationRegex string `json:"validation_regex,omitempty"`
45-
ValidationMin int32 `json:"validation_min,omitempty"`
46-
ValidationMax int32 `json:"validation_max,omitempty"`
47-
ValidationMonotonic ValidationMonotonicOrder `json:"validation_monotonic,omitempty" enums:"increasing,decreasing"`
36+
Name string `json:"name"`
37+
Description string `json:"description"`
38+
DescriptionPlaintext string `json:"description_plaintext"`
39+
Type string `json:"type" enums:"string,number,bool"`
40+
Mutable bool `json:"mutable"`
41+
DefaultValue string `json:"default_value"`
42+
Icon string `json:"icon"`
43+
Options []TemplateVersionParameterOption `json:"options"`
44+
ValidationError string `json:"validation_error,omitempty"`
45+
ValidationRegex string `json:"validation_regex,omitempty"`
46+
ValidationMin int32 `json:"validation_min,omitempty"`
47+
ValidationMax int32 `json:"validation_max,omitempty"`
48+
ValidationMonotonic ValidationMonotonicOrder `json:"validation_monotonic,omitempty" enums:"increasing,decreasing"`
4849
}
4950

5051
// TemplateVersionParameterOption represents a selectable option for a template parameter.

docs/api/schemas.md

+16-14
Original file line numberDiff line numberDiff line change
@@ -4505,6 +4505,7 @@ Parameter represents a set value for the scope.
45054505
{
45064506
"default_value": "string",
45074507
"description": "string",
4508+
"description_plaintext": "string",
45084509
"icon": "string",
45094510
"mutable": true,
45104511
"name": "string",
@@ -4527,20 +4528,21 @@ Parameter represents a set value for the scope.
45274528

45284529
### Properties
45294530

4530-
| Name | Type | Required | Restrictions | Description |
4531-
| ---------------------- | ------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- |
4532-
| `default_value` | string | false | | |
4533-
| `description` | string | false | | |
4534-
| `icon` | string | false | | |
4535-
| `mutable` | boolean | false | | |
4536-
| `name` | string | false | | |
4537-
| `options` | array of [codersdk.TemplateVersionParameterOption](#codersdktemplateversionparameteroption) | false | | |
4538-
| `type` | string | false | | |
4539-
| `validation_error` | string | false | | |
4540-
| `validation_max` | integer | false | | |
4541-
| `validation_min` | integer | false | | |
4542-
| `validation_monotonic` | [codersdk.ValidationMonotonicOrder](#codersdkvalidationmonotonicorder) | false | | |
4543-
| `validation_regex` | string | false | | |
4531+
| Name | Type | Required | Restrictions | Description |
4532+
| ----------------------- | ------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- |
4533+
| `default_value` | string | false | | |
4534+
| `description` | string | false | | |
4535+
| `description_plaintext` | string | false | | |
4536+
| `icon` | string | false | | |
4537+
| `mutable` | boolean | false | | |
4538+
| `name` | string | false | | |
4539+
| `options` | array of [codersdk.TemplateVersionParameterOption](#codersdktemplateversionparameteroption) | false | | |
4540+
| `type` | string | false | | |
4541+
| `validation_error` | string | false | | |
4542+
| `validation_max` | integer | false | | |
4543+
| `validation_min` | integer | false | | |
4544+
| `validation_monotonic` | [codersdk.ValidationMonotonicOrder](#codersdkvalidationmonotonicorder) | false | | |
4545+
| `validation_regex` | string | false | | |
45444546

45454547
#### Enumerated Values
45464548

0 commit comments

Comments
 (0)