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

Expand analytics for block insertion/replacement #5976

Merged
merged 11 commits into from
Jul 20, 2023
141 changes: 133 additions & 8 deletions src/analytics/redux/tracked_events.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,56 @@
import { select } from '@wordpress/data';
import { sendEventToHost } from '@wordpress/react-native-bridge';

const INSERTERS = {
HEADER_INSERTER: 'header-inserter',
SLASH_INSERTER: 'slash-inserter',
QUICK_INSERTER: 'quick-inserter',
};

/**
* Guess which inserter was used to insert/replace blocks.
*
* @param {string[]|string} originalBlockIds ids or blocks that are being replaced
fluiddot marked this conversation as resolved.
Show resolved Hide resolved
* @param {Object} metaData Meta data of the inserted block
* @return {string | undefined} Insertion source or undefined value
*/
const getBlockInserterUsed = ( originalBlockIds = [], metaData ) => {
const { inserterMethod, source } = metaData || {};
const clientIds = Array.isArray( originalBlockIds )
? originalBlockIds
: [ originalBlockIds ];

if ( source === 'inserter_menu' && ! inserterMethod ) {
return INSERTERS.HEADER_INSERTER;
}

if (
source === 'inserter_menu' &&
inserterMethod === INSERTERS.QUICK_INSERTER
) {
return INSERTERS.QUICK_INSERTER;
}

// Inserting a block using a slash command is always a block replacement of
// a paragraph block. Checks the block contents to see if it starts with '/'.
// This check must go _after_ the block switcher check because it's possible
// for the user to type something like "/abc" that matches no block type and
// then use the block switcher, and the following tests would incorrectly capture
// that case too.
if (
clientIds.length === 1 &&
select( 'core/block-editor' ).getBlockName( clientIds[ 0 ] ) ===
'core/paragraph' &&
select( 'core/block-editor' )
.getBlockAttributes( clientIds[ 0 ] )
.content.startsWith( '/' )
) {
return INSERTERS.SLASH_INSERTER;
}

return undefined;
};

/**
* Retrieves a block object. If the block is not an object,
* it tries to retrieve the block from the store.
Expand All @@ -19,6 +69,66 @@ function getBlockObject( block ) {
return select( 'core/block-editor' ).getBlock( block ) || {};
}

/**
* Track block replacement.
*
* @param {Array} originalBlockIds ID(s) or blocks that are being replaced
fluiddot marked this conversation as resolved.
Show resolved Hide resolved
* @param {Object | Array} blocks Block instance object or an array of such objects
* @param {number} indexToSelect Index of replacement block to select.
* @param {0|-1|null} initialPosition Index of caret after in the selected block after the operation.
* @param {?Object} meta Optional Meta values to be passed to the action object.
*
* @return {void}
fluiddot marked this conversation as resolved.
Show resolved Hide resolved
*/
const trackBlockReplacement = (
originalBlockIds,
blocks,
indexToSelect,
// eslint-disable-next-line no-unused-vars
initialPosition = 0,
fluiddot marked this conversation as resolved.
Show resolved Hide resolved
meta = {}
) => {
const insert_method = getBlockInserterUsed( originalBlockIds, meta );

// To avoid tracking block insertions when replacing a block, we only track replacements
// when the slash inserter is used.
if ( insert_method === INSERTERS.SLASH_INSERTER ) {
trackBlocksHandler( blocks, 'editor_block_inserted', ( { name } ) => ( {
block_name: name,
insert_method,
} ) );
}
};

/**
* Track block insertion.
*
* @param {Object | Array} blocks Block instance object or an array of such objects
* @param {?number} index Index at which block should be inserted.
* @param {?string} rootClientId Optional root client ID of block list on which to insert.
* @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to true.
* @param {0|-1|null} initialPosition Initial focus position. Setting it to null prevent focusing the inserted block.
* @param {?Object} meta Optional Meta values to be passed to the action object.
*
* @return {void}
*/
function trackBlockInsertion(
blocks,
index,
rootClientId,
updateSelection,
// eslint-disable-next-line no-unused-vars
initialPosition = 0,
fluiddot marked this conversation as resolved.
Show resolved Hide resolved
meta = {}
) {
const insert_method = getBlockInserterUsed( [], meta );

trackBlocksHandler( blocks, 'editor_block_inserted', ( { name } ) => ( {
block_name: name,
insert_method,
} ) );
}

/**
* Helper function to recursively track block events.
* Each inner block will be tracked as a separate event if block contains inner blocks.
Expand Down Expand Up @@ -111,20 +221,35 @@ function handleBlockMovedByPosition( clientIds, toIndex ) {

export const trackedEvents = {
'core/block-editor': {
insertBlock( blocks ) {
trackBlocksHandler(
insertBlock( blocks, index, rootClientId, updateSelection, meta ) {
trackBlockInsertion(
blocks,
'editor_block_inserted',
( { name } ) => ( { block_name: name } )
index,
rootClientId,
updateSelection,
undefined,
meta
fluiddot marked this conversation as resolved.
Show resolved Hide resolved
);
},
insertBlocks( blocks ) {
trackBlocksHandler(
insertBlocks(
blocks,
index,
rootClientId,
updateSelection,
initialPosition,
meta
) {
trackBlockInsertion(
blocks,
'editor_block_inserted',
( { name } ) => ( { block_name: name } )
index,
rootClientId,
updateSelection,
initialPosition,
meta
fluiddot marked this conversation as resolved.
Show resolved Hide resolved
);
},
replaceBlock: trackBlockReplacement,
fluiddot marked this conversation as resolved.
Show resolved Hide resolved
replaceBlocks: trackBlockReplacement,
moveBlocksUp( clientIds ) {
trackBlockMoved( clientIds, 'move_arrows_up' );
},
Expand Down