Skip to content
Draft
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
2 changes: 1 addition & 1 deletion packages/analytics-browser/src/browser-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient, An
// Set up the analytics connector to integrate with the experiment SDK.
// Send events from the experiment SDK and forward identifies to the
// identity store.
const connector = getAnalyticsConnector(options.instanceName);
const connector = getAnalyticsConnector(this.config.instanceName);
connector.identityStore.setIdentity({
userId: this.config.userId,
deviceId: this.config.deviceId,
Expand Down
4 changes: 2 additions & 2 deletions packages/analytics-browser/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class BrowserConfig extends Config implements IBrowserConfig {
public flushQueueSize: number = 30,
public identityStorage: IdentityStorageType = DEFAULT_IDENTITY_STORAGE,
public ingestionMetadata?: IngestionMetadata,
public instanceName?: string,
instanceName?: string,
lastEventId?: number,
lastEventTime?: number,
public loggerProvider: ILogger = new Logger(),
Expand Down Expand Up @@ -113,7 +113,7 @@ export class BrowserConfig extends Config implements IBrowserConfig {
public enableRequestBodyCompression: boolean = false,
public customEnrichment?: boolean | CustomEnrichmentOptions,
) {
super({ apiKey, storageProvider, transportProvider: createTransport(transport) });
super({ apiKey, instanceName, storageProvider, transportProvider: createTransport(transport) });
this._cookieStorage = cookieStorage;
this.deviceId = deviceId;
this.lastEventId = lastEventId;
Expand Down
52 changes: 52 additions & 0 deletions packages/analytics-browser/test/browser-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,58 @@ describe('browser-client', () => {
expect(identity.userId).toBe(userId);
});

test('should use the default connector for an empty instance name', async () => {
await client.init(apiKey, userId, {
optOut: true,
defaultTracking,
deviceId,
identityStorage: 'none',
instanceName: '',
}).promise;

const connector = getAnalyticsConnector();
expect(client.config.instanceName).toBe('$default_instance');
expect(getAnalyticsConnector('')).toBe(connector);
expect(connector.identityStore.getIdentity()).toMatchObject({ userId, deviceId });

const nextUserId = core.UUID();
const nextDeviceId = core.UUID();
client.setUserId(nextUserId);
client.setDeviceId(nextDeviceId);
expect(connector.identityStore.getIdentity()).toMatchObject({ userId: nextUserId, deviceId: nextDeviceId });

const track = jest.spyOn(client, 'track').mockReturnValueOnce({
promise: Promise.resolve({
code: 200,
message: '',
event: { event_type: 'experiment-exposure' },
}),
});
connector.eventBridge.logEvent({ eventType: 'experiment-exposure' });
expect(track).toHaveBeenCalledWith('experiment-exposure', {}, undefined);
});

test('should isolate a custom instance from the default connector', async () => {
const instanceName = `custom-${core.UUID()}`;
const defaultConnector = getAnalyticsConnector();
defaultConnector.identityStore.setIdentity({ userId: 'default-user', deviceId: 'default-device' });

await client.init(apiKey, userId, {
optOut: true,
defaultTracking,
deviceId,
identityStorage: 'none',
instanceName,
}).promise;

expect(client.config.instanceName).toBe(instanceName);
expect(getAnalyticsConnector(instanceName).identityStore.getIdentity()).toMatchObject({ userId, deviceId });
expect(defaultConnector.identityStore.getIdentity()).toMatchObject({
userId: 'default-user',
deviceId: 'default-device',
});
});

test('should set up event bridge and track events', async () => {
await client.init(apiKey, userId, {
optOut: false,
Expand Down
28 changes: 28 additions & 0 deletions packages/analytics-browser/test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ describe('config', () => {
},
defaultTracking: undefined,
identityStorage: DEFAULT_IDENTITY_STORAGE,
instanceName: '$default_instance',
flushIntervalMillis: 1000,
flushMaxRetries: 5,
flushQueueSize: 30,
Expand Down Expand Up @@ -98,6 +99,31 @@ describe('config', () => {
expect(config.fetchRemoteConfig).toBe(true);
expect(config.remoteConfig?.fetchRemoteConfig).toBe(true);
});

test.each([
[undefined, '$default_instance'],
['', '$default_instance'],
['custom-instance', 'custom-instance'],
[' ', ' '],
])('should retain normalized instance name %p as %p', (instanceName, expected) => {
const config = new Config.BrowserConfig(
apiKey,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
instanceName,
);

expect(config.instanceName).toBe(expected);
});
});

describe('useBrowserConfig', () => {
Expand Down Expand Up @@ -125,6 +151,7 @@ describe('config', () => {
},
defaultTracking: undefined,
identityStorage: DEFAULT_IDENTITY_STORAGE,
instanceName: '$default_instance',
flushIntervalMillis: 1000,
flushMaxRetries: 5,
flushQueueSize: 30,
Expand Down Expand Up @@ -251,6 +278,7 @@ describe('config', () => {
flushMaxRetries: 5,
flushQueueSize: 30,
identityStorage: DEFAULT_IDENTITY_STORAGE,
instanceName: '$default_instance',
ingestionMetadata: {
sourceName: 'ampli',
sourceVersion: '2.0.0',
Expand Down
6 changes: 3 additions & 3 deletions packages/analytics-core/src/analytics-connector.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AnalyticsConnector } from '@amplitude/analytics-connector';
import { DEFAULT_INSTANCE_NAME } from './types/constants';
import { normalizeInstanceName } from './utils/instance-name';

export const getAnalyticsConnector = (instanceName = DEFAULT_INSTANCE_NAME): AnalyticsConnector => {
return AnalyticsConnector.getInstance(instanceName);
export const getAnalyticsConnector = (instanceName?: string): AnalyticsConnector => {
return AnalyticsConnector.getInstance(normalizeInstanceName(instanceName));
};

export const setConnectorUserId = (userId: string | undefined, instanceName?: string): void => {
Expand Down
3 changes: 2 additions & 1 deletion packages/analytics-core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { Logger, ILogger } from './logger';
import { LogLevel } from './types/loglevel';
import { ConfigOptions, IRequestMetadata, IHistogramOptions, HistogramKey, IConfig } from './types/config/core-config';
import { normalizeInstanceName } from './utils/instance-name';

export const getDefaultConfig = () => ({
flushMaxRetries: 12,
Expand Down Expand Up @@ -63,7 +64,7 @@ export class Config implements IConfig {
this.flushIntervalMillis = options.flushIntervalMillis ?? defaultConfig.flushIntervalMillis;
this.flushMaxRetries = options.flushMaxRetries || defaultConfig.flushMaxRetries;
this.flushQueueSize = options.flushQueueSize || defaultConfig.flushQueueSize;
this.instanceName = options.instanceName || defaultConfig.instanceName;
this.instanceName = normalizeInstanceName(options.instanceName);
this.loggerProvider = options.loggerProvider || defaultConfig.loggerProvider;
this.logLevel = options.logLevel ?? defaultConfig.logLevel;
this.minIdLength = options.minIdLength;
Expand Down
1 change: 1 addition & 0 deletions packages/analytics-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { IConfig } from './types/config/core-config';
export { Logger, ILogger, LogConfig } from './logger';
export { getGlobalScope } from './global-scope';
export { getAnalyticsConnector, setConnectorDeviceId, setConnectorUserId } from './analytics-connector';
export { normalizeInstanceName } from './utils/instance-name';
export { isNewSession } from './session';
export { getCookieName, getOldCookieName } from './cookie-name';
export { getLanguage } from './language';
Expand Down
5 changes: 5 additions & 0 deletions packages/analytics-core/src/utils/instance-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { DEFAULT_INSTANCE_NAME } from '../types/constants';

export const normalizeInstanceName = (instanceName?: string): string => {
return instanceName === undefined || instanceName === '' ? DEFAULT_INSTANCE_NAME : instanceName;
};
14 changes: 14 additions & 0 deletions packages/analytics-core/test/analytics-connector.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AnalyticsConnector } from '@amplitude/analytics-connector';
import { getAnalyticsConnector, setConnectorDeviceId, setConnectorUserId } from '../src/analytics-connector';
import { DEFAULT_INSTANCE_NAME } from '../src/types/constants';

describe('analytics-connector', () => {
describe('getAnalyticsConnector', () => {
Expand All @@ -9,6 +10,19 @@ describe('analytics-connector', () => {
expect(getAnalyticsConnector()).toBe(instance);
expect(getInstance).toHaveBeenCalledTimes(1);
});

test.each([
[undefined, DEFAULT_INSTANCE_NAME],
['', DEFAULT_INSTANCE_NAME],
['custom-instance', 'custom-instance'],
[' ', ' '],
])('should normalize instance name %p to %p', (instanceName, expected) => {
const instance = new AnalyticsConnector();
const getInstance = jest.spyOn(AnalyticsConnector, 'getInstance').mockReturnValueOnce(instance);

expect(getAnalyticsConnector(instanceName)).toBe(instance);
expect(getInstance).toHaveBeenCalledWith(expected);
});
});

describe('setConnectorUserId', () => {
Expand Down
17 changes: 17 additions & 0 deletions packages/analytics-core/test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,23 @@ describe('config', () => {
});
expect(config.flushIntervalMillis).toEqual(0);
});

test.each([
[undefined, DEFAULT_INSTANCE_NAME],
['', DEFAULT_INSTANCE_NAME],
['custom-instance', 'custom-instance'],
[' ', ' '],
])('should normalize instance name %p to %p', (instanceName, expected) => {
const defaultConfig = useDefaultConfig();
const config = new Config({
apiKey: API_KEY,
instanceName,
storageProvider: defaultConfig.storageProvider,
transportProvider: defaultConfig.transportProvider,
});

expect(config.instanceName).toBe(expected);
});
});

describe('RequestMetadata', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/analytics-core/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
RequestMetadata,
getGlobalScope,
getAnalyticsConnector,
normalizeInstanceName,
setConnectorDeviceId,
setConnectorUserId,
isNewSession,
Expand Down Expand Up @@ -112,6 +113,7 @@ describe('index', () => {
expect(typeof getStorageKey).toBe('function');
expect(typeof getGlobalScope).toBe('function');
expect(typeof getAnalyticsConnector).toBe('function');
expect(typeof normalizeInstanceName).toBe('function');
expect(typeof setConnectorDeviceId).toBe('function');
expect(typeof setConnectorUserId).toBe('function');
expect(typeof isNewSession).toBe('function');
Expand Down
6 changes: 5 additions & 1 deletion packages/plugin-experiment-browser/src/experiment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ export class ExperimentPlugin implements EnrichmentPlugin<BrowserClient, Browser
}

async setup(config: BrowserConfig, _client: BrowserClient) {
this.experiment = initializeWithAmplitudeAnalytics(this.config?.deploymentKey || config.apiKey, this.config);
const experimentConfig =
this.config?.instanceName !== undefined || config.instanceName === undefined
? this.config
: { ...this.config, instanceName: config.instanceName };
this.experiment = initializeWithAmplitudeAnalytics(this.config?.deploymentKey || config.apiKey, experimentConfig);
}
}

Expand Down
32 changes: 30 additions & 2 deletions packages/plugin-experiment-browser/test/experiment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('ExperimentPlugin', () => {
language: true,
platform: true,
},
instanceName: '$default_instance',
} as unknown as BrowserConfig;
const mockAmplitude: MockedBrowserClient = {
add: jest.fn(),
Expand Down Expand Up @@ -86,7 +87,10 @@ describe('ExperimentPlugin', () => {
(initializeWithAmplitudeAnalytics as jest.Mock).mockReturnValue(mockExperimentClient);
const plugin = new ExperimentPlugin(experimentConfig);
await plugin.setup(mockConfig, mockAmplitude);
expect(initializeWithAmplitudeAnalytics).toHaveBeenCalledWith(mockConfig.apiKey, experimentConfig);
expect(initializeWithAmplitudeAnalytics).toHaveBeenCalledWith(mockConfig.apiKey, {
...(experimentConfig ?? {}),
instanceName: mockConfig.instanceName,
});
expect(plugin.experiment).toBe(mockExperimentClient);
});

Expand All @@ -99,9 +103,33 @@ describe('ExperimentPlugin', () => {
(initializeWithAmplitudeAnalytics as jest.Mock).mockReturnValue(mockExperimentClient);
const plugin = new ExperimentPlugin(experimentConfig);
await plugin.setup(mockConfig, mockAmplitude);
expect(initializeWithAmplitudeAnalytics).toHaveBeenCalledWith(experimentConfig.deploymentKey, experimentConfig);
expect(initializeWithAmplitudeAnalytics).toHaveBeenCalledWith(experimentConfig.deploymentKey, {
...experimentConfig,
instanceName: mockConfig.instanceName,
});
expect(plugin.experiment).toBe(mockExperimentClient);
});

test('should inherit a custom analytics instance name', async () => {
const plugin = new ExperimentPlugin({ debug: true });
await plugin.setup({ ...mockConfig, instanceName: 'analytics-instance' }, mockAmplitude);

expect(initializeWithAmplitudeAnalytics).toHaveBeenCalledWith(mockConfig.apiKey, {
debug: true,
instanceName: 'analytics-instance',
});
});

test.each(['experiment-instance', ''])(
'should preserve an explicit experiment instance name %p',
async (instanceName) => {
const experimentConfig: ExperimentPluginConfig = { instanceName };
const plugin = new ExperimentPlugin(experimentConfig);
await plugin.setup({ ...mockConfig, instanceName: 'analytics-instance' }, mockAmplitude);

expect(initializeWithAmplitudeAnalytics).toHaveBeenCalledWith(mockConfig.apiKey, experimentConfig);
},
);
});

describe('experimentPlugin', () => {
Expand Down
Loading