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: use SDK metadata #378

Merged
merged 12 commits into from
Aug 12, 2024
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/google/uuid v1.6.0
github.com/iancoleman/strcase v0.3.0
github.com/launchdarkly/api-client-go/v14 v14.0.0
github.com/launchdarkly/sdk-meta/api v0.1.1
github.com/mitchellh/go-homedir v1.1.0
github.com/muesli/reflow v0.3.0
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
Expand All @@ -19,6 +20,7 @@ require (
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
golang.org/x/term v0.18.0
gopkg.in/yaml.v3 v3.0.1
)
Expand Down Expand Up @@ -69,9 +71,8 @@ require (
github.com/yuin/goldmark-emoji v1.0.1 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/oauth2 v0.15.0 // indirect
golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/launchdarkly/api-client-go/v14 v14.0.0 h1:fZfi5zKwgjpaOgK4NKcU5mJT2C8sYsR8nnuJYTaFvNU=
github.com/launchdarkly/api-client-go/v14 v14.0.0/go.mod h1:K7ejD5nn9ar94p/5qrQ0t9iJygdIQyH70U9M9rYvw5Y=
github.com/launchdarkly/sdk-meta/api v0.1.1 h1:B9UaOFTDGQSDzbSTwqiBmaTN3xDfrlIu1JQ9LCs2UiA=
github.com/launchdarkly/sdk-meta/api v0.1.1/go.mod h1:vXfR0z4XBz49IYT/2GDEza+Iat3PcuBCC438AZT6oDg=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
Expand Down Expand Up @@ -345,8 +347,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
243 changes: 97 additions & 146 deletions internal/quickstart/choose_sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ package quickstart
import (
"fmt"
"io"
"io/fs"
"path/filepath"
"strings"

"github.com/launchdarkly/ldcli/internal/sdks"
"github.com/launchdarkly/sdk-meta/api/sdkmeta"
"golang.org/x/exp/slices"

"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
Expand All @@ -18,23 +24,18 @@ var (
titleBarStyle = lipgloss.NewStyle().MarginBottom(1)
)

const (
clientSideSDK = "client"
mobileSDK = "mobile"
serverSideSDK = "server"
)

type chooseSDKModel struct {
help help.Model
helpKeys keyMap
list list.Model
sdkDetails []sdkDetail
selectedIndex int
selectedSDK sdkDetail
}

func NewChooseSDKModel(selectedIndex int) tea.Model {
initSDKs()
l := list.New(sdksToItems(), sdkDelegate{}, 30, 9)
sdkDetails := initSDKs()
Copy link
Contributor

Choose a reason for hiding this comment

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

I updated the PR to not use the global var but pass this list around instead. It felt weird to modify it when we could encapsulate it in the model.

l := list.New(sdksToItems(sdkDetails), sdkDelegate{}, 30, 9)
l.FilterInput.PromptStyle = lipgloss.NewStyle()

l.Title = "Select your SDK:"
Expand All @@ -61,15 +62,90 @@ func NewChooseSDKModel(selectedIndex int) tea.Model {
Quit: BindingQuit,
},
list: l,
sdkDetails: sdkDetails,
selectedIndex: selectedIndex,
}
}

// initSDKs sets the index of each SDK based on place in list.
func initSDKs() {
for i := range SDKs {
SDKs[i].index = i
// The CLI uses the sdkmeta project to obtain metadata about each SDK, including the display names
// and types (client, server, etc.)
// Currently, there is no sdkmeta for code examples associated with each SDK, so we hard-code the examples here.
// Once they are part of sdkmeta we can remove this list.
var sdkExamples = map[string]string{
"react-client-sdk": "https://github.com/launchdarkly/react-client-sdk/tree/main/examples/typescript",
"vue": "https://github.com/launchdarkly/vue-client-sdk/tree/main/example",
"react-native": "https://github.com/launchdarkly/js-core/tree/main/packages/sdk/react-native/example",
"cpp-client-sdk": "https://github.com/launchdarkly/cpp-sdks/tree/main/examples/hello-cpp-client",
"cpp-server-sdk": "https://github.com/launchdarkly/cpp-sdks/tree/main/examples/hello-cpp-server",
"lua-server-server": "https://github.com/launchdarkly/lua-server-sdk/tree/main/examples/hello-lua-server",
}

// sdkOrder is a list of IDs the SDKs in order that they should be rendered based on popularity.
var sdkOrder = []string{
cwaldren-ld marked this conversation as resolved.
Show resolved Hide resolved
"react-client-sdk",
"node-server",
"python-server-sdk",
"java-server-sdk",
"dotnet-server-sdk",
"js-client-sdk",
"vue",
"swift-client-sdk",
"go-server-sdk",
"android",
"react-native",
"ruby-server-sdk",
"flutter-client-sdk",
"dotnet-client-sdk",
"erlang-server-sdk",
"rust-server-sdk",
"cpp-client-sdk",
"roku",
"node-client-sdk",
"cpp-server-sdk",
"lua-server-sdk",
"haskell-server-sdk",
"php-server-sdk",
}

// initSDKs is responsible for loading SDK quickstart instructions from the embedded filesystem.
//
// The names of the files are special: they are the ID of the SDK (e.g. react-native), and are used as an index or
// key to lookup associated sdk metadata (display name, SDK type, etc.)
//
// Therefore, take care when naming the files. A list of valid SDK IDs can be found here:
// https://github.com/launchdarkly/sdk-meta/blob/main/products/names.json
func initSDKs() []sdkDetail {
items, err := sdks.InstructionFiles.ReadDir("sdk_instructions")
if err != nil {
panic("failed to load embedded SDK quickstart instructions: " + err.Error())
}

slices.SortFunc(items, func(a fs.DirEntry, b fs.DirEntry) int {
return strings.Compare(a.Name(), b.Name())
})

details := make([]sdkDetail, 0, len(items))
for _, item := range items {
id, _, _ := strings.Cut(filepath.Base(item.Name()), ".")
if _, ok := sdkmeta.Names[id]; !ok {
continue
}
index := slices.Index(sdkOrder, id)
if index == -1 {
// if we missed an SDK don't add it with an invalid index
Copy link
Contributor

Choose a reason for hiding this comment

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

Without this, there's a panic since it would later try to reference a -1 index. It shouldn't happen unless we change the SDK ID and forget to update the order list.

continue
}

details = append(details, sdkDetail{
id: id,
index: index,
displayName: sdkmeta.Names[id],
sdkType: sdkmeta.Types[id],
url: sdkExamples[id],
})
}

return details
}

// Init sends commands when the model is created that will:
Expand Down Expand Up @@ -116,145 +192,20 @@ func (m chooseSDKModel) View() string {
}

type sdkDetail struct {
canonicalName string
displayName string
index int
kind string
url string // custom URL if it differs from the other SDKs
id string
displayName string
index int
sdkType sdkmeta.Type
url string // custom URL if it differs from the other SDKs
}

func (s sdkDetail) FilterValue() string { return s.displayName }

var SDKs = []sdkDetail{
{
canonicalName: "react",
displayName: "React",
kind: clientSideSDK,
url: "https://github.com/launchdarkly/react-client-sdk/tree/main/examples/typescript",
},
{
canonicalName: "node-server",
displayName: "Node.js (server-side)",
kind: serverSideSDK,
},
{
canonicalName: "python",
displayName: "Python",
kind: serverSideSDK,
},
{
canonicalName: "java",
displayName: "Java",
kind: serverSideSDK,
},
{
canonicalName: "dotnet-server",
displayName: ".NET (server-side)",
kind: serverSideSDK,
},
{
canonicalName: "js",
displayName: "JavaScript",
kind: clientSideSDK,
},
{
canonicalName: "vue",
displayName: "Vue",
kind: clientSideSDK,
url: "https://github.com/launchdarkly/vue-client-sdk/tree/main/example",
},
{
canonicalName: "ios-swift",
displayName: "iOS",
kind: mobileSDK,
},
{
canonicalName: "go",
displayName: "Go",
kind: serverSideSDK,
},
{
canonicalName: "android",
displayName: "Android",
kind: mobileSDK,
},
{
canonicalName: "react-native",
displayName: "React Native",
kind: mobileSDK,
url: "https://github.com/launchdarkly/js-core/tree/main/packages/sdk/react-native/example",
},
{
canonicalName: "ruby",
displayName: "Ruby",
kind: serverSideSDK,
},
{
canonicalName: "flutter",
displayName: "Flutter",
kind: mobileSDK,
},
{
canonicalName: "dotnet-client",
displayName: ".NET (client-side)",
kind: clientSideSDK,
},
{
canonicalName: "erlang",
displayName: "Erlang",
kind: serverSideSDK,
},
{
canonicalName: "rust",
displayName: "Rust",
kind: serverSideSDK,
},
{
canonicalName: "c-client",
displayName: "C/C++ (client-side)",
kind: clientSideSDK,
url: "https://github.com/launchdarkly/cpp-sdks/tree/main/examples/hello-cpp-client",
},
{
canonicalName: "roku",
displayName: "Roku",
kind: clientSideSDK,
},
{
canonicalName: "node-client",
displayName: "Node.js (client-side)",
kind: clientSideSDK,
},
{
canonicalName: "c-server",
displayName: "C/C++ (server-side)",
kind: serverSideSDK,
url: "https://github.com/launchdarkly/cpp-sdks/tree/main/examples/hello-cpp-server",
},
{
canonicalName: "lua-server",
displayName: "Lua",
kind: serverSideSDK,
url: "https://github.com/launchdarkly/lua-server-sdk/tree/main/examples/hello-lua-server",
},
{
canonicalName: "haskell-server",
displayName: "Haskell",
kind: serverSideSDK,
},
{
canonicalName: "php",
displayName: "PHP",
kind: serverSideSDK,
},
}

func sdksToItems() []list.Item {
items := make([]list.Item, len(SDKs))
for i, sdk := range SDKs {
items[i] = list.Item(sdk)
func sdksToItems(sdkDetails []sdkDetail) []list.Item {
items := make([]list.Item, len(sdkDetails))
for _, info := range sdkDetails {
items[info.index] = list.Item(info)
}

return items
}

Expand Down
12 changes: 6 additions & 6 deletions internal/quickstart/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@ func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.baseURI,
m.height,
m.width,
m.sdk.canonicalName,
m.sdk.id,
m.sdk.displayName,
m.sdk.url,
m.sdk.kind,
m.sdk.sdkType,
m.flagKey,
m.environment,
)
Expand All @@ -153,10 +153,10 @@ func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.baseURI,
m.height,
m.width,
msg.sdk.canonicalName,
msg.sdk.id,
msg.sdk.displayName,
msg.sdk.url,
msg.sdk.kind,
msg.sdk.sdkType,
m.flagKey,
m.environment,
)
Expand Down Expand Up @@ -196,7 +196,7 @@ func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.accessToken,
m.baseURI,
m.flagKey,
m.sdk.kind,
m.sdk.id,
)
cmd = m.currentModel.Init()
m.currentStep += 1
Expand All @@ -213,7 +213,7 @@ func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmd = tea.Batch(
cmd,
trackSetupStepStartedEvent(m.analyticsTracker, m.currentStep.String()),
trackSetupSDKSelectedEvent(m.analyticsTracker, m.sdk.canonicalName),
trackSetupSDKSelectedEvent(m.analyticsTracker, m.sdk.id),
)
case m.currentStep == stepToggleFlag && !m.flagToggled:
// the first time we get to the flag toggled view
Expand Down
2 changes: 1 addition & 1 deletion internal/quickstart/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ type choseSDKMsg struct {
func chooseSDK(sdk sdkDetail) tea.Cmd {
return func() tea.Msg {
if sdk.url == "" {
sdk.url = fmt.Sprintf("https://github.com/launchdarkly/hello-%s", sdk.canonicalName)
sdk.url = fmt.Sprintf("https://github.com/launchdarkly/hello-%s", sdk.id)
}

return choseSDKMsg{
Expand Down
Loading
Loading