Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 17 additions & 29 deletions packages/core/src/DdSdkReactNative.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { BufferSingleton } from './sdk/DatadogProvider/Buffer/BufferSingleton';
import { NativeDdSdk } from './sdk/DdSdkInternal';
import { GlobalState } from './sdk/GlobalState/GlobalState';
import { UserInfoSingleton } from './sdk/UserInfoSingleton/UserInfoSingleton';
import type { UserInfo } from './sdk/UserInfoSingleton/types';
import { adaptLongTaskThreshold } from './utils/longTasksUtils';
import { version as sdkVersion } from './version';

Expand Down Expand Up @@ -251,19 +252,23 @@ export class DdSdkReactNative {
};

/**
* Sets the user information.
* @param id: A mandatory unique user identifier (relevant to your business domain).
* @param name: The user name or alias.
* @param email: The user email.
* @param extraInfo: Additional information.
* Sets the user information. Requires a user ID — setting user properties
* like name or email implies a known user, so an ID must be provided.
* To add custom attributes without setting a user ID, use {@link addUserExtraInfo} instead.
* @param userInfo: The user object (id is required, name, email and extraInfo are optional).
* @returns a Promise.
*/
static setUserInfo = async (userInfo: {
id: string;
name?: string;
email?: string;
extraInfo?: Record<string, unknown>;
}): Promise<void> => {
static setUserInfo = async (
userInfo: UserInfo & { id: string }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of this but I agree that it may be the only option we have to make this work without making id mandatory at UserInfo level.

I'd leave a comment here in any case explaining why we are enforcing this in this manner and not directly on type definition.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll add a comment explaining it, but also add it here for future reference.

The basic idea is that we don't want users to be able to specify the other userInfo properties like name or email without setting an id, because if you're setting any of these values you should have an id , but we do want to allow them to add custom properties (as these can be anything) without the need to set an id.

): Promise<void> => {
if (typeof userInfo.id !== 'string' || userInfo.id.length === 0) {
InternalLog.log(
'setUserInfo requires a valid user ID. Please provide a non-empty string as the id field.',
SdkVerbosity.WARN
);
return;
}

InternalLog.log(
`Setting user ${JSON.stringify(userInfo)}`,
SdkVerbosity.DEBUG
Expand Down Expand Up @@ -296,25 +301,8 @@ export class DdSdkReactNative {
SdkVerbosity.DEBUG
);

const userInfo = UserInfoSingleton.getInstance().getUserInfo();
if (!userInfo) {
InternalLog.log(
'Skipped adding User Extra Info: User Info is currently undefined. A user ID must be set before adding extra info. Please call setUserInfo() first.',
SdkVerbosity.WARN
);

return;
}
const updatedUserInfo = {
...userInfo,
extraInfo: {
...userInfo.extraInfo,
...extraUserInfo
}
};

await NativeDdSdk.addUserExtraInfo(extraUserInfo);
UserInfoSingleton.getInstance().setUserInfo(updatedUserInfo);
UserInfoSingleton.getInstance().addUserExtraInfo(extraUserInfo);
};

/**
Expand Down
24 changes: 21 additions & 3 deletions packages/core/src/__tests__/DdSdkReactNative.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,24 @@ describe('DdSdkReactNative', () => {
}
});
});

it('calls SDK method when addUserExtraInfo without prior setUserInfo', async () => {
// GIVEN
const extraInfo = { testId: 'abc123' };

// WHEN
await DdSdkReactNative.addUserExtraInfo(extraInfo);

// THEN
Comment thread
cdn34dd marked this conversation as resolved.
expect(NativeDdSdk.addUserExtraInfo).toHaveBeenCalledWith(
extraInfo
);
expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual({
extraInfo: {
testId: 'abc123'
}
});
});
});

describe('clearUserInfo', () => {
Expand All @@ -1320,9 +1338,9 @@ describe('DdSdkReactNative', () => {
// THEN
expect(NativeDdSdk.clearUserInfo).toHaveBeenCalledTimes(1);
expect(NativeDdSdk.setUserInfo).toHaveBeenCalled();
expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual(
undefined
);
expect(
UserInfoSingleton.getInstance().getUserInfo()
).toBeUndefined();
});
});

Expand Down
17 changes: 15 additions & 2 deletions packages/core/src/sdk/UserInfoSingleton/UserInfoSingleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,22 @@ import type { UserInfo } from './types';
class UserInfoProvider {
private userInfo: UserInfo | undefined = undefined;

setUserInfo = (userInfo: UserInfo) => {
setUserInfo = (userInfo: UserInfo & { id: string }) => {
if (typeof userInfo.id !== 'string' || userInfo.id.length === 0) {
return;
}
this.userInfo = userInfo;
Comment thread
cdn34dd marked this conversation as resolved.
setCachedUserId(this.userInfo.id);
setCachedUserId(userInfo.id);
};

addUserExtraInfo = (extraInfo: Record<string, unknown>) => {
this.userInfo = {
...this.userInfo,
extraInfo: {
...this.userInfo?.extraInfo,
...extraInfo
}
};
};

getUserInfo = (): UserInfo | undefined => {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/sdk/UserInfoSingleton/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

export type UserInfo = {
readonly id: string;
readonly id?: string;
readonly name?: string;
readonly email?: string;
readonly extraInfo?: Record<string, unknown>;
Expand Down
13 changes: 6 additions & 7 deletions packages/core/src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,12 @@ export type DdSdkType = {
removeAttributes(keys: string[]): Promise<void>;

/**
* Sets the user information.
* @param id: A unique user identifier (relevant to your business domain)
* @param name: The user name or alias.
* @param email: The user email.
* @param extraInfo: Additional information.
* Sets the user information. Requires a user ID — setting user properties
* like name or email implies a known user, so an ID must be provided.
* To add custom attributes without setting a user ID, use {@link addUserExtraInfo} instead.
* @param userInfo: The user object (id is required, name, email and extraInfo are optional).
*/
setUserInfo(userInfo: UserInfo): Promise<void>;
setUserInfo(userInfo: UserInfo & { id: string }): Promise<void>;

/**
* Clears the user information.
Expand Down Expand Up @@ -128,7 +127,7 @@ export type DdTraceType = {
// Core

export type UserInfo = {
id: string;
id?: string;
name?: string;
email?: string;
extraInfo?: object;
Expand Down
Loading