* Use a store to render CES tracks

* Use the CES store to trigger CES survey when users change date range or single product filters.

* Support onClick event for the Compare button

* Trigger CES survey when a user clicks the Compare button on products, variations, categories, coupon, and taxes pages

* Set default text for onSubmitLabel in js and remove its requirement

* Add addCesSurveyTrackForAnalytics action so that it can be shared in other components in analytics pages -- remove duplicates

* Call addCesSurveyTrack from addCesSurveyTrackForAnalytics to avoid duplicate

* Remove 'tracks' from the method name
This commit is contained in:
Moon 2020-11-24 15:18:50 -08:00 committed by GitHub
parent ea86886d88
commit eb7a779e1f
16 changed files with 306 additions and 43 deletions

View File

@ -2,9 +2,10 @@
* External dependencies
*/
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import PropTypes from 'prop-types';
import { omitBy, isUndefined, snakeCase } from 'lodash';
import { withSelect } from '@wordpress/data';
import { withSelect, withDispatch } from '@wordpress/data';
import { ReportFilters as Filters } from '@woocommerce/components';
import { LOCALE } from '@woocommerce/wc-admin-settings';
import { SETTINGS_STORE_NAME } from '@woocommerce/data';
@ -19,36 +20,52 @@ import { recordEvent } from '@woocommerce/tracks';
* Internal dependencies
*/
import { CurrencyContext } from '../../../lib/currency-context';
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
class ReportFilters extends Component {
constructor() {
super();
this.trackDateSelect = this.trackDateSelect.bind( this );
this.trackFilterSelect = this.trackFilterSelect.bind( this );
this.trackAdvancedFilterAction = this.trackAdvancedFilterAction.bind(
this
);
this.onDateSelect = this.onDateSelect.bind( this );
this.onFilterSelect = this.onFilterSelect.bind( this );
this.onAdvancedFilterAction = this.onAdvancedFilterAction.bind( this );
}
trackDateSelect( data ) {
const { report } = this.props;
onDateSelect( data ) {
const { report, addCesSurveyForAnalytics } = this.props;
addCesSurveyForAnalytics();
recordEvent( 'datepicker_update', {
report,
...omitBy( data, isUndefined ),
} );
}
trackFilterSelect( data ) {
const { report } = this.props;
onFilterSelect( data ) {
const { report, addCesSurveyForAnalytics } = this.props;
// This event gets triggered in the following cases.
// 1. Select "Single Product" and choose a product.
// 2. Select "Comparison" or any other filter types.
// The comparsion and other filter types require a user to click
// a button to execute a query, so this is not a good place to
// trigger a CES survey for those.
const triggerCesFor = [
'single_product',
'single_category',
'single_coupon',
'single_variation',
];
const filterName = data.filter || data[ 'filter-variations' ];
if ( triggerCesFor.includes( filterName ) ) {
addCesSurveyForAnalytics();
}
recordEvent( 'analytics_filter', {
report,
filter: data.filter || 'all',
} );
}
trackAdvancedFilterAction( action, data ) {
const { report } = this.props;
onAdvancedFilterAction( action, data ) {
const { report, addCesSurveyForAnalytics } = this.props;
switch ( action ) {
case 'add':
recordEvent( 'analytics_filters_add', {
@ -70,6 +87,7 @@ class ReportFilters extends Component {
},
{}
);
addCesSurveyForAnalytics();
recordEvent( 'analytics_filters_filter', {
report,
...snakeCaseData,
@ -123,9 +141,9 @@ class ReportFilters extends Component {
filters={ filters }
advancedFilters={ advancedFilters }
showDatePicker={ showDatePicker }
onDateSelect={ this.trackDateSelect }
onFilterSelect={ this.trackFilterSelect }
onAdvancedFilterAction={ this.trackAdvancedFilterAction }
onDateSelect={ this.onDateSelect }
onFilterSelect={ this.onFilterSelect }
onAdvancedFilterAction={ this.onAdvancedFilterAction }
dateQuery={ dateQuery }
isoDateFormat={ isoDateFormat }
/>
@ -135,12 +153,18 @@ class ReportFilters extends Component {
ReportFilters.contextType = CurrencyContext;
export default withSelect( ( select ) => {
const { woocommerce_default_date_range: defaultDateRange } = select(
SETTINGS_STORE_NAME
).getSetting( 'wc_admin', 'wcAdminSettings' );
return { defaultDateRange };
} )( ReportFilters );
export default compose(
withSelect( ( select ) => {
const { woocommerce_default_date_range: defaultDateRange } = select(
SETTINGS_STORE_NAME
).getSetting( 'wc_admin', 'wcAdminSettings' );
return { defaultDateRange };
} ),
withDispatch( ( dispatch ) => {
const { addCesSurveyForAnalytics } = dispatch( CES_STORE_KEY );
return { addCesSurveyForAnalytics };
} )
)( ReportFilters );
ReportFilters.propTypes = {
/**

View File

@ -4,6 +4,7 @@
import { Component, Fragment } from '@wordpress/element';
import PropTypes from 'prop-types';
import { __ } from '@wordpress/i18n';
import { withDispatch } from '@wordpress/data';
/**
* Internal dependencies
@ -15,8 +16,9 @@ import ReportChart from '../../components/report-chart';
import ReportSummary from '../../components/report-summary';
import ProductsReportTable from '../products/table';
import ReportFilters from '../../components/report-filters';
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
export default class CategoriesReport extends Component {
class CategoriesReport extends Component {
getChartMeta() {
const { query } = this.props;
const isCompareView =
@ -42,7 +44,12 @@ export default class CategoriesReport extends Component {
}
render() {
const { isRequesting, query, path } = this.props;
const {
isRequesting,
query,
path,
addCesSurveyForAnalytics,
} = this.props;
const { mode, itemsLabel, isSingleCategoryView } = this.getChartMeta();
const chartQuery = {
@ -55,6 +62,10 @@ export default class CategoriesReport extends Component {
: 'category';
}
filters[ 0 ].filters.find(
( item ) => item.value === 'compare-categories'
).settings.onClick = addCesSurveyForAnalytics;
return (
<Fragment>
<ReportFilters
@ -122,3 +133,8 @@ CategoriesReport.propTypes = {
query: PropTypes.object.isRequired,
path: PropTypes.string.isRequired,
};
export default withDispatch( ( dispatch ) => {
const { addCesSurveyForAnalytics } = dispatch( CES_STORE_KEY );
return { addCesSurveyForAnalytics };
} )( CategoriesReport );

View File

@ -4,6 +4,7 @@
import { Component, Fragment } from '@wordpress/element';
import PropTypes from 'prop-types';
import { __ } from '@wordpress/i18n';
import { withDispatch } from '@wordpress/data';
/**
* Internal dependencies
@ -14,8 +15,9 @@ import getSelectedChart from '../../../lib/get-selected-chart';
import ReportChart from '../../components/report-chart';
import ReportSummary from '../../components/report-summary';
import ReportFilters from '../../components/report-filters';
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
export default class CouponsReport extends Component {
class CouponsReport extends Component {
getChartMeta() {
const { query } = this.props;
const isCompareView =
@ -33,9 +35,18 @@ export default class CouponsReport extends Component {
}
render() {
const { isRequesting, query, path } = this.props;
const {
isRequesting,
query,
path,
addCesSurveyForAnalytics,
} = this.props;
const { mode, itemsLabel } = this.getChartMeta();
filters[ 0 ].filters.find(
( item ) => item.value === 'compare-coupons'
).settings.onClick = addCesSurveyForAnalytics;
const chartQuery = {
...query,
};
@ -88,3 +99,8 @@ export default class CouponsReport extends Component {
CouponsReport.propTypes = {
query: PropTypes.object.isRequired,
};
export default withDispatch( ( dispatch ) => {
const { addCesSurveyForAnalytics } = dispatch( CES_STORE_KEY );
return { addCesSurveyForAnalytics };
} )( CouponsReport );

View File

@ -6,7 +6,7 @@ import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import PropTypes from 'prop-types';
import { ITEMS_STORE_NAME } from '@woocommerce/data';
import { withSelect } from '@wordpress/data';
import { withSelect, withDispatch } from '@wordpress/data';
/**
* Internal dependencies
@ -19,6 +19,7 @@ import ReportError from '../../components/report-error';
import ReportSummary from '../../components/report-summary';
import VariationsReportTable from '../variations/table';
import ReportFilters from '../../components/report-filters';
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
class ProductsReport extends Component {
getChartMeta() {
@ -60,6 +61,7 @@ class ProductsReport extends Component {
isError,
isRequesting,
isSingleProductVariable,
addCesSurveyForAnalytics,
} = this.props;
if ( isError ) {
@ -75,6 +77,10 @@ class ProductsReport extends Component {
compareObject === 'products' ? 'product' : 'variation';
}
filters[ 0 ].filters.find(
( item ) => item.value === 'compare-products'
).settings.onClick = addCesSurveyForAnalytics;
return (
<Fragment>
<ReportFilters
@ -177,8 +183,8 @@ export default compose(
'is-variable': isVariable,
},
isSingleProductView,
isSingleProductVariable: isVariable,
isRequesting: isProductsRequesting,
isSingleProductVariable: isVariable,
isError: isProductsError,
};
}
@ -187,5 +193,9 @@ export default compose(
query,
isSingleProductView,
};
} ),
withDispatch( ( dispatch ) => {
const { addCesSurveyForAnalytics } = dispatch( CES_STORE_KEY );
return { addCesSurveyForAnalytics };
} )
)( ProductsReport );

View File

@ -4,6 +4,7 @@
import { Component, Fragment } from '@wordpress/element';
import PropTypes from 'prop-types';
import { __ } from '@wordpress/i18n';
import { withDispatch } from '@wordpress/data';
/**
* Internal dependencies
@ -14,8 +15,9 @@ import ReportChart from '../../components/report-chart';
import ReportSummary from '../../components/report-summary';
import TaxesReportTable from './table';
import ReportFilters from '../../components/report-filters';
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
export default class TaxesReport extends Component {
class TaxesReport extends Component {
getChartMeta() {
const { query } = this.props;
const isCompareTaxView = query.filter === 'compare-taxes';
@ -29,9 +31,18 @@ export default class TaxesReport extends Component {
}
render() {
const { isRequesting, query, path } = this.props;
const {
isRequesting,
query,
path,
addCesSurveyForAnalytics,
} = this.props;
const { mode, itemsLabel } = this.getChartMeta();
filters[ 0 ].filters.find(
( item ) => item.value === 'compare-taxes'
).settings.onClick = addCesSurveyForAnalytics;
const chartQuery = {
...query,
};
@ -82,3 +93,8 @@ export default class TaxesReport extends Component {
TaxesReport.propTypes = {
query: PropTypes.object.isRequired,
};
export default withDispatch( ( dispatch ) => {
const { addCesSurveyForAnalytics } = dispatch( CES_STORE_KEY );
return { addCesSurveyForAnalytics };
} )( TaxesReport );

View File

@ -4,6 +4,7 @@
import { __ } from '@wordpress/i18n';
import { Fragment } from '@wordpress/element';
import PropTypes from 'prop-types';
import { withDispatch } from '@wordpress/data';
/**
* Internal dependencies
@ -15,6 +16,7 @@ import ReportError from '../../components/report-error';
import ReportSummary from '../../components/report-summary';
import VariationsReportTable from './table';
import ReportFilters from '../../components/report-filters';
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
const getChartMeta = ( { query } ) => {
const isCompareView =
@ -31,7 +33,13 @@ const getChartMeta = ( { query } ) => {
const VariationsReport = ( props ) => {
const { itemsLabel, mode } = getChartMeta( props );
const { path, query, isError, isRequesting } = props;
const {
path,
query,
isError,
isRequesting,
addCesSurveyForAnalytics,
} = props;
if ( isError ) {
return <ReportError isError />;
@ -45,6 +53,10 @@ const VariationsReport = ( props ) => {
chartQuery.segmentby = 'variation';
}
filters[ 0 ].filters.find(
( item ) => item.value === 'compare-variations'
).settings.onClick = addCesSurveyForAnalytics;
return (
<Fragment>
<ReportFilters
@ -91,4 +103,7 @@ VariationsReport.propTypes = {
query: PropTypes.object.isRequired,
};
export default VariationsReport;
export default withDispatch( ( dispatch ) => {
const { addCesSurveyForAnalytics } = dispatch( CES_STORE_KEY );
return { addCesSurveyForAnalytics };
} )( VariationsReport );

View File

@ -10,6 +10,8 @@ import PropTypes from 'prop-types';
* Internal dependencies
*/
import CustomerEffortScoreTracks from './customer-effort-score-tracks';
import { STORE_KEY, QUEUE_OPTION_NAME } from './data/constants';
import './data';
/**
* Maps the queue of CES tracks surveys to CustomerEffortScoreTracks
@ -73,11 +75,9 @@ CustomerEffortScoreTracksContainer.propTypes = {
export default compose(
withSelect( ( select ) => {
const { getOption, isResolving } = select( OPTIONS_STORE_NAME );
const queue = getOption( 'woocommerce_ces_tracks_queue' ) || [];
const resolving = isResolving( 'getOption', [
'woocommerce_ces_tracks_queue',
] );
const { getCesSurveyQueue, isResolving } = select( STORE_KEY );
const queue = getCesSurveyQueue();
const resolving = isResolving( 'getOption', [ QUEUE_OPTION_NAME ] );
return { queue, resolving };
} ),

View File

@ -35,7 +35,7 @@ function CustomerEffortScoreTracks( {
action,
trackProps,
label,
onSubmitLabel,
onSubmitLabel = __( 'Thank you for your feedback!', 'woocommerce-admin' ),
cesShownForActions,
allowTracking,
resolving,
@ -144,7 +144,7 @@ CustomerEffortScoreTracks.propTypes = {
/**
* The label for the snackbar that appears upon survey submission.
*/
onSubmitLabel: PropTypes.string.isRequired,
onSubmitLabel: PropTypes.string,
/**
* The array of actions that the CES modal has been shown for.
*/

View File

@ -0,0 +1,6 @@
const TYPES = {
SET_CES_SURVEY_QUEUE: 'SET_CES_SURVEY_QUEUE',
ADD_CES_SURVEY: 'ADD_CES_SURVEY',
};
export default TYPES;

View File

@ -0,0 +1,62 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import TYPES from './action-types';
/**
* Initialize the state
*
* @param {Object} queue initial queue
*/
export function setCesSurveyQueue( queue ) {
return {
type: TYPES.SET_CES_SURVEY_QUEUE,
queue,
};
}
/**
* Add a new CES track to the state.
*
* @param {string} action action name for the survey
* @param {string} label label for the snackback
* @param {string} pageNow value of window.pagenow
* @param {string} adminPage value of window.adminpage
* @param {string} onsubmit_label label for the snackback onsubmit
*/
export function addCesSurvey(
action,
label,
pageNow = window.pagenow,
adminPage = window.adminpage,
onsubmit_label = undefined
) {
return {
type: TYPES.ADD_CES_SURVEY,
action,
label,
pageNow,
adminPage,
onsubmit_label,
};
}
/**
* Add a new CES survey track for the pages in Analytics menu
*/
export function addCesSurveyForAnalytics() {
return addCesSurvey(
'analytics_filtered',
__(
'How easy was it to filter your store analytics?',
'woocommerce-admin'
),
'woocommerce_page_wc-admin',
'woocommerce_page_wc-admin'
);
}

View File

@ -0,0 +1,3 @@
export const STORE_KEY = 'wc/customer-effort-score';
export const API_NAMESPACE = '/wc-admin';
export const QUEUE_OPTION_NAME = 'woocommerce_ces_tracks_queue';

View File

@ -0,0 +1,22 @@
/**
* External dependencies
*/
import { registerStore } from '@wordpress/data';
import { controls } from '@wordpress/data-controls';
/**
* Internal dependencies
*/
import * as actions from './actions';
import * as resolvers from './resolvers';
import * as selectors from './selectors';
import reducer from './reducer';
import { STORE_KEY } from './constants';
export default registerStore( STORE_KEY, {
actions,
selectors,
resolvers,
controls,
reducer,
} );

View File

@ -0,0 +1,42 @@
/**
* Internal dependencies
*/
import TYPES from './action-types';
const DEFAULT_STATE = {
queue: [],
};
const reducer = ( state = DEFAULT_STATE, action ) => {
switch ( action.type ) {
case TYPES.SET_CES_SURVEY_QUEUE:
return {
...state,
queue: action.queue,
};
case TYPES.ADD_CES_SURVEY:
// Prevent duplicate
const hasDuplicate = state.queue.filter(
( track ) => track.action === action.action
);
if ( hasDuplicate.length ) {
return state;
}
const newTrack = {
action: action.action,
label: action.label,
pagenow: action.pageNow,
adminpage: action.adminPage,
onSubmitLabel: action.onSubmitLabel,
};
return {
...state,
queue: [ ...state.queue, newTrack ],
};
default:
return state;
}
};
export default reducer;

View File

@ -0,0 +1,22 @@
/**
* External dependencies
*/
import { apiFetch } from '@wordpress/data-controls';
/**
* Internal dependencies
*/
import { setCesSurveyQueue } from './actions';
import { API_NAMESPACE, QUEUE_OPTION_NAME } from './constants';
export function* getCesSurveyQueue() {
const response = yield apiFetch( {
path: `${ API_NAMESPACE }/options?options=${ QUEUE_OPTION_NAME }`,
} );
if ( response ) {
yield setCesSurveyQueue( response[ QUEUE_OPTION_NAME ] || [] );
} else {
throw new Error();
}
}

View File

@ -0,0 +1,3 @@
export function getCesSurveyQueue( state ) {
return state.queue;
}

View File

@ -4,7 +4,7 @@
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { isEqual } from 'lodash';
import { isEqual, isFunction } from 'lodash';
import PropTypes from 'prop-types';
import { getIdsFromQuery, updateQueryString } from '@woocommerce/navigation';
@ -27,11 +27,10 @@ export class CompareFilter extends Component {
this.state = {
selected: [],
};
this.clearQuery = this.clearQuery.bind( this );
this.updateQuery = this.updateQuery.bind( this );
this.updateLabels = this.updateLabels.bind( this );
this.onButtonClicked = this.onButtonClicked.bind( this );
if ( query[ param ] ) {
getLabels( query[ param ], query ).then( this.updateLabels );
}
@ -79,6 +78,13 @@ export class CompareFilter extends Component {
updateQueryString( { [ param ]: idList.join( ',' ) }, path, query );
}
onButtonClicked( e ) {
this.updateQuery( e );
if ( isFunction( this.props.onClick ) ) {
this.props.onClick( e );
}
}
render() {
const { labels, type } = this.props;
const { selected } = this.state;
@ -101,7 +107,7 @@ export class CompareFilter extends Component {
<CompareButton
count={ selected.length }
helpText={ labels.helpText }
onClick={ this.updateQuery }
onClick={ this.onButtonClicked }
>
{ labels.update }
</CompareButton>