Skip to content

Commit 381f54d

Browse files
feat: Added getFeatureVariableJson, getFeatureVariable and getAllFeatureVariables (#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 <zashraf@folio3.com>
1 parent 1badf9f commit 381f54d

File tree

5 files changed

+294
-13
lines changed

5 files changed

+294
-13
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ The following type definitions are used in the `ReactSDKClient` interface:
484484
- `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.
485485
- `activate(experimentKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): string | null` Activate an experiment, and return the variation for the given user.
486486
- `getVariation(experimentKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): string | null` Return the variation for the given experiment and user.
487-
- `getFeatureVariables(featureKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): VariableValuesObject`: Decide and return variable values for the given feature and user
487+
- `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
488488
- `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
489489
- `getFeatureVariableInteger(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): number | null` Decide and return the variable value for the given feature, variable, and user
490490
- `getFeatureVariableBoolean(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): boolean | null` Decide and return the variable value for the given feature, variable, and user

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
},
3030
"dependencies": {
3131
"@optimizely/js-sdk-logging": "^0.1.0",
32-
"@optimizely/optimizely-sdk": "4.0.0",
32+
"@optimizely/optimizely-sdk": "4.1.0",
3333
"hoist-non-react-statics": "^3.3.0",
3434
"prop-types": "^15.6.2",
3535
"utility-types": "^2.1.0 || ^3.0.0"

src/client.spec.ts

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ describe('ReactSDKClient', () => {
3838
getForcedVariation: jest.fn(() => null),
3939
getFeatureVariableBoolean: jest.fn(() => null),
4040
getFeatureVariableDouble: jest.fn(() => null),
41+
getFeatureVariableJSON: jest.fn(() => null),
42+
getAllFeatureVariables: jest.fn(() => { return {} }),
43+
getFeatureVariable: jest.fn(() => null),
4144
getFeatureVariableInteger: jest.fn(() => null),
4245
getFeatureVariableString: jest.fn(() => null),
4346
getOptimizelyConfig: jest.fn(() => null),
@@ -358,6 +361,68 @@ describe('ReactSDKClient', () => {
358361
expect(mockInnerClient.getFeatureVariableDouble).toBeCalledWith('feat1', 'dvar1', 'user2', { bar: 'baz' });
359362
});
360363

364+
it('can use pre-set and override user for getFeatureVariableJSON', () => {
365+
const mockFn = mockInnerClient.getFeatureVariableJSON as jest.Mock;
366+
mockFn.mockReturnValue({
367+
num_buttons: 0,
368+
text: 'default value',
369+
});
370+
let result = instance.getFeatureVariableJSON('feat1', 'dvar1');
371+
expect(result).toEqual({
372+
num_buttons: 0,
373+
text: 'default value',
374+
});
375+
expect(mockFn).toBeCalledTimes(1);
376+
expect(mockFn).toBeCalledWith('feat1', 'dvar1', 'user1', {
377+
foo: 'bar',
378+
});
379+
mockFn.mockReset();
380+
mockFn.mockReturnValue({
381+
num_buttons: 0,
382+
text: 'variable value',
383+
});
384+
result = instance.getFeatureVariableJSON('feat1', 'dvar1', 'user2', {
385+
bar: 'baz',
386+
});
387+
expect(result).toEqual({
388+
num_buttons: 0,
389+
text: 'variable value',
390+
});
391+
expect(mockInnerClient.getFeatureVariableJSON).toBeCalledTimes(1);
392+
expect(mockInnerClient.getFeatureVariableJSON).toBeCalledWith('feat1', 'dvar1', 'user2', { bar: 'baz' });
393+
});
394+
395+
it('can use pre-set and override user for getFeatureVariable', () => {
396+
const mockFn = mockInnerClient.getFeatureVariable as jest.Mock;
397+
mockFn.mockReturnValue({
398+
num_buttons: 0,
399+
text: 'default value',
400+
});
401+
let result = instance.getFeatureVariable('feat1', 'dvar1', 'user1');
402+
expect(result).toEqual({
403+
num_buttons: 0,
404+
text: 'default value',
405+
});
406+
expect(mockFn).toBeCalledTimes(1);
407+
expect(mockFn).toBeCalledWith('feat1', 'dvar1', 'user1', {
408+
foo: 'bar',
409+
});
410+
mockFn.mockReset();
411+
mockFn.mockReturnValue({
412+
num_buttons: 0,
413+
text: 'variable value',
414+
});
415+
result = instance.getFeatureVariable('feat1', 'dvar1', 'user2', {
416+
bar: 'baz',
417+
});
418+
expect(result).toEqual({
419+
num_buttons: 0,
420+
text: 'variable value',
421+
});
422+
expect(mockInnerClient.getFeatureVariable).toBeCalledTimes(1);
423+
expect(mockInnerClient.getFeatureVariable).toBeCalledWith('feat1', 'dvar1', 'user2', { bar: 'baz' });
424+
});
425+
361426
it('can use pre-set and override user for setForcedVariation', () => {
362427
const mockFn = mockInnerClient.setForcedVariation as jest.Mock;
363428
mockFn.mockReturnValue(true);
@@ -398,6 +463,7 @@ describe('ReactSDKClient', () => {
398463
anyClient.getFeatureVariableString.mockReturnValue(null);
399464
anyClient.getFeatureVariableInteger.mockReturnValue(null);
400465
anyClient.getFeatureVariableDouble.mockReturnValue(null);
466+
anyClient.getFeatureVariableJSON.mockReturnValue(null);
401467
const instance = createInstance(config);
402468
const result = instance.getFeatureVariables('feat1');
403469
expect(result).toEqual({});
@@ -427,6 +493,10 @@ describe('ReactSDKClient', () => {
427493
type: 'double',
428494
key: 'dvar',
429495
},
496+
{
497+
type: 'json',
498+
key: 'jvar',
499+
},
430500
],
431501
},
432502
},
@@ -437,6 +507,9 @@ describe('ReactSDKClient', () => {
437507
anyClient.getFeatureVariableString.mockReturnValue('whatsup');
438508
anyClient.getFeatureVariableInteger.mockReturnValue(10);
439509
anyClient.getFeatureVariableDouble.mockReturnValue(-10.5);
510+
anyClient.getFeatureVariableJSON.mockReturnValue({
511+
value: 'json value'
512+
});
440513
const instance = createInstance(config);
441514
instance.setUser({
442515
id: 'user1123',
@@ -447,7 +520,105 @@ describe('ReactSDKClient', () => {
447520
svar: 'whatsup',
448521
ivar: 10,
449522
dvar: -10.5,
523+
jvar: {
524+
value: 'json value'
525+
}
526+
});
527+
});
528+
});
529+
530+
describe('getAllFeatureVariables', () => {
531+
it('returns an empty object when the inner SDK returns no variables', () => {
532+
const anyClient = mockInnerClient.getAllFeatureVariables as jest.Mock;
533+
anyClient.mockReturnValue({});
534+
const instance = createInstance(config);
535+
const result = instance.getAllFeatureVariables('feat1', 'user1');
536+
expect(result).toEqual({});
537+
});
538+
539+
it('returns an object with variables of all types returned from the inner sdk ', () => {
540+
const anyClient = mockInnerClient.getAllFeatureVariables as jest.Mock;
541+
anyClient.mockReturnValue({
542+
bvar: true,
543+
svar: 'whatsup',
544+
ivar: 10,
545+
dvar: -10.5,
546+
jvar: {
547+
value: 'json value'
548+
}
549+
});
550+
const instance = createInstance(config);
551+
instance.setUser({
552+
id: 'user1123',
553+
});
554+
const result = instance.getAllFeatureVariables('feat1', 'user1');
555+
expect(result).toEqual({
556+
bvar: true,
557+
svar: 'whatsup',
558+
ivar: 10,
559+
dvar: -10.5,
560+
jvar: {
561+
value: 'json value'
562+
}
563+
});
564+
});
565+
566+
it('can use pre-set and override user for getAllFeatureVariables', () => {
567+
const mockFn = mockInnerClient.getAllFeatureVariables as jest.Mock;
568+
mockFn.mockReturnValue({
569+
bvar: true,
570+
svar: 'whatsup',
571+
ivar: 10,
572+
dvar: -10.5,
573+
jvar: {
574+
value: 'json value'
575+
}
576+
});
577+
const instance = createInstance(config);
578+
instance.setUser({
579+
id: 'user1',
580+
attributes: {
581+
foo: 'bar',
582+
},
583+
});
584+
let result = instance.getAllFeatureVariables('feat1', 'user1');
585+
expect(result).toEqual({
586+
bvar: true,
587+
svar: 'whatsup',
588+
ivar: 10,
589+
dvar: -10.5,
590+
jvar: {
591+
value: 'json value'
592+
}
593+
});
594+
expect(mockFn).toBeCalledTimes(1);
595+
expect(mockFn).toBeCalledWith('feat1', 'user1', {
596+
foo: 'bar',
597+
});
598+
mockFn.mockReset();
599+
mockFn.mockReturnValue({
600+
bvar: false,
601+
svar: 'another var',
602+
ivar: 11,
603+
dvar: -11.5,
604+
jvar: {
605+
value: 'json another value'
606+
}
607+
});
608+
result = instance.getAllFeatureVariables('feat1', 'user2', {
609+
bar: 'baz',
610+
});
611+
expect(result).toEqual({
612+
bvar: false,
613+
svar: 'another var',
614+
ivar: 11,
615+
dvar: -11.5,
616+
jvar: {
617+
value: 'json another value'
618+
}
450619
});
620+
expect(mockInnerClient.getAllFeatureVariables).toBeCalledTimes(1);
621+
expect(mockInnerClient.getAllFeatureVariables).toBeCalledWith('feat1', 'user2', { bar: 'baz' });
451622
});
452623
});
453624
});

src/client.ts

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import * as optimizely from '@optimizely/optimizely-sdk';
1818
import * as logging from '@optimizely/js-sdk-logging';
19+
import { UserAttributes } from "@optimizely/optimizely-sdk";
1920

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

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

93+
getFeatureVariableJSON(
94+
featureKey: string,
95+
variableKey: string,
96+
overrideUserId?: string,
97+
overrideAttributes?: optimizely.UserAttributes,
98+
): unknown
99+
100+
getFeatureVariable(
101+
featureKey: string,
102+
variableKey: string,
103+
overrideUserId: string,
104+
overrideAttributes?: optimizely.UserAttributes
105+
): unknown
106+
107+
getAllFeatureVariables(
108+
featureKey: string,
109+
overrideUserId: string,
110+
overrideAttributes?: optimizely.UserAttributes
111+
): { [variableKey: string]: unknown }
112+
92113
isFeatureEnabled(
93114
featureKey: string,
94115
overrideUserId?: string,
@@ -305,8 +326,14 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
305326
}
306327

307328
/**
329+
* @deprecated since 2.1.0
330+
* getAllFeatureVariables is added in JavaScript SDK which is similarly returning all the feature variables, but
331+
* it sends only single notification of type "all-feature-variables" instead of sending for each variable.
332+
* As getFeatureVariables was added when this functionality wasn't provided by JavaScript SDK, so there is no
333+
* need of it now and it would be removed in next major release
334+
*
308335
* Get all variables for a feature, regardless of the feature being enabled/disabled
309-
* @param {string} feature
336+
* @param {string} featureKey
310337
* @param {string} [overrideUserId]
311338
* @param {optimizely.UserAttributes} [overrideAttributes]
312339
* @returns {VariableValuesObject}
@@ -354,6 +381,10 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
354381
case 'double':
355382
variableObj[key] = this._client.getFeatureVariableDouble(featureKey, key, userId, userAttributes);
356383
break;
384+
385+
case 'json':
386+
variableObj[key] = this._client.getFeatureVariableJSON(featureKey, key, userId, userAttributes);
387+
break;
357388
}
358389
});
359390

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

486+
/**
487+
* Returns value for the given json variable attached to the given feature
488+
* flag
489+
* @param {string} feature
490+
* @param {string} variable
491+
* @param {string} [overrideUserId]
492+
* @param {optimizely.UserAttributes} [overrideAttributes]
493+
* @returns {(unknown | null)}
494+
* @memberof OptimizelyReactSDKClient
495+
*/
496+
public getFeatureVariableJSON(
497+
feature: string,
498+
variable: string,
499+
overrideUserId?: string,
500+
overrideAttributes?: optimizely.UserAttributes
501+
): unknown {
502+
const user = this.getUserContextWithOverrides(overrideUserId, overrideAttributes);
503+
if (user.id === null) {
504+
return null
505+
}
506+
return this._client.getFeatureVariableJSON(
507+
feature,
508+
variable,
509+
user.id,
510+
user.attributes,
511+
);
512+
}
513+
514+
/**
515+
* Returns dynamically-typed value of the variable attached to the given
516+
* feature flag. Returns null if the feature key or variable key is invalid.
517+
* @param {string} featureKey
518+
* @param {string} variableKey
519+
* @param {string} [overrideUserId]
520+
* @param {optimizely.UserAttributes} [overrideAttributes]
521+
* @returns {(unknown | null)}
522+
* @memberof OptimizelyReactSDKClient
523+
*/
524+
getFeatureVariable(
525+
featureKey: string,
526+
variableKey: string,
527+
overrideUserId: string,
528+
overrideAttributes?: optimizely.UserAttributes
529+
): unknown {
530+
const user = this.getUserContextWithOverrides(overrideUserId, overrideAttributes);
531+
if (user.id === null) {
532+
return null
533+
}
534+
return this._client.getFeatureVariable(
535+
featureKey,
536+
variableKey,
537+
user.id,
538+
user.attributes,
539+
);
540+
}
541+
542+
/**
543+
* Returns values for all the variables attached to the given feature flag
544+
* @param {string} featureKey
545+
* @param {string} overrideUserId
546+
* @param {optimizely.UserAttributes} [overrideAttributes]
547+
* @returns {({ [variableKey: string]: unknown } | null)}
548+
* @memberof OptimizelyReactSDKClient
549+
*/
550+
getAllFeatureVariables(
551+
featureKey: string,
552+
overrideUserId: string,
553+
overrideAttributes?: optimizely.UserAttributes
554+
): { [variableKey: string]: unknown } {
555+
const user = this.getUserContextWithOverrides(overrideUserId, overrideAttributes);
556+
if (user.id === null) {
557+
return {};
558+
}
559+
return this._client.getAllFeatureVariables(
560+
featureKey,
561+
user.id,
562+
user.attributes,
563+
);
564+
}
565+
455566
/**
456567
* Get an array of all enabled features
457568
* @param {string} [overrideUserId]

0 commit comments

Comments
 (0)