Skip to content

[FSSDK-9082] [React] Handle Null and Empty Values in sendOdpEvent method #200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 22, 2023
23 changes: 23 additions & 0 deletions src/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1558,4 +1558,27 @@ describe('ReactSDKClient', () => {
});
});
});

describe('sendOdpEvent', () => {
let instance: ReactSDKClient;
beforeEach(() => {
instance = createInstance(config);
});

it('should throw error when action param is falsy', async () => {
const badValues = ['', ' '];
badValues.forEach(item => {
instance.sendOdpEvent(item);
});

expect(logger.error).toHaveBeenCalledTimes(badValues.length);
expect(logger.error).toBeCalledWith('ODP action is not valid (cannot be empty).');
});

it('should call sendOdpEvent once', async () => {
instance.sendOdpEvent('test');

expect(mockInnerClient.sendOdpEvent).toHaveBeenCalledTimes(1);
});
});
});
81 changes: 43 additions & 38 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,6 @@ export interface ReactSDKClient extends Omit<optimizely.Client, 'createUserConte
export const DEFAULT_ON_READY_TIMEOUT = 5000;

class OptimizelyReactSDKClient implements ReactSDKClient {
public initialConfig: optimizely.Config;
public user: UserInfo = {
id: null,
attributes: {},
};
private userContext: optimizely.OptimizelyUserContext | null = null;
private userPromiseResolver: (user: UserInfo) => void;
private userPromise: Promise<OnReadyResult>;
Expand All @@ -207,6 +202,12 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
// promise keeping track of async requests for initializing client instance
private dataReadyPromise: Promise<OnReadyResult>;

public initialConfig: optimizely.Config;
public user: UserInfo = {
id: null,
attributes: {},
};

/**
* Creates an instance of OptimizelyReactSDKClient.
* @param {optimizely.Config} [config={}]
Expand Down Expand Up @@ -258,15 +259,29 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
}
}

getIsReadyPromiseFulfilled(): boolean {
protected getUserContextWithOverrides(
overrideUserId?: string,
overrideAttributes?: optimizely.UserAttributes
): UserInfo {
const finalUserId: string | null = overrideUserId === undefined ? this.user.id : overrideUserId;
const finalUserAttributes: optimizely.UserAttributes | undefined =
overrideAttributes === undefined ? this.user.attributes : overrideAttributes;

return {
id: finalUserId,
attributes: finalUserAttributes,
};
}

public getIsReadyPromiseFulfilled(): boolean {
return this.isReadyPromiseFulfilled;
}

getIsUsingSdkKey(): boolean {
public getIsUsingSdkKey(): boolean {
return this.isUsingSdkKey;
}

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 All @@ -291,7 +306,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
});
}

getUserContextInstance(userInfo: UserInfo): optimizely.OptimizelyUserContext | null {
public getUserContextInstance(userInfo: UserInfo): optimizely.OptimizelyUserContext | null {
if (!this._client) {
logger.warn(
'Unable to get user context for user id "%s" because Optimizely client failed to initialize.',
Expand Down Expand Up @@ -323,7 +338,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
return null;
}

async fetchQualifiedSegments(): Promise<boolean> {
public async fetchQualifiedSegments(): Promise<boolean> {
if (!this.userContext) {
logger.warn('Unable to fetch qualified segments for user because Optimizely client failed to initialize.');
return false;
Expand All @@ -332,7 +347,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
return await this.userContext.fetchQualifiedSegments();
}

setUser(userInfo: UserInfo): void {
public setUser(userInfo: UserInfo): void {
// TODO add check for valid user
if (userInfo.id) {
this.user.id = userInfo.id;
Expand Down Expand Up @@ -360,7 +375,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
this.onUserUpdateHandlers.forEach(handler => handler(this.user));
}

onUserUpdate(handler: OnUserUpdateHandler): DisposeFn {
public onUserUpdate(handler: OnUserUpdateHandler): DisposeFn {
this.onUserUpdateHandlers.push(handler);

return () => {
Expand All @@ -377,7 +392,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
* @param {OnForcedVariationsUpdateHandler} handler
* @returns {DisposeFn}
*/
onForcedVariationsUpdate(handler: OnForcedVariationsUpdateHandler): DisposeFn {
public onForcedVariationsUpdate(handler: OnForcedVariationsUpdateHandler): DisposeFn {
this.onForcedVariationsUpdateHandlers.push(handler);

return (): void => {
Expand All @@ -388,7 +403,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
};
}

isReady(): boolean {
public isReady(): boolean {
// React SDK Instance only becomes ready when both JS SDK client and the user info is ready.
return this.isUserReady && this.isClientReady;
}
Expand Down Expand Up @@ -952,7 +967,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
* @returns {(unknown | null)}
* @memberof OptimizelyReactSDKClient
*/
getFeatureVariable(
public getFeatureVariable(
featureKey: string,
variableKey: string,
overrideUserId: string,
Expand Down Expand Up @@ -989,7 +1004,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
* @returns {({ [variableKey: string]: unknown } | null)}
* @memberof OptimizelyReactSDKClient
*/
getAllFeatureVariables(
public getAllFeatureVariables(
featureKey: string,
overrideUserId: string,
overrideAttributes?: optimizely.UserAttributes
Expand Down Expand Up @@ -1174,33 +1189,23 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
return this._client.notificationCenter;
}

protected getUserContextWithOverrides(
overrideUserId?: string,
overrideAttributes?: optimizely.UserAttributes
): UserInfo {
const finalUserId: string | null = overrideUserId === undefined ? this.user.id : overrideUserId;
const finalUserAttributes: optimizely.UserAttributes | undefined =
overrideAttributes === undefined ? this.user.attributes : overrideAttributes;

return {
id: finalUserId,
attributes: finalUserAttributes,
};
}

// TODO: discuss if we want to expose these method and provide implementation
getVuid(): string | undefined {
// TODO: this is tobe removed in future once the js-sdk gets updated
public getVuid(): string | undefined {
return undefined;
}

// TODO: discuss if we want to expose these method and provide implementation
sendOdpEvent(
public sendOdpEvent(
action: string,
type: string | undefined,
identifiers: Map<string, string> | undefined,
data: Map<string, unknown> | undefined
type?: string,
identifiers?: Map<string, string>,
data?: Map<string, unknown>
): void {
// no-op
if (!action || !action.trim()) {
logger.error('ODP action is not valid (cannot be empty).');
return;
}

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

Expand Down