Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add toggle flag step #111

Merged
merged 6 commits into from
Apr 3, 2024
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
10 changes: 2 additions & 8 deletions cmd/flags/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,8 @@ func runUpdate(client flags.Client) func(*cobra.Command, []string) error {
var patch []flags.UpdateInput
if cmd.CalledAs() == "toggle-on" || cmd.CalledAs() == "toggle-off" {
_ = viper.BindPFlag(cliflags.EnvironmentFlag, cmd.Flags().Lookup(cliflags.EnvironmentFlag))
err := json.Unmarshal([]byte(buildPatch(viper.GetString(cliflags.EnvironmentFlag), cmd.CalledAs() == "toggle-on")), &patch)
if err != nil {
return err
}
envKey := viper.GetString(cliflags.EnvironmentFlag)
patch = flags.BuildToggleFlagPatch(envKey, cmd.CalledAs() == "toggle-on")
Comment on lines +126 to +127
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored this to play better with where we call this in the quickstart

} else {
err := json.Unmarshal([]byte(viper.GetString(cliflags.DataFlag)), &patch)
if err != nil {
Expand All @@ -151,7 +149,3 @@ func runUpdate(client flags.Client) func(*cobra.Command, []string) error {
return nil
}
}

func buildPatch(envKey string, toggleValue bool) string {
return fmt.Sprintf(`[{"op": "replace", "path": "/environments/%s/on", "value": %t}]`, envKey, toggleValue)
}
80 changes: 80 additions & 0 deletions cmd/flags/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,83 @@ func TestUpdate(t *testing.T) {
assert.EqualError(t, err, "base-uri is invalid"+errorHelp)
})
}

func TestToggle(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added tests to make sure i didn't break anything

errorHelp := ". See `ldcli flags toggle-on --help` for supported flags and usage."
mockArgs := []interface{}{
"testAccessToken",
"http://test.com",
"test-proj-key",
"test-flag-key",
[]flags.UpdateInput{
{
Op: "replace",
Path: "/environments/test-env-key/on",
Value: true,
},
},
}
t.Run("with valid flags calls projects API", func(t *testing.T) {
client := flags.MockClient{}
client.
On("Update", mockArgs...).
Return([]byte(cmd.ValidResponse), nil)
args := []string{
"flags", "toggle-on",
"--access-token", "testAccessToken",
"--base-uri", "http://test.com",
"--flag", "test-flag-key",
"--project", "test-proj-key",
"--environment", "test-env-key",
}

output, err := cmd.CallCmd(t, nil, &client, nil, nil, args)

require.NoError(t, err)
assert.JSONEq(t, `{"valid": true}`, string(output))
})

t.Run("with an error response is an error", func(t *testing.T) {
client := flags.MockClient{}
client.
On("Update", mockArgs...).
Return([]byte(`{}`), errors.NewError("An error"))
args := []string{
"flags", "toggle-on",
"--access-token", "testAccessToken",
"--base-uri", "http://test.com",
"--flag", "test-flag-key",
"--project", "test-proj-key",
"--environment", "test-env-key",
}

_, err := cmd.CallCmd(t, nil, &client, nil, nil, args)

require.EqualError(t, err, "An error")
})

t.Run("with missing required flags is an error", func(t *testing.T) {
args := []string{
"flags", "toggle-on",
}

_, err := cmd.CallCmd(t, nil, &flags.MockClient{}, nil, nil, args)

assert.EqualError(t, err, `required flag(s) "access-token", "environment", "flag", "project" not set`+errorHelp)
})

t.Run("with invalid base-uri is an error", func(t *testing.T) {
args := []string{
"flags", "toggle-on",
"--access-token", "testAccessToken",
"--base-uri", "invalid",
"--flag", "test-flag-key",
"--project", "test-proj-key",
"--environment", "test-env-key",
}

_, err := cmd.CallCmd(t, nil, &flags.MockClient{}, nil, nil, args)

assert.EqualError(t, err, "base-uri is invalid"+errorHelp)
})
}
5 changes: 5 additions & 0 deletions internal/flags/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package flags
import (
"context"
"encoding/json"
"fmt"

ldapi "github.com/launchdarkly/api-client-go/v14"

Expand Down Expand Up @@ -92,3 +93,7 @@ func (c FlagsClient) Update(

return responseJSON, nil
}

func BuildToggleFlagPatch(envKey string, enabled bool) []UpdateInput {
return []UpdateInput{{Op: "replace", Path: fmt.Sprintf("/environments/%s/on", envKey), Value: enabled}}
}
17 changes: 12 additions & 5 deletions internal/quickstart/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import (
"ldcli/internal/flags"
)

const (
defaultProjKey = "default"
defaultEnvKey = "test"
)

// ContainerModel is a high level container model that controls the nested models wher each
// represents a step in the quick-start flow.
type ContainerModel struct {
Expand All @@ -23,13 +28,14 @@ type ContainerModel struct {
accessToken string
baseUri string
currentModel tea.Model
sdkKind string
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like flagKey storing anything we might need in a subsequent step on the container model

}

func NewContainerModel(flagsClient flags.Client, accessToken string, baseUri string) tea.Model {
return ContainerModel{
accessToken: accessToken,
baseUri: baseUri,
currentModel: NewCreateFlagModel(flagsClient),
currentModel: NewCreateFlagModel(flagsClient, accessToken, baseUri),
flagsClient: flagsClient,
}
}
Expand All @@ -53,18 +59,19 @@ func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case choseSDKMsg:
m.currentModel = NewShowSDKInstructionsModel(m.accessToken, m.baseUri, msg.canonicalName, msg.url, m.flagKey)
cmd = m.currentModel.Init()
m.sdkKind = msg.sdkKind
case createdFlagMsg:
m.currentModel = NewChooseSDKModel()
m.flagKey = msg.flagKey // TODO: figure out if we maintain state here or pass in another message
case errMsg:
m.err = msg.err
case noInstructionsMsg:
// TODO: set currentModel to toggle flag model
// m.currentModel = NewToggleFlagModel(m.flagsClient, m.flagKey)
case fetchedSDKInstructions, fetchedEnv:
// skip the ShowSDKInstructionsModel and move along to toggling the flag
m.currentModel = NewToggleFlagModel(m.flagsClient, m.accessToken, m.baseUri, m.flagKey, m.sdkKind)
case fetchedSDKInstructions, fetchedEnv, toggledFlagMsg:
m.currentModel, cmd = m.currentModel.Update(msg)
case showToggleFlagMsg:
m.currentModel = NewToggleFlagModel(m.flagKey)
m.currentModel = NewToggleFlagModel(m.flagsClient, m.accessToken, m.baseUri, m.flagKey, m.sdkKind)
default:
log.Println("container default - bad", msg)
}
Expand Down
23 changes: 10 additions & 13 deletions internal/quickstart/create_flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ package quickstart

import (
"fmt"
"ldcli/cmd/cliflags"

"github.com/spf13/viper"

"ldcli/internal/flags"

"github.com/charmbracelet/bubbles/key"
Expand All @@ -17,20 +13,24 @@ import (
const defaultFlagName = "my new flag"

type createFlagModel struct {
client flags.Client
textInput textinput.Model
accessToken string
baseUri string
client flags.Client
textInput textinput.Model
}

func NewCreateFlagModel(client flags.Client) tea.Model {
func NewCreateFlagModel(client flags.Client, accessToken, baseUri string) tea.Model {
ti := textinput.New()
ti.Focus()
ti.CharLimit = 156
ti.Width = 20
ti.Placeholder = defaultFlagName

return createFlagModel{
client: client,
textInput: ti,
accessToken: accessToken,
baseUri: baseUri,
client: client,
textInput: ti,
}
}

Expand All @@ -53,10 +53,7 @@ func (m createFlagModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, sendErr(err)
}

accessToken := viper.GetString(cliflags.AccessTokenFlag)
baseUri := viper.GetString(cliflags.BaseURIFlag)
Comment on lines -56 to -57
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed this since we have this at the container level/should set these values on the createFlagModel


return m, sendCreateFlagMsg(m.client, accessToken, baseUri, input, flagKey, "default")
return m, sendCreateFlagMsg(m.client, m.accessToken, m.baseUri, input, flagKey, defaultProjKey)
case key.Matches(msg, keys.Quit):
return m, tea.Quit
default:
Expand Down
21 changes: 21 additions & 0 deletions internal/quickstart/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ func sendErr(err error) tea.Cmd {
}
}

type toggledFlagMsg struct{}

func sendToggleFlagMsg(client flags.Client, accessToken, baseUri, flagKey string, enabled bool) tea.Cmd {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make more sense to name this after the message it sends, so in this case it would be sendToggledFlagMsg (past tense)? I could see it implicitly sending a toggleFlagMsg and returning the results, either a toggledFlagMsg or errMsg, but I could also see that as confusing because there isn't a specific toggleFlagMsg type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya I did actually get a bit tripped on this looking for the the actual msg type it specified when I picked it back up again, but it is also Doing The Thing here

return func() tea.Msg {
_, err := client.Update(
context.Background(),
accessToken,
baseUri,
flagKey,
defaultProjKey,
flags.BuildToggleFlagPatch(defaultEnvKey, enabled),
)
if err != nil {
return sendErr(err)
}
return toggledFlagMsg{}
}
}

type createdFlagMsg struct {
flagKey string
}
Expand Down Expand Up @@ -75,6 +94,7 @@ type fetchedSDKInstructions struct {
type choseSDKMsg struct {
canonicalName string
displayName string
sdkKind string
url string
}

Expand All @@ -88,6 +108,7 @@ func sendChoseSDKMsg(sdk sdkDetail) tea.Cmd {
canonicalName: sdk.canonicalName,
displayName: sdk.displayName,
url: sdk.url,
sdkKind: sdk.kind,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/quickstart/show_sdk_instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func NewShowSDKInstructionsModel(
func (m showSDKInstructionsModel) Init() tea.Cmd {
return tea.Sequence(
sendFetchSDKInstructionsMsg(m.url),
sendFetchEnv(m.accessToken, m.baseUri, "test", "default"),
sendFetchEnv(m.accessToken, m.baseUri, defaultEnvKey, defaultProjKey),
)
}

Expand Down
47 changes: 40 additions & 7 deletions internal/quickstart/toggle_flag.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
package quickstart

import (
"fmt"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"ldcli/internal/flags"
)

type toggleFlagModel struct {
flagKey string
accessToken string
baseUri string
client flags.Client
enabled bool
flagKey string
flagWasEnabled bool
sdkKind string
}

func NewToggleFlagModel(flagKey string) tea.Model {
func NewToggleFlagModel(client flags.Client, accessToken string, baseUri string, flagKey string, sdkKind string) tea.Model {
return toggleFlagModel{
flagKey: flagKey,
accessToken: accessToken,
baseUri: baseUri,
client: client,
flagKey: flagKey,
sdkKind: sdkKind,
}
}

Expand All @@ -25,22 +37,43 @@ func (m toggleFlagModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Quit):
return m, tea.Quit
case key.Matches(msg, keys.Tab):
// TODO: toggle flag
m.flagWasEnabled = true
m.enabled = !m.enabled
return m, sendToggleFlagMsg(m.client, m.accessToken, m.baseUri, m.flagKey, m.enabled)
}
}

return m, cmd
}

var logTypeMap = map[string]string{
serverSideSDK: "application logs",
clientSideSDK: "browser",
}

func (m toggleFlagModel) View() string {
var furtherInstructions string
title := "Toggle your feature flag (press tab)"
toggle := "OFF"
bgColor := "#646a73"
margin := 1
if m.enabled {
bgColor = "#3d9c51"
margin = 2
toggle = "ON"
}

if m.flagWasEnabled {
furtherInstructions = fmt.Sprintf("\n\nCheck your %s to see the change!\n\n(press ctrl + c to quit)", logTypeMap[m.sdkKind])
}

toggleStyle := lipgloss.NewStyle().
Background(lipgloss.Color("#646a73")).
Background(lipgloss.Color(bgColor)).
Padding(0, 1).
MarginRight(1)
MarginRight(margin)

return title + "\n\n" + toggleStyle.Render(toggle) + m.flagKey
return title + "\n\n" + toggleStyle.Render(toggle) + m.flagKey + furtherInstructions
}
Loading