Skip to content

Commit

Permalink
[FSSDK-9726] AAT gap fill (#221)
Browse files Browse the repository at this point in the history
* chore: only yarn install during container create

* chore: add copilot & chat to dev container

* fix: add missing getVuid method

* test: add unit tests for getVuid

* fix: user context not saving to this.userContext

* feat: add getQualifedSegments

* test(wip): adding getQualifedSegments tests

* chore: lint fix

* test: fix test & implementation

* fix: requsted PR adjustments

* test: update tests per PR request

* feat: add getUserContext

* chore: lint fixes

---------

Co-authored-by: Mike Chu <[email protected]>
  • Loading branch information
mikechu-optimizely and mikechu-optimizely authored Nov 6, 2023
1 parent c4afe04 commit bc69201
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 20 deletions.
8 changes: 3 additions & 5 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
{
"name": "React SDK",

"image": "mcr.microsoft.com/devcontainers/javascript-node:1-18-bullseye",

"postCreateCommand": "npm install -g npm && yarn install",

"postCreateCommand": "yarn install",
"customizations": {
"vscode": {
"extensions": [
Expand All @@ -14,7 +11,8 @@
"Gruntfuggly.todo-tree",
"github.vscode-github-actions",
"Orta.vscode-jest",
"ms-vscode.test-adapter-converter"
"ms-vscode.test-adapter-converter",
"GitHub.copilot-chat"
],
"settings": {
"files.eol": "\n"
Expand Down
71 changes: 70 additions & 1 deletion src/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ describe('ReactSDKClient', () => {
await instance.setUser({
id: 'xxfueaojfe8&86',
});
await instance.onReady()
await instance.onReady();

await instance.setUser({
id: 'xxfueaojfe8&87',
Expand Down Expand Up @@ -1624,4 +1624,73 @@ describe('ReactSDKClient', () => {
expect(mockInnerClient.sendOdpEvent).toHaveBeenCalledTimes(1);
});
});

describe('getVuid', () => {
const vuidFormat = /^vuid_[a-f0-9]{27}$/;
let instance: ReactSDKClient;

beforeEach(async () => {
instance = createInstance(config);
});

it('should return undefined if client is null', () => {
// @ts-ignore
instance._client = null;

const vuid = instance.getVuid();

expect(vuid).toBeUndefined();
});

it('should return a valid vuid', async () => {
const validVuid = 'vuid_8de3bb278fce47f6b000cadc1ac';
const mockGetVuid = mockInnerClient.getVuid as jest.Mock;
mockGetVuid.mockReturnValue(validVuid);

const vuid = instance.getVuid();

expect(vuid).toMatch(vuidFormat);
expect(vuid).toEqual(validVuid);
});
});

describe('getUserContext', () => {
let instance: ReactSDKClient;

beforeEach(async () => {
instance = createInstance(config);
});

it('should log a warning and return null if client is not defined', () => {
// @ts-ignore
instance._client = null;

instance.getUserContext();

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toBeCalledWith("Unable to get user context because Optimizely client failed to initialize.");
});


it('should log a warning and return null if setUser is not called first', () => {
instance.getUserContext();

expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toBeCalledWith("Unable to get user context because user was not set.");
});

it('should return a userContext if setUser is called', () => {
instance.setUser({
id: 'user1',
attributes: {
foo: 'bar',
},
});

const currentUserContext = instance.getUserContext();

expect(logger.warn).toHaveBeenCalledTimes(0);
expect(currentUserContext).not.toBeNull();
});
});
});
58 changes: 44 additions & 14 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ const default_user: UserInfo = {
attributes: {},
};

export interface ReactSDKClient extends Omit<optimizely.Client, 'createUserContext' | 'getVuid'> {
export interface ReactSDKClient extends Omit<optimizely.Client, 'createUserContext'> {
user: UserInfo;

onReady(opts?: { timeout?: number }): Promise<any>;
onReady(opts?: { timeout?: number; }): Promise<any>;
setUser(userInfo: UserInfo): Promise<void>;
onUserUpdate(handler: OnUserUpdateHandler): DisposeFn;
isReady(): boolean;
Expand Down Expand Up @@ -123,7 +123,7 @@ export interface ReactSDKClient extends Omit<optimizely.Client, 'createUserConte
featureKey: string,
overrideUserId: string,
overrideAttributes?: optimizely.UserAttributes
): { [variableKey: string]: unknown } | null;
): { [variableKey: string]: unknown; } | null;

isFeatureEnabled(
featureKey: string,
Expand Down Expand Up @@ -159,14 +159,14 @@ export interface ReactSDKClient extends Omit<optimizely.Client, 'createUserConte
options?: optimizely.OptimizelyDecideOption[],
overrideUserId?: string,
overrideAttributes?: optimizely.UserAttributes
): { [key: string]: OptimizelyDecision };
): { [key: string]: OptimizelyDecision; };

decideForKeys(
keys: string[],
options?: optimizely.OptimizelyDecideOption[],
overrideUserId?: string,
overrideAttributes?: optimizely.UserAttributes
): { [key: string]: OptimizelyDecision };
): { [key: string]: OptimizelyDecision; };

setForcedDecision(
decisionContext: optimizely.OptimizelyDecisionContext,
Expand All @@ -180,9 +180,13 @@ export interface ReactSDKClient extends Omit<optimizely.Client, 'createUserConte
getForcedDecision(decisionContext: optimizely.OptimizelyDecisionContext): optimizely.OptimizelyForcedDecision | null;

fetchQualifiedSegments(options?: optimizely.OptimizelySegmentOption[]): Promise<boolean>;

getUserContext(): optimizely.OptimizelyUserContext | null;

getVuid(): string | undefined;
}

export const DEFAULT_ON_READY_TIMEOUT = 5000;
export const DEFAULT_ON_READY_TIMEOUT = 5_000;

class OptimizelyReactSDKClient implements ReactSDKClient {
private userContext: optimizely.OptimizelyUserContext | null = null;
Expand Down Expand Up @@ -291,7 +295,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
return this.isUsingSdkKey;
}

public onReady(config: { timeout?: number } = {}): Promise<OnReadyResult> {
public onReady(config: { timeout?: number; } = {}): Promise<OnReadyResult> {
let timeoutId: number | undefined;
let timeout: number = DEFAULT_ON_READY_TIMEOUT;
if (config && config.timeout !== undefined) {
Expand Down Expand Up @@ -326,6 +330,24 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
});
}

public getUserContext(): optimizely.OptimizelyUserContext | null {
if (!this._client) {
logger.warn(
'Unable to get user context because Optimizely client failed to initialize.'
);
return null;
}

if (!this.userContext) {
logger.warn(
'Unable to get user context because user was not set.'
);
return null;
}

return this.userContext;
}

public getUserContextInstance(userInfo: UserInfo): optimizely.OptimizelyUserContext | null {
if (!this._client) {
logger.warn(
Expand Down Expand Up @@ -501,7 +523,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
options: optimizely.OptimizelyDecideOption[] = [],
overrideUserId?: string,
overrideAttributes?: optimizely.UserAttributes
): { [key: string]: OptimizelyDecision } {
): { [key: string]: OptimizelyDecision; } {
if (!this._client) {
logger.warn('Unable to evaluate features for keys because Optimizely client failed to initialize.');
return {};
Expand All @@ -517,7 +539,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
const optlyUserContext = this.getUserContextInstance(user);
if (optlyUserContext) {
return Object.entries(optlyUserContext.decideForKeys(keys, options)).reduce(
(decisions: { [key: string]: OptimizelyDecision }, [key, decision]) => {
(decisions: { [key: string]: OptimizelyDecision; }, [key, decision]) => {
decisions[key] = {
...decision,
userContext: {
Expand All @@ -537,7 +559,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
options: optimizely.OptimizelyDecideOption[] = [],
overrideUserId?: string,
overrideAttributes?: optimizely.UserAttributes
): { [key: string]: OptimizelyDecision } {
): { [key: string]: OptimizelyDecision; } {
if (!this._client) {
logger.warn('Unable to evaluate all feature decisions because Optimizely client is not initialized.');
return {};
Expand All @@ -553,7 +575,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
const optlyUserContext = this.getUserContextInstance(user);
if (optlyUserContext) {
return Object.entries(optlyUserContext.decideAll(options)).reduce(
(decisions: { [key: string]: OptimizelyDecision }, [key, decision]) => {
(decisions: { [key: string]: OptimizelyDecision; }, [key, decision]) => {
decisions[key] = {
...decision,
userContext: {
Expand Down Expand Up @@ -1033,7 +1055,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
featureKey: string,
overrideUserId: string,
overrideAttributes?: optimizely.UserAttributes
): { [variableKey: string]: unknown } | null {
): { [variableKey: string]: unknown; } | null {
if (!this._client) {
logger.warn(
'Unable to get all feature variables from feature "%s" because Optimizely client failed to initialize.',
Expand Down Expand Up @@ -1162,7 +1184,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
* Cleanup method for killing an running timers and flushing eventQueue
* @returns {Promise<{ success: boolean; reason?: string }>}
*/
public close(): Promise<{ success: boolean; reason?: string }> {
public close(): Promise<{ success: boolean; reason?: string; }> {
if (!this._client) {
/**
* Note:
Expand All @@ -1171,7 +1193,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
* - If we resolve as "false", then the cleanup for timers and the event queue will never trigger.
* - Not triggering cleanup may lead to memory leaks and other inefficiencies.
*/
return new Promise<{ success: boolean; reason: string }>((resolve, reject) =>
return new Promise<{ success: boolean; reason: string; }>((resolve, reject) =>
resolve({
success: true,
reason: 'Optimizely client is not initialized.',
Expand Down Expand Up @@ -1227,6 +1249,14 @@ class OptimizelyReactSDKClient implements ReactSDKClient {

this.client?.sendOdpEvent(action, type, identifiers, data);
}

public getVuid(): string | undefined {
if (!this._client) {
logger.warn('Unable to get VUID because Optimizely client failed to initialize.');
return undefined;
}
return this._client.getVuid();
}
}

export function createInstance(config: optimizely.Config): ReactSDKClient {
Expand Down

0 comments on commit bc69201

Please sign in to comment.