Skip to content

Commit a0c89bf

Browse files
authored
feat: get_optimizely_config API to get static experiments and features data (#220)
## Summary Added a `get_optimizely_config` API which returns static experiments and features data. ## Test plan 1. Manually tested thoroughly 2. Added Unit tests 3. Full Stack Integration tests passed
1 parent 80e8618 commit a0c89bf

File tree

4 files changed

+261
-0
lines changed

4 files changed

+261
-0
lines changed

.rubocop_todo.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Naming/AccessorMethodName:
3434
Exclude:
3535
- 'lib/optimizely/config_manager/http_project_config_manager.rb'
3636
- 'spec/optimizely_factory_spec.rb'
37+
- 'lib/optimizely.rb'
3738

3839
# Offense count: 1
3940
# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist, MethodDefinitionMacros.

lib/optimizely.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
require_relative 'optimizely/helpers/variable_type'
3434
require_relative 'optimizely/logger'
3535
require_relative 'optimizely/notification_center'
36+
require_relative 'optimizely/optimizely_config'
3637

3738
module Optimizely
3839
class Project
@@ -523,6 +524,52 @@ def close
523524
@event_processor.stop! if @event_processor.respond_to?(:stop!)
524525
end
525526

527+
def get_optimizely_config
528+
# Get OptimizelyConfig object containing experiments and features data
529+
# Returns Object
530+
#
531+
# OptimizelyConfig Object Schema
532+
# {
533+
# 'experimentsMap' => {
534+
# 'my-fist-experiment' => {
535+
# 'id' => '111111',
536+
# 'key' => 'my-fist-experiment'
537+
# 'variationsMap' => {
538+
# 'variation_1' => {
539+
# 'id' => '121212',
540+
# 'key' => 'variation_1',
541+
# 'variablesMap' => {
542+
# 'age' => {
543+
# 'id' => '222222',
544+
# 'key' => 'age',
545+
# 'type' => 'integer',
546+
# 'value' => '0',
547+
# }
548+
# }
549+
# }
550+
# }
551+
# }
552+
# },
553+
# 'featuresMap' => {
554+
# 'awesome-feature' => {
555+
# 'id' => '333333',
556+
# 'key' => 'awesome-feature',
557+
# 'experimentsMap' => Object,
558+
# 'variablesMap' => Object,
559+
# }
560+
# },
561+
# 'revision' => '13',
562+
# }
563+
#
564+
unless is_valid
565+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_optimizely_config').message)
566+
return nil
567+
end
568+
569+
optimizely_config = OptimizelyConfig.new(project_config)
570+
optimizely_config.config
571+
end
572+
526573
private
527574

528575
def get_variation_with_config(experiment_key, user_id, attributes, config)

lib/optimizely/optimizely_config.rb

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright 2019, Optimizely and contributors
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
module Optimizely
19+
class OptimizelyConfig
20+
def initialize(project_config)
21+
@project_config = project_config
22+
end
23+
24+
def config
25+
experiments_map_object = experiments_map
26+
features_map = get_features_map(experiments_map_object)
27+
{
28+
'experimentsMap' => experiments_map_object,
29+
'featuresMap' => features_map,
30+
'revision' => @project_config.revision
31+
}
32+
end
33+
34+
private
35+
36+
def experiments_map
37+
feature_variables_map = @project_config.feature_flags.reduce({}) do |result_map, feature|
38+
result_map.update(feature['id'] => feature['variables'])
39+
end
40+
@project_config.experiments.reduce({}) do |experiments_map, experiment|
41+
experiments_map.update(
42+
experiment['key'] => {
43+
'id' => experiment['id'],
44+
'key' => experiment['key'],
45+
'variationsMap' => experiment['variations'].reduce({}) do |variations_map, variation|
46+
variation_object = {
47+
'id' => variation['id'],
48+
'key' => variation['key'],
49+
'variablesMap' => get_merged_variables_map(variation, experiment['id'], feature_variables_map)
50+
}
51+
variation_object['featureEnabled'] = variation['featureEnabled'] if @project_config.feature_experiment?(experiment['id'])
52+
variations_map.update(variation['key'] => variation_object)
53+
end
54+
}
55+
)
56+
end
57+
end
58+
59+
# Merges feature key and type from feature variables to variation variables.
60+
def get_merged_variables_map(variation, experiment_id, feature_variables_map)
61+
feature_ids = @project_config.experiment_feature_map[experiment_id]
62+
return {} unless feature_ids
63+
64+
experiment_feature_variables = feature_variables_map[feature_ids[0]]
65+
# temporary variation variables map to get values to merge.
66+
temp_variables_id_map = {}
67+
if variation['variables']
68+
temp_variables_id_map = variation['variables'].reduce({}) do |variables_map, variable|
69+
variables_map.update(
70+
variable['id'] => {
71+
'id' => variable['id'],
72+
'value' => variable['value']
73+
}
74+
)
75+
end
76+
end
77+
experiment_feature_variables.reduce({}) do |variables_map, feature_variable|
78+
variation_variable = temp_variables_id_map[feature_variable['id']]
79+
variable_value = variation['featureEnabled'] && variation_variable ? variation_variable['value'] : feature_variable['defaultValue']
80+
variables_map.update(
81+
feature_variable['key'] => {
82+
'id' => feature_variable['id'],
83+
'key' => feature_variable['key'],
84+
'type' => feature_variable['type'],
85+
'value' => variable_value
86+
}
87+
)
88+
end
89+
end
90+
91+
def get_features_map(all_experiments_map)
92+
@project_config.feature_flags.reduce({}) do |features_map, feature|
93+
features_map.update(
94+
feature['key'] => {
95+
'id' => feature['id'],
96+
'key' => feature['key'],
97+
'experimentsMap' => feature['experimentIds'].reduce({}) do |experiments_map, experiment_id|
98+
experiment_key = @project_config.experiment_id_map[experiment_id]['key']
99+
experiments_map.update(experiment_key => all_experiments_map[experiment_key])
100+
end,
101+
'variablesMap' => feature['variables'].reduce({}) do |variables, variable|
102+
variables.update(
103+
variable['key'] => {
104+
'id' => variable['id'],
105+
'key' => variable['key'],
106+
'type' => variable['type'],
107+
'value' => variable['defaultValue']
108+
}
109+
)
110+
end
111+
}
112+
)
113+
end
114+
end
115+
end
116+
end

spec/optimizely_config_spec.rb

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# frozen_string_literal: true
2+
3+
#
4+
# Copyright 2019, Optimizely and contributors
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
require 'spec_helper'
20+
21+
describe Optimizely::OptimizelyConfig do
22+
let(:config_body_JSON) { OptimizelySpec::VALID_CONFIG_BODY_JSON }
23+
let(:config_typed_audience_JSON) { JSON.dump(OptimizelySpec::CONFIG_DICT_WITH_TYPED_AUDIENCES) }
24+
let(:error_handler) { Optimizely::NoOpErrorHandler.new }
25+
let(:spy_logger) { spy('logger') }
26+
let(:project_config) { Optimizely::DatafileProjectConfig.new(config_body_JSON, spy_logger, error_handler) }
27+
let(:project_instance) { Optimizely::Project.new(config_body_JSON, nil, spy_logger, error_handler) }
28+
let(:optimizely_config) { project_instance.get_optimizely_config }
29+
30+
it 'should return all experiments' do
31+
experiments_map = optimizely_config['experimentsMap']
32+
expect(experiments_map.length).to eq(11)
33+
project_config.experiments.each do |experiment|
34+
expect(experiments_map[experiment['key']]).to include(
35+
'id' => experiment['id'],
36+
'key' => experiment['key']
37+
)
38+
variations_map = experiments_map[experiment['key']]['variationsMap']
39+
experiment['variations'].each do |variation|
40+
expect(variations_map[variation['key']]).to include(
41+
'id' => variation['id'],
42+
'key' => variation['key']
43+
)
44+
end
45+
end
46+
end
47+
48+
it 'should return all feature flags' do
49+
features_map = optimizely_config['featuresMap']
50+
expect(features_map.length).to eq(8)
51+
project_config.feature_flags.each do |feature_flag|
52+
expect(features_map[feature_flag['key']]).to include(
53+
'id' => feature_flag['id'],
54+
'key' => feature_flag['key']
55+
)
56+
experiments_map = features_map[feature_flag['key']]['experimentsMap']
57+
feature_flag['experimentIds'].each do |experiment_id|
58+
experiment_key = project_config.get_experiment_key(experiment_id)
59+
expect(experiments_map[experiment_key]).to be_truthy
60+
end
61+
variables_map = features_map[feature_flag['key']]['variablesMap']
62+
feature_flag['variables'].each do |variable|
63+
expect(variables_map[variable['key']]).to include(
64+
'id' => variable['id'],
65+
'key' => variable['key'],
66+
'type' => variable['type'],
67+
'value' => variable['defaultValue']
68+
)
69+
end
70+
end
71+
end
72+
73+
it 'should correctly merge all feature variables' do
74+
project_config.feature_flags.each do |feature_flag|
75+
feature_flag['experimentIds'].each do |experiment_id|
76+
experiment = project_config.experiment_id_map[experiment_id]
77+
variations = experiment['variations']
78+
variations_map = optimizely_config['experimentsMap'][experiment['key']]['variationsMap']
79+
variations.each do |variation|
80+
feature_flag['variables'].each do |variable|
81+
variable_to_assert = variations_map[variation['key']]['variablesMap'][variable['key']]
82+
expect(variable).to include(
83+
'id' => variable_to_assert['id'],
84+
'key' => variable_to_assert['key'],
85+
'type' => variable_to_assert['type']
86+
)
87+
expect(variable['defaultValue']).to eq(variable_to_assert['value']) unless variation['featureEnabled']
88+
end
89+
end
90+
end
91+
end
92+
end
93+
94+
it 'should return correct config revision' do
95+
expect(project_config.revision).to eq(optimizely_config['revision'])
96+
end
97+
end

0 commit comments

Comments
 (0)