Skip to content

Commit

Permalink
feat: Added getFeatureVariableJson, getFeatureVariable and getAllFeat…
Browse files Browse the repository at this point in the history
…ureVariables (#53)

## Summary
- Update `@optimizely/optimizely-sdk` dependency to 4.1.0, adding support for JSON feature variables
- Implemented getFeatureVariableJSON, getFeatureVariable and getAllFeatureVariables in client interface
- Added deprecation note about `getFeatureVariables` client method

## Test plan
Added unit tests for getFeatureVariableJSON, getFeatureVariable and getAllFeatureVariables 


Co-authored-by: Zeeshan Ashraf <[email protected]>
  • Loading branch information
fayyazarshad and zashraf1985 authored Jul 8, 2020
1 parent 1badf9f commit 381f54d
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 13 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ The following type definitions are used in the `ReactSDKClient` interface:
- `onUserUpdate(handler: (userInfo: User) => void): () => void` Subscribe a callback to be called when this instance's current user changes. Returns a function that will unsubscribe the callback.
- `activate(experimentKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): string | null` Activate an experiment, and return the variation for the given user.
- `getVariation(experimentKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): string | null` Return the variation for the given experiment and user.
- `getFeatureVariables(featureKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): VariableValuesObject`: Decide and return variable values for the given feature and user
- `getFeatureVariables(featureKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): VariableValuesObject`: Decide and return variable values for the given feature and user <br /> <b>Warning:</b> Deprecated since 2.1.0 <br /> `getAllFeatureVariables` is added in JavaScript SDK which is similarly returning all the feature variables, but it sends only single notification of type `all-feature-variables` instead of sending for each variable. As `getFeatureVariables` was added when this functionality wasn't provided by `JavaScript SDK`, so there is no need of it now and it would be removed in next major release
- `getFeatureVariableString(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: optimizely.UserAttributes): string | null`: Decide and return the variable value for the given feature, variable, and user
- `getFeatureVariableInteger(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): number | null` Decide and return the variable value for the given feature, variable, and user
- `getFeatureVariableBoolean(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): boolean | null` Decide and return the variable value for the given feature, variable, and user
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
},
"dependencies": {
"@optimizely/js-sdk-logging": "^0.1.0",
"@optimizely/optimizely-sdk": "4.0.0",
"@optimizely/optimizely-sdk": "4.1.0",
"hoist-non-react-statics": "^3.3.0",
"prop-types": "^15.6.2",
"utility-types": "^2.1.0 || ^3.0.0"
Expand Down
171 changes: 171 additions & 0 deletions src/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ describe('ReactSDKClient', () => {
getForcedVariation: jest.fn(() => null),
getFeatureVariableBoolean: jest.fn(() => null),
getFeatureVariableDouble: jest.fn(() => null),
getFeatureVariableJSON: jest.fn(() => null),
getAllFeatureVariables: jest.fn(() => { return {} }),
getFeatureVariable: jest.fn(() => null),
getFeatureVariableInteger: jest.fn(() => null),
getFeatureVariableString: jest.fn(() => null),
getOptimizelyConfig: jest.fn(() => null),
Expand Down Expand Up @@ -358,6 +361,68 @@ describe('ReactSDKClient', () => {
expect(mockInnerClient.getFeatureVariableDouble).toBeCalledWith('feat1', 'dvar1', 'user2', { bar: 'baz' });
});

it('can use pre-set and override user for getFeatureVariableJSON', () => {
const mockFn = mockInnerClient.getFeatureVariableJSON as jest.Mock;
mockFn.mockReturnValue({
num_buttons: 0,
text: 'default value',
});
let result = instance.getFeatureVariableJSON('feat1', 'dvar1');
expect(result).toEqual({
num_buttons: 0,
text: 'default value',
});
expect(mockFn).toBeCalledTimes(1);
expect(mockFn).toBeCalledWith('feat1', 'dvar1', 'user1', {
foo: 'bar',
});
mockFn.mockReset();
mockFn.mockReturnValue({
num_buttons: 0,
text: 'variable value',
});
result = instance.getFeatureVariableJSON('feat1', 'dvar1', 'user2', {
bar: 'baz',
});
expect(result).toEqual({
num_buttons: 0,
text: 'variable value',
});
expect(mockInnerClient.getFeatureVariableJSON).toBeCalledTimes(1);
expect(mockInnerClient.getFeatureVariableJSON).toBeCalledWith('feat1', 'dvar1', 'user2', { bar: 'baz' });
});

it('can use pre-set and override user for getFeatureVariable', () => {
const mockFn = mockInnerClient.getFeatureVariable as jest.Mock;
mockFn.mockReturnValue({
num_buttons: 0,
text: 'default value',
});
let result = instance.getFeatureVariable('feat1', 'dvar1', 'user1');
expect(result).toEqual({
num_buttons: 0,
text: 'default value',
});
expect(mockFn).toBeCalledTimes(1);
expect(mockFn).toBeCalledWith('feat1', 'dvar1', 'user1', {
foo: 'bar',
});
mockFn.mockReset();
mockFn.mockReturnValue({
num_buttons: 0,
text: 'variable value',
});
result = instance.getFeatureVariable('feat1', 'dvar1', 'user2', {
bar: 'baz',
});
expect(result).toEqual({
num_buttons: 0,
text: 'variable value',
});
expect(mockInnerClient.getFeatureVariable).toBeCalledTimes(1);
expect(mockInnerClient.getFeatureVariable).toBeCalledWith('feat1', 'dvar1', 'user2', { bar: 'baz' });
});

it('can use pre-set and override user for setForcedVariation', () => {
const mockFn = mockInnerClient.setForcedVariation as jest.Mock;
mockFn.mockReturnValue(true);
Expand Down Expand Up @@ -398,6 +463,7 @@ describe('ReactSDKClient', () => {
anyClient.getFeatureVariableString.mockReturnValue(null);
anyClient.getFeatureVariableInteger.mockReturnValue(null);
anyClient.getFeatureVariableDouble.mockReturnValue(null);
anyClient.getFeatureVariableJSON.mockReturnValue(null);
const instance = createInstance(config);
const result = instance.getFeatureVariables('feat1');
expect(result).toEqual({});
Expand Down Expand Up @@ -427,6 +493,10 @@ describe('ReactSDKClient', () => {
type: 'double',
key: 'dvar',
},
{
type: 'json',
key: 'jvar',
},
],
},
},
Expand All @@ -437,6 +507,9 @@ describe('ReactSDKClient', () => {
anyClient.getFeatureVariableString.mockReturnValue('whatsup');
anyClient.getFeatureVariableInteger.mockReturnValue(10);
anyClient.getFeatureVariableDouble.mockReturnValue(-10.5);
anyClient.getFeatureVariableJSON.mockReturnValue({
value: 'json value'
});
const instance = createInstance(config);
instance.setUser({
id: 'user1123',
Expand All @@ -447,7 +520,105 @@ describe('ReactSDKClient', () => {
svar: 'whatsup',
ivar: 10,
dvar: -10.5,
jvar: {
value: 'json value'
}
});
});
});

describe('getAllFeatureVariables', () => {
it('returns an empty object when the inner SDK returns no variables', () => {
const anyClient = mockInnerClient.getAllFeatureVariables as jest.Mock;
anyClient.mockReturnValue({});
const instance = createInstance(config);
const result = instance.getAllFeatureVariables('feat1', 'user1');
expect(result).toEqual({});
});

it('returns an object with variables of all types returned from the inner sdk ', () => {
const anyClient = mockInnerClient.getAllFeatureVariables as jest.Mock;
anyClient.mockReturnValue({
bvar: true,
svar: 'whatsup',
ivar: 10,
dvar: -10.5,
jvar: {
value: 'json value'
}
});
const instance = createInstance(config);
instance.setUser({
id: 'user1123',
});
const result = instance.getAllFeatureVariables('feat1', 'user1');
expect(result).toEqual({
bvar: true,
svar: 'whatsup',
ivar: 10,
dvar: -10.5,
jvar: {
value: 'json value'
}
});
});

it('can use pre-set and override user for getAllFeatureVariables', () => {
const mockFn = mockInnerClient.getAllFeatureVariables as jest.Mock;
mockFn.mockReturnValue({
bvar: true,
svar: 'whatsup',
ivar: 10,
dvar: -10.5,
jvar: {
value: 'json value'
}
});
const instance = createInstance(config);
instance.setUser({
id: 'user1',
attributes: {
foo: 'bar',
},
});
let result = instance.getAllFeatureVariables('feat1', 'user1');
expect(result).toEqual({
bvar: true,
svar: 'whatsup',
ivar: 10,
dvar: -10.5,
jvar: {
value: 'json value'
}
});
expect(mockFn).toBeCalledTimes(1);
expect(mockFn).toBeCalledWith('feat1', 'user1', {
foo: 'bar',
});
mockFn.mockReset();
mockFn.mockReturnValue({
bvar: false,
svar: 'another var',
ivar: 11,
dvar: -11.5,
jvar: {
value: 'json another value'
}
});
result = instance.getAllFeatureVariables('feat1', 'user2', {
bar: 'baz',
});
expect(result).toEqual({
bvar: false,
svar: 'another var',
ivar: 11,
dvar: -11.5,
jvar: {
value: 'json another value'
}
});
expect(mockInnerClient.getAllFeatureVariables).toBeCalledTimes(1);
expect(mockInnerClient.getAllFeatureVariables).toBeCalledWith('feat1', 'user2', { bar: 'baz' });
});
});
});
Expand Down
113 changes: 112 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import * as optimizely from '@optimizely/optimizely-sdk';
import * as logging from '@optimizely/js-sdk-logging';
import { UserAttributes } from "@optimizely/optimizely-sdk";

const logger = logging.getLogger('ReactSDK');

Expand Down Expand Up @@ -89,6 +90,26 @@ export interface ReactSDKClient extends optimizely.Client {
overrideAttributes?: optimizely.UserAttributes
): number | null;

getFeatureVariableJSON(
featureKey: string,
variableKey: string,
overrideUserId?: string,
overrideAttributes?: optimizely.UserAttributes,
): unknown

getFeatureVariable(
featureKey: string,
variableKey: string,
overrideUserId: string,
overrideAttributes?: optimizely.UserAttributes
): unknown

getAllFeatureVariables(
featureKey: string,
overrideUserId: string,
overrideAttributes?: optimizely.UserAttributes
): { [variableKey: string]: unknown }

isFeatureEnabled(
featureKey: string,
overrideUserId?: string,
Expand Down Expand Up @@ -305,8 +326,14 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
}

/**
* @deprecated since 2.1.0
* getAllFeatureVariables is added in JavaScript SDK which is similarly returning all the feature variables, but
* it sends only single notification of type "all-feature-variables" instead of sending for each variable.
* As getFeatureVariables was added when this functionality wasn't provided by JavaScript SDK, so there is no
* need of it now and it would be removed in next major release
*
* Get all variables for a feature, regardless of the feature being enabled/disabled
* @param {string} feature
* @param {string} featureKey
* @param {string} [overrideUserId]
* @param {optimizely.UserAttributes} [overrideAttributes]
* @returns {VariableValuesObject}
Expand Down Expand Up @@ -354,6 +381,10 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
case 'double':
variableObj[key] = this._client.getFeatureVariableDouble(featureKey, key, userId, userAttributes);
break;

case 'json':
variableObj[key] = this._client.getFeatureVariableJSON(featureKey, key, userId, userAttributes);
break;
}
});

Expand Down Expand Up @@ -452,6 +483,86 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
return this._client.getFeatureVariableDouble(feature, variable, user.id, user.attributes);
}

/**
* Returns value for the given json variable attached to the given feature
* flag
* @param {string} feature
* @param {string} variable
* @param {string} [overrideUserId]
* @param {optimizely.UserAttributes} [overrideAttributes]
* @returns {(unknown | null)}
* @memberof OptimizelyReactSDKClient
*/
public getFeatureVariableJSON(
feature: string,
variable: string,
overrideUserId?: string,
overrideAttributes?: optimizely.UserAttributes
): unknown {
const user = this.getUserContextWithOverrides(overrideUserId, overrideAttributes);
if (user.id === null) {
return null
}
return this._client.getFeatureVariableJSON(
feature,
variable,
user.id,
user.attributes,
);
}

/**
* Returns dynamically-typed value of the variable attached to the given
* feature flag. Returns null if the feature key or variable key is invalid.
* @param {string} featureKey
* @param {string} variableKey
* @param {string} [overrideUserId]
* @param {optimizely.UserAttributes} [overrideAttributes]
* @returns {(unknown | null)}
* @memberof OptimizelyReactSDKClient
*/
getFeatureVariable(
featureKey: string,
variableKey: string,
overrideUserId: string,
overrideAttributes?: optimizely.UserAttributes
): unknown {
const user = this.getUserContextWithOverrides(overrideUserId, overrideAttributes);
if (user.id === null) {
return null
}
return this._client.getFeatureVariable(
featureKey,
variableKey,
user.id,
user.attributes,
);
}

/**
* Returns values for all the variables attached to the given feature flag
* @param {string} featureKey
* @param {string} overrideUserId
* @param {optimizely.UserAttributes} [overrideAttributes]
* @returns {({ [variableKey: string]: unknown } | null)}
* @memberof OptimizelyReactSDKClient
*/
getAllFeatureVariables(
featureKey: string,
overrideUserId: string,
overrideAttributes?: optimizely.UserAttributes
): { [variableKey: string]: unknown } {
const user = this.getUserContextWithOverrides(overrideUserId, overrideAttributes);
if (user.id === null) {
return {};
}
return this._client.getAllFeatureVariables(
featureKey,
user.id,
user.attributes,
);
}

/**
* Get an array of all enabled features
* @param {string} [overrideUserId]
Expand Down
Loading

0 comments on commit 381f54d

Please sign in to comment.