Skip to content

tested and added root org support #22

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
vendor/
.idea/
.vscode/
.DS_Store
internal/.DS_Store
local_evaluation.go
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ module github.com/LambdaTest/lambda-featureflag-go-sdk
go 1.20

require (
github.com/amplitude/analytics-go v1.0.1
github.com/joho/godotenv v1.5.1
github.com/sirupsen/logrus v1.9.3
github.com/spaolacci/murmur3 v1.1.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)

require (
github.com/amplitude/analytics-go v1.0.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
)
8 changes: 0 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
github.com/amplitude/analytics-go v1.0.1 h1:rrdC5VBctlJigSk0kw7ktwSijob/wyH4bop2SqWduCU=
github.com/amplitude/analytics-go v1.0.1/go.mod h1:kAQG8OQ6aPOxZrEZ3+/NFCfxdYSyjqXZhgkjWFD3/vo=
github.com/amplitude/experiment-go-server v1.3.0 h1:zsrbMiaI6yDwNesDREX6Wy83w4kZ0vqJEdxko69DmU8=
github.com/amplitude/experiment-go-server v1.3.0/go.mod h1:5HERnGGohucx2mNr/0fLeQ9xFWNmeMbtzsxH2beB+hs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -16,19 +14,13 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
84 changes: 59 additions & 25 deletions localEvaluation/localEvaluation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@ package localEvaluation

import (
"fmt"

"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/experiment"
"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/experiment/local"
"github.com/joho/godotenv"

"os"
"strconv"
"time"

"github.com/joho/godotenv"

"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/experiment"
"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/experiment/local"
"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/rootOrg"
)

var (
client *local.Client
LocalEvaluationConfigDebug = true
LocalEvaluationConfigServerUrl = "https://api.lambdatest.com"
LocalEvaluationConfigPollInterval = 120
LocalEvaluationConfigPollerRequestTimeout = 60
LocalEvaluationDeploymentKey = "server-jAqqJaX3l8PgNiJpcv9j20ywPzANQQFh"
rootOrgClient *rootOrg.Client
localEvaluationConfigDebug = true
localEvaluationConfigServerUrl = "https://api.lambdatest.com"
localEvaluationConfigPollInterval = 120
localEvaluationConfigPollerRequestTimeout = 60
localEvaluationDeploymentKey = "server-jAqqJaX3l8PgNiJpcv9j20ywPzANQQFh"
retries = 5
)

Expand All @@ -43,7 +45,7 @@ type UserProperties struct {
TemplateId string `json:"template_id,omitempty"`
}

func Init() {
func initVars() {
err := godotenv.Load()
if err != nil {
fmt.Printf("No .env file found")
Expand All @@ -52,31 +54,31 @@ func Init() {
}

if os.Getenv("LOCAL_EVALUATION_CONFIG_DEBUG") != "" {
LocalEvaluationConfigDebug, _ = strconv.ParseBool(os.Getenv("LOCAL_EVALUATION_CONFIG_DEBUG"))
localEvaluationConfigDebug, _ = strconv.ParseBool(os.Getenv("LOCAL_EVALUATION_CONFIG_DEBUG"))
}
if os.Getenv("LOCAL_EVALUATION_CONFIG_SERVER_URL") != "" {
LocalEvaluationConfigServerUrl = os.Getenv("LOCAL_EVALUATION_CONFIG_SERVER_URL")
localEvaluationConfigServerUrl = os.Getenv("LOCAL_EVALUATION_CONFIG_SERVER_URL")
}
if os.Getenv("LOCAL_EVALUATION_CONFIG_POLL_INTERVAL") != "" {
LocalEvaluationConfigPollInterval, _ = strconv.Atoi(os.Getenv("LOCAL_EVALUATION_CONFIG_POLL_INTERVAL"))
localEvaluationConfigPollInterval, _ = strconv.Atoi(os.Getenv("LOCAL_EVALUATION_CONFIG_POLL_INTERVAL"))
}
if os.Getenv("LOCAL_EVALUATION_CONFIG_POLLER_REQUEST_TIMEOUT") != "" {
LocalEvaluationConfigPollerRequestTimeout, _ = strconv.Atoi(os.Getenv("LOCAL_EVALUATION_CONFIG_POLLER_REQUEST_TIMEOUT"))
localEvaluationConfigPollerRequestTimeout, _ = strconv.Atoi(os.Getenv("LOCAL_EVALUATION_CONFIG_POLLER_REQUEST_TIMEOUT"))
}
if os.Getenv("LOCAL_EVALUATION_DEPLOYMENT_KEY") != "" {
LocalEvaluationDeploymentKey = os.Getenv("LOCAL_EVALUATION_DEPLOYMENT_KEY")
localEvaluationDeploymentKey = os.Getenv("LOCAL_EVALUATION_DEPLOYMENT_KEY")
}
}

func Initialize() {
Init()
initVars()
config := local.Config{
Debug: LocalEvaluationConfigDebug,
ServerUrl: LocalEvaluationConfigServerUrl,
FlagConfigPollerInterval: time.Duration(LocalEvaluationConfigPollInterval) * time.Second,
FlagConfigPollerRequestTimeout: time.Duration(LocalEvaluationConfigPollerRequestTimeout) * time.Second,
Debug: localEvaluationConfigDebug,
ServerUrl: localEvaluationConfigServerUrl,
FlagConfigPollerInterval: time.Duration(localEvaluationConfigPollInterval) * time.Second,
FlagConfigPollerRequestTimeout: time.Duration(localEvaluationConfigPollerRequestTimeout) * time.Second,
}
client = local.Initialize(LocalEvaluationDeploymentKey, &config)
client = local.Initialize(localEvaluationDeploymentKey, &config)
var err error
for i := 0; i < retries; i++ {
err = client.Start()
Expand Down Expand Up @@ -111,8 +113,32 @@ func InitializeWithConfig(conf local.Config, deploymentKey string) {
}
}

func InitializeRootOrg() error {
initVars()
rootOrgClient = rootOrg.NewClient(localEvaluationDeploymentKey, &rootOrg.Config{
ServerUrl: localEvaluationConfigServerUrl,
FlagConfigPollerInterval: time.Duration(localEvaluationConfigPollInterval) * time.Second,
FlagConfigPollerRequestTimeout: time.Duration(localEvaluationConfigPollerRequestTimeout) * time.Second,
})
var err error
for i := 0; i < retries; i++ {
err = rootOrgClient.Start()
if err != nil {
err = fmt.Errorf("unable to get root orgs with given config %+v attempt:%v with error %s", rootOrgClient.Config, i+1, err.Error())
continue
} else {
break
}
}
if err != nil {
err = fmt.Errorf("unable to get root orgs with given config %+v with error %s", rootOrgClient.Config, err.Error())
return err
}
return nil
}

func fetch(user UserProperties) (map[string]experiment.Variant, error) {
userProp := map[string]interface{}{
userProp := map[string]any{
"org_id": user.OrgId,
"org_name": user.OrgName,
"username": user.Username,
Expand All @@ -128,6 +154,14 @@ func fetch(user UserProperties) (map[string]experiment.Variant, error) {
UserProperties: userProp,
}

// Evaluate root org to get the parent org id
if expUser.UserProperties["org_id"] != "" && rootOrgClient != nil {
rootOrg, ok := rootOrgClient.Evaluate(expUser.UserProperties["org_id"])
if ok {
expUser.UserProperties["org_id"] = rootOrg
}
}

result, err := client.EvaluateV2(&expUser, []string{})
if err != nil {
return nil, err
Expand All @@ -137,7 +171,7 @@ func fetch(user UserProperties) (map[string]experiment.Variant, error) {

func getValue(flagName string, user UserProperties) Variant {
result, _ := fetch(user)
if result != nil && len(result) != 0 {
if len(result) != 0 {
if value, ok := result[flagName]; ok {
return Variant{
Key: value.Key,
Expand All @@ -151,7 +185,7 @@ func getValue(flagName string, user UserProperties) Variant {
func getMapOfValue(user UserProperties) map[string]interface{} {
flags := make(map[string]interface{})
result, _ := fetch(user)
if result != nil && len(result) != 0 {
if len(result) != 0 {
for k, v := range result {
if v.Value != "" {
flags[k] = v.Value
Expand Down
18 changes: 18 additions & 0 deletions model/lums.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package model

type OrgId int64

type OrgMap map[OrgId]OrgId

// ErrorResponse represents the error response from the LUMS API
type ErrorResponse struct {
Type string `json:"type"`
Title string `json:"title"`
Message string `json:"message"`
}

// SuccessResponse represents the success response from the LUMS API
type SuccessResponse struct {
Type string `json:"type"`
Data OrgMap `json:"data"`
}
7 changes: 3 additions & 4 deletions pkg/experiment/local/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/amplitude/analytics-go/amplitude"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"sync"

"github.com/LambdaTest/lambda-featureflag-go-sdk/internal/evaluation"

"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/experiment"
"github.com/amplitude/analytics-go/amplitude"

"github.com/LambdaTest/lambda-featureflag-go-sdk/internal/evaluation"
"github.com/LambdaTest/lambda-featureflag-go-sdk/internal/logger"
"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/experiment"
)

var clients = map[string]*Client{}
Expand Down
65 changes: 65 additions & 0 deletions pkg/rootOrg/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package rootOrg

import (
"strconv"
"sync"

"github.com/LambdaTest/lambda-featureflag-go-sdk/model"
)

type Client struct {
apiKey string
Config *Config
poller *poller
flags *model.OrgMap
flagsMutex *sync.RWMutex
}

func NewClient(apiKey string, config *Config) *Client {
return &Client{
apiKey: apiKey,
Config: config,
flagsMutex: &sync.RWMutex{},
flags: &model.OrgMap{},
poller: newPoller(),
}
}

func (c *Client) Start() error {
c.poller = newPoller()
c.poller.Poll(c.Config.FlagConfigPollerInterval, func() {
c.pollFlags()
})
return c.pollFlags()
}

func (c *Client) Stop() {
close(c.poller.shutdown)
}

func (c *Client) pollFlags() error {
c.flagsMutex.Lock()
defer c.flagsMutex.Unlock()
rootOrgs, err := GetRootOrgs(c.apiKey, c.Config.ServerUrl, c.Config.FlagConfigPollerRequestTimeout)
if err != nil {
return err
}
c.flags = rootOrgs
return nil
}

func (c *Client) Evaluate(orgId any) (any, bool) {
c.flagsMutex.RLock()
defer c.flagsMutex.RUnlock()

orgIdStr, ok := orgId.(string)
if !ok {
return nil, false
}
orgIdInt, err := strconv.ParseInt(orgIdStr, 10, 64)
if err != nil {
return nil, false
}
org, ok := (*c.flags)[model.OrgId(orgIdInt)]
return org, ok
}
17 changes: 17 additions & 0 deletions pkg/rootOrg/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package rootOrg

import (
"time"
)

type Config struct {
ServerUrl string
FlagConfigPollerInterval time.Duration
FlagConfigPollerRequestTimeout time.Duration
}

var DefaultConfig = &Config{
ServerUrl: "https://api.lambdatest.com",
FlagConfigPollerInterval: 120 * time.Second,
FlagConfigPollerRequestTimeout: 60 * time.Second,
}
28 changes: 28 additions & 0 deletions pkg/rootOrg/poller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package rootOrg

import "time"

type poller struct {
shutdown chan struct{}
}

func newPoller() *poller {
return &poller{
shutdown: make(chan struct{}),
}
}

func (p *poller) Poll(interval time.Duration, function func()) {
ticker := time.NewTicker(interval)
go func() {
for {
select {
case <-p.shutdown:
ticker.Stop()
return
case <-ticker.C:
go function()
}
}
}()
}
Loading