Skip to content

Commit

Permalink
feat: add sdk wizard step (#29)
Browse files Browse the repository at this point in the history
* read sdk instructions from md file

* fix article typo

* add text wrapping to view step

* add comment

* fix python md
  • Loading branch information
k3llymariee authored Mar 15, 2024
1 parent 88765a2 commit c4dc7d0
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 3 deletions.
2 changes: 1 addition & 1 deletion internal/setup/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func NewFlag() tea.Model {
}

l := list.New(flagsToItems(flags), flagDelegate{}, 30, 14)
l.Title = "Select an flag"
l.Title = "Select a flag"
l.SetShowStatusBar(false)
l.SetFilteringEnabled(false)

Expand Down
11 changes: 11 additions & 0 deletions internal/setup/sdk_build_instructions/js.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Build Instructions
1. Edit `index.html` and set the value of `clientSideID` to your LaunchDarkly client-side ID. If there is an existing boolean feature flag in your LaunchDarkly project that you want to evaluate, set `flagKey` to the flag key.

```
const clientSideID = '1234567890abcdef';
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".
15 changes: 15 additions & 0 deletions internal/setup/sdk_build_instructions/python.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Build instructions

1. Install the LaunchDarkly Python SDK by running `pip install -r requirements.txt`
2. On the command line, set the value of the environment variable `LAUNCHDARKLY_SERVER_KEY` to your LaunchDarkly SDK key.
```bash
export LAUNCHDARKLY_SERVER_KEY="1234567890abcdef"
```
3. On the command line, set the value of the environment variable `LAUNCHDARKLY_FLAG_KEY` to an existing boolean feature flag in your LaunchDarkly project that you want to evaluate.

```bash
export LAUNCHDARKLY_FLAG_KEY="my-flag-key"
```
4. Run `python test.py`.

You should receive the message `"Feature flag 'my-flag-key' is <true/false> for this user"`.
118 changes: 118 additions & 0 deletions internal/setup/sdks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package setup

import (
"fmt"
"io"
"strings"

"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

var (
sdkStyle = lipgloss.NewStyle().PaddingLeft(4)
selectedSdkItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))

_ list.Item = sdk{}
)

type sdk struct {
Name string `json:"name"`
InstructionsFileName string `json:"instructionFile"`
}

func (s sdk) FilterValue() string { return "" }

type sdkModel struct {
choice sdk
instructions string
err error
list list.Model
}

const sdkInstructionsFilePath = "internal/setup/sdk_build_instructions/"

func NewSdk() tea.Model {
sdks := []sdk{
{
Name: "JavaScript",
InstructionsFileName: sdkInstructionsFilePath + "js.md",
},
{
Name: "Python",
InstructionsFileName: sdkInstructionsFilePath + "python.md",
},
}

l := list.New(sdksToItems(sdks), sdkDelegate{}, 30, 14)
l.Title = "Select your SDK."
l.SetShowStatusBar(false)
l.SetFilteringEnabled(false)

return sdkModel{
list: l,
}
}

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

// This method has drifted from the ProjectModel's version, but it should do something similar.
func (m sdkModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Enter):
i, ok := m.list.SelectedItem().(sdk)
if ok {
m.choice = i
}
case key.Matches(msg, keys.Quit):
return m, tea.Quit
default:
m.list, cmd = m.list.Update(msg)
}
}

return m, cmd
}

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

type sdkDelegate struct{}

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

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

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

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

func sdksToItems(sdks []sdk) []list.Item {
items := make([]list.Item, len(sdks))
for i, proj := range sdks {
items[i] = list.Item(proj)
}

return items
}
40 changes: 38 additions & 2 deletions internal/setup/wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package setup

import (
"fmt"
"os"
"strings"

"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/muesli/reflow/wordwrap"
)

// TODO: we may want to rename this for clarity
Expand All @@ -16,6 +19,7 @@ const (
projectsStep
environmentsStep
flagsStep
sdksStep
)

// WizardModel is a high level container model that controls the nested models which each
Expand All @@ -29,6 +33,8 @@ type WizardModel struct {
currProjectKey string
currEnvironmentKey string
currFlagKey string
currSdk sdk
width int
}

func NewWizardModel() tea.Model {
Expand All @@ -40,6 +46,7 @@ func NewWizardModel() tea.Model {
NewProject(),
NewEnvironment(),
NewFlag(),
NewSdk(),
}

return WizardModel{
Expand All @@ -56,6 +63,8 @@ func (m WizardModel) Init() tea.Cmd {
// the user is on.
func (m WizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Enter):
Expand Down Expand Up @@ -110,10 +119,22 @@ func (m WizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.currFlagKey = f.choice
m.currStep += 1
}
case sdksStep:
model, _ := m.steps[sdksStep].Update(msg)
f, ok := model.(sdkModel)
if ok {
m.currSdk = f.choice
m.currStep += 1
}
// add additional cases for additional steps
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 {
m.currStep -= 1
Expand All @@ -139,8 +160,23 @@ func (m WizardModel) View() string {
return fmt.Sprintf("ERROR: %s", m.err)
}

if m.currStep > flagsStep {
return fmt.Sprintf("envKey is %s, projKey is %s, flagKey is %s", m.currEnvironmentKey, m.currProjectKey, m.currFlagKey)
if m.currStep > sdksStep {
// consider moving this to its own view (in a new model?)
content, err := os.ReadFile(m.currSdk.InstructionsFileName)
if err != nil {
fmt.Println("could not load file:", err)
os.Exit(1)
}
sdkInstructions := strings.ReplaceAll(string(content), "my-flag-key", m.currFlagKey)
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,
m.currSdk.Name,
sdkInstructions,
),
m.width,
)
}

return fmt.Sprintf("\nstep %d of %d\n"+m.steps[m.currStep].View(), m.currStep+1, len(m.steps))
Expand Down

0 comments on commit c4dc7d0

Please sign in to comment.