Skip to content

Commit 273fcd9

Browse files
authored
feat(decide): Adding support for decide options and decision reasons. (#298)
A part of multiple PRs for Decide API: - added DecideOptions. - added DecisionReasons. - Refactored interfaces with additional parameter of reasons and options. - Fixed unit tests throughout the sdk.
1 parent a7b01d7 commit 273fcd9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+706
-380
lines changed

pkg/client/client.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,8 @@ func (o *OptimizelyClient) getFeatureDecision(featureKey, variableKey string, us
657657
}
658658

659659
decisionContext.Variable = variable
660-
featureDecision, err = o.DecisionService.GetFeatureDecision(decisionContext, userContext)
660+
options := decide.OptimizelyDecideOptions{}
661+
featureDecision, err = o.DecisionService.GetFeatureDecision(decisionContext, userContext, options, decide.NewDecisionReasons(options))
661662
if err != nil {
662663
o.logger.Warning(fmt.Sprintf(`Received error while making a decision for feature "%s": %s`, featureKey, err))
663664
return decisionContext, featureDecision, nil
@@ -687,7 +688,8 @@ func (o *OptimizelyClient) getExperimentDecision(experimentKey string, userConte
687688
ProjectConfig: projectConfig,
688689
}
689690

690-
experimentDecision, err = o.DecisionService.GetExperimentDecision(decisionContext, userContext)
691+
options := decide.OptimizelyDecideOptions{}
692+
experimentDecision, err = o.DecisionService.GetExperimentDecision(decisionContext, userContext, options, decide.NewDecisionReasons(options))
691693
if err != nil {
692694
o.logger.Warning(fmt.Sprintf(`Received error while making a decision for experiment "%s": %s`, experimentKey, err))
693695
return decisionContext, experimentDecision, nil

pkg/client/client_test.go

Lines changed: 34 additions & 33 deletions
Large diffs are not rendered by default.

pkg/client/fixtures_test.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019, Optimizely, Inc. and contributors *
2+
* Copyright 2019-2020, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222

2323
"github.com/optimizely/go-sdk/pkg/config"
24+
"github.com/optimizely/go-sdk/pkg/decide"
2425
"github.com/optimizely/go-sdk/pkg/decision"
2526
"github.com/optimizely/go-sdk/pkg/entities"
2627
"github.com/optimizely/go-sdk/pkg/event"
@@ -116,13 +117,13 @@ type MockDecisionService struct {
116117
mock.Mock
117118
}
118119

119-
func (m *MockDecisionService) GetFeatureDecision(decisionContext decision.FeatureDecisionContext, userContext entities.UserContext) (decision.FeatureDecision, error) {
120-
args := m.Called(decisionContext, userContext)
120+
func (m *MockDecisionService) GetFeatureDecision(decisionContext decision.FeatureDecisionContext, userContext entities.UserContext, options decide.OptimizelyDecideOptions, reasons decide.DecisionReasons) (decision.FeatureDecision, error) {
121+
args := m.Called(decisionContext, userContext, options, reasons)
121122
return args.Get(0).(decision.FeatureDecision), args.Error(1)
122123
}
123124

124-
func (m *MockDecisionService) GetExperimentDecision(decisionContext decision.ExperimentDecisionContext, userContext entities.UserContext) (decision.ExperimentDecision, error) {
125-
args := m.Called(decisionContext, userContext)
125+
func (m *MockDecisionService) GetExperimentDecision(decisionContext decision.ExperimentDecisionContext, userContext entities.UserContext, options decide.OptimizelyDecideOptions, reasons decide.DecisionReasons) (decision.ExperimentDecision, error) {
126+
args := m.Called(decisionContext, userContext, options, reasons)
126127
return args.Get(0).(decision.ExperimentDecision), args.Error(1)
127128
}
128129

@@ -159,11 +160,11 @@ func (m *PanickingConfigManager) GetConfig() (config.ProjectConfig, error) {
159160
type PanickingDecisionService struct {
160161
}
161162

162-
func (m *PanickingDecisionService) GetFeatureDecision(decisionContext decision.FeatureDecisionContext, userContext entities.UserContext) (decision.FeatureDecision, error) {
163+
func (m *PanickingDecisionService) GetFeatureDecision(decisionContext decision.FeatureDecisionContext, userContext entities.UserContext, options decide.OptimizelyDecideOptions, reasons decide.DecisionReasons) (decision.FeatureDecision, error) {
163164
panic("I'm panicking")
164165
}
165166

166-
func (m *PanickingDecisionService) GetExperimentDecision(decisionContext decision.ExperimentDecisionContext, userContext entities.UserContext) (decision.ExperimentDecision, error) {
167+
func (m *PanickingDecisionService) GetExperimentDecision(decisionContext decision.ExperimentDecisionContext, userContext entities.UserContext, options decide.OptimizelyDecideOptions, reasons decide.DecisionReasons) (decision.ExperimentDecision, error) {
167168
panic("I'm panicking")
168169
}
169170

pkg/decide/decide_errors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ type decideError string
2424
const (
2525
// SDKNotReady when sdk is not ready
2626
SDKNotReady decideError = "Optimizely SDK not configured properly yet"
27+
// FlagKeyInvalid when invalid flag key is provided
28+
FlagKeyInvalid decideError = `No flag was found for key "%s".`
29+
// VariableValueInvalid when invalid variable value is provided
30+
VariableValueInvalid decideError = `Variable value for key "%s" is invalid or wrong type.`
2731
)
2832

2933
// GetError returns error for decide error type

pkg/decide/decision_reasons.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/****************************************************************************
2+
* Copyright 2020, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
17+
// Package decide //
18+
package decide
19+
20+
// DecisionReasons defines the reasons for which the decision was made.
21+
type DecisionReasons interface {
22+
AddError(format string, arguments ...interface{})
23+
AddInfo(format string, arguments ...interface{}) string
24+
ToReport() []string
25+
}

pkg/decide/decision_reasons_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/****************************************************************************
2+
* Copyright 2020, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
17+
package decide
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
)
24+
25+
func TestNewDecisionReasonsWithEmptyOptions(t *testing.T) {
26+
reasons := NewDecisionReasons(OptimizelyDecideOptions{})
27+
assert.Equal(t, 0, len(reasons.ToReport()))
28+
}
29+
30+
func TestAddErrorWorksWithEveryOption(t *testing.T) {
31+
options := OptimizelyDecideOptions{
32+
DisableDecisionEvent: true,
33+
EnabledFlagsOnly: true,
34+
IgnoreUserProfileService: true,
35+
ExcludeVariables: true,
36+
IncludeReasons: true,
37+
}
38+
reasons := NewDecisionReasons(options)
39+
reasons.AddError("error message")
40+
41+
reportedReasons := reasons.ToReport()
42+
assert.Equal(t, 1, len(reportedReasons))
43+
assert.Equal(t, "error message", reportedReasons[0])
44+
}
45+
46+
func TestInfoLogsAreOnlyReportedWhenIncludeReasonsOptionIsSet(t *testing.T) {
47+
options := OptimizelyDecideOptions{
48+
DisableDecisionEvent: true,
49+
EnabledFlagsOnly: true,
50+
IgnoreUserProfileService: true,
51+
ExcludeVariables: true,
52+
}
53+
reasons := NewDecisionReasons(options)
54+
reasons.AddError("error message")
55+
reasons.AddError("error message: code %d", 121)
56+
reasons.AddInfo("info message")
57+
reasons.AddInfo("info message: %s", "unexpected string")
58+
59+
reportedReasons := reasons.ToReport()
60+
assert.Equal(t, 2, len(reportedReasons))
61+
assert.Equal(t, "error message", reportedReasons[0])
62+
assert.Equal(t, "error message: code 121", reportedReasons[1])
63+
64+
options.IncludeReasons = true
65+
reasons = NewDecisionReasons(options)
66+
reasons.AddError("error message")
67+
reasons.AddError("error message: code %d", 121)
68+
reasons.AddInfo("info message")
69+
reasons.AddInfo("info message: %s", "unexpected string")
70+
71+
reportedReasons = reasons.ToReport()
72+
assert.Equal(t, 4, len(reportedReasons))
73+
assert.Equal(t, "error message", reportedReasons[0])
74+
assert.Equal(t, "error message: code 121", reportedReasons[1])
75+
assert.Equal(t, "info message", reportedReasons[2])
76+
assert.Equal(t, "info message: unexpected string", reportedReasons[3])
77+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/****************************************************************************
2+
* Copyright 2020, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
17+
// Package decide //
18+
package decide
19+
20+
import (
21+
"fmt"
22+
)
23+
24+
// DefaultDecisionReasons provides the default implementation of DecisionReasons.
25+
type DefaultDecisionReasons struct {
26+
errors, logs []string
27+
includeReasons bool
28+
}
29+
30+
// NewDecisionReasons returns a new instance of DecisionReasons.
31+
func NewDecisionReasons(options OptimizelyDecideOptions) *DefaultDecisionReasons {
32+
return &DefaultDecisionReasons{
33+
errors: []string{},
34+
logs: []string{},
35+
includeReasons: options.IncludeReasons,
36+
}
37+
}
38+
39+
// AddError appends given message to the error list.
40+
func (o *DefaultDecisionReasons) AddError(format string, arguments ...interface{}) {
41+
o.errors = append(o.errors, fmt.Sprintf(format, arguments...))
42+
}
43+
44+
// AddInfo appends given info message to the info list after formatting.
45+
func (o *DefaultDecisionReasons) AddInfo(format string, arguments ...interface{}) string {
46+
message := fmt.Sprintf(format, arguments...)
47+
if !o.includeReasons {
48+
return message
49+
}
50+
o.logs = append(o.logs, message)
51+
return message
52+
}
53+
54+
// ToReport returns reasons to be reported.
55+
func (o *DefaultDecisionReasons) ToReport() []string {
56+
reasons := o.errors
57+
if !o.includeReasons {
58+
return reasons
59+
}
60+
return append(reasons, o.logs...)
61+
}

pkg/decision/composite_experiment_service.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019, Optimizely, Inc. and contributors *
2+
* Copyright 2019-2020, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -19,6 +19,8 @@ package decision
1919

2020
import (
2121
"fmt"
22+
23+
"github.com/optimizely/go-sdk/pkg/decide"
2224
"github.com/optimizely/go-sdk/pkg/entities"
2325
"github.com/optimizely/go-sdk/pkg/logging"
2426
)
@@ -54,7 +56,7 @@ func NewCompositeExperimentService(sdkKey string, options ...CESOptionFunc) *Com
5456
// 1. Overrides (if supplied)
5557
// 2. Whitelist
5658
// 3. Bucketing (with User profile integration if supplied)
57-
compositeExperimentService := &CompositeExperimentService{logger:logging.GetLogger(sdkKey, "CompositeExperimentService")}
59+
compositeExperimentService := &CompositeExperimentService{logger: logging.GetLogger(sdkKey, "CompositeExperimentService")}
5860
for _, opt := range options {
5961
opt(compositeExperimentService)
6062
}
@@ -81,11 +83,10 @@ func NewCompositeExperimentService(sdkKey string, options ...CESOptionFunc) *Com
8183
}
8284

8385
// GetDecision returns a decision for the given experiment and user context
84-
func (s CompositeExperimentService) GetDecision(decisionContext ExperimentDecisionContext, userContext entities.UserContext) (decision ExperimentDecision, err error) {
85-
86+
func (s CompositeExperimentService) GetDecision(decisionContext ExperimentDecisionContext, userContext entities.UserContext, options decide.OptimizelyDecideOptions, reasons decide.DecisionReasons) (decision ExperimentDecision, err error) {
8687
// Run through the various decision services until we get a decision
8788
for _, experimentService := range s.experimentServices {
88-
decision, err = experimentService.GetDecision(decisionContext, userContext)
89+
decision, err = experimentService.GetDecision(decisionContext, userContext, options, reasons)
8990
if err != nil {
9091
s.logger.Debug(fmt.Sprintf("%v", err))
9192
}

0 commit comments

Comments
 (0)