Display CES modal once per action per store (https://github.com/woocommerce/woocommerce-admin/pull/5352)
* Spike out customer effort score * Refactor CustomerEffortScore as Package (https://github.com/woocommerce/woocommerce-admin/pull/5342) * Tidy up _webpack.config.js * Fix linter issues * refactor setting up CES tracking, add try..catch around loading from localStorage * Add CES feature toggle (https://github.com/woocommerce/woocommerce-admin/pull/5387) * Add feature toggle to only enable the customer effort score in development * Move check into Loader * fix logic 🙄 Co-authored-by: Rebecca Scott <me@becdetat.com> * Add client-side check of the feature flag * fix tabs in config * Fix comment * Use product lifecycle and options * Add product count to tracks props * Display CES modal once per event per store * Remove TODO * appease the formatting gods * Use Loader::load_features instead of DIY * Drop CustomerEffortScoreTracks singleton * Fix tracks event name * bump ci * Pull package-lock.json from main * Refactor to remove some duplication Co-authored-by: Rebecca Scott <me@becdetat.com> Co-authored-by: Adrian Duffell <9312929+adrianduffell@users.noreply.github.com>
This commit is contained in:
parent
15b45c9db4
commit
00eeede732
|
@ -47,7 +47,7 @@ function CustomerEffortScoreTracksContainer( {
|
|||
<CustomerEffortScoreTracks
|
||||
key={ index }
|
||||
initiallyVisible={ true }
|
||||
trackName={ item.track_name }
|
||||
action={ item.action }
|
||||
label={ item.label }
|
||||
trackProps={ item.props || {} }
|
||||
/>
|
||||
|
|
|
@ -6,9 +6,10 @@ import PropTypes from 'prop-types';
|
|||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import CustomerEffortScore from '@woocommerce/customer-effort-score';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withSelect } from '@wordpress/data';
|
||||
import { withSelect, withDispatch } from '@wordpress/data';
|
||||
import { OPTIONS_STORE_NAME, MONTH } from '@woocommerce/data';
|
||||
|
||||
const SHOWN_FOR_ACTIONS_OPTION_NAME = 'woocommerce_ces_shown_for_actions';
|
||||
const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME =
|
||||
'woocommerce_admin_install_timestamp';
|
||||
|
||||
|
@ -16,30 +17,52 @@ const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME =
|
|||
* A CustomerEffortScore wrapper that uses tracks to track the selected
|
||||
* customer effort score.
|
||||
*
|
||||
* @param {Object} props Component props.
|
||||
* @param {boolean} props.initiallyVisible Whether or not the tracks modal is initially visible.
|
||||
* @param {string} props.trackName The name sent to Tracks.
|
||||
* @param {Object} props.trackProps Additional props sent to Tracks.
|
||||
* @param {string} props.label The label displayed in the modal.
|
||||
* @param {boolean} props.resolving Are values still being resolving.
|
||||
* @param {Object} props Component props.
|
||||
* @param {boolean} props.initiallyVisible Whether or not the tracks modal is initially visible.
|
||||
* @param {string} props.action The action name sent to Tracks.
|
||||
* @param {Object} props.trackProps Additional props sent to Tracks.
|
||||
* @param {string} props.label The label displayed in the modal.
|
||||
* @param {Array} props.cesShownForActions The array of actions that the CES modal has been shown for.
|
||||
* @param {boolean} props.resolving Are values still being resolved.
|
||||
* @param {number} props.storeAge The age of the store in months.
|
||||
* @param {Function} props.updateOptions Function to update options.
|
||||
*/
|
||||
function CustomerEffortScoreTracks( {
|
||||
initiallyVisible,
|
||||
trackName,
|
||||
action,
|
||||
trackProps,
|
||||
label,
|
||||
cesShownForActions,
|
||||
resolving,
|
||||
storeAge,
|
||||
updateOptions,
|
||||
} ) {
|
||||
const [ visible, setVisible ] = useState( initiallyVisible );
|
||||
const [ shown, setShown ] = useState( false );
|
||||
|
||||
if ( resolving ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( cesShownForActions.indexOf( action ) !== -1 && ! shown ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use the `shown` state value to only update the shown_for_actions option
|
||||
// once.
|
||||
if ( visible && ! shown ) {
|
||||
setShown( true );
|
||||
updateOptions( {
|
||||
[ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [
|
||||
action,
|
||||
...cesShownForActions,
|
||||
],
|
||||
} );
|
||||
}
|
||||
|
||||
const trackCallback = ( score ) => {
|
||||
recordEvent( trackName, {
|
||||
recordEvent( 'ces_feedback', {
|
||||
action,
|
||||
score,
|
||||
store_age: storeAge,
|
||||
...trackProps,
|
||||
|
@ -65,9 +88,9 @@ CustomerEffortScoreTracks.propTypes = {
|
|||
*/
|
||||
initiallyVisible: PropTypes.bool,
|
||||
/**
|
||||
* The name sent to Tracks.
|
||||
* The action name sent to Tracks.
|
||||
*/
|
||||
trackName: PropTypes.string.isRequired,
|
||||
action: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Additional props sent to Tracks.
|
||||
*/
|
||||
|
@ -76,6 +99,10 @@ CustomerEffortScoreTracks.propTypes = {
|
|||
* The label displayed in the modal.
|
||||
*/
|
||||
label: PropTypes.string.isRequired,
|
||||
/**
|
||||
* The array of actions that the CES modal has been shown for.
|
||||
*/
|
||||
cesShownForActions: PropTypes.arrayOf( PropTypes.string ).isRequired,
|
||||
/**
|
||||
* Whether props are still being resolved.
|
||||
*/
|
||||
|
@ -90,20 +117,31 @@ export default compose(
|
|||
withSelect( ( select ) => {
|
||||
const { getOption, isResolving } = select( OPTIONS_STORE_NAME );
|
||||
|
||||
const cesShownForActions =
|
||||
getOption( SHOWN_FOR_ACTIONS_OPTION_NAME ) || [];
|
||||
|
||||
const adminInstallTimestamp =
|
||||
getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) || 0;
|
||||
// Date.now() is ms since Unix epoch, adminInstallTimestamp is in
|
||||
// seconds since Unix epoch.
|
||||
const storeAgeInSeconds = Date.now() - adminInstallTimestamp * 1000;
|
||||
const storeAge = Math.round( storeAgeInSeconds / MONTH );
|
||||
const storeAgeInMs = Date.now() - adminInstallTimestamp * 1000;
|
||||
const storeAge = Math.round( storeAgeInMs / MONTH );
|
||||
|
||||
const resolving = isResolving( 'getOption', [
|
||||
ADMIN_INSTALL_TIMESTAMP_OPTION_NAME,
|
||||
] );
|
||||
const resolving =
|
||||
isResolving( 'getOption', [ SHOWN_FOR_ACTIONS_OPTION_NAME ] ) ||
|
||||
isResolving( 'getOption', [ ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ] );
|
||||
|
||||
return {
|
||||
cesShownForActions,
|
||||
storeAge,
|
||||
resolving,
|
||||
};
|
||||
} ),
|
||||
withDispatch( ( dispatch ) => {
|
||||
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
|
||||
|
||||
return {
|
||||
updateOptions,
|
||||
};
|
||||
} )
|
||||
)( CustomerEffortScoreTracks );
|
||||
|
|
|
@ -13,10 +13,32 @@ defined( 'ABSPATH' ) || exit;
|
|||
* Triggers customer effort score on several different actions.
|
||||
*/
|
||||
class CustomerEffortScoreTracks {
|
||||
const CES_TRACKS_QUEUE_OPTION_NAME = 'woocommerce_ces_tracks_queue';
|
||||
/**
|
||||
* Option name for the CES Tracks queue.
|
||||
*/
|
||||
const CES_TRACKS_QUEUE_OPTION_NAME
|
||||
= 'woocommerce_ces_tracks_queue';
|
||||
|
||||
const CLEAR_CES_TRACKS_QUEUE_FOR_PAGE_OPTION_NAME
|
||||
= 'woocommerce_clear_ces_tracks_queue_for_page';
|
||||
/**
|
||||
* Option name for the clear CES Tracks queue for page.
|
||||
*/
|
||||
const CLEAR_CES_TRACKS_QUEUE_FOR_PAGE_OPTION_NAME =
|
||||
'woocommerce_clear_ces_tracks_queue_for_page';
|
||||
|
||||
/**
|
||||
* Option name for the set of actions that have been shown.
|
||||
*/
|
||||
const SHOWN_FOR_ACTIONS_OPTION_NAME = 'woocommerce_ces_shown_for_actions';
|
||||
|
||||
/**
|
||||
* Action name for product add/publish.
|
||||
*/
|
||||
const PRODUCT_ADD_PUBLISH_ACTION_NAME = 'product_add_publish';
|
||||
|
||||
/**
|
||||
* Action name for product update.
|
||||
*/
|
||||
const PRODUCT_UPDATE_ACTION_NAME = 'product_update';
|
||||
|
||||
/**
|
||||
* Constructor. Sets up filters to hook into WooCommerce.
|
||||
|
@ -88,48 +110,85 @@ class CustomerEffortScoreTracks {
|
|||
return $product_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the action has already been shown.
|
||||
*
|
||||
* @param string $action The action to check.
|
||||
*
|
||||
* @return bool Whether the action has already been shown.
|
||||
*/
|
||||
private function has_been_shown( $action ) {
|
||||
$shown_for_features = get_option( self::SHOWN_FOR_ACTIONS_OPTION_NAME, array() );
|
||||
$has_been_shown = in_array( $action, $shown_for_features, true );
|
||||
|
||||
return $has_been_shown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the item to the CES tracks queue.
|
||||
*
|
||||
* @param object $item The item to enqueue.
|
||||
*/
|
||||
private function enqueue_to_ces_tracks( $item ) {
|
||||
$queue = get_option(
|
||||
self::CES_TRACKS_QUEUE_OPTION_NAME,
|
||||
array()
|
||||
);
|
||||
|
||||
$queue[] = $item;
|
||||
|
||||
update_option(
|
||||
self::CES_TRACKS_QUEUE_OPTION_NAME,
|
||||
$queue
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the CES survey trigger for a new product.
|
||||
*/
|
||||
private function enqueue_ces_survey_for_new_product() {
|
||||
$queue = get_option( self::CES_TRACKS_QUEUE_OPTION_NAME, array() );
|
||||
if ( $this->has_been_shown( self::PRODUCT_ADD_PUBLISH_ACTION_NAME ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$queue[] = array(
|
||||
'track_name' => 'product_add_publish_effort_score',
|
||||
'label' => __(
|
||||
'How easy was it to add a product?',
|
||||
'woocommerce-admin'
|
||||
),
|
||||
'pagenow' => 'product',
|
||||
'adminpage' => 'post-php',
|
||||
'props' => array(
|
||||
'product_count' => $this->get_product_count(),
|
||||
),
|
||||
$this->enqueue_to_ces_tracks(
|
||||
array(
|
||||
'action' => self::PRODUCT_ADD_PUBLISH_ACTION_NAME,
|
||||
'label' => __(
|
||||
'How easy was it to add a product?',
|
||||
'woocommerce-admin'
|
||||
),
|
||||
'pagenow' => 'product',
|
||||
'adminpage' => 'post-php',
|
||||
'props' => array(
|
||||
'product_count' => $this->get_product_count(),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
update_option( self::CES_TRACKS_QUEUE_OPTION_NAME, $queue );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the CES survey trigger for an existing product.
|
||||
*/
|
||||
private function enqueue_ces_survey_for_edited_product() {
|
||||
$queue = get_option( self::CES_TRACKS_QUEUE_OPTION_NAME, array() );
|
||||
if ( $this->has_been_shown( self::PRODUCT_UPDATE_ACTION_NAME ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$queue[] = array(
|
||||
'track_name' => 'product_update_effort_score',
|
||||
'label' => __(
|
||||
'How easy was it to edit your product?',
|
||||
'woocommerce-admin'
|
||||
),
|
||||
'pagenow' => 'product',
|
||||
'adminpage' => 'post-php',
|
||||
'props' => array(
|
||||
'product_count' => $this->get_product_count(),
|
||||
),
|
||||
$this->enqueue_to_ces_tracks(
|
||||
array(
|
||||
'action' => self::PRODUCT_UPDATE_ACTION_NAME,
|
||||
'label' => __(
|
||||
'How easy was it to edit your product?',
|
||||
'woocommerce-admin'
|
||||
),
|
||||
'pagenow' => 'product',
|
||||
'adminpage' => 'post-php',
|
||||
'props' => array(
|
||||
'product_count' => $this->get_product_count(),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
update_option( self::CES_TRACKS_QUEUE_OPTION_NAME, $queue );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue