diff --git a/.changeset/silly-panthers-mix.md b/.changeset/silly-panthers-mix.md new file mode 100644 index 00000000000..e671bfe6d8f --- /dev/null +++ b/.changeset/silly-panthers-mix.md @@ -0,0 +1,5 @@ +--- +'@firebase/analytics': minor +--- + +Add function `setDefaultEventParameters()` to set data that will be logged on every Analytics SDK event diff --git a/common/api-review/analytics.api.md b/common/api-review/analytics.api.md index 00d2cac0ce0..851e26c23cf 100644 --- a/common/api-review/analytics.api.md +++ b/common/api-review/analytics.api.md @@ -391,6 +391,9 @@ export function setAnalyticsCollectionEnabled(analyticsInstance: Analytics, enab // @public @deprecated export function setCurrentScreen(analyticsInstance: Analytics, screenName: string, options?: AnalyticsCallOptions): void; +// @public +export function setDefaultEventParameters(customParams: CustomParams): void; + // @public export function settings(options: SettingsOptions): void; diff --git a/packages/analytics/src/api.test.ts b/packages/analytics/src/api.test.ts index 573468a715e..a9a936ea373 100644 --- a/packages/analytics/src/api.test.ts +++ b/packages/analytics/src/api.test.ts @@ -19,15 +19,22 @@ import { expect } from 'chai'; import { SinonStub, stub } from 'sinon'; import '../testing/setup'; import { getFullApp } from '../testing/get-fake-firebase-services'; -import { getAnalytics, initializeAnalytics } from './api'; +import { + getAnalytics, + initializeAnalytics, + setDefaultEventParameters +} from './api'; import { FirebaseApp, deleteApp } from '@firebase/app'; import { AnalyticsError } from './errors'; import * as init from './initialize-analytics'; const fakeAppParams = { appId: 'abcdefgh12345:23405', apiKey: 'AAbbCCdd12345' }; +import * as factory from './factory'; +import { defaultEventParametersForInit } from './functions'; describe('FirebaseAnalytics API tests', () => { let initStub: SinonStub = stub(); let app: FirebaseApp; + const wrappedGtag: SinonStub = stub(); beforeEach(() => { initStub = stub(init, '_initializeAnalytics').resolves( @@ -94,4 +101,26 @@ describe('FirebaseAnalytics API tests', () => { const analyticsInstance = initializeAnalytics(app); expect(getAnalytics(app)).to.equal(analyticsInstance); }); + it('setDefaultEventParameters() updates defaultEventParametersForInit if gtag does not exist ', () => { + const eventParametersForInit = { + 'github_user': 'dwyfrequency', + 'company': 'google' + }; + app = getFullApp(fakeAppParams); + setDefaultEventParameters(eventParametersForInit); + expect(defaultEventParametersForInit).to.deep.equal(eventParametersForInit); + }); + it('setDefaultEventParameters() calls gtag set if wrappedGtagFunction exists', () => { + const eventParametersForInit = { + 'github_user': 'dwyfrequency', + 'company': 'google' + }; + stub(factory, 'wrappedGtagFunction').get(() => wrappedGtag); + app = getFullApp(fakeAppParams); + setDefaultEventParameters(eventParametersForInit); + expect(wrappedGtag).to.have.been.calledWithExactly( + 'set', + eventParametersForInit + ); + }); }); diff --git a/packages/analytics/src/api.ts b/packages/analytics/src/api.ts index 41a5f2166a3..4e3888bd724 100644 --- a/packages/analytics/src/api.ts +++ b/packages/analytics/src/api.ts @@ -35,7 +35,7 @@ import { getModularInstance, deepEqual } from '@firebase/util'; -import { ANALYTICS_TYPE } from './constants'; +import { ANALYTICS_TYPE, GtagCommand } from './constants'; import { AnalyticsService, initializationPromisesMap, @@ -47,7 +47,8 @@ import { setCurrentScreen as internalSetCurrentScreen, setUserId as internalSetUserId, setUserProperties as internalSetUserProperties, - setAnalyticsCollectionEnabled as internalSetAnalyticsCollectionEnabled + setAnalyticsCollectionEnabled as internalSetAnalyticsCollectionEnabled, + _setDefaultEventParametersForInit } from './functions'; import { ERROR_FACTORY, AnalyticsError } from './errors'; @@ -224,6 +225,23 @@ export function setAnalyticsCollectionEnabled( enabled ).catch(e => logger.error(e)); } + +/** + * Adds data that will be set on every event logged from the SDK, including automatic ones. + * With gtag's "set" command, the values passed persist on the current page and are passed with + * all subsequent events. + * @public + * @param customParams Any custom params the user may pass to gtag.js. + */ +export function setDefaultEventParameters(customParams: CustomParams): void { + // Check if reference to existing gtag function on window object exists + if (wrappedGtagFunction) { + wrappedGtagFunction(GtagCommand.SET, customParams); + } else { + _setDefaultEventParametersForInit(customParams); + } +} + /** * Sends a Google Analytics event with given `eventParams`. This method * automatically associates this logged event with this Firebase web diff --git a/packages/analytics/src/functions.test.ts b/packages/analytics/src/functions.test.ts index 9c5928b3a49..0f806849f72 100644 --- a/packages/analytics/src/functions.test.ts +++ b/packages/analytics/src/functions.test.ts @@ -23,7 +23,9 @@ import { logEvent, setUserId, setUserProperties, - setAnalyticsCollectionEnabled + setAnalyticsCollectionEnabled, + defaultEventParametersForInit, + _setDefaultEventParametersForInit } from './functions'; import { GtagCommand } from './constants'; @@ -170,4 +172,24 @@ describe('FirebaseAnalytics methods', () => { expect(window[`ga-disable-${fakeMeasurementId}`]).to.be.true; delete window[`ga-disable-${fakeMeasurementId}`]; }); + it('_setDefaultEventParametersForInit() stores individual params correctly', async () => { + const eventParametersForInit = { + 'github_user': 'dwyfrequency', + 'company': 'google' + }; + _setDefaultEventParametersForInit(eventParametersForInit); + expect(defaultEventParametersForInit).to.deep.equal(eventParametersForInit); + }); + it('_setDefaultEventParametersForInit() replaces previous params with new params', async () => { + const eventParametersForInit = { + 'github_user': 'dwyfrequency', + 'company': 'google' + }; + const additionalParams = { 'food': 'sushi' }; + _setDefaultEventParametersForInit(eventParametersForInit); + _setDefaultEventParametersForInit(additionalParams); + expect(defaultEventParametersForInit).to.deep.equal({ + ...additionalParams + }); + }); }); diff --git a/packages/analytics/src/functions.ts b/packages/analytics/src/functions.ts index f35b61d98cd..535b3bd530b 100644 --- a/packages/analytics/src/functions.ts +++ b/packages/analytics/src/functions.ts @@ -23,6 +23,12 @@ import { } from './public-types'; import { Gtag } from './types'; import { GtagCommand } from './constants'; + +/** + * Event parameters to set on 'gtag' during initialization. + */ +export let defaultEventParametersForInit: CustomParams | undefined; + /** * Logs an analytics event through the Firebase SDK. * @@ -142,3 +148,15 @@ export async function setAnalyticsCollectionEnabled( const measurementId = await initializationPromise; window[`ga-disable-${measurementId}`] = !enabled; } + +/** + * Sets the variable `defaultEventParametersForInit` for use in the initialization of + * analytics. + * + * @param customParams Any custom params the user may pass to gtag.js. + */ +export function _setDefaultEventParametersForInit( + customParams?: CustomParams +): void { + defaultEventParametersForInit = customParams; +} diff --git a/packages/analytics/src/initialize-analytics.test.ts b/packages/analytics/src/initialize-analytics.test.ts index 34c799fc83c..2ed8053bdad 100644 --- a/packages/analytics/src/initialize-analytics.test.ts +++ b/packages/analytics/src/initialize-analytics.test.ts @@ -29,6 +29,8 @@ import { FirebaseApp } from '@firebase/app'; import { Deferred } from '@firebase/util'; import { _FirebaseInstallationsInternal } from '@firebase/installations'; import { removeGtagScript } from '../testing/gtag-script-util'; +import { setDefaultEventParameters } from './api'; +import { defaultEventParametersForInit } from './functions'; const fakeMeasurementId = 'abcd-efgh-ijkl'; const fakeFid = 'fid-1234-zyxw'; @@ -97,6 +99,25 @@ describe('initializeAnalytics()', () => { 'send_page_view': false }); }); + it('calls gtag set if there are default event parameters', async () => { + stubFetch(); + const eventParametersForInit = { + 'github_user': 'dwyfrequency', + 'company': 'google' + }; + setDefaultEventParameters(eventParametersForInit); + await _initializeAnalytics( + app, + dynamicPromisesList, + measurementIdToAppId, + fakeInstallations, + gtagStub, + 'dataLayer' + ); + expect(gtagStub).to.be.calledWith(GtagCommand.SET, eventParametersForInit); + // defaultEventParametersForInit is reset after initialization. + expect(defaultEventParametersForInit).to.equal(undefined); + }); it('puts dynamic fetch promise into dynamic promises list', async () => { stubFetch(); await _initializeAnalytics( diff --git a/packages/analytics/src/initialize-analytics.ts b/packages/analytics/src/initialize-analytics.ts index e38f939b3d7..cdd73378f55 100644 --- a/packages/analytics/src/initialize-analytics.ts +++ b/packages/analytics/src/initialize-analytics.ts @@ -28,6 +28,10 @@ import { import { ERROR_FACTORY, AnalyticsError } from './errors'; import { findGtagScriptOnPage, insertScriptTag } from './helpers'; import { AnalyticsSettings } from './public-types'; +import { + defaultEventParametersForInit, + _setDefaultEventParametersForInit +} from './functions'; async function validateIndexedDB(): Promise { if (!isIndexedDBAvailable()) { @@ -140,5 +144,12 @@ export async function _initializeAnalytics( // Note: This will trigger a page_view event unless 'send_page_view' is set to false in // `configProperties`. gtagCore(GtagCommand.CONFIG, dynamicConfig.measurementId, configProperties); + + // Detects if there is data that will be set on every event logged from the SDK. + if (defaultEventParametersForInit) { + gtagCore(GtagCommand.SET, defaultEventParametersForInit); + _setDefaultEventParametersForInit(undefined); + } + return dynamicConfig.measurementId; }