Skip to content

Commit e7ccbaf

Browse files
committed
Merge branch 'main' into rafrdz/fix-icon-removed
2 parents 1775321 + afb54f6 commit e7ccbaf

File tree

95 files changed

+3517
-802
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+3517
-802
lines changed

.github/actions/setup-go/action.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: |
44
inputs:
55
version:
66
description: "The Go version to use."
7-
default: "1.24.4"
7+
default: "1.24.6"
88
use-preinstalled-go:
99
description: "Whether to use preinstalled Go."
1010
default: "false"

CODEOWNERS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,8 @@ site/src/api/countriesGenerated.ts
2929
site/src/api/rbacresourcesGenerated.ts
3030
site/src/api/typesGenerated.ts
3131
site/CLAUDE.md
32+
33+
# The blood and guts of the autostop algorithm, which is quite complex and
34+
# requires elite ball knowledge of most of the scheduling code to make changes
35+
# without inadvertently affecting other parts of the codebase.
36+
coderd/schedule/autostop.go @deansheather @DanielleMaywood

agent/agentssh/x11_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func TestServer_X11_EvictionLRU(t *testing.T) {
135135
t.Skip("X11 forwarding is only supported on Linux")
136136
}
137137

138-
ctx := testutil.Context(t, testutil.WaitLong)
138+
ctx := testutil.Context(t, testutil.WaitSuperLong)
139139
logger := testutil.Logger(t)
140140
fs := afero.NewMemMapFs()
141141

@@ -238,7 +238,9 @@ func TestServer_X11_EvictionLRU(t *testing.T) {
238238
payload := "hello world"
239239
go func() {
240240
conn, err := inproc.Dial(ctx, testutil.NewAddr("tcp", fmt.Sprintf("localhost:%d", agentssh.X11StartPort+agentssh.X11DefaultDisplayOffset)))
241-
assert.NoError(t, err)
241+
if !assert.NoError(t, err) {
242+
return
243+
}
242244
_, err = conn.Write([]byte(payload))
243245
assert.NoError(t, err)
244246
_ = conn.Close()

cli/cliui/parameter.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,16 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te
3838
// Move the cursor up a single line for nicer display!
3939
_, _ = fmt.Fprint(inv.Stdout, "\033[1A")
4040

41-
var options []string
42-
err = json.Unmarshal([]byte(templateVersionParameter.DefaultValue), &options)
41+
var defaults []string
42+
err = json.Unmarshal([]byte(templateVersionParameter.DefaultValue), &defaults)
4343
if err != nil {
4444
return "", err
4545
}
4646

47-
values, err := MultiSelect(inv, MultiSelectOptions{
48-
Options: options,
49-
Defaults: options,
47+
values, err := RichMultiSelect(inv, RichMultiSelectOptions{
48+
Options: templateVersionParameter.Options,
49+
Defaults: defaults,
50+
EnableCustomInput: templateVersionParameter.FormType == "tag-select",
5051
})
5152
if err == nil {
5253
v, err := json.Marshal(&values)

cli/cliui/select.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"os/signal"
8+
"slices"
89
"strings"
910
"syscall"
1011

@@ -299,6 +300,77 @@ func (m selectModel) filteredOptions() []string {
299300
return options
300301
}
301302

303+
type RichMultiSelectOptions struct {
304+
Message string
305+
Options []codersdk.TemplateVersionParameterOption
306+
Defaults []string
307+
EnableCustomInput bool
308+
}
309+
310+
func RichMultiSelect(inv *serpent.Invocation, richOptions RichMultiSelectOptions) ([]string, error) {
311+
var opts []string
312+
var defaultOpts []string
313+
314+
asLine := func(option codersdk.TemplateVersionParameterOption) string {
315+
line := option.Name
316+
if len(option.Description) > 0 {
317+
line += ": " + option.Description
318+
}
319+
return line
320+
}
321+
322+
var predefinedOpts []string
323+
for i, option := range richOptions.Options {
324+
opts = append(opts, asLine(option)) // Some options may have description defined.
325+
326+
// Check if option is selected by default
327+
if slices.Contains(richOptions.Defaults, option.Value) {
328+
defaultOpts = append(defaultOpts, opts[i])
329+
predefinedOpts = append(predefinedOpts, option.Value)
330+
}
331+
}
332+
333+
// Check if "defaults" contains extra/custom options, user could select them.
334+
for _, def := range richOptions.Defaults {
335+
if !slices.Contains(predefinedOpts, def) {
336+
opts = append(opts, def)
337+
defaultOpts = append(defaultOpts, def)
338+
}
339+
}
340+
341+
selected, err := MultiSelect(inv, MultiSelectOptions{
342+
Message: richOptions.Message,
343+
Options: opts,
344+
Defaults: defaultOpts,
345+
EnableCustomInput: richOptions.EnableCustomInput,
346+
})
347+
if err != nil {
348+
return nil, err
349+
}
350+
351+
// Check selected option, convert descriptions (line) to values
352+
//
353+
// The function must return an initialized empty array, since it is later marshaled
354+
// into JSON. Otherwise, `var results []string` would be marshaled to "null".
355+
// See: https://github.com/golang/go/issues/27589
356+
results := []string{}
357+
for _, sel := range selected {
358+
custom := true
359+
for i, option := range richOptions.Options {
360+
if asLine(option) == sel {
361+
results = append(results, richOptions.Options[i].Value)
362+
custom = false
363+
break
364+
}
365+
}
366+
367+
if custom {
368+
results = append(results, sel)
369+
}
370+
}
371+
return results, nil
372+
}
373+
302374
type MultiSelectOptions struct {
303375
Message string
304376
Options []string

cli/cliui/select_test.go

Lines changed: 114 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,8 @@ func TestRichSelect(t *testing.T) {
5252
go func() {
5353
resp, err := newRichSelect(ptty, cliui.RichSelectOptions{
5454
Options: []codersdk.TemplateVersionParameterOption{
55-
{
56-
Name: "A-Name",
57-
Value: "A-Value",
58-
Description: "A-Description.",
59-
}, {
60-
Name: "B-Name",
61-
Value: "B-Value",
62-
Description: "B-Description.",
63-
},
55+
{Name: "A-Name", Value: "A-Value", Description: "A-Description."},
56+
{Name: "B-Name", Value: "B-Value", Description: "B-Description."},
6457
},
6558
})
6659
assert.NoError(t, err)
@@ -86,63 +79,130 @@ func newRichSelect(ptty *ptytest.PTY, opts cliui.RichSelectOptions) (string, err
8679
return value, inv.Run()
8780
}
8881

89-
func TestMultiSelect(t *testing.T) {
82+
func TestRichMultiSelect(t *testing.T) {
9083
t.Parallel()
91-
t.Run("MultiSelect", func(t *testing.T) {
92-
items := []string{"aaa", "bbb", "ccc"}
9384

94-
t.Parallel()
95-
ptty := ptytest.New(t)
96-
msgChan := make(chan []string)
97-
go func() {
98-
resp, err := newMultiSelect(ptty, items)
99-
assert.NoError(t, err)
100-
msgChan <- resp
101-
}()
102-
require.Equal(t, items, <-msgChan)
103-
})
85+
tests := []struct {
86+
name string
87+
options []codersdk.TemplateVersionParameterOption
88+
defaults []string
89+
allowCustom bool
90+
want []string
91+
}{
92+
{
93+
name: "Predefined",
94+
options: []codersdk.TemplateVersionParameterOption{
95+
{Name: "AAA", Description: "This is AAA", Value: "aaa"},
96+
{Name: "BBB", Description: "This is BBB", Value: "bbb"},
97+
{Name: "CCC", Description: "This is CCC", Value: "ccc"},
98+
},
99+
defaults: []string{"bbb", "ccc"},
100+
allowCustom: false,
101+
want: []string{"bbb", "ccc"},
102+
},
103+
{
104+
name: "Custom",
105+
options: []codersdk.TemplateVersionParameterOption{
106+
{Name: "AAA", Description: "This is AAA", Value: "aaa"},
107+
{Name: "BBB", Description: "This is BBB", Value: "bbb"},
108+
{Name: "CCC", Description: "This is CCC", Value: "ccc"},
109+
},
110+
defaults: []string{"aaa", "bbb"},
111+
allowCustom: true,
112+
want: []string{"aaa", "bbb"},
113+
},
114+
{
115+
name: "NoOptionSelected",
116+
options: []codersdk.TemplateVersionParameterOption{
117+
{Name: "AAA", Description: "This is AAA", Value: "aaa"},
118+
{Name: "BBB", Description: "This is BBB", Value: "bbb"},
119+
{Name: "CCC", Description: "This is CCC", Value: "ccc"},
120+
},
121+
defaults: []string{},
122+
allowCustom: false,
123+
want: []string{},
124+
},
125+
}
104126

105-
t.Run("MultiSelectWithCustomInput", func(t *testing.T) {
106-
t.Parallel()
107-
items := []string{"Code", "Chairs", "Whale", "Diamond", "Carrot"}
108-
ptty := ptytest.New(t)
109-
msgChan := make(chan []string)
110-
go func() {
111-
resp, err := newMultiSelectWithCustomInput(ptty, items)
112-
assert.NoError(t, err)
113-
msgChan <- resp
114-
}()
115-
require.Equal(t, items, <-msgChan)
116-
})
117-
}
127+
for _, tt := range tests {
128+
t.Run(tt.name, func(t *testing.T) {
129+
t.Parallel()
118130

119-
func newMultiSelectWithCustomInput(ptty *ptytest.PTY, items []string) ([]string, error) {
120-
var values []string
121-
cmd := &serpent.Command{
122-
Handler: func(inv *serpent.Invocation) error {
123-
selectedItems, err := cliui.MultiSelect(inv, cliui.MultiSelectOptions{
124-
Options: items,
125-
Defaults: items,
126-
EnableCustomInput: true,
127-
})
128-
if err == nil {
129-
values = selectedItems
131+
var selectedItems []string
132+
var err error
133+
cmd := &serpent.Command{
134+
Handler: func(inv *serpent.Invocation) error {
135+
selectedItems, err = cliui.RichMultiSelect(inv, cliui.RichMultiSelectOptions{
136+
Options: tt.options,
137+
Defaults: tt.defaults,
138+
EnableCustomInput: tt.allowCustom,
139+
})
140+
return err
141+
},
130142
}
131-
return err
143+
144+
doneChan := make(chan struct{})
145+
go func() {
146+
defer close(doneChan)
147+
err := cmd.Invoke().Run()
148+
assert.NoError(t, err)
149+
}()
150+
<-doneChan
151+
152+
require.Equal(t, tt.want, selectedItems)
153+
})
154+
}
155+
}
156+
157+
func TestMultiSelect(t *testing.T) {
158+
t.Parallel()
159+
160+
tests := []struct {
161+
name string
162+
items []string
163+
allowCustom bool
164+
want []string
165+
}{
166+
{
167+
name: "MultiSelect",
168+
items: []string{"aaa", "bbb", "ccc"},
169+
allowCustom: false,
170+
want: []string{"aaa", "bbb", "ccc"},
171+
},
172+
{
173+
name: "MultiSelectWithCustomInput",
174+
items: []string{"Code", "Chairs", "Whale", "Diamond", "Carrot"},
175+
allowCustom: true,
176+
want: []string{"Code", "Chairs", "Whale", "Diamond", "Carrot"},
132177
},
133178
}
134-
inv := cmd.Invoke()
135-
ptty.Attach(inv)
136-
return values, inv.Run()
179+
180+
for _, tt := range tests {
181+
t.Run(tt.name, func(t *testing.T) {
182+
t.Parallel()
183+
184+
ptty := ptytest.New(t)
185+
msgChan := make(chan []string)
186+
187+
go func() {
188+
resp, err := newMultiSelect(ptty, tt.items, tt.allowCustom)
189+
assert.NoError(t, err)
190+
msgChan <- resp
191+
}()
192+
193+
require.Equal(t, tt.want, <-msgChan)
194+
})
195+
}
137196
}
138197

139-
func newMultiSelect(ptty *ptytest.PTY, items []string) ([]string, error) {
198+
func newMultiSelect(pty *ptytest.PTY, items []string, custom bool) ([]string, error) {
140199
var values []string
141200
cmd := &serpent.Command{
142201
Handler: func(inv *serpent.Invocation) error {
143202
selectedItems, err := cliui.MultiSelect(inv, cliui.MultiSelectOptions{
144-
Options: items,
145-
Defaults: items,
203+
Options: items,
204+
Defaults: items,
205+
EnableCustomInput: custom,
146206
})
147207
if err == nil {
148208
values = selectedItems
@@ -151,6 +211,6 @@ func newMultiSelect(ptty *ptytest.PTY, items []string) ([]string, error) {
151211
},
152212
}
153213
inv := cmd.Invoke()
154-
ptty.Attach(inv)
214+
pty.Attach(inv)
155215
return values, inv.Run()
156216
}

cli/exp_prompts.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,20 @@ func (RootCmd) promptExample() *serpent.Command {
174174
_, _ = fmt.Fprintf(inv.Stdout, "%q are nice choices.\n", strings.Join(multiSelectValues, ", "))
175175
return multiSelectError
176176
}, useThingsOption, enableCustomInputOption),
177+
promptCmd("rich-multi-select", func(inv *serpent.Invocation) error {
178+
if len(multiSelectValues) == 0 {
179+
multiSelectValues, multiSelectError = cliui.MultiSelect(inv, cliui.MultiSelectOptions{
180+
Message: "Select some things:",
181+
Options: []string{
182+
"Apples", "Plums", "Grapes", "Oranges", "Bananas",
183+
},
184+
Defaults: []string{"Grapes", "Plums"},
185+
EnableCustomInput: enableCustomInput,
186+
})
187+
}
188+
_, _ = fmt.Fprintf(inv.Stdout, "%q are nice choices.\n", strings.Join(multiSelectValues, ", "))
189+
return multiSelectError
190+
}, useThingsOption, enableCustomInputOption),
177191
promptCmd("rich-parameter", func(inv *serpent.Invocation) error {
178192
value, err := cliui.RichSelect(inv, cliui.RichSelectOptions{
179193
Options: []codersdk.TemplateVersionParameterOption{

0 commit comments

Comments
 (0)