mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix(coderd): truncate task prompt to 160 characters in notifications (#20147)
Truncates the task prompt used in notifications to a maximum of 160 characters. The length of 160 characters was chosen arbitrarily.
This commit is contained in:
@@ -23,15 +23,64 @@ func JoinWithConjunction(s []string) string {
|
||||
)
|
||||
}
|
||||
|
||||
// Truncate returns the first n characters of s.
|
||||
func Truncate(s string, n int) string {
|
||||
type TruncateOption int
|
||||
|
||||
func (o TruncateOption) String() string {
|
||||
switch o {
|
||||
case TruncateWithEllipsis:
|
||||
return "TruncateWithEllipsis"
|
||||
case TruncateWithFullWords:
|
||||
return "TruncateWithFullWords"
|
||||
default:
|
||||
return fmt.Sprintf("TruncateOption(%d)", o)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// TruncateWithEllipsis adds a Unicode ellipsis character to the end of the string.
|
||||
TruncateWithEllipsis TruncateOption = 1 << 0
|
||||
// TruncateWithFullWords ensures that words are not split in the middle.
|
||||
// As a special case, if there is no word boundary, the string is truncated.
|
||||
TruncateWithFullWords TruncateOption = 1 << 1
|
||||
)
|
||||
|
||||
// Truncate truncates s to n characters.
|
||||
// Additional behaviors can be specified using TruncateOptions.
|
||||
func Truncate(s string, n int, opts ...TruncateOption) string {
|
||||
var options TruncateOption
|
||||
for _, opt := range opts {
|
||||
options |= opt
|
||||
}
|
||||
if n < 1 {
|
||||
return ""
|
||||
}
|
||||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
return s[:n]
|
||||
|
||||
maxLen := n
|
||||
if options&TruncateWithEllipsis != 0 {
|
||||
maxLen--
|
||||
}
|
||||
var sb strings.Builder
|
||||
// If we need to truncate to full words, find the last word boundary before n.
|
||||
if options&TruncateWithFullWords != 0 {
|
||||
lastWordBoundary := strings.LastIndexFunc(s[:maxLen], unicode.IsSpace)
|
||||
if lastWordBoundary < 0 {
|
||||
// We cannot find a word boundary. At this point, we'll truncate the string.
|
||||
// It's better than nothing.
|
||||
_, _ = sb.WriteString(s[:maxLen])
|
||||
} else { // lastWordBoundary <= maxLen
|
||||
_, _ = sb.WriteString(s[:lastWordBoundary])
|
||||
}
|
||||
} else {
|
||||
_, _ = sb.WriteString(s[:maxLen])
|
||||
}
|
||||
|
||||
if options&TruncateWithEllipsis != 0 {
|
||||
_, _ = sb.WriteString("…")
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
var bmPolicy = bluemonday.StrictPolicy()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package strings_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -23,17 +24,47 @@ func TestTruncate(t *testing.T) {
|
||||
s string
|
||||
n int
|
||||
expected string
|
||||
options []strings.TruncateOption
|
||||
}{
|
||||
{"foo", 4, "foo"},
|
||||
{"foo", 3, "foo"},
|
||||
{"foo", 2, "fo"},
|
||||
{"foo", 1, "f"},
|
||||
{"foo", 0, ""},
|
||||
{"foo", -1, ""},
|
||||
{"foo", 4, "foo", nil},
|
||||
{"foo", 3, "foo", nil},
|
||||
{"foo", 2, "fo", nil},
|
||||
{"foo", 1, "f", nil},
|
||||
{"foo", 0, "", nil},
|
||||
{"foo", -1, "", nil},
|
||||
{"foo bar", 7, "foo bar", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 6, "foo b…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 5, "foo …", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 4, "foo…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 3, "fo…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 2, "f…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 1, "…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 0, "", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 7, "foo bar", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
||||
{"foo bar", 6, "foo", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
||||
{"foo bar", 5, "foo", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
||||
{"foo bar", 4, "foo", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
||||
{"foo bar", 3, "foo", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
||||
{"foo bar", 2, "fo", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
||||
{"foo bar", 1, "f", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
||||
{"foo bar", 0, "", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
||||
{"foo bar", 7, "foo bar", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 6, "foo…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 5, "foo…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 4, "foo…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 3, "fo…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 2, "f…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 1, "…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
||||
{"foo bar", 0, "", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
||||
{"This is a very long task prompt that should be truncated to 160 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 160, "This is a very long task prompt that should be truncated to 160 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
||||
} {
|
||||
t.Run(tt.expected, func(t *testing.T) {
|
||||
tName := fmt.Sprintf("%s_%d", tt.s, tt.n)
|
||||
for _, opt := range tt.options {
|
||||
tName += fmt.Sprintf("_%v", opt)
|
||||
}
|
||||
t.Run(tName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
actual := strings.Truncate(tt.s, tt.n)
|
||||
actual := strings.Truncate(tt.s, tt.n, tt.options...)
|
||||
require.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user