* 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:
Bec Scott 2020-11-03 11:57:47 +10:00 committed by GitHub
parent 15b45c9db4
commit 00eeede732
3 changed files with 145 additions and 48 deletions

View File

@ -47,7 +47,7 @@ function CustomerEffortScoreTracksContainer( {
<CustomerEffortScoreTracks
key={ index }
initiallyVisible={ true }
trackName={ item.track_name }
action={ item.action }
label={ item.label }
trackProps={ item.props || {} }
/>

View File

@ -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 );

View File

@ -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 );
}
/**