Skip to content

Commit

Permalink
feat: Pick from list of real variations (#420)
Browse files Browse the repository at this point in the history
This adds better UX for multivariate flags. Previously, you just had the ability to edit the raw json values. Now you can pick from the available variations already defined in the LD service or you can create your own "local override". This is implemented by fetching all flags from the flag list API and writing the variations to the sqlite db. The data written to SQLite is then used to serve subsequent requests. I took this approach because it means that the pulled flag state should be in-sync with the available variations and it enables offline work. It does mean that add project & sync project operations take significantly longer in projects with large amounts of flags.

Some other changes are along for the ride because this branch was longer lived than ideal.

search bar for flags
display source environment
dark mode
fix patch endpoint so that the side-effect resync causes the observers to fire
  • Loading branch information
mike-zorn authored Sep 6, 2024
1 parent 2019b77 commit 91e8cbf
Show file tree
Hide file tree
Showing 59 changed files with 11,667 additions and 2,195 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
github.com/oapi-codegen/runtime v1.1.1
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/pkg/errors v0.9.1
github.com/samber/lo v1.47.0
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.18.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
Expand Down
68 changes: 68 additions & 0 deletions internal/dev_server/adapters/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package adapters

import (
"context"
"log"
"net/url"
"strconv"

ldapi "github.com/launchdarkly/api-client-go/v14"
"github.com/pkg/errors"
Expand All @@ -20,6 +23,7 @@ func GetApi(ctx context.Context) Api {
//go:generate go run go.uber.org/mock/mockgen -destination mocks/api.go -package mocks . Api
type Api interface {
GetSdkKey(ctx context.Context, projectKey, environmentKey string) (string, error)
GetAllFlags(ctx context.Context, projectKey string) ([]ldapi.FeatureFlag, error)
}

type apiClientApi struct {
Expand All @@ -31,9 +35,73 @@ func NewApi(client ldapi.APIClient) Api {
}

func (a apiClientApi) GetSdkKey(ctx context.Context, projectKey, environmentKey string) (string, error) {
log.Printf("GetSdkKey - projectKey: %s, environmentKey: %s", projectKey, environmentKey)
environment, _, err := a.apiClient.EnvironmentsApi.GetEnvironment(ctx, projectKey, environmentKey).Execute()
if err != nil {
return "", errors.Wrap(err, "unable to get SDK key from LD API")
}
return environment.ApiKey, nil
}

func (a apiClientApi) GetAllFlags(ctx context.Context, projectKey string) ([]ldapi.FeatureFlag, error) {
log.Printf("Fetching all flags for project '%s'", projectKey)
flags, err := a.getFlags(ctx, projectKey, nil)
if err != nil {
err = errors.Wrap(err, "unable to get all flags from LD API")
}
return flags, err
}

func (a apiClientApi) getFlags(ctx context.Context, projectKey string, href *string) ([]ldapi.FeatureFlag, error) {
var featureFlags *ldapi.FeatureFlags
var err error
if href == nil {
featureFlags, _, err = a.apiClient.FeatureFlagsApi.GetFeatureFlags(ctx, projectKey).
Summary(false).
Execute()
if err != nil {
return nil, err
}
} else {
limit, offset, err := parseHref(*href)
if err != nil {
return nil, errors.Wrapf(err, "unable to parse href for next link: %s", *href)
}
featureFlags, _, err = a.apiClient.FeatureFlagsApi.GetFeatureFlags(ctx, projectKey).
Summary(false).
Limit(limit).
Offset(offset).
Execute()
if err != nil {
return nil, err
}
}
flags := featureFlags.Items
if next, ok := featureFlags.Links["next"]; ok && next.Href != nil {
newFlags, err := a.getFlags(ctx, projectKey, next.Href)
if err != nil {
return nil, err
}
flags = append(flags, newFlags...)
}
return flags, nil
}

func parseHref(href string) (limit, offset int64, err error) {
parsedUrl, err := url.Parse(href)
if err != nil {
return
}
l, err := strconv.Atoi(parsedUrl.Query().Get("limit"))
if err != nil {
return
}
o, err := strconv.Atoi(parsedUrl.Query().Get("offset"))
if err != nil {
return
}

limit = int64(l)
offset = int64(o)
return
}
16 changes: 16 additions & 0 deletions internal/dev_server/adapters/mocks/api.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 28 additions & 6 deletions internal/dev_server/api/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ paths:
summary: updates the flag state for the given project and source environment
parameters:
- $ref: "#/components/parameters/projectKey"
- $ref: "#/components/parameters/expand"
- $ref: "#/components/parameters/projectExpand"
responses:
200:
$ref: "#/components/responses/Project"
Expand All @@ -39,7 +39,7 @@ paths:
summary: get the specified project and its configuration for syncing from the LaunchDarkly Service
parameters:
- $ref: "#/components/parameters/projectKey"
- $ref: "#/components/parameters/expand"
- $ref: "#/components/parameters/projectExpand"
responses:
200:
$ref: "#/components/responses/Project"
Expand All @@ -49,7 +49,7 @@ paths:
summary: updates the project context or sourceEnvironmentKey
parameters:
- $ref: "#/components/parameters/projectKey"
- $ref: "#/components/parameters/expand"
- $ref: "#/components/parameters/projectExpand"
requestBody:
content:
application/json:
Expand Down Expand Up @@ -79,7 +79,7 @@ paths:
summary: Add the project to the dev server
parameters:
- $ref: "#/components/parameters/projectKey"
- $ref: "#/components/parameters/expand"
- $ref: "#/components/parameters/projectExpand"
requestBody:
content:
application/json:
Expand Down Expand Up @@ -143,7 +143,7 @@ components:
required: true
schema:
type: string
expand:
projectExpand:
name: expand
description: Available expand options for this endpoint.
in: query
Expand All @@ -153,6 +153,7 @@ components:
type: string
enum:
- overrides
- availableVariations
schemas:
FlagValue:
description: value of a feature flag variation
Expand All @@ -173,6 +174,20 @@ components:
default:
key: "dev-environment"
kind: "user"
Variation:
description: variation of a flag
required:
- _id
- value
properties:
_id:
type: string
name:
type: string
description:
type: string
value:
$ref: '#/components/schemas/FlagValue'
Project:
description: Project
type: object
Expand All @@ -194,10 +209,17 @@ components:
path: github.com/launchdarkly/ldcli/internal/dev_server/model
overrides:
type: object
description: flags and their values and version for a given project in the source environment
description: overridden flags for the project
x-go-type: model.FlagsState
x-go-type-import:
path: github.com/launchdarkly/ldcli/internal/dev_server/model
availableVariations:
type: object
description: variations
additionalProperties:
type: array
items:
$ref: '#/components/schemas/Variation'
_lastSyncedFromSource:
type: integer
x-go-type: int64
Expand Down
20 changes: 20 additions & 0 deletions internal/dev_server/api/data_mapping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package api

import "github.com/launchdarkly/ldcli/internal/dev_server/model"

func availableVariationsToResponseFormat(availableVariations map[string][]model.Variation) map[string][]Variation {
respAvailableVariations := make(map[string][]Variation, len(availableVariations))
for flagKey, variationsForFlag := range availableVariations {
respVariationsForFlag := make([]Variation, 0, len(variationsForFlag))
for _, variation := range variationsForFlag {
respVariationsForFlag = append(respVariationsForFlag, Variation{
Id: variation.Id,
Description: variation.Description,
Name: variation.Name,
Value: variation.Value,
})
}
respAvailableVariations[flagKey] = respVariationsForFlag
}
return respAvailableVariations
}
39 changes: 28 additions & 11 deletions internal/dev_server/api/server.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 91e8cbf

Please sign in to comment.