Skip to content

Commit

Permalink
Fix setConsent to properly use the update gtag command (#8243)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
DellaBitta committed May 15, 2024
1 parent 2ce9569 commit f66769c
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 27 deletions.
5 changes: 5 additions & 0 deletions .changeset/cold-brooms-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@firebase/analytics': patch
---

Analytics - fixed an issue where setConsent was clobbering the consentSettings before passing them to the gtag implementation.
112 changes: 87 additions & 25 deletions packages/analytics/src/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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(
Expand All @@ -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.
Expand All @@ -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);
}
);

Expand All @@ -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.
Expand All @@ -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);
}
);

Expand All @@ -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);
}
);

Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -373,29 +416,48 @@ 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);
await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches.
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);
});
});

Expand Down
9 changes: 7 additions & 2 deletions packages/analytics/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit f66769c

Please sign in to comment.