diff --git a/labs/optimizely-agent-client-dart/.gitignore b/labs/optimizely-agent-client-dart/.gitignore new file mode 100644 index 0000000..b0ef3b0 --- /dev/null +++ b/labs/optimizely-agent-client-dart/.gitignore @@ -0,0 +1,4 @@ +.packages +pubspec.lock +.dart_tool/* +launch.json \ No newline at end of file diff --git a/labs/optimizely-agent-client-dart/README.md b/labs/optimizely-agent-client-dart/README.md new file mode 100644 index 0000000..8b9037f --- /dev/null +++ b/labs/optimizely-agent-client-dart/README.md @@ -0,0 +1,114 @@ +# Dart / FlutterClient for Optimizely Agent +This is a dart / flutter client to facilitate communication with Optimizely Agent. + +## Initialization +``` +OptimizelyAgent(String sdkKey, String url, UserContext userContext) +``` +The client can be initialized by providing `sdkKey`, url where `agent` is deployed and `userContext` which contains `userId` and `attributes`. The client memoizes the user information and reuses it for subsequent calls. + +#### Example +``` +OptimizelyAgent agent = new OptimizelyAgent('{sdkKey}', 'http://localhost:8080', UserContext('user1', {'group': 'premium'})); +``` + +## Decision Caching +By default, the client makes a new http call to the agent for every decision. Optionally, to avoid latency, all decisions can be loaded and cached for a userContext. + +``` +loadAndCacheDecisions([UserContext overrideUserContext]) → Future +``` + +When no arguments are provided, it will load decisions for the memoized user. An optional`overrideUserContext` can be provided to load and cache decisions for a different user. + +#### +``` +await agent.loadAndCacheDecisions(); +``` + +## Decide + +``` +decide( + String key, + [List optimizelyDecideOptions = const [], + UserContext overrideUserContext +]) → Future +``` + +`decide` takes flag Key as a required parameter and evaluates the decision for the memoized user. It can also optionally take decide options or override User. `decide` returns a cached decision if available otherwise it makes an API call to the agent. + +## Decide All + +``` +decideAll( + [List optimizelyDecideOptions = const [], + UserContext overrideUserContext +]) → Future> +``` + +`decideAll` evaluates all the decisions for the memoized user. It can also optionally take decide options or override User. `decideAll` does not make use of the cache and always makes a new API call to agent. + +## Activate (Legacy) +``` +activate({ + List featureKey, + List experimentKey, + bool disableTracking, + DecisionType type, + bool enabled, + UserContext overrideUserContext +}) → Future> +``` + +Activate is a Legacy API and should only be used with legacy experiments. I uses memoized user and takes a combination of optional arguments and returns a list of decisions. Activate does not leverage decision caching. + +#### Example +``` +List optimizelyDecisions = await agent.activate(type: DecisionType.experiment, enabled: true); +if (optimizelyDecisions != null) { + print('Total Decisions ${optimizelyDecisions.length}'); + optimizelyDecisions.forEach((OptimizelyDecisionLegacy decision) { + print(decision.variationKey); + }); +} +``` + +## Track +``` +track({ + @required String eventKey, + Map eventTags, + UserContext overrideUserContext +}) → Future +``` + +Track takes `eventKey` as a required argument and a combination of optional arguments and sends an event. + +#### Example +``` +await agent.track(eventKey: 'button1_click'); +``` + +## Optimizely Config +``` +getOptimizelyConfig() → Future +``` + +Returns `OptimizelyConfig` object which contains revision, a map of experiments and a map of features. + +#### Example +``` +OptimizelyConfig config = await agent.getOptimizelyConfig(); +if (config != null) { + print('Revision ${config.revision}'); + config.experimentsMap.forEach((String key, OptimizelyExperiment experiment) { + print('Experiment Key: $key'); + print('Experiment Id: ${experiment.id}'); + experiment.variationsMap.forEach((String key, OptimizelyVariation variation) { + print(' Variation Key: $key'); + print(' Variation Id: ${variation.id}'); + }); + }); +} +``` diff --git a/labs/optimizely-agent-client-dart/example/example.dart b/labs/optimizely-agent-client-dart/example/example.dart new file mode 100644 index 0000000..dc25cb8 --- /dev/null +++ b/labs/optimizely-agent-client-dart/example/example.dart @@ -0,0 +1,58 @@ +import 'package:optimizely_agent_client/optimizely_agent.dart'; + +void main() async { + OptimizelyAgent agent = new OptimizelyAgent('{SDK_KEY}', '{AGENT_URL}', UserContext('{USER_ID}')); + + await agent.loadAndCacheDecisions(); + + print('---- Calling DecideAll API ----'); + var decisions = await agent.decideAll( + [ + OptimizelyDecideOption.DISABLE_DECISION_EVENT, + OptimizelyDecideOption.INCLUDE_REASONS + ], + ); + decisions?.forEach((decision) { + print(decision.toJson()); + }); + print(''); + + var decision = await agent.decide('{FLAG_KEY}', [ + OptimizelyDecideOption.DISABLE_DECISION_EVENT, + OptimizelyDecideOption.INCLUDE_REASONS + ]); + print(decision.toJson()); + + print('---- Calling OptimizelyConfig API ----'); + OptimizelyConfig config = await agent.getOptimizelyConfig(); + if (config != null) { + print('Revision ${config.revision}'); + config.experimentsMap.forEach((String key, OptimizelyExperiment experiment) { + print('Experiment Key: $key'); + print('Experiment Id: ${experiment.id}'); + experiment.variationsMap.forEach((String key, OptimizelyVariation variation) { + print(' Variation Key: $key'); + print(' Variation Id: ${variation.id}'); + }); + }); + + config.featuresMap.forEach((String key, OptimizelyFeature feature) { + print('Feature Key: $key'); + }); + } + print(''); + + print('---- Calling Activate API ----'); + List optimizelyDecisionsLegacy = await agent.activate(type: DecisionType.experiment, enabled: true); + if (optimizelyDecisionsLegacy != null) { + print('Total Decisions ${optimizelyDecisionsLegacy.length}'); + optimizelyDecisionsLegacy.forEach((OptimizelyDecisionLegacy decision) { + print(decision.toJson()); + }); + } + print(''); + + print('---- Calling Track API ----'); + await agent.track(eventKey: '{EVENT_NAME}'); + print('Done!'); +} diff --git a/labs/optimizely-agent-client-dart/lib/optimizely_agent.dart b/labs/optimizely-agent-client-dart/lib/optimizely_agent.dart new file mode 100644 index 0000000..3622dee --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/optimizely_agent.dart @@ -0,0 +1,189 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +import 'package:meta/meta.dart'; +import 'package:dio/dio.dart'; + +import './src/models/optimizely_decision_legacy.dart'; +import './src/models/decision_types.dart'; +import './src/models/optimizely_decide_option.dart'; +import './src/models/optimizely_decision.dart'; +import './src/models/user_context.dart'; +import './src/models/optimizely_config/optimizely_config.dart'; +import './src/request_manager.dart'; +import './src/decision_cache.dart'; + +// Exporting all the required classes +export './src/models/optimizely_decision.dart'; +export './src/models/decision_types.dart'; +export './src/models/optimizely_decision_legacy.dart'; +export './src/models/optimizely_decide_option.dart'; +export './src/models/user_context.dart'; + +// Exporting all OptimizelyConfig entities +export './src/models/optimizely_config/optimizely_config.dart'; +export './src/models/optimizely_config/optimizely_experiment.dart'; +export './src/models/optimizely_config/optimizely_feature.dart'; +export './src/models/optimizely_config/optimizely_variable.dart'; +export './src/models/optimizely_config/optimizely_variation.dart'; + +class OptimizelyAgent { + RequestManager _requestmanager; + UserContext userContext; + DecisionCache decisionCache = new DecisionCache(); + + OptimizelyAgent(String sdkKey, String url, UserContext userContext) { + _requestmanager = RequestManager(sdkKey, url); + this.userContext = userContext; + } + + /// Returns status code and OptimizelyConfig object + Future getOptimizelyConfig() async { + Response resp = await _requestmanager.getOptimizelyConfig(); + return resp.statusCode == 200 ? OptimizelyConfig.fromJson(resp.data) : null; + } + + /// Tracks an event and returns nothing. + Future track({ + @required String eventKey, + Map eventTags, + UserContext overrideUserContext + }) { + UserContext resolvedUserContext = userContext; + if (overrideUserContext != null) { + resolvedUserContext = overrideUserContext; + } + if (!isUserContextValid(resolvedUserContext)) { + print('Invalid User Context, Failing `track`'); + return null; + } + return _requestmanager.track( + eventKey: eventKey, + userId: resolvedUserContext.userId, + eventTags: eventTags, + userAttributes: resolvedUserContext.attributes + ); + } + + /// Activate makes feature and experiment decisions for the selected query parameters + /// and returns list of OptimizelyDecision + Future> activate({ + List featureKey, + List experimentKey, + bool disableTracking, + DecisionType type, + bool enabled, + UserContext overrideUserContext + }) async { + UserContext resolvedUserContext = userContext; + if (overrideUserContext != null) { + resolvedUserContext = overrideUserContext; + } + if (!isUserContextValid(resolvedUserContext)) { + print('Invalid User Context, Failing `activate`'); + return null; + } + Response resp = await _requestmanager.activate( + userId: resolvedUserContext.userId, + userAttributes: resolvedUserContext.attributes, + featureKey: featureKey, + experimentKey: experimentKey, + disableTracking: disableTracking, + type: type, + enabled: enabled + ); + if (resp.statusCode == 200) { + List optimizelyDecisions = []; + resp.data.forEach((element) { + optimizelyDecisions.add(OptimizelyDecisionLegacy.fromJson(element)); + }); + return optimizelyDecisions; + } + return null; + } + + Future decide( + String key, + [ + List optimizelyDecideOptions = const [], + UserContext overrideUserContext + ] + ) async { + UserContext resolvedUserContext = userContext; + if (overrideUserContext != null) { + resolvedUserContext = overrideUserContext; + } + if (!isUserContextValid(resolvedUserContext)) { + print('Invalid User Context, Failing `decide`'); + return null; + } + OptimizelyDecision cachedDecision = decisionCache.getDecision(resolvedUserContext, key); + if (cachedDecision != null) { + print('--- Cache Hit!!! Returning Cached decision ---'); + return cachedDecision; + } else { + print('--- Cache Miss!!! Making a call to agent ---'); + } + + Response resp = await _requestmanager.decide(userContext: resolvedUserContext, key: key, optimizelyDecideOptions: optimizelyDecideOptions); + if (resp.statusCode == 200) { + return OptimizelyDecision.fromJson(resp.data); + } + return null; + } + + Future> decideAll( + [ + List optimizelyDecideOptions = const [], + UserContext overrideUserContext + ] + ) async { + UserContext resolvedUserContext = userContext; + if (overrideUserContext != null) { + resolvedUserContext = overrideUserContext; + } + if (!isUserContextValid(resolvedUserContext)) { + print('Invalid User Context, Failing `decideAll`'); + return null; + } + Response resp = await _requestmanager.decide(userContext: resolvedUserContext, optimizelyDecideOptions: optimizelyDecideOptions); + if (resp.statusCode == 200) { + List optimizelyDecisions = []; + resp.data.forEach((element) { + optimizelyDecisions.add(OptimizelyDecision.fromJson(element)); + }); + return optimizelyDecisions; + } + return null; + } + + isUserContextValid(UserContext userContext) => userContext?.userId != null && userContext?.userId != ''; + + Future loadAndCacheDecisions([UserContext overrideUserContext]) async { + UserContext resolvedUserContext = userContext; + if (overrideUserContext != null) { + resolvedUserContext = overrideUserContext; + } + if (!isUserContextValid(resolvedUserContext)) { + print('Invalid User Context, Failing `loadAndCacheDecisions`'); + return null; + } + List decisions = await decideAll([OptimizelyDecideOption.DISABLE_DECISION_EVENT], resolvedUserContext); + decisions.forEach((decision) => decisionCache.addDecision(resolvedUserContext, decision.flagKey, decision)); + } + + resetCache() => decisionCache.reset(); +} diff --git a/labs/optimizely-agent-client-dart/lib/src/decision_cache.dart b/labs/optimizely-agent-client-dart/lib/src/decision_cache.dart new file mode 100644 index 0000000..11872f4 --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/decision_cache.dart @@ -0,0 +1,41 @@ +/**************************************************************************** + * Copyright 2021, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +import './models/optimizely_decision.dart'; +import './models/user_context.dart'; + +class DecisionCache { + Map> cache = {}; + + void addDecision(UserContext userContext, String flagKey, OptimizelyDecision decision) { + String userId = userContext.userId; + if (cache.containsKey(userId)) { + cache[userId][flagKey] = decision; + } else { + cache[userId] = { flagKey: decision}; + } + } + + OptimizelyDecision getDecision(UserContext userContext, String flagKey) { + String userId = userContext.userId; + if (cache[userId] != null && cache[userId][flagKey] != null) { + return cache[userId][flagKey]; + } + return null; + } + + void reset() => cache = {}; +} diff --git a/labs/optimizely-agent-client-dart/lib/src/models/decision_types.dart b/labs/optimizely-agent-client-dart/lib/src/models/decision_types.dart new file mode 100644 index 0000000..c54fb94 --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/models/decision_types.dart @@ -0,0 +1,17 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +enum DecisionType { feature, experiment } diff --git a/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_config.dart b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_config.dart new file mode 100644 index 0000000..76b5883 --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_config.dart @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +import './optimizely_experiment.dart'; +import './optimizely_feature.dart'; + +class OptimizelyConfig { + String revision; + Map experimentsMap; + Map featuresMap; + + OptimizelyConfig(this.revision, this.experimentsMap, this.featuresMap); + + factory OptimizelyConfig.fromJson(Map json) => + _$OptimizelyConfigFromJson(json); + + Map toJson() => _$OptimizelyConfigToJson(this); +} + +OptimizelyConfig _$OptimizelyConfigFromJson(Map json) { + return OptimizelyConfig( + json['revision'] as String, + (json['experimentsMap'] as Map)?.map( + (k, e) => MapEntry( + k, + e == null + ? null + : OptimizelyExperiment.fromJson(e as Map)), + ), + (json['featuresMap'] as Map)?.map( + (k, e) => MapEntry( + k, + e == null + ? null + : OptimizelyFeature.fromJson(e as Map)), + ), + ); +} + +Map _$OptimizelyConfigToJson(OptimizelyConfig instance) => + { + 'revision': instance.revision, + 'experimentsMap': instance.experimentsMap, + 'featuresMap': instance.featuresMap, + }; diff --git a/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_experiment.dart b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_experiment.dart new file mode 100644 index 0000000..7499390 --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_experiment.dart @@ -0,0 +1,52 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +import './optimizely_variation.dart'; + +class OptimizelyExperiment { + String id; + String key; + Map variationsMap; + + OptimizelyExperiment(this.id, this.key, this.variationsMap); + + factory OptimizelyExperiment.fromJson(Map json) => + _$OptimizelyExperimentFromJson(json); + + Map toJson() => _$OptimizelyExperimentToJson(this); +} + +OptimizelyExperiment _$OptimizelyExperimentFromJson(Map json) { + return OptimizelyExperiment( + json['id'] as String, + json['key'] as String, + (json['variationsMap'] as Map)?.map( + (k, e) => MapEntry( + k, + e == null + ? null + : OptimizelyVariation.fromJson(e as Map)), + ), + ); +} + +Map _$OptimizelyExperimentToJson( + OptimizelyExperiment instance) => + { + 'id': instance.id, + 'key': instance.key, + 'variationsMap': instance.variationsMap, + }; diff --git a/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_feature.dart b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_feature.dart new file mode 100644 index 0000000..1bb536b --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_feature.dart @@ -0,0 +1,61 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +import './optimizely_experiment.dart'; +import './optimizely_variable.dart'; + +class OptimizelyFeature { + String id; + String key; + Map experimentsMap; + Map variablesMap; + + OptimizelyFeature(this.id, this.key, this.experimentsMap, this.variablesMap); + + factory OptimizelyFeature.fromJson(Map json) => + _$OptimizelyFeatureFromJson(json); + + Map toJson() => _$OptimizelyFeatureToJson(this); +} + +OptimizelyFeature _$OptimizelyFeatureFromJson(Map json) { + return OptimizelyFeature( + json['id'] as String, + json['key'] as String, + (json['experimentsMap'] as Map)?.map( + (k, e) => MapEntry( + k, + e == null + ? null + : OptimizelyExperiment.fromJson(e as Map)), + ), + (json['variablesMap'] as Map)?.map( + (k, e) => MapEntry( + k, + e == null + ? null + : OptimizelyVariable.fromJson(e as Map)), + ), + ); +} + +Map _$OptimizelyFeatureToJson(OptimizelyFeature instance) => + { + 'id': instance.id, + 'key': instance.key, + 'experimentsMap': instance.experimentsMap, + 'variablesMap': instance.variablesMap, + }; diff --git a/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_variable.dart b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_variable.dart new file mode 100644 index 0000000..e1b9496 --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_variable.dart @@ -0,0 +1,46 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +class OptimizelyVariable { + String id; + String key; + String type; + String value; + + OptimizelyVariable(this.id, this.key, this.type, this.value); + + factory OptimizelyVariable.fromJson(Map json) => + _$OptimizelyVariableFromJson(json); + + Map toJson() => _$OptimizelyVariableToJson(this); +} + +OptimizelyVariable _$OptimizelyVariableFromJson(Map json) { + return OptimizelyVariable( + json['id'] as String, + json['key'] as String, + json['type'] as String, + json['value'] as String, + ); +} + +Map _$OptimizelyVariableToJson(OptimizelyVariable instance) => + { + 'id': instance.id, + 'key': instance.key, + 'type': instance.type, + 'value': instance.value, + }; diff --git a/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_variation.dart b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_variation.dart new file mode 100644 index 0000000..09e2fa5 --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_config/optimizely_variation.dart @@ -0,0 +1,56 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +import './optimizely_variable.dart'; + +class OptimizelyVariation { + String id; + String key; + bool featureEnabled; + Map variablesMap; + + OptimizelyVariation( + this.id, this.key, this.featureEnabled, this.variablesMap); + + factory OptimizelyVariation.fromJson(Map json) => + _$OptimizelyVariationFromJson(json); + + Map toJson() => _$OptimizelyVariationToJson(this); +} + +OptimizelyVariation _$OptimizelyVariationFromJson(Map json) { + return OptimizelyVariation( + json['id'] as String, + json['key'] as String, + json['featureEnabled'] as bool, + (json['variablesMap'] as Map)?.map( + (k, e) => MapEntry( + k, + e == null + ? null + : OptimizelyVariable.fromJson(e as Map)), + ), + ); +} + +Map _$OptimizelyVariationToJson( + OptimizelyVariation instance) => + { + 'id': instance.id, + 'key': instance.key, + 'featureEnabled': instance.featureEnabled, + 'variablesMap': instance.variablesMap, + }; diff --git a/labs/optimizely-agent-client-dart/lib/src/models/optimizely_decide_option.dart b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_decide_option.dart new file mode 100644 index 0000000..b9892c8 --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_decide_option.dart @@ -0,0 +1,23 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +enum OptimizelyDecideOption { + DISABLE_DECISION_EVENT, + ENABLED_FLAGS_ONLY, + IGNORE_USER_PROFILE_SERVICE, + EXCLUDE_VARIABLES, + INCLUDE_REASONS +} diff --git a/labs/optimizely-agent-client-dart/lib/src/models/optimizely_decision.dart b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_decision.dart new file mode 100644 index 0000000..389bb23 --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_decision.dart @@ -0,0 +1,48 @@ +/**************************************************************************** + * Copyright 2021, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +import './user_context.dart'; + +class OptimizelyDecision { + Map variables; + String variationKey; + bool enabled; + String ruleKey; + String flagKey; + UserContext userContext; + List reasons; + + OptimizelyDecision.fromJson(Map json) + : variables = json['variables'] as Map ?? {}, + variationKey = json['variationKey'], + enabled = json['enabled'], + ruleKey = json['ruleKey'], + flagKey = json['flagKey'], + userContext = UserContext.fromJson(json['userContext']), + reasons = (json['reasons'] as List).map((r) => r.toString()).toList(); + + Map toJson() { + return { + 'userContext': this.userContext.toJson(), + 'ruleKey': this.ruleKey, + 'flagKey': this.flagKey, + 'variationKey': this.variationKey, + 'variables': this.variables, + 'enabled': this.enabled, + 'reasons': this.reasons, + }; + } +} diff --git a/labs/optimizely-agent-client-dart/lib/src/models/optimizely_decision_legacy.dart b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_decision_legacy.dart new file mode 100644 index 0000000..5b742fe --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/models/optimizely_decision_legacy.dart @@ -0,0 +1,54 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +class OptimizelyDecisionLegacy { + OptimizelyDecisionLegacy(this.userId, this.experimentKey, this.error); + + String userId; + String experimentKey; + String featureKey; + String variationKey; + String type; + Map variables; + bool enabled; + String error; + + factory OptimizelyDecisionLegacy.fromJson(Map json) { + return OptimizelyDecisionLegacy( + json['userId'] as String, + json['experimentKey'] as String, + json['error'] as String ?? '', + ) + ..featureKey = json['featureKey'] as String + ..variationKey = json['variationKey'] as String + ..type = json['type'] as String + ..variables = json['variables'] as Map ?? {} + ..enabled = json['enabled'] as bool; + } + + Map toJson() { + return { + 'userId': this.userId, + 'experimentKey': this.experimentKey, + 'featureKey': this.featureKey, + 'variationKey': this.variationKey, + 'type': this.type, + 'variables': this.variables, + 'enabled': this.enabled, + 'error': this.error, + }; + } +} diff --git a/labs/optimizely-agent-client-dart/lib/src/models/user_context.dart b/labs/optimizely-agent-client-dart/lib/src/models/user_context.dart new file mode 100644 index 0000000..a600031 --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/models/user_context.dart @@ -0,0 +1,35 @@ +/**************************************************************************** + * Copyright 2021, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +class UserContext { + String userId; + Map attributes = new Map(); + + UserContext(userId, [attributes]) + : this.userId = userId, + this.attributes = attributes; + + UserContext.fromJson(Map json) + : userId = json['userId'], + attributes = (json['attributes'] as Map).map((k, e) => MapEntry(k, e as String)); + + Map toJson() { + return { + "userId": this.userId, + "attributes": this.attributes + }; + } +} \ No newline at end of file diff --git a/labs/optimizely-agent-client-dart/lib/src/network/http_manager.dart b/labs/optimizely-agent-client-dart/lib/src/network/http_manager.dart new file mode 100644 index 0000000..2d808bb --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/network/http_manager.dart @@ -0,0 +1,40 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +import 'dart:io'; +import 'package:dio/dio.dart'; + +class HttpManager { + final String _sdkKey; + final String _url; + final _client = Dio(); + + HttpManager(this._sdkKey, this._url) { + _client.options.baseUrl = _url; + _client.options.headers = { + "X-Optimizely-SDK-Key": _sdkKey, + HttpHeaders.contentTypeHeader: "application/json" + }; + } + + Future getRequest(String endpoint) { + return _client.get('$_url$endpoint'); + } + + Future postRequest(String endpoint, Object body, [Map queryParams]) { + return _client.post(endpoint, data: body, queryParameters: queryParams); + } +} diff --git a/labs/optimizely-agent-client-dart/lib/src/request_manager.dart b/labs/optimizely-agent-client-dart/lib/src/request_manager.dart new file mode 100644 index 0000000..4a8fd8c --- /dev/null +++ b/labs/optimizely-agent-client-dart/lib/src/request_manager.dart @@ -0,0 +1,145 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +import 'package:meta/meta.dart'; +import 'package:dio/dio.dart'; + +import './models/user_context.dart'; +import './models/decision_types.dart'; +import './models/optimizely_decide_option.dart'; +import './network/http_manager.dart'; + +class RequestManager { + HttpManager _manager; + + RequestManager(String sdkKey, url) { + _manager = HttpManager(sdkKey, url); + } + + Future getOptimizelyConfig() async { + Response resp; + try { + resp = await _manager.getRequest("/v1/config"); + } on DioError catch(err) { + resp = err.response != null ? err.response : new Response(statusCode: 0, statusMessage: err.message); + } + return resp; + } + + Future track({ + @required String eventKey, + String userId, + Map eventTags, + Map userAttributes + }) async { + Map body = {}; + + if (userId != null) { + body["userId"] = userId; + } + + if (eventTags != null) { + body["eventTags"] = eventTags; + } + + if (userAttributes != null) { + body["userAttributes"] = userAttributes; + } + + Response resp; + try { + resp = await _manager.postRequest("/v1/track", body, {"eventKey": eventKey}); + } on DioError catch(err) { + resp = err.response != null ? err.response : new Response(statusCode: 0, statusMessage: err.message); + } + return resp; + } + + Future activate({ + @required String userId, + Map userAttributes, + List featureKey, + List experimentKey, + bool disableTracking, + DecisionType type, + bool enabled, + }) async { + Map body = { "userId": userId }; + + if (userAttributes != null) { + body["userAttributes"] = userAttributes; + } + + Map queryParams = {}; + + if (featureKey != null) { + queryParams["featureKey"] = featureKey.join(','); + } + + if (experimentKey != null) { + queryParams["experimentKey"] = experimentKey.join(','); + } + + if (disableTracking != null) { + queryParams["disableTracking"] = disableTracking.toString(); + } + + if (type != null) { + queryParams["type"] = type.toString().split('.').last; + } + + if (enabled != null) { + queryParams["enabled"] = enabled.toString(); + } + + Response resp; + try { + resp = await _manager.postRequest("/v1/activate", body, queryParams); + } on DioError catch(err) { + resp = err.response != null ? err.response : new Response(statusCode: 0, statusMessage: err.message); + } + return resp; + } + + Future decide({ + @required UserContext userContext, + String key, + List optimizelyDecideOptions = const [] + }) async { + Map body = { + "userId": userContext.userId, + "decideOptions": optimizelyDecideOptions?.map((option) => option.toString().split('.').last)?.toList(), + }; + + if (userContext.attributes != null) { + body["userAttributes"] = userContext.attributes; + } + + Map queryParams = {}; + + if (key != null) { + queryParams['keys'] = key; + } + + Response resp; + try { + resp = await _manager.postRequest("/v1/decide", body, queryParams); + } on DioError catch(err) { + resp = err.response != null ? err.response : new Response(statusCode: 0, statusMessage: err.message); + } + return resp; + } +} diff --git a/labs/optimizely-agent-client-dart/pubspec.yaml b/labs/optimizely-agent-client-dart/pubspec.yaml new file mode 100644 index 0000000..e0062fb --- /dev/null +++ b/labs/optimizely-agent-client-dart/pubspec.yaml @@ -0,0 +1,14 @@ +name: optimizely_agent_client +description: Optimizely Agent Client. + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +version: 0.1.0 + +environment: + sdk: ">=2.7.0 <3.0.0" + +dependencies: + dio: ^3.0.10