From cd936fc26f0cfd4645fab3dd322a193971b23725 Mon Sep 17 00:00:00 2001 From: Chadi Laoulaou <49269946+Chadiii@users.noreply.github.com> Date: Mon, 4 Nov 2024 18:15:41 +0100 Subject: [PATCH] Feat/fs3 1550 (#5) * fix: Add new aliases for WE & FE * fix: Read output-format var in request file * chore: Set working dir, identifier and email when switching account and login * feat: Add sendHitAnalytics when requesting * fix: Add responser for /users/me and fix test --- .../feature_experimentation.go | 2 +- cmd/root.go | 4 +- cmd/web-experimentation/account/use.go | 24 +++++ cmd/web-experimentation/auth/login.go | 35 +++++++ .../web_experimentation.go | 2 +- models/token.go | 12 +++ utils/config/config.go | 40 ++++++++ utils/const.go | 1 + utils/http_request/common/request.go | 92 +++++++++++++++++++ utils/http_request/common/token.go | 16 ++++ .../web_experimentation/account.go | 11 ++- .../web_experimentation/account_test.go | 15 +++ .../web_experimentation/account.go | 17 ++++ 13 files changed, 263 insertions(+), 8 deletions(-) diff --git a/cmd/feature-experimentation/feature_experimentation.go b/cmd/feature-experimentation/feature_experimentation.go index 5fb75c94..be54a64f 100644 --- a/cmd/feature-experimentation/feature_experimentation.go +++ b/cmd/feature-experimentation/feature_experimentation.go @@ -34,7 +34,7 @@ import ( // FeatureExperimentationCmd represents the feature experimentation command var FeatureExperimentationCmd = &cobra.Command{ Use: "feature-experimentation [auth|account|account-environment|project|campaign|flag|goal|targeting-key|variation-group|variation]", - Aliases: []string{"feature-experimentation", "feature-exp", "fe", "feat-exp"}, + Aliases: []string{"feature-experimentation", "feature-exp", "fe", "feat-exp", "feature"}, Short: "Manage resources related to the feature experimentation product", Long: `Manage resources related to the feature experimentation product`, PersistentPreRun: func(cmd *cobra.Command, args []string) { diff --git a/cmd/root.go b/cmd/root.go index 696e123e..fb683ce7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,8 +17,6 @@ import ( "github.com/spf13/viper" ) -var outputFormat string - // RootCmd represents the base command when called without any subcommands var RootCmd = &cobra.Command{ Use: "abtasty-cli", @@ -55,7 +53,7 @@ func addSubCommandPalettes() { func init() { cobra.OnInitialize(initConfig) - RootCmd.PersistentFlags().StringVarP(&outputFormat, "output-format", "", config.OutputFormat, "output format for the get and list subcommands for AB Tasty resources. Only 3 format are possible: table, json, json-pretty") + RootCmd.PersistentFlags().StringVarP(&common.OutputFormat, "output-format", "", config.OutputFormat, "output format for the get and list subcommands for AB Tasty resources. Only 3 format are possible: table, json, json-pretty") RootCmd.PersistentFlags().StringVarP(&common.UserAgent, "user-agent", "", config.DefaultUserAgent, "custom user agent") viper.BindPFlag("output_format", RootCmd.PersistentFlags().Lookup("output-format")) diff --git a/cmd/web-experimentation/account/use.go b/cmd/web-experimentation/account/use.go index 1eeb407f..7c6960bc 100644 --- a/cmd/web-experimentation/account/use.go +++ b/cmd/web-experimentation/account/use.go @@ -7,8 +7,10 @@ import ( "fmt" "log" + "github.com/flagship-io/abtasty-cli/models/web_experimentation" "github.com/flagship-io/abtasty-cli/utils" "github.com/flagship-io/abtasty-cli/utils/config" + "github.com/flagship-io/abtasty-cli/utils/http_request/common" "github.com/spf13/cobra" ) @@ -28,6 +30,28 @@ var useCmd = &cobra.Command{ log.Fatalf("error occurred: %s", err) } + err = config.SetWorkingDir(utils.WEB_EXPERIMENTATION, utils.DefaultGlobalCodeWorkingDir()) + if err != nil { + log.Fatalf("error occurred: %s", err) + } + + currentUser, err := common.HTTPGetIdentifierWE() + if err != nil { + log.Fatalf("error occurred: %v", err) + } + + if currentUser.LastAccount != (web_experimentation.AccountWE{}) { + err := config.SetIdentifier(utils.WEB_EXPERIMENTATION, currentUser.LastAccount.Identifier) + if err != nil { + log.Fatalf("error occurred: %s", err) + } + + err = config.SetEmail(utils.WEB_EXPERIMENTATION, currentUser.Email) + if err != nil { + log.Fatalf("error occurred: %s", err) + } + } + fmt.Fprintln(cmd.OutOrStdout(), "Account ID set to : "+AccountID) }, diff --git a/cmd/web-experimentation/auth/login.go b/cmd/web-experimentation/auth/login.go index d6a3d0dd..05dfcb9d 100644 --- a/cmd/web-experimentation/auth/login.go +++ b/cmd/web-experimentation/auth/login.go @@ -9,6 +9,7 @@ import ( "os" "slices" + "github.com/flagship-io/abtasty-cli/models/web_experimentation" "github.com/flagship-io/abtasty-cli/utils" "github.com/flagship-io/abtasty-cli/utils/config" "github.com/flagship-io/abtasty-cli/utils/http_request/common" @@ -80,6 +81,23 @@ var loginCmd = &cobra.Command{ log.Fatalf("error occurred: %v", err) } + currentUser, err := common.HTTPGetIdentifierWE() + if err != nil { + log.Fatalf("error occurred: %v", err) + } + + if currentUser.LastAccount != (web_experimentation.AccountWE{}) { + err := config.SetIdentifier(utils.WEB_EXPERIMENTATION, currentUser.LastAccount.Identifier) + if err != nil { + log.Fatalf("error occurred: %s", err) + } + + err = config.SetEmail(utils.WEB_EXPERIMENTATION, currentUser.Email) + if err != nil { + log.Fatalf("error occurred: %s", err) + } + } + if AccountID != "" { err = config.SetAccountID(utils.WEB_EXPERIMENTATION, AccountID) if err != nil { @@ -111,6 +129,23 @@ var loginCmd = &cobra.Command{ log.Fatalf("error occurred: %v", err) } + currentUser, err := common.HTTPGetIdentifierWE() + if err != nil { + log.Fatalf("error occurred: %v", err) + } + + if currentUser.LastAccount != (web_experimentation.AccountWE{}) { + err := config.SetIdentifier(utils.WEB_EXPERIMENTATION, currentUser.LastAccount.Identifier) + if err != nil { + log.Fatalf("error occurred: %s", err) + } + + err = config.SetEmail(utils.WEB_EXPERIMENTATION, currentUser.Email) + if err != nil { + log.Fatalf("error occurred: %s", err) + } + } + if AccountID != "" { err = config.SetAccountID(utils.WEB_EXPERIMENTATION, AccountID) if err != nil { diff --git a/cmd/web-experimentation/web_experimentation.go b/cmd/web-experimentation/web_experimentation.go index ed06971d..867f5062 100644 --- a/cmd/web-experimentation/web_experimentation.go +++ b/cmd/web-experimentation/web_experimentation.go @@ -30,7 +30,7 @@ import ( // WebExperimentationCmd represents the web experimentation command var WebExperimentationCmd = &cobra.Command{ Use: "web-experimentation [auth|account|campaign|global-code|variation]", - Aliases: []string{"web-experimentation", "web-exp", "we"}, + Aliases: []string{"web-experimentation", "web-exp", "we", "web"}, Short: "Manage resources related to the web experimentation product", Long: `Manage resources related to the web experimentation product`, PersistentPreRun: func(cmd *cobra.Command, args []string) { diff --git a/models/token.go b/models/token.go index b3e5c37e..c7b23469 100644 --- a/models/token.go +++ b/models/token.go @@ -1,5 +1,7 @@ package models +import models "github.com/flagship-io/abtasty-cli/models/web_experimentation" + type MfaRequestWE struct { MfaToken string `json:"mfa_token"` MfaMethods []string `json:"mfa_methods"` @@ -37,6 +39,16 @@ type TokenResponse struct { Scope string `json:"scope"` } +type UserMe struct { + Id int `json:"id,omitempty"` + Email string `json:"email"` + FirstName string `json:"firstname"` + LastName string `json:"lastname"` + Societe string `json:"societe"` + IsABTasty bool `json:"is_abtasty"` + LastAccount models.AccountWE `json:"last_account"` +} + type ClientCredentialsRequest struct { GrantType string `json:"grant_type"` Scope string `json:"scope,omitempty"` diff --git a/utils/config/config.go b/utils/config/config.go index 53e93b9a..9579e254 100644 --- a/utils/config/config.go +++ b/utils/config/config.go @@ -158,6 +158,46 @@ func SetAccountID(product, accountID string) error { return nil } +func SetIdentifier(product, identifier string) error { + var v = viper.New() + configFilepath, err := CredentialPath(product, utils.HOME_CLI) + if err != nil { + return err + } + + v.SetConfigFile(configFilepath) + v.MergeInConfig() + + v.Set("identifier", identifier) + + err = v.WriteConfigAs(configFilepath) + if err != nil { + return err + } + + return nil +} + +func SetEmail(product string, email string) error { + var v = viper.New() + configFilepath, err := CredentialPath(product, utils.HOME_CLI) + if err != nil { + return err + } + + v.SetConfigFile(configFilepath) + v.MergeInConfig() + + v.Set("email", email) + + err = v.WriteConfigAs(configFilepath) + if err != nil { + return err + } + + return nil +} + func SetWorkingDir(product, path string) error { var v = viper.New() configFilepath, err := CredentialPath(product, utils.HOME_CLI) diff --git a/utils/const.go b/utils/const.go index 3afc70fe..af178f1c 100644 --- a/utils/const.go +++ b/utils/const.go @@ -66,3 +66,4 @@ func DefaultGlobalCodeWorkingDir() string { const FEATURE_EXPERIMENTATION = "fe" const WEB_EXPERIMENTATION = "we" const HOME_CLI = ".cli" +const HIT_ANALYTICS_URL = "https://events.flagship.io/analytics" diff --git a/utils/http_request/common/request.go b/utils/http_request/common/request.go index 156e3150..0fd2d545 100644 --- a/utils/http_request/common/request.go +++ b/utils/http_request/common/request.go @@ -22,6 +22,7 @@ import ( ) var UserAgent string +var OutputFormat string var c = http.Client{Timeout: time.Duration(10) * time.Second} var counter = false @@ -35,6 +36,8 @@ type ResourceRequest struct { AccountID string `mapstructure:"account_id"` AccountEnvironmentID string `mapstructure:"account_environment_id"` WorkingDir string `mapstructure:"working_dir"` + Identifier string `mapstructure:"identifier"` + Email string `mapstructure:"email"` } func (c *ResourceRequest) Init(cL *RequestConfig) { @@ -68,6 +71,30 @@ type RequestConfig struct { CurrentUsedCredential string `mapstructure:"current_used_credential"` OutputFormat string `mapstructure:"output_format"` WorkingDirectory string `mapstructure:"working_dir"` + Identifier string `mapstructure:"identifier"` + Email string `mapstructure:"email"` +} + +type HitRequest struct { + DS string `json:"ds"` + ClientID string `json:"cid"` + VisitorID string `json:"vid"` + Type string `json:"t"` + CustomVariable CustomVariableRequest `json:"cv"` +} + +type CustomVariableRequest struct { + Version string `json:"version"` + Timestamp string `json:"timestamp"` + StackType string `json:"stack.type"` + UserAgent string `json:"user.agent"` + OutputFormat string `json:"output.format"` + EnvironmentId string `json:"envId"` + AccountId string `json:"accountId"` + HttpMethod string `json:"http.method"` + HttpURL string `json:"http.url"` + ABTastyProduct string `json:"abtasty.product"` + Identifier string `json:"identifier"` } var cred RequestConfig @@ -106,6 +133,10 @@ func regenerateToken(product, configName string) { } func HTTPRequest[T any](method string, url string, body []byte) ([]byte, error) { + if !strings.Contains(cred.Email, "@abtasty.com") && (utils.WEB_EXPERIMENTATION == cred.Product || (utils.FEATURE_EXPERIMENTATION == cred.Product && cred.AccountEnvironmentID != "")) { + sendAnalyticHit(method, url) + } + var bodyIO io.Reader = nil if body != nil { bodyIO = bytes.NewBuffer(body) @@ -261,3 +292,64 @@ func HTTPGetAllPagesWE[T any](resource string) ([]T, error) { } return results, nil } + +func sendAnalyticHit(method string, url string) (int, error) { + var bodyIO io.Reader = nil + var clientID = "" + + if cred.Product == utils.FEATURE_EXPERIMENTATION { + clientID = cred.AccountEnvironmentID + } + + if cred.Product == utils.WEB_EXPERIMENTATION { + clientID = cred.Identifier + } + + var customVariable = CustomVariableRequest{ + Version: "1", + Timestamp: time.Now().UTC().Format("2006-01-02T15:04:05.000Z"), + StackType: "Tools", + UserAgent: UserAgent, + ABTastyProduct: cred.Product, + EnvironmentId: cred.AccountEnvironmentID, + Identifier: cred.Identifier, + AccountId: cred.AccountID, + HttpMethod: method, + HttpURL: url, + OutputFormat: OutputFormat, + } + + var hit = HitRequest{ + DS: "APP", + ClientID: clientID, + VisitorID: cred.AccountID, + Type: "USAGE", + CustomVariable: customVariable, + } + + body, err := json.Marshal(hit) + if err != nil { + log.Fatalf("error occurred: %v", err) + } + + if body != nil { + bodyIO = bytes.NewBuffer(body) + } + + req, err := http.NewRequest(http.MethodPost, utils.HIT_ANALYTICS_URL, bodyIO) + if err != nil { + log.Panicf("error occurred on request creation: %v", err) + } + + req.Header.Add("Accept", `*/*`) + req.Header.Add("Accept-Encoding", `gzip, deflate, br`) + req.Header.Add("Content-Type", "application/json") + + resp, err := c.Do(req) + if err != nil { + return 0, err + } + defer resp.Body.Close() + + return resp.StatusCode, nil +} diff --git a/utils/http_request/common/token.go b/utils/http_request/common/token.go index 23077246..974b50a2 100644 --- a/utils/http_request/common/token.go +++ b/utils/http_request/common/token.go @@ -106,6 +106,22 @@ func HTTPRefreshTokenWE(cred RequestConfig) (models.TokenResponse, error) { return authenticationResponse, err } +func HTTPGetIdentifierWE() (models.UserMe, error) { + var currentUser models.UserMe + + respBody, err := HTTPRequest[models.Token](http.MethodGet, utils.GetWebExperimentationHost()+"/v1"+"/users"+"/me", nil) + if err != nil { + return models.UserMe{}, err + } + + err = json.Unmarshal(respBody, ¤tUser) + if err != nil { + return models.UserMe{}, err + } + + return currentUser, err +} + func InitiateBrowserAuth(username, clientID, clientSecret string) (models.TokenResponse, error) { if clientID == "" || clientSecret == "" { log.Fatal("Error while login, required fields (username, client ID, client secret)") diff --git a/utils/http_request/web_experimentation/account.go b/utils/http_request/web_experimentation/account.go index 2b6eece3..eb20ce92 100644 --- a/utils/http_request/web_experimentation/account.go +++ b/utils/http_request/web_experimentation/account.go @@ -1,7 +1,8 @@ package web_experimentation import ( - models "github.com/flagship-io/abtasty-cli/models/web_experimentation" + "github.com/flagship-io/abtasty-cli/models" + models_ "github.com/flagship-io/abtasty-cli/models/web_experimentation" "github.com/flagship-io/abtasty-cli/utils" "github.com/flagship-io/abtasty-cli/utils/http_request/common" ) @@ -10,6 +11,10 @@ type AccountWERequester struct { *common.ResourceRequest } -func (a *AccountWERequester) HTTPListAccount() ([]models.AccountWE, error) { - return common.HTTPGetAllPagesWE[models.AccountWE](utils.GetWebExperimentationHost() + "/v1/accounts?") +func (a *AccountWERequester) HTTPListAccount() ([]models_.AccountWE, error) { + return common.HTTPGetAllPagesWE[models_.AccountWE](utils.GetWebExperimentationHost() + "/v1/accounts?") +} + +func HTTPUserMe() (models.UserMe, error) { + return common.HTTPGetItem[models.UserMe](utils.GetWebExperimentationHost() + "/v1/users/me") } diff --git a/utils/http_request/web_experimentation/account_test.go b/utils/http_request/web_experimentation/account_test.go index deea3159..fc3fdc02 100644 --- a/utils/http_request/web_experimentation/account_test.go +++ b/utils/http_request/web_experimentation/account_test.go @@ -22,3 +22,18 @@ func TestHTTPListAccount(t *testing.T) { assert.Equal(t, "account_role", respBody[0].Role) } + +func TestHTTPUserMe(t *testing.T) { + + respBody, err := HTTPUserMe() + + assert.NotNil(t, respBody) + assert.Nil(t, err) + + assert.Equal(t, 100000, respBody.Id) + assert.Equal(t, "fake@example.com", respBody.Email) + assert.Equal(t, "john", respBody.FirstName) + assert.Equal(t, "doe", respBody.LastName) + assert.Equal(t, "Example", respBody.Societe) + assert.Equal(t, false, respBody.IsABTasty) +} diff --git a/utils/mock_function/web_experimentation/account.go b/utils/mock_function/web_experimentation/account.go index 3e2288a7..cdf9aaf7 100644 --- a/utils/mock_function/web_experimentation/account.go +++ b/utils/mock_function/web_experimentation/account.go @@ -30,6 +30,15 @@ var TestAccountGlobalCode = models.AccountWE{ GlobalCode: TestGlobalCode, } +var TestUserMe = models_.UserMe{ + Id: 100000, + Email: "fake@example.com", + FirstName: "john", + LastName: "doe", + Societe: "Example", + IsABTasty: false, +} + func APIAccount() { resp := utils.HTTPListResponseWE[models.AccountWE]{ @@ -62,4 +71,12 @@ func APIAccount() { return resp, nil }, ) + + httpmock.RegisterResponder("GET", utils.GetWebExperimentationHost()+"/v1/users/me", + func(req *http.Request) (*http.Response, error) { + resp, _ := httpmock.NewJsonResponse(200, TestUserMe) + return resp, nil + }, + ) + }