From f66769cca243019354f88ac9dc8de07caf9de56e Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 15 May 2024 14:11:21 -0400 Subject: [PATCH] Fix setConsent to properly use the `update` gtag command (#8243) We wrap the gtag function internally and pass setConsent calls through the wrapper function, which in turn calls the gtag SDK. While we invoke the wrapped function with parameters identical to those listed in the gtag functions, we were dropping the update string on the floor inside the wrapped function. Additionally, we were feeding the consentSettings as the second parameter to gag, which should have been the update string parameter. This ended up clobbering the data, and not send update commands properly. This change fixes our wrappedGtag func impl to properly pass update to gtag, along with the provided ConsentSettings argument. Closes #8210 --- .changeset/cold-brooms-run.md | 5 ++ packages/analytics/src/helpers.test.ts | 112 +++++++++++++++++++------ packages/analytics/src/helpers.ts | 9 +- 3 files changed, 99 insertions(+), 27 deletions(-) create mode 100644 .changeset/cold-brooms-run.md diff --git a/.changeset/cold-brooms-run.md b/.changeset/cold-brooms-run.md new file mode 100644 index 00000000000..acedccf16cb --- /dev/null +++ b/.changeset/cold-brooms-run.md @@ -0,0 +1,5 @@ +--- +'@firebase/analytics': patch +--- + +Analytics - fixed an issue where setConsent was clobbering the consentSettings before passing them to the gtag implementation. diff --git a/packages/analytics/src/helpers.test.ts b/packages/analytics/src/helpers.test.ts index b35a3f6867b..ff06ba3ea6c 100644 --- a/packages/analytics/src/helpers.test.ts +++ b/packages/analytics/src/helpers.test.ts @@ -175,10 +175,11 @@ describe('Gtag wrapping functions', () => { 'gtag' ); window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + const eventObject = { 'transaction_id': 'abcd123', 'send_to': 'some_group' - }); + }; + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', eventObject); expect((window['dataLayer'] as DataLayer).length).to.equal(0); initPromise1.resolve(fakeMeasurementId); // Resolves first initialization promise. @@ -187,8 +188,12 @@ describe('Gtag wrapping functions', () => { initPromise2.resolve('other-measurement-id'); // Resolves second initialization promise. await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() await promiseAllSettled(fakeDynamicConfigPromises); - - expect((window['dataLayer'] as DataLayer).length).to.equal(1); + const dataLayer = window['dataLayer'] as DataLayer; + expect(dataLayer.length).to.equal(1); + const data = dataLayer[0]; + expect(data[0]).to.equal('event'); + expect(data[1]).to.equal('purchase'); + expect(data[2]).to.equal(eventObject); }); it( @@ -208,10 +213,11 @@ describe('Gtag wrapping functions', () => { 'gtag' ); window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + const eventObject = { 'transaction_id': 'abcd123', 'send_to': [fakeMeasurementId, 'some_group'] - }); + }; + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', eventObject); expect((window['dataLayer'] as DataLayer).length).to.equal(0); initPromise1.resolve(); // Resolves first initialization promise. @@ -221,7 +227,12 @@ describe('Gtag wrapping functions', () => { await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() await promiseAllSettled(fakeDynamicConfigPromises); - expect((window['dataLayer'] as DataLayer).length).to.equal(1); + const dataLayer = window['dataLayer'] as DataLayer; + expect(dataLayer.length).to.equal(1); + const data = dataLayer[0]; + expect(data[0]).to.equal('event'); + expect(data[1]).to.equal('purchase'); + expect(data[2]).to.equal(eventObject); } ); @@ -242,9 +253,10 @@ describe('Gtag wrapping functions', () => { 'gtag' ); window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + const eventObject = { 'transaction_id': 'abcd123' - }); + }; + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', eventObject); expect((window['dataLayer'] as DataLayer).length).to.equal(0); initPromise1.resolve(); // Resolves first initialization promise. @@ -253,7 +265,12 @@ describe('Gtag wrapping functions', () => { initPromise2.resolve(); // Resolves second initialization promise. await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() - expect((window['dataLayer'] as DataLayer).length).to.equal(1); + const dataLayer = window['dataLayer'] as DataLayer; + expect(dataLayer.length).to.equal(1); + const data = dataLayer[0]; + expect(data[0]).to.equal('event'); + expect(data[1]).to.equal('purchase'); + expect(data[2]).to.equal(eventObject); } ); @@ -274,17 +291,23 @@ describe('Gtag wrapping functions', () => { 'gtag' ); window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + const eventObject = { 'transaction_id': 'abcd123', 'send_to': fakeMeasurementId - }); + }; + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', eventObject); expect((window['dataLayer'] as DataLayer).length).to.equal(0); initPromise1.resolve(); // Resolves first initialization promise. await promiseAllSettled(fakeDynamicConfigPromises); await Promise.all([initPromise1]); // Wait for resolution of Promise.all() - expect((window['dataLayer'] as DataLayer).length).to.equal(1); + const dataLayer = window['dataLayer'] as DataLayer; + expect(dataLayer.length).to.equal(1); + const data = dataLayer[0]; + expect(data[0]).to.equal('event'); + expect(data[1]).to.equal('purchase'); + expect(data[2]).to.equal(eventObject); } ); @@ -307,8 +330,13 @@ describe('Gtag wrapping functions', () => { 'gtag' ); window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.SET, { 'language': 'en' }); - expect((window['dataLayer'] as DataLayer).length).to.equal(1); + const eventObject = { 'language': 'en' }; + (window['gtag'] as Gtag)(GtagCommand.SET, eventObject); + const dataLayer = window['dataLayer'] as DataLayer; + expect(dataLayer.length).to.equal(1); + const data = dataLayer[0]; + expect(data[0]).to.equal('set'); + expect(data[1]).to.equal(eventObject); }); it('new window.gtag function does not wait when sending "consent" calls', async () => { @@ -329,7 +357,12 @@ describe('Gtag wrapping functions', () => { 'update', consentParameters ); - expect((window['dataLayer'] as DataLayer).length).to.equal(1); + const dataLayer = window['dataLayer'] as DataLayer; + expect(dataLayer.length).to.equal(1); + const data = dataLayer[0]; + expect(data[0]).to.equal('consent'); + expect(data[1]).to.equal('update'); + expect(data[2]).to.equal(consentParameters); }); it('new window.gtag function does not wait when sending "get" calls', async () => { @@ -347,7 +380,13 @@ describe('Gtag wrapping functions', () => { 'client_id', clientId => console.log(clientId) ); - expect((window['dataLayer'] as DataLayer).length).to.equal(1); + const dataLayer = window['dataLayer'] as DataLayer; + expect(dataLayer.length).to.equal(1); + const data = dataLayer[0]; + expect(data[0]).to.equal('get'); + expect(data[1]).to.equal(fakeMeasurementId); + expect(data[2]).to.equal('client_id'); + expect(data[3]).to.not.be.undefined; }); it('new window.gtag function does not wait when sending an unknown command', async () => { @@ -360,7 +399,11 @@ describe('Gtag wrapping functions', () => { ); window['dataLayer'] = []; (window['gtag'] as Gtag)('new-command-from-gtag-team', fakeMeasurementId); - expect((window['dataLayer'] as DataLayer).length).to.equal(1); + const dataLayer = window['dataLayer'] as DataLayer; + expect(dataLayer.length).to.equal(1); + const data = dataLayer[0]; + expect(data[0]).to.equal('new-command-from-gtag-team'); + expect(data[1]).to.equal(fakeMeasurementId); }); it('new window.gtag function waits for initialization promise when sending "config" calls', async () => { @@ -373,9 +416,14 @@ describe('Gtag wrapping functions', () => { 'gtag' ); window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.CONFIG, fakeMeasurementId, { + const eventObject = { 'language': 'en' - }); + }; + (window['gtag'] as Gtag)( + GtagCommand.CONFIG, + fakeMeasurementId, + eventObject + ); expect((window['dataLayer'] as DataLayer).length).to.equal(0); initPromise1.resolve(fakeMeasurementId); @@ -383,19 +431,33 @@ describe('Gtag wrapping functions', () => { expect((window['dataLayer'] as DataLayer).length).to.equal(0); await Promise.all([initPromise1]); // Wait for resolution of Promise.all() - - expect((window['dataLayer'] as DataLayer).length).to.equal(1); + const dataLayer = window['dataLayer'] as DataLayer; + expect(dataLayer.length).to.equal(1); + const data = dataLayer[0]; + expect(data[0]).to.equal('config'); + expect(data[1]).to.equal(fakeMeasurementId); + expect(data[2]).to.equal(eventObject); }); it('new window.gtag function does not wait when sending "config" calls if there are no pending initialization promises', async () => { wrapOrCreateGtag({}, fakeDynamicConfigPromises, {}, 'dataLayer', 'gtag'); window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.CONFIG, fakeMeasurementId, { + const eventObject = { 'transaction_id': 'abcd123' - }); + }; + (window['gtag'] as Gtag)( + GtagCommand.CONFIG, + fakeMeasurementId, + eventObject + ); await promiseAllSettled(fakeDynamicConfigPromises); await Promise.resolve(); // Config call is always chained onto initialization promise list, even if empty. - expect((window['dataLayer'] as DataLayer).length).to.equal(1); + const dataLayer = window['dataLayer'] as DataLayer; + expect(dataLayer.length).to.equal(1); + const data = dataLayer[0]; + expect(data[0]).to.equal('config'); + expect(data[1]).to.equal(fakeMeasurementId); + expect(data[2]).to.equal(eventObject); }); }); diff --git a/packages/analytics/src/helpers.ts b/packages/analytics/src/helpers.ts index 7f9582900c2..2e9a46e03b2 100644 --- a/packages/analytics/src/helpers.ts +++ b/packages/analytics/src/helpers.ts @@ -304,8 +304,13 @@ function wrapGtag( gtagParams as GtagConfigOrEventParams ); } else if (command === GtagCommand.CONSENT) { - const [gtagParams] = args; - gtagCore(GtagCommand.CONSENT, 'update', gtagParams as ConsentSettings); + const [consentAction, gtagParams] = args; + // consentAction can be one of 'default' or 'update'. + gtagCore( + GtagCommand.CONSENT, + consentAction, + gtagParams as ConsentSettings + ); } else if (command === GtagCommand.GET) { const [measurementId, fieldName, callback] = args; gtagCore(