Add additional question to CES modal (#35680)

This commit is contained in:
Joel Thiessen 2022-11-29 08:15:58 -08:00 committed by GitHub
parent cfffb27c45
commit 4db5c0d8cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 350 additions and 93 deletions

View File

@ -0,0 +1,4 @@
Significance: major
Type: update
Updating to accept two questions to display in CES modal.

View File

@ -14,8 +14,14 @@ import { CustomerFeedbackModal } from './customer-feedback-modal';
const noop = () => {}; const noop = () => {};
type CustomerEffortScoreProps = { type CustomerEffortScoreProps = {
recordScoreCallback: ( score: number, comments: string ) => void; recordScoreCallback: (
label: string; score: number,
secondScore: number,
comments: string
) => void;
title: string;
firstQuestion: string;
secondQuestion: string;
onNoticeShownCallback?: () => void; onNoticeShownCallback?: () => void;
onNoticeDismissedCallback?: () => void; onNoticeDismissedCallback?: () => void;
onModalShownCallback?: () => void; onModalShownCallback?: () => void;
@ -30,7 +36,9 @@ type CustomerEffortScoreProps = {
* *
* @param {Object} props Component props. * @param {Object} props Component props.
* @param {Function} props.recordScoreCallback Function to call when the score should be recorded. * @param {Function} props.recordScoreCallback Function to call when the score should be recorded.
* @param {string} props.label The label displayed in the modal. * @param {string} props.title The title displayed in the modal.
* @param {string} props.firstQuestion The first survey question.
* @param {string} props.secondQuestion The second survey question.
* @param {Function} props.onNoticeShownCallback Function to call when the notice is shown. * @param {Function} props.onNoticeShownCallback Function to call when the notice is shown.
* @param {Function} props.onNoticeDismissedCallback Function to call when the notice is dismissed. * @param {Function} props.onNoticeDismissedCallback Function to call when the notice is dismissed.
* @param {Function} props.onModalShownCallback Function to call when the modal is shown. * @param {Function} props.onModalShownCallback Function to call when the modal is shown.
@ -38,7 +46,9 @@ type CustomerEffortScoreProps = {
*/ */
const CustomerEffortScore: React.VFC< CustomerEffortScoreProps > = ( { const CustomerEffortScore: React.VFC< CustomerEffortScoreProps > = ( {
recordScoreCallback, recordScoreCallback,
label, title,
firstQuestion,
secondQuestion,
onNoticeShownCallback = noop, onNoticeShownCallback = noop,
onNoticeDismissedCallback = noop, onNoticeDismissedCallback = noop,
onModalShownCallback = noop, onModalShownCallback = noop,
@ -53,7 +63,7 @@ const CustomerEffortScore: React.VFC< CustomerEffortScoreProps > = ( {
return; return;
} }
createNotice( 'success', label, { createNotice( 'success', title, {
actions: [ actions: [
{ {
label: __( 'Give feedback', 'woocommerce' ), label: __( 'Give feedback', 'woocommerce' ),
@ -83,7 +93,9 @@ const CustomerEffortScore: React.VFC< CustomerEffortScoreProps > = ( {
return ( return (
<CustomerFeedbackModal <CustomerFeedbackModal
label={ label } title={ title }
firstQuestion={ firstQuestion }
secondQuestion={ secondQuestion }
recordScoreCallback={ recordScoreCallback } recordScoreCallback={ recordScoreCallback }
/> />
); );
@ -95,9 +107,9 @@ CustomerEffortScore.propTypes = {
*/ */
recordScoreCallback: PropTypes.func.isRequired, recordScoreCallback: PropTypes.func.isRequired,
/** /**
* The label displayed in the modal. * The title displayed in the modal.
*/ */
label: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
/** /**
* The function to call when the notice is shown. * The function to call when the notice is shown.
*/ */

View File

@ -25,18 +25,28 @@ import { __ } from '@wordpress/i18n';
* *
* @param {Object} props Component props. * @param {Object} props Component props.
* @param {Function} props.recordScoreCallback Function to call when the results are sent. * @param {Function} props.recordScoreCallback Function to call when the results are sent.
* @param {string} props.label Question to ask the customer. * @param {string} props.title Title displayed in the modal.
* @param {string} props.firstQuestion The first survey question.
* @param {string} props.secondQuestion The second survey question.
* @param {string} props.defaultScore Default score. * @param {string} props.defaultScore Default score.
* @param {Function} props.onCloseModal Callback for when user closes modal by clicking cancel. * @param {Function} props.onCloseModal Callback for when user closes modal by clicking cancel.
*/ */
function CustomerFeedbackModal( { function CustomerFeedbackModal( {
recordScoreCallback, recordScoreCallback,
label, title,
firstQuestion,
secondQuestion,
defaultScore = NaN, defaultScore = NaN,
onCloseModal, onCloseModal,
}: { }: {
recordScoreCallback: ( score: number, comments: string ) => void; recordScoreCallback: (
label: string; score: number,
secondScore: number,
comments: string
) => void;
title: string;
firstQuestion: string;
secondQuestion: string;
defaultScore?: number; defaultScore?: number;
onCloseModal?: () => void; onCloseModal?: () => void;
} ): JSX.Element | null { } ): JSX.Element | null {
@ -63,7 +73,12 @@ function CustomerFeedbackModal( {
}, },
]; ];
const [ score, setScore ] = useState( defaultScore || NaN ); const [ firstQuestionScore, setFirstQuestionScore ] = useState(
defaultScore || NaN
);
const [ secondQuestionScore, setSecondQuestionScore ] = useState(
defaultScore || NaN
);
const [ comments, setComments ] = useState( '' ); const [ comments, setComments ] = useState( '' );
const [ showNoScoreMessage, setShowNoScoreMessage ] = useState( false ); const [ showNoScoreMessage, setShowNoScoreMessage ] = useState( false );
const [ isOpen, setOpen ] = useState( true ); const [ isOpen, setOpen ] = useState( true );
@ -75,19 +90,31 @@ function CustomerFeedbackModal( {
} }
}; };
const onRadioControlChange = ( value: string ) => { const onRadioControlChange = (
value: string,
setter: ( val: number ) => void
) => {
const valueAsInt = parseInt( value, 10 ); const valueAsInt = parseInt( value, 10 );
setScore( valueAsInt ); setter( valueAsInt );
setShowNoScoreMessage( ! Number.isInteger( valueAsInt ) ); setShowNoScoreMessage( ! Number.isInteger( valueAsInt ) );
}; };
const sendScore = () => { const sendScore = () => {
if ( ! Number.isInteger( score ) ) { if (
! (
Number.isInteger( firstQuestionScore ) &&
Number.isInteger( secondQuestionScore )
)
) {
setShowNoScoreMessage( true ); setShowNoScoreMessage( true );
return; return;
} }
setOpen( false ); setOpen( false );
recordScoreCallback( score, comments ); recordScoreCallback(
firstQuestionScore,
secondQuestionScore,
comments
);
}; };
if ( ! isOpen ) { if ( ! isOpen ) {
@ -97,10 +124,24 @@ function CustomerFeedbackModal( {
return ( return (
<Modal <Modal
className="woocommerce-customer-effort-score" className="woocommerce-customer-effort-score"
title={ __( 'Please share your feedback', 'woocommerce' ) } title={ title }
onRequestClose={ closeModal } onRequestClose={ closeModal }
shouldCloseOnClickOutside={ false } shouldCloseOnClickOutside={ false }
> >
<Text
variant="body"
as="p"
className="woocommerce-customer-effort-score__intro"
size={ 14 }
lineHeight="20px"
marginBottom="1.5em"
>
{ __(
'Your feedback will help create a better experience for thousands of merchants like you. Please tell us to what extent you agree or disagree with the statements below.',
'woocommerce'
) }
</Text>
<Text <Text
variant="subtitle.small" variant="subtitle.small"
as="p" as="p"
@ -108,26 +149,63 @@ function CustomerFeedbackModal( {
size="14" size="14"
lineHeight="20px" lineHeight="20px"
> >
{ label } { firstQuestion }
</Text> </Text>
<div className="woocommerce-customer-effort-score__selection"> <div className="woocommerce-customer-effort-score__selection">
<RadioControl <RadioControl
selected={ score.toString( 10 ) } selected={ firstQuestionScore.toString( 10 ) }
options={ options } options={ options }
onChange={ onRadioControlChange } onChange={ ( value ) =>
onRadioControlChange(
value as string,
setFirstQuestionScore
)
}
/> />
</div> </div>
{ ( score === 1 || score === 2 ) && ( <Text
variant="subtitle.small"
as="p"
weight="600"
size="14"
lineHeight="20px"
>
{ secondQuestion }
</Text>
<div className="woocommerce-customer-effort-score__selection">
<RadioControl
selected={ secondQuestionScore.toString( 10 ) }
options={ options }
onChange={ ( value ) =>
onRadioControlChange(
value as string,
setSecondQuestionScore
)
}
/>
</div>
{ [ firstQuestionScore, secondQuestionScore ].some(
( score ) => score === 1 || score === 2
) && (
<div className="woocommerce-customer-effort-score__comments"> <div className="woocommerce-customer-effort-score__comments">
<TextareaControl <TextareaControl
label={ __( 'Comments (optional)', 'woocommerce' ) } label={ __(
'How is that screen useful to you? What features would you add or change?',
'woocommerce'
) }
help={ __( help={ __(
'Your feedback will go to the WooCommerce development team', 'Your feedback will go to the WooCommerce development team',
'woocommerce' 'woocommerce'
) } ) }
value={ comments } value={ comments }
placeholder={ __(
'Optional, but much apprecated. We love reading your feedback!',
'woocommerce'
) }
onChange={ ( value: string ) => setComments( value ) } onChange={ ( value: string ) => setComments( value ) }
rows={ 5 } rows={ 5 }
/> />
@ -153,7 +231,7 @@ function CustomerFeedbackModal( {
{ __( 'Cancel', 'woocommerce' ) } { __( 'Cancel', 'woocommerce' ) }
</Button> </Button>
<Button isPrimary onClick={ sendScore } name="send"> <Button isPrimary onClick={ sendScore } name="send">
{ __( 'Send', 'woocommerce' ) } { __( 'Share', 'woocommerce' ) }
</Button> </Button>
</div> </div>
</Modal> </Modal>
@ -162,7 +240,9 @@ function CustomerFeedbackModal( {
CustomerFeedbackModal.propTypes = { CustomerFeedbackModal.propTypes = {
recordScoreCallback: PropTypes.func.isRequired, recordScoreCallback: PropTypes.func.isRequired,
label: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
firstQuestion: PropTypes.string.isRequired,
secondQuestion: PropTypes.string.isRequired,
defaultScore: PropTypes.number, defaultScore: PropTypes.number,
onCloseModal: PropTypes.func, onCloseModal: PropTypes.func,
}; };

View File

@ -16,7 +16,9 @@ describe( 'CustomerFeedbackModal', () => {
render( render(
<CustomerFeedbackModal <CustomerFeedbackModal
recordScoreCallback={ mockRecordScoreCallback } recordScoreCallback={ mockRecordScoreCallback }
label="Testing" title="Testing"
firstQuestion="First question"
secondQuestion="Second question"
/> />
); );
@ -33,13 +35,15 @@ describe( 'CustomerFeedbackModal', () => {
render( render(
<CustomerFeedbackModal <CustomerFeedbackModal
recordScoreCallback={ mockRecordScoreCallback } recordScoreCallback={ mockRecordScoreCallback }
label="Testing" title="Testing"
firstQuestion="First question"
secondQuestion="Second question"
/> />
); );
await screen.findByRole( 'dialog' ); // Wait for the modal to render. await screen.findByRole( 'dialog' ); // Wait for the modal to render.
fireEvent.click( screen.getByRole( 'button', { name: /send/i } ) ); // Press send button. fireEvent.click( screen.getByRole( 'button', { name: /share/i } ) ); // Press send button.
// Wait for error message. // Wait for error message.
await screen.findByRole( 'alert' ); await screen.findByRole( 'alert' );
@ -51,7 +55,9 @@ describe( 'CustomerFeedbackModal', () => {
render( render(
<CustomerFeedbackModal <CustomerFeedbackModal
recordScoreCallback={ mockRecordScoreCallback } recordScoreCallback={ mockRecordScoreCallback }
label="Testing" title="Testing"
firstQuestion="First question"
secondQuestion="Second question"
/> />
); );
@ -59,7 +65,9 @@ describe( 'CustomerFeedbackModal', () => {
await screen.findByRole( 'dialog' ); await screen.findByRole( 'dialog' );
expect( expect(
screen.queryByLabelText( 'Comments (optional)' ) screen.queryByLabelText(
'How is that screen useful to you? What features would you add or change?'
)
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
} ); } );
@ -69,7 +77,9 @@ describe( 'CustomerFeedbackModal', () => {
render( render(
<CustomerFeedbackModal <CustomerFeedbackModal
recordScoreCallback={ mockRecordScoreCallback } recordScoreCallback={ mockRecordScoreCallback }
label="Testing" title="Testing"
firstQuestion="First question"
secondQuestion="Second question"
/> />
); );
@ -77,18 +87,22 @@ describe( 'CustomerFeedbackModal', () => {
await screen.findByRole( 'dialog' ); await screen.findByRole( 'dialog' );
// Select the option. // Select the option.
fireEvent.click( screen.getByLabelText( labelText ) ); fireEvent.click( screen.getAllByLabelText( labelText )[ 0 ] );
// Wait for comments field to show. // Wait for comments field to show.
await screen.findByLabelText( 'Comments (optional)' ); await screen.findByLabelText(
'How is that screen useful to you? What features would you add or change?'
);
// Select neutral score. // Select neutral score.
fireEvent.click( screen.getByLabelText( 'Neutral' ) ); fireEvent.click( screen.getAllByLabelText( 'Neutral' )[ 0 ] );
// Wait for comments field to hide. // Wait for comments field to hide.
await waitFor( () => { await waitFor( () => {
expect( expect(
screen.queryByLabelText( 'Comments (optional)' ) screen.queryByLabelText(
'How is that screen useful to you? What features would you add or change?'
)
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
} ); } );
} }

View File

@ -1,7 +1,7 @@
@import 'customer-feedback-simple/customer-feedback-simple.scss'; @import 'customer-feedback-simple/customer-feedback-simple.scss';
.woocommerce-customer-effort-score__selection { .woocommerce-customer-effort-score__selection {
margin: 1em 0; margin: 1em 0 1.5em 0;
.components-base-control__field { .components-base-control__field {
display: flex; display: flex;
@ -91,10 +91,14 @@
} }
.woocommerce-customer-effort-score__comments { .woocommerce-customer-effort-score__comments {
margin-bottom: 1.5em;
label { label {
display: block; display: block;
color: inherit; color: inherit;
font-weight: bold; font-weight: bold;
text-transform: none;
font-size: 14px;
} }
textarea { textarea {
@ -109,3 +113,8 @@
margin-left: 1em; margin-left: 1em;
} }
} }
.woocommerce-customer-effort-score .woocommerce-customer-effort-score__intro {
max-width: 550px;
margin: 0 0 1.5em 0;
}

View File

@ -35,7 +35,9 @@ describe( 'CustomerEffortScore', () => {
render( render(
<CustomerEffortScore <CustomerEffortScore
recordScoreCallback={ noop } recordScoreCallback={ noop }
label={ 'label' } title={ 'title' }
firstQuestion="First question"
secondQuestion="Second question"
onNoticeDismissedCallback={ noop } onNoticeDismissedCallback={ noop }
icon={ icon } icon={ icon }
/> />
@ -45,7 +47,7 @@ describe( 'CustomerEffortScore', () => {
// Notice status. // Notice status.
expect.any( String ), expect.any( String ),
// Notice message. // Notice message.
'label', 'title',
// Notice options. // Notice options.
expect.objectContaining( { expect.objectContaining( {
icon, icon,
@ -63,7 +65,9 @@ describe( 'CustomerEffortScore', () => {
const { rerender } = render( const { rerender } = render(
<CustomerEffortScore <CustomerEffortScore
recordScoreCallback={ noop } recordScoreCallback={ noop }
label={ 'label' } title={ 'title' }
firstQuestion="First question"
secondQuestion="Second question"
/> />
); );
@ -71,7 +75,9 @@ describe( 'CustomerEffortScore', () => {
rerender( rerender(
<CustomerEffortScore <CustomerEffortScore
recordScoreCallback={ noop } recordScoreCallback={ noop }
label={ 'label2' } title={ 'title2' }
firstQuestion="First question"
secondQuestion="Second question"
/> />
); );
@ -82,7 +88,9 @@ describe( 'CustomerEffortScore', () => {
render( render(
<CustomerEffortScore <CustomerEffortScore
recordScoreCallback={ noop } recordScoreCallback={ noop }
label={ 'label' } title={ 'title' }
firstQuestion="First question"
secondQuestion="Second question"
/> />
); );
@ -121,7 +129,9 @@ describe( 'CustomerEffortScore', () => {
render( render(
<CustomerEffortScore <CustomerEffortScore
recordScoreCallback={ noop } recordScoreCallback={ noop }
label={ 'label' } title={ 'title' }
firstQuestion="First question"
secondQuestion="Second question"
onModalShownCallback={ mockOnModalShownCallback } onModalShownCallback={ mockOnModalShownCallback }
/> />
); );

View File

@ -49,7 +49,9 @@ function CustomerEffortScoreTracksContainer( {
<CustomerEffortScoreTracks <CustomerEffortScoreTracks
key={ index } key={ index }
action={ item.action } action={ item.action }
label={ item.label } firstQuestion={ item.firstQuestion }
secondQuestion={ item.secondQuestion }
title={ item.title }
onSubmitLabel={ item.onsubmit_label } onSubmitLabel={ item.onsubmit_label }
trackProps={ item.props || {} } trackProps={ item.props || {} }
/> />

View File

@ -22,7 +22,9 @@ const ALLOW_TRACKING_OPTION_NAME = 'woocommerce_allow_tracking';
* @param {Object} props Component props. * @param {Object} props Component props.
* @param {string} props.action The action 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.title The title displayed in the modal.
* @param {string} props.firstQuestion The first survey question.
* @param {string} props.secondQuestion The second survey question.
* @param {string} props.onSubmitLabel The label displayed upon survey submission. * @param {string} props.onSubmitLabel The label displayed upon survey submission.
* @param {Array} props.cesShownForActions The array of actions that the CES modal has been shown for. * @param {Array} props.cesShownForActions The array of actions that the CES modal has been shown for.
* @param {boolean} props.allowTracking Whether tracking is allowed or not. * @param {boolean} props.allowTracking Whether tracking is allowed or not.
@ -34,7 +36,9 @@ const ALLOW_TRACKING_OPTION_NAME = 'woocommerce_allow_tracking';
function CustomerEffortScoreTracks( { function CustomerEffortScoreTracks( {
action, action,
trackProps, trackProps,
label, title,
firstQuestion,
secondQuestion,
onSubmitLabel = __( 'Thank you for your feedback!', 'woocommerce' ), onSubmitLabel = __( 'Thank you for your feedback!', 'woocommerce' ),
cesShownForActions, cesShownForActions,
allowTracking, allowTracking,
@ -104,10 +108,12 @@ function CustomerEffortScoreTracks( {
addActionToShownOption(); addActionToShownOption();
}; };
const recordScore = ( score, comments ) => { const recordScore = ( score, secondScore, comments ) => {
recordEvent( 'ces_feedback', { recordEvent( 'ces_feedback', {
action, action,
score, score,
score_second_question: secondScore,
score_combined: score + secondScore,
comments: comments || '', comments: comments || '',
store_age: storeAgeInWeeks, store_age: storeAgeInWeeks,
...trackProps, ...trackProps,
@ -118,7 +124,9 @@ function CustomerEffortScoreTracks( {
return ( return (
<CustomerEffortScore <CustomerEffortScore
recordScoreCallback={ recordScore } recordScoreCallback={ recordScore }
label={ label } title={ title }
firstQuestion={ firstQuestion }
secondQuestion={ secondQuestion }
onNoticeShownCallback={ onNoticeShown } onNoticeShownCallback={ onNoticeShown }
onNoticeDismissedCallback={ onNoticeDismissed } onNoticeDismissedCallback={ onNoticeDismissed }
onModalShownCallback={ onModalShown } onModalShownCallback={ onModalShown }

View File

@ -23,25 +23,32 @@ export function setCesSurveyQueue( queue ) {
/** /**
* Add a new CES track to the state. * Add a new CES track to the state.
* *
* @param {string} action action name for the survey * @param {Object} args All arguments.
* @param {string} label label for the snackback * @param {string} args.action action name for the survey
* @param {string} pageNow value of window.pagenow * @param {string} args.title title for the snackback
* @param {string} adminPage value of window.adminpage * @param {string} args.firstQuestion first question for modal survey
* @param {string} onsubmitLabel label for the snackback onsubmit * @param {string} args.secondQuestion second question for modal survey
* @param {Object} props object for optional props * @param {string} args.pageNow value of window.pagenow
* @param {string} args.adminPage value of window.adminpage
* @param {string} args.onsubmitLabel label for the snackback onsubmit
* @param {Object} args.props object for optional props
*/ */
export function addCesSurvey( export function addCesSurvey( {
action, action,
label, title,
firstQuestion,
secondQuestion,
pageNow = window.pagenow, pageNow = window.pagenow,
adminPage = window.adminpage, adminPage = window.adminpage,
onsubmitLabel = undefined, onsubmitLabel = undefined,
props = {} props = {},
) { } ) {
return { return {
type: TYPES.ADD_CES_SURVEY, type: TYPES.ADD_CES_SURVEY,
action, action,
label, title,
firstQuestion,
secondQuestion,
pageNow, pageNow,
adminPage, adminPage,
onsubmit_label: onsubmitLabel, onsubmit_label: onsubmitLabel,
@ -53,26 +60,45 @@ export function addCesSurvey(
* Add a new CES survey track for the pages in Analytics menu * Add a new CES survey track for the pages in Analytics menu
*/ */
export function addCesSurveyForAnalytics() { export function addCesSurveyForAnalytics() {
return addCesSurvey( return addCesSurvey( {
'analytics_filtered', action: 'analytics_filtered',
__( 'How easy was it to filter your store analytics?', 'woocommerce' ), title: __(
'woocommerce_page_wc-admin', 'How easy was it to filter your store analytics?',
'woocommerce_page_wc-admin' 'woocommerce'
); ),
firstQuestion: __(
'The filters in the analytics screen are easy to use.',
'woocommerce'
),
secondQuestion: __(
`The filters' functionality meets my needs.`,
'woocommerce'
),
pageNow: 'woocommerce_page_wc-admin',
adminPage: 'woocommerce_page_wc-admin',
} );
} }
/** /**
* Add a new CES survey track on searching customers. * Add a new CES survey track on searching customers.
*/ */
export function addCesSurveyForCustomerSearch() { export function addCesSurveyForCustomerSearch() {
return addCesSurvey( return addCesSurvey( {
'ces_search', action: 'ces_search',
__( 'How easy was it to use search?', 'woocommerce' ), title: __( 'How easy was it to use search?', 'woocommerce' ),
'woocommerce_page_wc-admin', firstQuestion: __(
'woocommerce_page_wc-admin', 'The search feature in WooCommerce is easy to use.',
undefined, 'woocommerce'
{ ),
secondQuestion: __(
`The search's functionality meets my needs.`,
'woocommerce'
),
pageNow: 'woocommerce_page_wc-admin',
adminPage: 'woocommerce_page_wc-admin',
onsubmit_label: undefined,
props: {
search_area: 'customer', search_area: 'customer',
} },
); } );
} }

View File

@ -24,7 +24,9 @@ const reducer = ( state = DEFAULT_STATE, action ) => {
} }
const newTrack = { const newTrack = {
action: action.action, action: action.action,
label: action.label, title: action.title,
firstQuestion: action.firstQuestion,
secondQuestion: action.secondQuestion,
pagenow: action.pageNow, pagenow: action.pageNow,
adminpage: action.adminPage, adminpage: action.adminPage,
onSubmitLabel: action.onSubmitLabel, onSubmitLabel: action.onSubmitLabel,

View File

@ -97,10 +97,20 @@ export const TaskListCompletedHeader: React.FC<
} }
}, [ hasSubmittedScore ] ); }, [ hasSubmittedScore ] );
const submitScore = ( recordedScore: number, comments?: string ) => { const submitScore = ( {
firstScore,
secondScore,
comments,
}: {
firstScore: number;
secondScore?: number;
comments?: string;
} ) => {
recordEvent( 'ces_feedback', { recordEvent( 'ces_feedback', {
action: CUSTOMER_EFFORT_SCORE_ACTION, action: CUSTOMER_EFFORT_SCORE_ACTION,
score: recordedScore, score: firstScore,
score_second_question: secondScore ?? null,
score_combined: firstScore + ( secondScore ?? 0 ),
comments: comments || '', comments: comments || '',
store_age: storeAgeInWeeks, store_age: storeAgeInWeeks,
} ); } );
@ -116,7 +126,7 @@ export const TaskListCompletedHeader: React.FC<
const recordScore = ( recordedScore: number ) => { const recordScore = ( recordedScore: number ) => {
if ( recordedScore > 2 ) { if ( recordedScore > 2 ) {
setScore( recordedScore ); setScore( recordedScore );
submitScore( recordedScore ); submitScore( { firstScore: recordedScore } );
} else { } else {
setScore( recordedScore ); setScore( recordedScore );
setShowCesModal( true ); setShowCesModal( true );
@ -127,9 +137,13 @@ export const TaskListCompletedHeader: React.FC<
} }
}; };
const recordModalScore = ( recordedScore: number, comments: string ) => { const recordModalScore = (
firstScore: number,
secondScore: number,
comments: string
) => {
setShowCesModal( false ); setShowCesModal( false );
submitScore( recordedScore, comments ); submitScore( { firstScore, secondScore, comments } );
}; };
return ( return (
@ -230,7 +244,15 @@ export const TaskListCompletedHeader: React.FC<
</div> </div>
{ showCesModal ? ( { showCesModal ? (
<CustomerFeedbackModal <CustomerFeedbackModal
label={ __( 'How was your experience?', 'woocommerce' ) } title={ __( 'How was your experience?', 'woocommerce' ) }
firstQuestion={ __(
'The store setup is easy to complete.',
'woocommerce'
) }
secondQuestion={ __(
'The store setup process meets my needs.',
'woocommerce'
) }
defaultScore={ score } defaultScore={ score }
recordScoreCallback={ recordModalScore } recordScoreCallback={ recordModalScore }
onCloseModal={ () => { onCloseModal={ () => {

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Updating all CES events to support two questions in modal.

View File

@ -142,11 +142,13 @@ class CustomerEffortScoreTracks {
* an increase of the number of rows in tags table * an increase of the number of rows in tags table
* *
* @param string $action Action name for the survey. * @param string $action Action name for the survey.
* @param string $label Label for the snackbar. * @param string $title Title for the snackbar.
* @param string $first_question The text for the first question.
* @param string $second_question The text for the second question.
* *
* @return string Generated JavaScript to append to page. * @return string Generated JavaScript to append to page.
*/ */
private function get_script_track_edit_php( $action, $label ) { private function get_script_track_edit_php( $action, $title, $first_question, $second_question ) {
return sprintf( return sprintf(
"(function( $ ) { "(function( $ ) {
'use strict'; 'use strict';
@ -158,7 +160,7 @@ class CustomerEffortScoreTracks {
if ( $('.tags tbody > tr').length > initialCount ) { if ( $('.tags tbody > tr').length > initialCount ) {
// New tag detected. // New tag detected.
clearInterval( interval ); clearInterval( interval );
wp.data.dispatch('wc/customer-effort-score').addCesSurvey( '%s', '%s', window.pagenow, window.adminpage, '%s' ); wp.data.dispatch('wc/customer-effort-score').addCesSurvey({ action: '%s', title: '%s', firstQuestion: '%s', secondQuestion: '%s', onsubmitLabel: '%s' });
} else { } else {
// Form is no longer loading, most likely failed. // Form is no longer loading, most likely failed.
if ( $( '#addtag .submit .spinner.is-active' ).length < 1 ) { if ( $( '#addtag .submit .spinner.is-active' ).length < 1 ) {
@ -169,7 +171,9 @@ class CustomerEffortScoreTracks {
}); });
})( jQuery );", })( jQuery );",
esc_js( $action ), esc_js( $action ),
esc_js( $label ), esc_js( $title ),
esc_js( $first_question ),
esc_js( $second_question ),
esc_js( $this->onsubmit_label ) esc_js( $this->onsubmit_label )
); );
} }
@ -271,10 +275,18 @@ class CustomerEffortScoreTracks {
$this->enqueue_to_ces_tracks( $this->enqueue_to_ces_tracks(
array( array(
'action' => self::SEARCH_ACTION_NAME, 'action' => self::SEARCH_ACTION_NAME,
'label' => __( 'title' => __(
'How easy was it to use search?', 'How easy was it to use search?',
'woocommerce' 'woocommerce'
), ),
'firstQuestion' => __(
'The search feature in WooCommerce is easy to use.',
'woocommerce'
),
'secondQuestion' => __(
'The search\'s functionality meets my needs.',
'woocommerce'
),
'onsubmit_label' => $this->onsubmit_label, 'onsubmit_label' => $this->onsubmit_label,
'pagenow' => $page_now, 'pagenow' => $page_now,
'adminpage' => $admin_page, 'adminpage' => $admin_page,
@ -337,10 +349,18 @@ class CustomerEffortScoreTracks {
$this->enqueue_to_ces_tracks( $this->enqueue_to_ces_tracks(
array( array(
'action' => self::PRODUCT_ADD_PUBLISH_ACTION_NAME, 'action' => self::PRODUCT_ADD_PUBLISH_ACTION_NAME,
'label' => __( 'title' => __(
'How easy was it to add a product?', 'How easy was it to add a product?',
'woocommerce' 'woocommerce'
), ),
'firstQuestion' => __(
'The product creation screen is easy to use.',
'woocommerce'
),
'secondQuestion' => __(
'The product creation screen\'s functionality meets my needs.',
'woocommerce'
),
'onsubmit_label' => $this->onsubmit_label, 'onsubmit_label' => $this->onsubmit_label,
'pagenow' => 'product', 'pagenow' => 'product',
'adminpage' => 'post-php', 'adminpage' => 'post-php',
@ -362,10 +382,18 @@ class CustomerEffortScoreTracks {
$this->enqueue_to_ces_tracks( $this->enqueue_to_ces_tracks(
array( array(
'action' => self::PRODUCT_UPDATE_ACTION_NAME, 'action' => self::PRODUCT_UPDATE_ACTION_NAME,
'label' => __( 'title' => __(
'How easy was it to edit your product?', 'How easy was it to edit your product?',
'woocommerce' 'woocommerce'
), ),
'firstQuestion' => __(
'The product update process is easy to complete.',
'woocommerce'
),
'secondQuestion' => __(
'The product update process meets my needs.',
'woocommerce'
),
'onsubmit_label' => $this->onsubmit_label, 'onsubmit_label' => $this->onsubmit_label,
'pagenow' => 'product', 'pagenow' => 'product',
'adminpage' => 'post-php', 'adminpage' => 'post-php',
@ -387,10 +415,18 @@ class CustomerEffortScoreTracks {
$this->enqueue_to_ces_tracks( $this->enqueue_to_ces_tracks(
array( array(
'action' => self::SHOP_ORDER_UPDATE_ACTION_NAME, 'action' => self::SHOP_ORDER_UPDATE_ACTION_NAME,
'label' => __( 'title' => __(
'How easy was it to update an order?', 'How easy was it to update an order?',
'woocommerce' 'woocommerce'
), ),
'firstQuestion' => __(
'The order details screen is easy to use.',
'woocommerce'
),
'secondQuestion' => __(
'The order details screen\'s functionality meets my needs.',
'woocommerce'
),
'onsubmit_label' => $this->onsubmit_label, 'onsubmit_label' => $this->onsubmit_label,
'pagenow' => 'shop_order', 'pagenow' => 'shop_order',
'adminpage' => 'post-php', 'adminpage' => 'post-php',
@ -447,7 +483,9 @@ class CustomerEffortScoreTracks {
wc_enqueue_js( wc_enqueue_js(
$this->get_script_track_edit_php( $this->get_script_track_edit_php(
self::ADD_PRODUCT_CATEGORIES_ACTION_NAME, self::ADD_PRODUCT_CATEGORIES_ACTION_NAME,
__( 'How easy was it to add product category?', 'woocommerce' ) __( 'How easy was it to add product category?', 'woocommerce' ),
__( 'The product category details screen is easy to use.', 'woocommerce' ),
__( "The product category details screen's functionality meets my needs.", 'woocommerce' )
) )
); );
} }
@ -463,7 +501,9 @@ class CustomerEffortScoreTracks {
wc_enqueue_js( wc_enqueue_js(
$this->get_script_track_edit_php( $this->get_script_track_edit_php(
self::ADD_PRODUCT_TAGS_ACTION_NAME, self::ADD_PRODUCT_TAGS_ACTION_NAME,
__( 'How easy was it to add a product tag?', 'woocommerce' ) __( 'How easy was it to add a product tag?', 'woocommerce' ),
__( 'The product tag details screen is easy to use.', 'woocommerce' ),
__( "The product tag details screen's functionality meets my needs.", 'woocommerce' )
) )
); );
} }
@ -484,10 +524,18 @@ class CustomerEffortScoreTracks {
$this->enqueue_to_ces_tracks( $this->enqueue_to_ces_tracks(
array( array(
'action' => self::IMPORT_PRODUCTS_ACTION_NAME, 'action' => self::IMPORT_PRODUCTS_ACTION_NAME,
'label' => __( 'title' => __(
'How easy was it to import products?', 'How easy was it to import products?',
'woocommerce' 'woocommerce'
), ),
'firstQuestion' => __(
'The product import process is easy to complete.',
'woocommerce'
),
'secondQuestion' => __(
'The product import process meets my needs.',
'woocommerce'
),
'onsubmit_label' => $this->onsubmit_label, 'onsubmit_label' => $this->onsubmit_label,
'pagenow' => 'product_page_product_importer', 'pagenow' => 'product_page_product_importer',
'adminpage' => 'product_page_product_importer', 'adminpage' => 'product_page_product_importer',
@ -519,10 +567,18 @@ class CustomerEffortScoreTracks {
$this->enqueue_to_ces_tracks( $this->enqueue_to_ces_tracks(
array( array(
'action' => self::SETTINGS_CHANGE_ACTION_NAME, 'action' => self::SETTINGS_CHANGE_ACTION_NAME,
'label' => __( 'title' => __(
'How easy was it to update your settings?', 'How easy was it to update your settings?',
'woocommerce' 'woocommerce'
), ),
'firstQuestion' => __(
'The settings screen is easy to use.',
'woocommerce'
),
'secondQuestion' => __(
'The settings screen\'s functionality meets my needs.',
'woocommerce'
),
'onsubmit_label' => $this->onsubmit_label, 'onsubmit_label' => $this->onsubmit_label,
'pagenow' => 'woocommerce_page_wc-settings', 'pagenow' => 'woocommerce_page_wc-settings',
'adminpage' => 'woocommerce_page_wc-settings', 'adminpage' => 'woocommerce_page_wc-settings',
@ -542,10 +598,18 @@ class CustomerEffortScoreTracks {
$this->enqueue_to_ces_tracks( $this->enqueue_to_ces_tracks(
array( array(
'action' => self::ADD_PRODUCT_ATTRIBUTES_ACTION_NAME, 'action' => self::ADD_PRODUCT_ATTRIBUTES_ACTION_NAME,
'label' => __( 'title' => __(
'How easy was it to add a product attribute?', 'How easy was it to add a product attribute?',
'woocommerce' 'woocommerce'
), ),
'firstQuestion' => __(
'Product attributes are easy to use.',
'woocommerce'
),
'secondQuestion' => __(
'Product attributes\' functionality meets my needs.',
'woocommerce'
),
'onsubmit_label' => $this->onsubmit_label, 'onsubmit_label' => $this->onsubmit_label,
'pagenow' => 'product_page_product_attributes', 'pagenow' => 'product_page_product_attributes',
'adminpage' => 'product_page_product_attributes', 'adminpage' => 'product_page_product_attributes',