From 14d5ea6810b92aef8c76df6d144ccd32d266f97a Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Fri, 15 Mar 2024 09:44:50 -0700 Subject: [PATCH 1/5] read sdk instructions from md file --- internal/setup/sdk_build_instructions/js.md | 11 ++ .../setup/sdk_build_instructions/python.md | 15 +++ internal/setup/sdks.go | 118 ++++++++++++++++++ internal/setup/wizard.go | 33 ++++- 4 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 internal/setup/sdk_build_instructions/js.md create mode 100644 internal/setup/sdk_build_instructions/python.md create mode 100644 internal/setup/sdks.go diff --git a/internal/setup/sdk_build_instructions/js.md b/internal/setup/sdk_build_instructions/js.md new file mode 100644 index 00000000..5b7eb4a5 --- /dev/null +++ b/internal/setup/sdk_build_instructions/js.md @@ -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 '' is for this user". \ No newline at end of file diff --git a/internal/setup/sdk_build_instructions/python.md b/internal/setup/sdk_build_instructions/python.md new file mode 100644 index 00000000..44a1cdcd --- /dev/null +++ b/internal/setup/sdk_build_instructions/python.md @@ -0,0 +1,15 @@ +## Build instructions + +1. Install the LaunchDarkly Python SDK by running `pip install -r requirements.txt` +1. 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" + ``` +1. 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" + ``` +1. Run `python test.py`. + +You should receive the message `"Feature flag 'my-flag-key' is for this user"`. diff --git a/internal/setup/sdks.go b/internal/setup/sdks.go new file mode 100644 index 00000000..142b1772 --- /dev/null +++ b/internal/setup/sdks.go @@ -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 +} diff --git a/internal/setup/wizard.go b/internal/setup/wizard.go index 5b6a772a..b1d04504 100644 --- a/internal/setup/wizard.go +++ b/internal/setup/wizard.go @@ -2,6 +2,8 @@ package setup import ( "fmt" + "os" + "strings" "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" @@ -16,6 +18,7 @@ const ( projectsStep environmentsStep flagsStep + sdksStep ) // WizardModel is a high level container model that controls the nested models which each @@ -29,6 +32,7 @@ type WizardModel struct { currProjectKey string currEnvironmentKey string currFlagKey string + currSdk sdk } func NewWizardModel() tea.Model { @@ -40,6 +44,7 @@ func NewWizardModel() tea.Model { NewProject(), NewEnvironment(), NewFlag(), + NewSdk(), } return WizardModel{ @@ -110,10 +115,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 @@ -139,8 +156,20 @@ 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 { + 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 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, + ) } return fmt.Sprintf("\nstep %d of %d\n"+m.steps[m.currStep].View(), m.currStep+1, len(m.steps)) From a5efe575b7b519907fb5b81b77308584e9e706f7 Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Fri, 15 Mar 2024 09:45:15 -0700 Subject: [PATCH 2/5] fix article typo --- internal/setup/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/setup/flags.go b/internal/setup/flags.go index 76b79940..2f90ad05 100644 --- a/internal/setup/flags.go +++ b/internal/setup/flags.go @@ -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) From e7e30cc214ffb6d7c786051d15d67970ede73fc2 Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Fri, 15 Mar 2024 10:44:38 -0700 Subject: [PATCH 3/5] add text wrapping to view step --- internal/setup/wizard.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/setup/wizard.go b/internal/setup/wizard.go index b1d04504..06e6add4 100644 --- a/internal/setup/wizard.go +++ b/internal/setup/wizard.go @@ -7,6 +7,7 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" + "github.com/muesli/reflow/wordwrap" ) // TODO: we may want to rename this for clarity @@ -33,6 +34,7 @@ type WizardModel struct { currEnvironmentKey string currFlagKey string currSdk sdk + width int } func NewWizardModel() tea.Model { @@ -61,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): @@ -163,12 +167,14 @@ func (m WizardModel) View() string { os.Exit(1) } sdkInstructions := strings.ReplaceAll(string(content), "my-flag-key", m.currFlagKey) - return fmt.Sprintf( + 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, ) } From 8324cd0ecdfa4b345179d8cf3363d78a29245a3f Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Fri, 15 Mar 2024 11:39:09 -0700 Subject: [PATCH 4/5] add comment --- internal/setup/wizard.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/setup/wizard.go b/internal/setup/wizard.go index 06e6add4..ad289207 100644 --- a/internal/setup/wizard.go +++ b/internal/setup/wizard.go @@ -161,6 +161,7 @@ func (m WizardModel) View() string { } 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) From 31532f3687b659ccc18b7d6c3ddc14263f8d308f Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Fri, 15 Mar 2024 11:39:41 -0700 Subject: [PATCH 5/5] fix python md --- internal/setup/sdk_build_instructions/python.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/setup/sdk_build_instructions/python.md b/internal/setup/sdk_build_instructions/python.md index 44a1cdcd..45b263ed 100644 --- a/internal/setup/sdk_build_instructions/python.md +++ b/internal/setup/sdk_build_instructions/python.md @@ -1,15 +1,15 @@ ## Build instructions 1. Install the LaunchDarkly Python SDK by running `pip install -r requirements.txt` -1. On the command line, set the value of the environment variable `LAUNCHDARKLY_SERVER_KEY` to your LaunchDarkly SDK key. +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" ``` -1. 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. +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" ``` -1. Run `python test.py`. +4. Run `python test.py`. You should receive the message `"Feature flag 'my-flag-key' is for this user"`.