Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement/9379 detected lost events resolver #9487

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
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
192 changes: 162 additions & 30 deletions assets/js/modules/analytics-4/datastore/conversion-reporting.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,32 @@
* limitations under the License.
*/

/**
* External dependencies
*/
import invariant from 'invariant';

/**
* Internal dependencies
*/
import API from 'googlesitekit-api';
import { combineStores, createRegistrySelector } from 'googlesitekit-data';
import {
commonActions,
combineStores,
createRegistrySelector,
} from 'googlesitekit-data';
import { MODULES_ANALYTICS_4 } from './constants';
import { createFetchStore } from '../../../googlesitekit/data/create-fetch-store';

export const selectors = {
/**
* Checks whether the provided conversion reporting events are available.
*
* @since 1.135.0
*
* @param {Object} state Data store's state.
* @param {string|Array<string>} events Conversion reporting events to check.
* @return {(boolean|undefined)} True if all provided custom dimensions are available, otherwise false. Undefined if available custom dimensions are not loaded yet.
*/
hasConversionReportingEvents: createRegistrySelector(
( select ) => ( state, events ) => {
// Ensure events is always an array, even if a string is passed.
const eventsToCheck = Array.isArray( events ) ? events : [ events ];

const detectedEvents =
select( MODULES_ANALYTICS_4 ).getDetectedEvents();

if ( ! detectedEvents?.length ) {
return false;
}

return eventsToCheck.some( ( event ) =>
detectedEvents.includes( event )
);
}
),
};
function getInlineDataProperty( propName ) {
return createRegistrySelector( ( select ) => () => {
const inlineData =
select(
MODULES_ANALYTICS_4
).getConversionReportingEventsChange() || {};
return inlineData[ propName ];
} );
}

const dismissNewConversionReportingEventsStore = createFetchStore( {
baseName: 'dismissNewConversionReportingEvents',
Expand All @@ -75,7 +65,42 @@ const dismissLostConversionReportingEventsStore = createFetchStore( {
},
} );

const actions = {
// Actions.
const RECEIVE_CONVERSION_REPORTING_INLINE_DATA =
'RECEIVE_CONVERSION_REPORTING_INLINE_DATA';

export const initialState = {
detectedEventsChange: undefined,
};

export const resolvers = {
*getConversionReportingEventsChange() {
const registry = yield commonActions.getRegistry();

if (
registry
.select( MODULES_ANALYTICS_4 )
.getConversionReportingEventsChange()
) {
return;
}

if ( ! global._googlesitekitModulesData ) {
global.console.error( 'Could not load modules data.' );
return;
}

const { newEvents, lostEvents } =
global._googlesitekitModulesData[ 'analytics-4' ];

yield actions.receiveConversionReportingInlineData( {
newEvents,
lostEvents,
} );
},
};

export const actions = {
/**
* Dismiss new conversion reporting events.
*
Expand All @@ -86,6 +111,7 @@ const actions = {
dismissNewConversionReportingEvents() {
return dismissNewConversionReportingEventsStore.actions.fetchDismissNewConversionReportingEvents();
},

/**
* Dismiss lost conversion reporting events.
*
Expand All @@ -96,13 +122,119 @@ const actions = {
dismissLostConversionReportingEvents() {
return dismissLostConversionReportingEventsStore.actions.fetchDismissLostConversionReportingEvents();
},

/**
* Stores conversion reporting inline data in the datastore.
*
* @since n.e.x.t
* @private
*
* @param {Object} data Inline data, usually supplied via a global variable from PHP.
* @return {Object} Redux-style action.
*/
receiveConversionReportingInlineData( data ) {
invariant( data, 'data is required.' );

return {
payload: { data },
type: RECEIVE_CONVERSION_REPORTING_INLINE_DATA,
};
},
};

export const reducer = ( state, { payload, type } ) => {
switch ( type ) {
case RECEIVE_CONVERSION_REPORTING_INLINE_DATA: {
const { newEvents, lostEvents } = payload.data;

return {
...state,
detectedEventsChange: {
newEvents,
lostEvents,
},
};
}

default: {
return state;
}
}
};

export const selectors = {
/**
* Checks whether the provided conversion reporting events are available.
*
* @since 1.135.0
*
* @param {Object} state Data store's state.
* @param {string|Array<string>} events Conversion reporting events to check.
* @return {(boolean|undefined)} True if all provided custom dimensions are available, otherwise false. Undefined if available custom dimensions are not loaded yet.
*/
hasConversionReportingEvents: createRegistrySelector(
( select ) => ( state, events ) => {
// Ensure events is always an array, even if a string is passed.
const eventsToCheck = Array.isArray( events ) ? events : [ events ];

const detectedEvents =
select( MODULES_ANALYTICS_4 ).getDetectedEvents();

if ( ! detectedEvents?.length ) {
return false;
}

return eventsToCheck.some( ( event ) =>
detectedEvents.includes( event )
);
}
),

/**
* Gets all conversion reporting inline data from this data store.
*
* Not intended to be used publicly; this is largely here so other selectors can
* request data using the selector/resolver pattern.
*
* @since n.e.x.t
* @private
*
* @param {Object} state Data store's state.
* @return {(Object|undefined)} Conversion reporting inline data.
*/
getConversionReportingEventsChange( state ) {
return state.detectedEventsChange;
},

/**
* Gets newEvents list.
*
* @since n.e.x.t
*
* @param {Object} state Data store's state.
* @return {(Array|undefined)} `newEvents` list.
*/
hasNewConversionReportingEvents: getInlineDataProperty( 'newEvents' ),

/**
* Gets lostEvents list.
*
* @since n.e.x.t
*
* @param {Object} state Data store's state.
* @return {(Array|undefined)} `lostEvents` list.
*/
hasLostConversionReportingEvents: getInlineDataProperty( 'lostEvents' ),
};

export default combineStores(
dismissNewConversionReportingEventsStore,
dismissLostConversionReportingEventsStore,
{
initialState,
actions,
resolvers,
selectors,
reducer,
}
);
102 changes: 102 additions & 0 deletions assets/js/modules/analytics-4/datastore/conversion-reporting.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
createTestRegistry,
provideModules,
provideUserAuthentication,
untilResolved,
} from '../../../../../tests/js/utils';

describe( 'modules/analytics-4 conversion-reporting', () => {
Expand All @@ -37,6 +38,32 @@ describe( 'modules/analytics-4 conversion-reporting', () => {
} );

describe( 'actions', () => {
describe( 'receiveConversionReportingInlineData', () => {
it( 'requires the data param', () => {
expect( () => {
registry
.dispatch( MODULES_ANALYTICS_4 )
.receiveConversionReportingInlineData();
} ).toThrow( 'data is required.' );
} );

it( 'receives and sets inline data', async () => {
const data = {
newEvents: [ 'purchase' ],
lostEvents: [],
};

await registry
.dispatch( MODULES_ANALYTICS_4 )
.receiveConversionReportingInlineData( data );

expect(
registry
.select( MODULES_ANALYTICS_4 )
.getConversionReportingEventsChange()
).toMatchObject( data );
} );
} );
describe( 'dismissNewConversionReportingEvents', () => {
it( 'fetches clear new events endpoint', async () => {
fetchMock.postOnce(
Expand Down Expand Up @@ -129,5 +156,80 @@ describe( 'modules/analytics-4 conversion-reporting', () => {
expect( hasConversionReportingEvents ).toBe( true );
} );
} );

describe( 'getConversionReportingEventsChange', () => {
it( 'uses a resolver to load conversion reporting inline data from a global variable by default', async () => {
const inlineData = {
newEvents: [ 'contact' ],
lostEvents: [],
};

global._googlesitekitModulesData = {
'analytics-4': inlineData,
};

registry
.select( MODULES_ANALYTICS_4 )
.getConversionReportingEventsChange();
await untilResolved(
registry,
MODULES_ANALYTICS_4
).getConversionReportingEventsChange();

const data = registry
.select( MODULES_ANALYTICS_4 )
.getConversionReportingEventsChange();

expect( data ).toEqual( inlineData );

global._googlesitekitModulesData = undefined;
} );

it( 'will return initial state (undefined) when no data is available', async () => {
expect( global._googlesitekitModulesData ).toEqual( undefined );

const data = registry
.select( MODULES_ANALYTICS_4 )
.getConversionReportingEventsChange();

await untilResolved(
registry,
MODULES_ANALYTICS_4
).getConversionReportingEventsChange();

expect( data ).toBe( undefined );
expect( console ).toHaveErrored();
} );
} );

describe.each( [
[ 'hasNewConversionReportingEvents', 'newEvents' ],
[ 'hasLostConversionReportingEvents', 'lostEvents' ],
] )( '%s', ( selector, dataKey ) => {
it( 'uses a resolver to load conversion reporting data then returns the data when this specific selector is used', async () => {
const inlineData = {
newEvents: [ 'submit_lead_form' ],
lostEvents: [ 'contact' ],
};

global._googlesitekitModulesData = {
'analytics-4': inlineData,
};

registry.select( MODULES_ANALYTICS_4 )[ selector ]();

await untilResolved(
registry,
MODULES_ANALYTICS_4
).getConversionReportingEventsChange();

const data = registry
.select( MODULES_ANALYTICS_4 )
.getConversionReportingEventsChange();

expect( data ).toHaveProperty( dataKey );
expect( data ).toEqual( inlineData );
} );
} );
} );
} );
Loading