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

Skip to content

Commit 5190438

Browse files
committed
Improve workspace create flow
1 parent 8146d00 commit 5190438

28 files changed

+618
-274
lines changed

cli/cliui/cliui.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ var Styles = struct {
3030
Placeholder,
3131
Prompt,
3232
FocusedPrompt,
33+
Fuschia,
3334
Logo,
35+
Warn,
3436
Wrap lipgloss.Style
3537
}{
3638
Bold: lipgloss.NewStyle().Bold(true),
@@ -41,6 +43,8 @@ var Styles = struct {
4143
Placeholder: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
4244
Prompt: defaultStyles.Prompt.Foreground(lipgloss.AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"}),
4345
FocusedPrompt: defaultStyles.FocusedPrompt.Foreground(lipgloss.Color("#651fff")),
46+
Fuschia: defaultStyles.SelectedMenuItem.Copy(),
4447
Logo: defaultStyles.Logo.SetString("Coder"),
48+
Warn: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"}),
4549
Wrap: defaultStyles.Wrap,
4650
}

cli/cliui/job.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,150 @@
11
package cliui
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/signal"
7+
"time"
8+
9+
"github.com/briandowns/spinner"
10+
"github.com/charmbracelet/lipgloss"
11+
"github.com/spf13/cobra"
12+
13+
"github.com/coder/coder/codersdk"
14+
"github.com/coder/coder/database"
15+
)
16+
17+
type JobOptions struct {
18+
Title string
19+
Fetch func() (codersdk.ProvisionerJob, error)
20+
Cancel func() error
21+
Logs func() (<-chan codersdk.ProvisionerJobLog, error)
22+
}
23+
24+
// Job renders a provisioner job.
25+
func Job(cmd *cobra.Command, opts JobOptions) (codersdk.ProvisionerJob, error) {
26+
var (
27+
spin = spinner.New(spinner.CharSets[5], 100*time.Millisecond, spinner.WithColor("fgGreen"))
28+
29+
started = false
30+
completed = false
31+
job codersdk.ProvisionerJob
32+
)
33+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), fmt.Sprintf("%s%s %s", Styles.FocusedPrompt, opts.Title, Styles.Placeholder.Render("(ctrl+c to cancel)")))
34+
35+
spin.Writer = cmd.OutOrStdout()
36+
defer spin.Stop()
37+
38+
// Refreshes the job state!
39+
refresh := func() {
40+
var err error
41+
job, err = opts.Fetch()
42+
if err != nil {
43+
// If a single fetch fails, it could be a one-off.
44+
return
45+
}
46+
47+
if !started && job.StartedAt != nil {
48+
spin.Stop()
49+
fmt.Fprintf(cmd.OutOrStdout(), Styles.Prompt.String()+"Started "+Styles.Placeholder.Render("[%dms]")+"\n", job.StartedAt.Sub(job.CreatedAt).Milliseconds())
50+
spin.Start()
51+
started = true
52+
}
53+
if !completed && job.CompletedAt != nil {
54+
spin.Stop()
55+
msg := ""
56+
switch job.Status {
57+
case codersdk.ProvisionerJobCanceled:
58+
msg = "Canceled"
59+
case codersdk.ProvisionerJobFailed:
60+
msg = "Completed"
61+
case codersdk.ProvisionerJobSucceeded:
62+
msg = "Built"
63+
}
64+
started := job.CreatedAt
65+
if job.StartedAt != nil {
66+
started = *job.StartedAt
67+
}
68+
fmt.Fprintf(cmd.OutOrStderr(), Styles.Prompt.String()+msg+" "+Styles.Placeholder.Render("[%dms]")+"\n", job.CompletedAt.Sub(started).Milliseconds())
69+
spin.Start()
70+
completed = true
71+
}
72+
73+
switch job.Status {
74+
case codersdk.ProvisionerJobPending:
75+
spin.Suffix = " Queued"
76+
case codersdk.ProvisionerJobRunning:
77+
spin.Suffix = " Running"
78+
case codersdk.ProvisionerJobCanceling:
79+
spin.Suffix = " Canceling"
80+
}
81+
}
82+
refresh()
83+
spin.Start()
84+
85+
stopChan := make(chan os.Signal, 1)
86+
defer signal.Stop(stopChan)
87+
go func() {
88+
signal.Notify(stopChan, os.Interrupt)
89+
select {
90+
case <-cmd.Context().Done():
91+
return
92+
case _, ok := <-stopChan:
93+
if !ok {
94+
return
95+
}
96+
}
97+
spin.Stop()
98+
fmt.Fprintf(cmd.OutOrStdout(), Styles.FocusedPrompt.String()+"Gracefully canceling... wait for exit or data loss may occur!\n")
99+
spin.Start()
100+
err := opts.Cancel()
101+
if err != nil {
102+
fmt.Fprintf(cmd.OutOrStdout(), "Failed to cancel %s...\n", err)
103+
}
104+
refresh()
105+
}()
106+
107+
logs, err := opts.Logs()
108+
if err != nil {
109+
return job, err
110+
}
111+
112+
firstLog := false
113+
ticker := time.NewTicker(time.Second)
114+
for {
115+
select {
116+
case <-cmd.Context().Done():
117+
return job, cmd.Context().Err()
118+
case <-ticker.C:
119+
refresh()
120+
if job.CompletedAt != nil {
121+
return job, nil
122+
}
123+
case log, ok := <-logs:
124+
if !ok {
125+
refresh()
126+
continue
127+
}
128+
if !firstLog {
129+
refresh()
130+
firstLog = true
131+
}
132+
spin.Stop()
133+
var style lipgloss.Style
134+
switch log.Level {
135+
case database.LogLevelTrace:
136+
style = defaultStyles.Error
137+
case database.LogLevelDebug:
138+
style = defaultStyles.Error
139+
case database.LogLevelError:
140+
style = defaultStyles.Error
141+
case database.LogLevelWarn:
142+
style = Styles.Warn
143+
case database.LogLevelInfo:
144+
style = defaultStyles.Note
145+
}
146+
fmt.Fprintln(cmd.OutOrStdout(), fmt.Sprintf("%s %s %s", Styles.Placeholder.Render("|"), style.Render(string(log.Level)), log.Output))
147+
spin.Start()
148+
}
149+
}
150+
}

cli/cliui/parameter.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package cliui
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/spf13/cobra"
8+
9+
"github.com/coder/coder/coderd/parameter"
10+
"github.com/coder/coder/codersdk"
11+
)
12+
13+
func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.ProjectVersionParameterSchema) (string, error) {
14+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), Styles.Bold.Render("var."+parameterSchema.Name))
15+
if parameterSchema.Description != "" {
16+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.TrimSpace(strings.Join(strings.Split(parameterSchema.Description, "\n"), "\n "))+"\n")
17+
}
18+
19+
var err error
20+
var options []string
21+
if parameterSchema.ValidationCondition != "" {
22+
options, _, err = parameter.Contains(parameterSchema.ValidationCondition)
23+
if err != nil {
24+
return "", err
25+
}
26+
}
27+
var value string
28+
if len(options) > 0 {
29+
// Move the cursor up a single line for nicer display!
30+
fmt.Fprint(cmd.OutOrStdout(), "\033[1A")
31+
value, err = Select(cmd, SelectOptions{
32+
Options: options,
33+
HideSearch: true,
34+
})
35+
if err == nil {
36+
fmt.Fprintln(cmd.OutOrStdout())
37+
fmt.Fprintln(cmd.OutOrStdout(), " "+Styles.Prompt.String()+Styles.Field.Render(value))
38+
}
39+
} else {
40+
value, err = Prompt(cmd, PromptOptions{
41+
Text: Styles.Bold.Render("Enter a value:"),
42+
})
43+
}
44+
return value, err
45+
}

cli/cliui/prompt.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ func Prompt(cmd *cobra.Command, opts PromptOptions) (string, error) {
4545
} else {
4646
reader := bufio.NewReader(cmd.InOrStdin())
4747
line, err = reader.ReadString('\n')
48+
// Multiline with single quotes!
49+
if err == nil && strings.HasPrefix(line, "'") {
50+
rest, err := reader.ReadString('\'')
51+
if err == nil {
52+
line += rest
53+
line = strings.Trim(line, "'")
54+
}
55+
}
4856
}
4957
if err != nil {
5058
errCh <- err

cli/cliui/prompt_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,24 @@ func TestPrompt(t *testing.T) {
4545
ptty.WriteLine("yes")
4646
require.Equal(t, "yes", <-doneChan)
4747
})
48+
49+
t.Run("Multiline", func(t *testing.T) {
50+
t.Parallel()
51+
ptty := ptytest.New(t)
52+
doneChan := make(chan string)
53+
go func() {
54+
resp, err := newPrompt(ptty, cliui.PromptOptions{
55+
Text: "Example",
56+
})
57+
require.NoError(t, err)
58+
doneChan <- resp
59+
}()
60+
ptty.ExpectMatch("Example")
61+
ptty.WriteLine("'this is a")
62+
ptty.WriteLine("test'")
63+
require.Equal(t, `this is a
64+
test`, <-doneChan)
65+
})
4866
}
4967

5068
func newPrompt(ptty *ptytest.PTY, opts cliui.PromptOptions) (string, error) {

cli/cliui/select.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import (
1212
)
1313

1414
type SelectOptions struct {
15-
Options []string
16-
Size int
15+
Options []string
16+
Size int
17+
HideSearch bool
1718
}
1819

1920
// Select displays a list of user options.
@@ -29,19 +30,24 @@ func Select(cmd *cobra.Command, opts SelectOptions) (string, error) {
2930

3031
return strings.Contains(name, input)
3132
},
32-
Stdin: io.NopCloser(cmd.InOrStdin()),
33-
Stdout: &writeCloser{cmd.OutOrStdout()},
33+
HideHelp: opts.HideSearch,
34+
Stdin: io.NopCloser(cmd.InOrStdin()),
35+
Stdout: &writeCloser{cmd.OutOrStdout()},
3436
Templates: &promptui.SelectTemplates{
3537
FuncMap: template.FuncMap{
3638
"faint": func(value interface{}) string {
3739
return Styles.Placeholder.Render(value.(string))
3840
},
41+
"subtle": func(value interface{}) string {
42+
return defaultStyles.Subtle.Render(value.(string))
43+
},
3944
"selected": func(value interface{}) string {
40-
return defaultStyles.SelectedMenuItem.Render("▶ " + value.(string))
45+
return defaultStyles.Keyword.Render("> " + value.(string))
46+
// return defaultStyles.SelectedMenuItem.Render("> " + value.(string))
4147
},
4248
},
4349
Active: "{{ . | selected }}",
44-
Inactive: " {{.}}",
50+
Inactive: " {{ . }}",
4551
Label: "{{.}}",
4652
Selected: "{{ \"\" }}",
4753
Help: fmt.Sprintf(`{{ "Use" | faint }} {{ .SearchKey | faint }} {{ "to toggle search" | faint }}`),

0 commit comments

Comments
 (0)