* 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 <CustomerEffortScoreTracks
key={ index } key={ index }
initiallyVisible={ true } initiallyVisible={ true }
trackName={ item.track_name } action={ item.action }
label={ item.label } label={ item.label }
trackProps={ item.props || {} } trackProps={ item.props || {} }
/> />

View File

@ -6,9 +6,10 @@ import PropTypes from 'prop-types';
import { recordEvent } from '@woocommerce/tracks'; import { recordEvent } from '@woocommerce/tracks';
import CustomerEffortScore from '@woocommerce/customer-effort-score'; import CustomerEffortScore from '@woocommerce/customer-effort-score';
import { compose } from '@wordpress/compose'; import { compose } from '@wordpress/compose';
import { withSelect } from '@wordpress/data'; import { withSelect, withDispatch } from '@wordpress/data';
import { OPTIONS_STORE_NAME, MONTH } from '@woocommerce/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 = const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME =
'woocommerce_admin_install_timestamp'; 'woocommerce_admin_install_timestamp';
@ -18,28 +19,50 @@ const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME =
* *
* @param {Object} props Component props. * @param {Object} props Component props.
* @param {boolean} props.initiallyVisible Whether or not the tracks modal is initially visible. * @param {boolean} props.initiallyVisible Whether or not the tracks modal is initially visible.
* @param {string} props.trackName The name sent to Tracks. * @param {string} props.action The action name sent to Tracks.
* @param {Object} props.trackProps Additional props sent to Tracks. * @param {Object} props.trackProps Additional props sent to Tracks.
* @param {string} props.label The label displayed in the modal. * @param {string} props.label The label displayed in the modal.
* @param {boolean} props.resolving Are values still being resolving. * @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 {number} props.storeAge The age of the store in months.
* @param {Function} props.updateOptions Function to update options.
*/ */
function CustomerEffortScoreTracks( { function CustomerEffortScoreTracks( {
initiallyVisible, initiallyVisible,
trackName, action,
trackProps, trackProps,
label, label,
cesShownForActions,
resolving, resolving,
storeAge, storeAge,
updateOptions,
} ) { } ) {
const [ visible, setVisible ] = useState( initiallyVisible ); const [ visible, setVisible ] = useState( initiallyVisible );
const [ shown, setShown ] = useState( false );
if ( resolving ) { if ( resolving ) {
return null; 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 ) => { const trackCallback = ( score ) => {
recordEvent( trackName, { recordEvent( 'ces_feedback', {
action,
score, score,
store_age: storeAge, store_age: storeAge,
...trackProps, ...trackProps,
@ -65,9 +88,9 @@ CustomerEffortScoreTracks.propTypes = {
*/ */
initiallyVisible: PropTypes.bool, 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. * Additional props sent to Tracks.
*/ */
@ -76,6 +99,10 @@ CustomerEffortScoreTracks.propTypes = {
* The label displayed in the modal. * The label displayed in the modal.
*/ */
label: PropTypes.string.isRequired, 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. * Whether props are still being resolved.
*/ */
@ -90,20 +117,31 @@ export default compose(
withSelect( ( select ) => { withSelect( ( select ) => {
const { getOption, isResolving } = select( OPTIONS_STORE_NAME ); const { getOption, isResolving } = select( OPTIONS_STORE_NAME );
const cesShownForActions =
getOption( SHOWN_FOR_ACTIONS_OPTION_NAME ) || [];
const adminInstallTimestamp = const adminInstallTimestamp =
getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) || 0; getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) || 0;
// Date.now() is ms since Unix epoch, adminInstallTimestamp is in // Date.now() is ms since Unix epoch, adminInstallTimestamp is in
// seconds since Unix epoch. // seconds since Unix epoch.
const storeAgeInSeconds = Date.now() - adminInstallTimestamp * 1000; const storeAgeInMs = Date.now() - adminInstallTimestamp * 1000;
const storeAge = Math.round( storeAgeInSeconds / MONTH ); const storeAge = Math.round( storeAgeInMs / MONTH );
const resolving = isResolving( 'getOption', [ const resolving =
ADMIN_INSTALL_TIMESTAMP_OPTION_NAME, isResolving( 'getOption', [ SHOWN_FOR_ACTIONS_OPTION_NAME ] ) ||
] ); isResolving( 'getOption', [ ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ] );
return { return {
cesShownForActions,
storeAge, storeAge,
resolving, resolving,
}; };
} ),
withDispatch( ( dispatch ) => {
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
updateOptions,
};
} ) } )
)( CustomerEffortScoreTracks ); )( CustomerEffortScoreTracks );

View File

@ -13,10 +13,32 @@ defined( 'ABSPATH' ) || exit;
* Triggers customer effort score on several different actions. * Triggers customer effort score on several different actions.
*/ */
class CustomerEffortScoreTracks { 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. * Constructor. Sets up filters to hook into WooCommerce.
@ -88,14 +110,50 @@ class CustomerEffortScoreTracks {
return $product_count; 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. * Enqueue the CES survey trigger for a new product.
*/ */
private function enqueue_ces_survey_for_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( $this->enqueue_to_ces_tracks(
'track_name' => 'product_add_publish_effort_score', array(
'action' => self::PRODUCT_ADD_PUBLISH_ACTION_NAME,
'label' => __( 'label' => __(
'How easy was it to add a product?', 'How easy was it to add a product?',
'woocommerce-admin' 'woocommerce-admin'
@ -105,19 +163,21 @@ class CustomerEffortScoreTracks {
'props' => array( 'props' => array(
'product_count' => $this->get_product_count(), 'product_count' => $this->get_product_count(),
), ),
)
); );
update_option( self::CES_TRACKS_QUEUE_OPTION_NAME, $queue );
} }
/** /**
* Enqueue the CES survey trigger for an existing product. * Enqueue the CES survey trigger for an existing product.
*/ */
private function enqueue_ces_survey_for_edited_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( $this->enqueue_to_ces_tracks(
'track_name' => 'product_update_effort_score', array(
'action' => self::PRODUCT_UPDATE_ACTION_NAME,
'label' => __( 'label' => __(
'How easy was it to edit your product?', 'How easy was it to edit your product?',
'woocommerce-admin' 'woocommerce-admin'
@ -127,9 +187,8 @@ class CustomerEffortScoreTracks {
'props' => array( 'props' => array(
'product_count' => $this->get_product_count(), 'product_count' => $this->get_product_count(),
), ),
)
); );
update_option( self::CES_TRACKS_QUEUE_OPTION_NAME, $queue );
} }
/** /**