Skip to content

Commit

Permalink
Evaluation with FlagTagsOperator to match any or all tags
Browse files Browse the repository at this point in the history
  • Loading branch information
pacoguzman committed Oct 9, 2020
1 parent ae91b50 commit af774ce
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 12 deletions.
18 changes: 18 additions & 0 deletions docs/api_docs/bundle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,14 @@ definitions:
x-omitempty: true
items:
type: string
flagTagsOperator:
description: flagTags. flagTags looks up flags by tag. Either works.
type: string
x-omitempty: true
enum:
- "ANY"
- "ALL"
default: "ANY"
evalResult:
type: object
properties:
Expand Down Expand Up @@ -1403,6 +1411,16 @@ definitions:
type: string
minLength: 1
minItems: 1
flagTagsOperator:
description: >-
determine how flagTags is used to filter flags to be evaluated. OR extends the evaluation to those which
contains at least one of the provided flagTags or AND limit the evaluation to those which contains all the
flagTags.
type: string
enum:
- "ANY"
- "ALL"
default: "ANY"
evaluationBatchResponse:
type: object
required:
Expand Down
24 changes: 24 additions & 0 deletions integration_tests/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,29 @@ step_11_test_tag_batch_evaluation()

}

step_12_test_tag_operator_batch_evaluation()
{
flagr_url=$1:18000/api/v1
sleep 5

shakedown POST $flagr_url/evaluation/batch -H 'Content-Type:application/json' -d '{"entities":[{ "entityType": "externalalert", "entityContext": {"property_1": "value_2"} }],"flagTags": ["value_1", "value_2"], "flagTagsOperator": "ALL", "enableDebug": false }'
status 200
matches "\"flagID\":1"
matches "\"variantKey\":\"key_1\""
matches "\"variantID\":1"

shakedown POST $flagr_url/evaluation/batch -H 'Content-Type:application/json' -d '{"entities":[{ "entityType": "externalalert", "entityContext": {"property_1": "value_2"} }],"flagTags": ["value_1", "value_3"], "flagTagsOperator": "ALL", "enableDebug": false }'
status 200
contains "\"evaluationResults\":null"

shakedown POST $flagr_url/evaluation/batch -H 'Content-Type:application/json' -d '{"entities":[{ "entityType": "externalalert", "entityContext": {"property_1": "value_2"} }],"flagTags": ["value_1", "value_3"], "flagTagsOperator": "ANY", "enableDebug": false }'
status 200
matches "\"flagID\":1"
matches "\"variantKey\":\"key_1\""
matches "\"variantID\":1"

}


start_test()
{
Expand All @@ -387,6 +410,7 @@ start_test()
step_9_test_export $flagr_host
step_10_test_crud_tag $flagr_host
step_11_test_tag_batch_evaluation $flagr_host
step_12_test_tag_operator_batch_evaluation $flagr_host
}

start(){
Expand Down
20 changes: 14 additions & 6 deletions pkg/handler/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,19 @@ func (e *eval) PostEvaluationBatch(params evaluation.PostEvaluationBatchParams)
flagIDs := params.Body.FlagIDs
flagKeys := params.Body.FlagKeys
flagTags := params.Body.FlagTags
flagTagsOperator := params.Body.FlagTagsOperator
results := &models.EvaluationBatchResponse{}

// TODO make it concurrent
for _, entity := range entities {
if len(flagTags) > 0 {
evalContext := models.EvalContext{
EnableDebug: params.Body.EnableDebug,
EntityContext: entity.EntityContext,
EntityID: entity.EntityID,
EntityType: entity.EntityType,
FlagTags: flagTags,
EnableDebug: params.Body.EnableDebug,
EntityContext: entity.EntityContext,
EntityID: entity.EntityID,
EntityType: entity.EntityType,
FlagTags: flagTags,
FlagTagsOperator: flagTagsOperator,
}
evalResults := EvalFlagsByTags(evalContext)
results.EvaluationResults = append(results.EvaluationResults, evalResults...)
Expand Down Expand Up @@ -133,8 +135,14 @@ var LookupFlag = func(evalContext models.EvalContext) *entity.Flag {

var EvalFlagsByTags = func(evalContext models.EvalContext) []*models.EvalResult {
cache := GetEvalCache()
var operator string
if evalContext.FlagTagsOperator == nil {
operator = ""
} else {
operator = *evalContext.FlagTagsOperator
}

fs := cache.GetByTags(evalContext.FlagTags)
fs := cache.GetByTags(evalContext.FlagTags, operator)
results := []*models.EvalResult{}
for _, f := range fs {
results = append(results, EvalFlagWithContext(f, evalContext))
Expand Down
62 changes: 57 additions & 5 deletions pkg/handler/eval_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"sync/atomic"
"time"

"github.com/checkr/flagr/swagger_gen/models"

"github.com/checkr/flagr/pkg/config"
"github.com/checkr/flagr/pkg/entity"
"github.com/checkr/flagr/pkg/util"
Expand Down Expand Up @@ -59,9 +61,29 @@ func (ec *EvalCache) Start() {
}()
}

func (ec *EvalCache) GetByTags(tags []string) []*entity.Flag {
func (ec *EvalCache) GetByTags(tags []string, operator string) []*entity.Flag {
var results map[uint]*entity.Flag

if operator == "" || operator == models.EvaluationBatchRequestFlagTagsOperatorANY {
results = ec.getByTagsANY(tags)
}

if operator == models.EvaluationBatchRequestFlagTagsOperatorALL {
results = ec.getByTagsALL(tags)
}

values := make([]*entity.Flag, 0, len(results))
for _, f := range results {
values = append(values, f)
}

return values
}

func (ec *EvalCache) getByTagsANY(tags []string) map[uint]*entity.Flag {
results := map[uint]*entity.Flag{}
cache := ec.cache.Load().(*cacheContainer)

for _, t := range tags {
fSet, ok := cache.tagCache[t]
if ok {
Expand All @@ -70,13 +92,43 @@ func (ec *EvalCache) GetByTags(tags []string) []*entity.Flag {
}
}
}
return results
}

values := make([]*entity.Flag, 0, len(results))
for _, f := range results {
values = append(values, f)
func (ec *EvalCache) getByTagsALL(tags []string) map[uint]*entity.Flag {
results := map[uint]*entity.Flag{}
cache := ec.cache.Load().(*cacheContainer)

for i, t := range tags {
fSet, ok := cache.tagCache[t]
if !ok {
// clear as no flags
results = map[uint]*entity.Flag{}
continue
}

if i == 0 {
// store all the flags
for fID, f := range fSet {
results[fID] = f
}
} else {
if len(results) > 0 {
// accept only previous flags
missingFIDs := []uint{}
for fID := range results {
if fSet[fID] == nil {
missingFIDs = append(missingFIDs, fID)
}
}
for _, fID := range missingFIDs {
delete(results, fID)
}
}
}
}

return values
return results
}

// GetByFlagKeyOrID gets the flag by Key or ID
Expand Down
16 changes: 15 additions & 1 deletion pkg/handler/eval_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package handler
import (
"testing"

"github.com/checkr/flagr/swagger_gen/models"

"github.com/checkr/flagr/pkg/entity"

"github.com/prashantv/gostub"
Expand Down Expand Up @@ -35,8 +37,20 @@ func TestGetByTags(t *testing.T) {
for i, s := range fixtureFlag.Tags {
tags[i] = s.Value
}
f := ec.GetByTags(tags)
f := ec.GetByTags(tags, models.EvalContextFlagTagsOperatorANY)
assert.Len(t, f, 1)
assert.Equal(t, f[0].ID, fixtureFlag.ID)
assert.Equal(t, f[0].Tags[0].Value, fixtureFlag.Tags[0].Value)

tags = make([]string, len(fixtureFlag.Tags)+1)
for i, s := range fixtureFlag.Tags {
tags[i] = s.Value
}
tags[len(tags)-1] = "tag3"

f = ec.GetByTags(tags, models.EvalContextFlagTagsOperatorANY)
assert.Len(t, f, 1)

f = ec.GetByTags(tags, models.EvalContextFlagTagsOperatorALL)
assert.Len(t, f, 0)
}
53 changes: 53 additions & 0 deletions swagger_gen/models/eval_context.go

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

52 changes: 52 additions & 0 deletions swagger_gen/models/evaluation_batch_request.go

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

Loading

0 comments on commit af774ce

Please sign in to comment.