Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 21 additions & 126 deletions internal/setup/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package setup

import (
"fmt"
"io"
"strings"

"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
Expand All @@ -18,6 +16,8 @@ var (
_ list.Item = flag{}
)

const defaultFlagKey = "setup-test-flag"

type flag struct {
Key string `json:"key"`
Name string `json:"name"`
Expand All @@ -26,157 +26,52 @@ type flag struct {
func (p flag) FilterValue() string { return "" }

type flagModel struct {
choice string
input string
err error
list list.Model
parentKey string
}

var flags = map[string][]flag{
"env1": {
{
Key: "flag1",
Name: "flag 1",
},
{
Key: "flag2",
Name: "flag 2",
},
},
"env2": {
{
Key: "flag3",
Name: "flag 3",
},
{
Key: "flag4",
Name: "flag 4",
},
},
"env3": {
{
Key: "flag5",
Name: "flag 5",
},
{
Key: "flag6",
Name: "flag 6",
},
},
"env4": {
{
Key: "flag7",
Name: "flag 7",
},
{
Key: "flag8",
Name: "flag 8",
},
},
"env5": {
{
Key: "flag9",
Name: "flag 9",
},
{
Key: "flag10",
Name: "flag 10",
},
},
"env6": {
{
Key: "flag11",
Name: "flag 11",
},
{
Key: "flag12",
Name: "flag 12",
},
},
textInput textinput.Model
}

func NewFlag() tea.Model {
l := list.New(nil, flagDelegate{}, 30, 14)
l.Title = "Select a flag"
l.SetShowStatusBar(false)
l.SetFilteringEnabled(false)
ti := textinput.New()
ti.Focus()
ti.CharLimit = 156
ti.Width = 20
ti.Placeholder = defaultFlagKey

return flagModel{
list: l,
textInput: ti,
}
}

func (p flagModel) Init() tea.Cmd {
return nil
}

// This method has drifted from the ProjectModel's version, but it should do something similar.
func (m flagModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case fetchResources:
fs, err := getFlags(m.parentKey)
if err != nil {
m.err = err
return m, nil
}
m.list.SetItems(flagsToItems(fs))
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Enter):
i, ok := m.list.SelectedItem().(flag)
if ok {
m.choice = i.Key
input := m.textInput.Value()
if input == "" {
input = defaultFlagKey
}
m.input = input
case key.Matches(msg, keys.Quit):
return m, tea.Quit
default:
m.list, cmd = m.list.Update(msg)
m.textInput, cmd = m.textInput.Update(msg)
}
}

return m, cmd
}

func (m flagModel) View() string {
return "\n" + m.list.View()
}

func getFlags(envKey string) ([]flag, error) {
flagList := flags[envKey]
createNewOption := flag{Key: CreateNewResourceKey, Name: "Create a new flag"}
flagList = append(flagList, createNewOption)
return flagList, nil
}

type flagDelegate struct{}

func (d flagDelegate) Height() int { return 1 }
func (d flagDelegate) Spacing() int { return 0 }
func (d flagDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
func (d flagDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
i, ok := listItem.(flag)
if !ok {
return
}

str := fmt.Sprintf("%d. %s", index+1, i.Name)

fn := flagStyle.Render
if index == m.Index() {
fn = func(s ...string) string {
return selectedFlagItemStyle.Render("> " + strings.Join(s, " "))
}
}

fmt.Fprint(w, fn(str))
}

func flagsToItems(flags []flag) []list.Item {
items := make([]list.Item, len(flags))
for i, proj := range flags {
items[i] = list.Item(proj)
}

return items
return fmt.Sprintf(
"Name your first feature flag (enter for default value):\n\n%s\n\n%s",
m.textInput.View(),
"(esc to quit)",
) + "\n"
}
2 changes: 1 addition & 1 deletion internal/setup/sdk_build_instructions/go.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ const featureFlagKey = "my-flag-key"

3. Run `./hello-go`

You should see the message `"Feature flag '<flag key>' is <true/false> for this context"`.
You should see the message `"Feature flag 'my-flag-key' is <true/false> for this context"`.
2 changes: 1 addition & 1 deletion internal/setup/sdk_build_instructions/js.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ const flagKey = 'my-flag-key';

2. Open `index.html` in your browser.

You should receive the message "Feature flag key '<flag key>' is <true/false> for this user".
You should receive the message "Feature flag key 'my-flag-key' is <true/false> for this user".
80 changes: 6 additions & 74 deletions internal/setup/wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ type fetchResources struct{}

// list of steps in the wizard
const (
autoCreateStep sessionState = iota
projectsStep
environmentsStep
flagsStep
flagsStep sessionState = iota
sdksStep
)

Expand All @@ -46,15 +43,12 @@ func NewWizardModel() tea.Model {
// Since there isn't a model for the initial step, the currStep value will always be one ahead of the step in
// this slice. It may be convenient to add a model for the initial step to contain its own view logic and to
// prevent this off-by-one issue.
NewAutoCreate(),
NewProject(),
NewEnvironment(),
NewFlag(),
NewSdk(),
}

return WizardModel{
currStep: autoCreateStep,
currStep: 0,
steps: steps,
}
}
Expand All @@ -73,64 +67,11 @@ func (m WizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch {
case key.Matches(msg, keys.Enter):
switch m.currStep {
case autoCreateStep:
model, _ := m.steps[autoCreateStep].Update(msg)
p, ok := model.(autoCreateModel)
if ok {
m.useRecommendedResources = p.choice == "Yes"
if m.useRecommendedResources {
// create project, environment, and flag
// go to step after flagsStep
m.currProjectKey = "setup-wizard-project"
m.currEnvironmentKey = "test"
m.currFlagKey = "setup-wizard-flag"
m.currStep = flagsStep + 1
} else {
// pre-load projects
m.steps[projectsStep], _ = m.steps[projectsStep].Update(fetchResources{})
m.currStep += 1
}
}
case projectsStep:
projModel, _ := m.steps[projectsStep].Update(msg)
// we need to cast this to get the data out of it, but maybe we can create our own interface with
// common values such as Choice() and Err() so we don't have to cast
p, ok := projModel.(projectModel)
if ok {
m.currProjectKey = p.choice
// update projModel with new input model
m.steps[projectsStep] = p
// only progress if we don't want to show input
if !p.showInput {
// pre-load environments based on project selected
envModel := m.steps[environmentsStep]
e, ok := envModel.(environmentModel)
if ok {
e.parentKey = m.currProjectKey
m.steps[environmentsStep], _ = e.Update(fetchResources{})
m.currStep += 1
}
}
}
case environmentsStep:
envModel, _ := m.steps[environmentsStep].Update(msg)
p, ok := envModel.(environmentModel)
if ok {
m.currEnvironmentKey = p.choice
// pre-load flags based on environment selected
fModel := m.steps[flagsStep]
f, ok := fModel.(flagModel)
if ok {
f.parentKey = m.currEnvironmentKey
m.steps[flagsStep], _ = f.Update(fetchResources{})
m.currStep += 1
}
}
case flagsStep:
model, _ := m.steps[flagsStep].Update(msg)
f, ok := model.(flagModel)
if ok {
m.currFlagKey = f.choice
if ok && f.input != "" {
m.currFlagKey = f.input
m.currStep += 1
}
case sdksStep:
Expand All @@ -144,15 +85,8 @@ func (m WizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
default:
}
case key.Matches(msg, keys.Back):
// if we've opted to use recommended resources but want to go back from the SDK step,
// make sure we go back to the right step
if m.useRecommendedResources && m.currStep == sdksStep {
m.currStep = autoCreateStep
}
// only go back if not on the first step
if m.currStep > autoCreateStep {
// fetch resources for the previous step again in case we created new ones
m.steps[m.currStep-1], _ = m.steps[m.currStep-1].Update(fetchResources{})
if m.currStep > 0 {
m.currStep -= 1
}
case key.Matches(msg, keys.Quit):
Expand All @@ -179,9 +113,7 @@ func (m WizardModel) View() string {
if m.currStep > sdksStep {
return wordwrap.String(
fmt.Sprintf(
"Selected project: %s\nSelected environment: %s\n\nSet up your application. Here are the steps to incorporate the LaunchDarkly %s SDK into your code. \n\n%s",
m.currProjectKey,
m.currEnvironmentKey,
"Set up your application. Here are the steps to incorporate the LaunchDarkly %s SDK into your code. \n\n%s",
m.currSdk.Name,
m.renderMarkdown(),
),
Expand Down