Add/35300 ces feedback product mvp (#35690)

* Add ability to show CES modal through share Feedback button

* Make use of showCesModal in footer

* Update CES action for share feedback

* Update changes to support second CES question

* Add changelog

* Address some PR feedback
This commit is contained in:
louwie17 2022-12-02 11:59:42 -04:00 committed by GitHub
parent 0e8fbe083d
commit e7dd1a0be9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 206 additions and 61 deletions

View File

@ -0,0 +1,87 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { useDispatch, useSelect } from '@wordpress/data';
import { CustomerFeedbackModal } from '@woocommerce/customer-effort-score';
import { recordEvent } from '@woocommerce/tracks';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
/**
* Internal dependencies
*/
import { getStoreAgeInWeeks } from './utils';
import { ADMIN_INSTALL_TIMESTAMP_OPTION_NAME } from './constants';
import { STORE_KEY } from './data/constants';
export const PRODUCT_MVP_CES_ACTION_OPTION_NAME =
'woocommerce_ces_product_mvp_ces_action';
export const CustomerEffortScoreModalContainer: React.FC = () => {
const { createSuccessNotice } = useDispatch( 'core/notices' );
const { hideCesModal } = useDispatch( STORE_KEY );
const {
storeAgeInWeeks,
resolving: isLoading,
visibleCESModalData,
} = useSelect( ( select ) => {
const { getOption, hasFinishedResolution } =
select( OPTIONS_STORE_NAME );
const { getVisibleCESModalData } = select( STORE_KEY );
const adminInstallTimestamp =
( getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) as number ) || 0;
const resolving =
adminInstallTimestamp === null ||
! hasFinishedResolution( 'getOption', [
ADMIN_INSTALL_TIMESTAMP_OPTION_NAME,
] );
return {
storeAgeInWeeks: getStoreAgeInWeeks( adminInstallTimestamp ),
visibleCESModalData: getVisibleCESModalData(),
resolving,
};
} );
const recordScore = (
score: number,
secondScore: number,
comments: string
) => {
recordEvent( 'ces_feedback', {
action: visibleCESModalData.action,
score,
score_second_question: secondScore ?? null,
score_combined: score + ( secondScore ?? 0 ),
comments: comments || '',
store_age: storeAgeInWeeks,
} );
createSuccessNotice(
visibleCESModalData.onSubmitLabel ||
__(
"Thanks for the feedback. We'll put it to good use!",
'woocommerce'
),
visibleCESModalData.onSubmitNoticeProps || {}
);
};
if ( ! visibleCESModalData || isLoading ) {
return null;
}
return (
<CustomerFeedbackModal
title={ visibleCESModalData.label }
firstQuestion={ visibleCESModalData.firstQuestion }
secondQuestion={ visibleCESModalData.secondQuestion }
recordScoreCallback={ ( ...args ) => {
recordScore( ...args );
hideCesModal();
} }
onCloseModal={ () => hideCesModal() }
/>
);
};

View File

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

View File

@ -56,6 +56,36 @@ export function addCesSurvey( {
};
}
/**
* Add show CES modal.
*
* @param {Object} surveyProps props for CES survey, similar to addCesSurvey.
* @param {Object} props object for optional props
* @param {Object} onSubmitNoticeProps object for on submit notice props.
*/
export function showCesModal(
surveyProps = {},
props = {},
onSubmitNoticeProps = {}
) {
return {
type: TYPES.SHOW_CES_MODAL,
surveyProps,
onsubmit_label: surveyProps.onsubmitLabel || '',
props,
onSubmitNoticeProps,
};
}
/**
* Hide CES Modal.
*/
export function hideCesModal() {
return {
type: TYPES.HIDE_CES_MODAL,
};
}
/**
* Add a new CES survey track for the pages in Analytics menu
*/

View File

@ -5,6 +5,8 @@ import TYPES from './action-types';
const DEFAULT_STATE = {
queue: [],
cesModalData: undefined,
showCESModal: false,
};
const reducer = ( state = DEFAULT_STATE, action ) => {
@ -14,6 +16,27 @@ const reducer = ( state = DEFAULT_STATE, action ) => {
...state,
queue: action.queue,
};
case TYPES.HIDE_CES_MODAL:
return {
...state,
showCESModal: false,
cesModalData: undefined,
};
case TYPES.SHOW_CES_MODAL:
const cesModalData = {
action: action.surveyProps.action,
label: action.surveyProps.label,
onSubmitLabel: action.onSubmitLabel,
firstQuestion: action.surveyProps.firstQuestion,
secondQuestion: action.surveyProps.secondQuestion,
onSubmitNoticeProps: action.onSubmitNoticeProps || {},
props: action.props,
};
return {
...state,
showCESModal: true,
cesModalData,
};
case TYPES.ADD_CES_SURVEY:
// Prevent duplicate
const hasDuplicate = state.queue.filter(

View File

@ -1,3 +1,7 @@
export function getCesSurveyQueue( state ) {
return state.queue;
}
export function getVisibleCESModalData( state ) {
return state.showCESModal ? state.cesModalData : undefined;
}

View File

@ -1,2 +1,3 @@
export { default as CustomerEffortScoreTracks } from './customer-effort-score-tracks';
export { default as CustomerEffortScoreTracksContainer } from './customer-effort-score-tracks-container';
export * from './customer-effort-score-modal-container.tsx';

View File

@ -2,36 +2,30 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { closeSmall } from '@wordpress/icons';
import { Pill } from '@woocommerce/components';
import { CustomerFeedbackModal } from '@woocommerce/customer-effort-score';
import { recordEvent } from '@woocommerce/tracks';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
/**
* Internal dependencies
*/
import './product-mvp-ces-footer.scss';
import { getStoreAgeInWeeks } from './utils';
import {
ADMIN_INSTALL_TIMESTAMP_OPTION_NAME,
ALLOW_TRACKING_OPTION_NAME,
SHOWN_FOR_ACTIONS_OPTION_NAME,
} from './constants';
import { WooFooterItem } from '~/layout/footer';
import { STORE_KEY } from './data/constants';
export const PRODUCT_MVP_CES_ACTION_OPTION_NAME =
'woocommerce_ces_product_mvp_ces_action';
export const ProductMVPCESFooter: React.FC = () => {
const [ showFeedbackModal, setShowFeedbackModal ] = useState( false );
const { createSuccessNotice } = useDispatch( 'core/notices' );
const { showCesModal } = useDispatch( STORE_KEY );
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
const {
storeAgeInWeeks,
cesAction,
allowTracking,
cesShownForActions,
@ -47,9 +41,6 @@ export const ProductMVPCESFooter: React.FC = () => {
const shownForActions =
( getOption( SHOWN_FOR_ACTIONS_OPTION_NAME ) as string[] ) || [];
const adminInstallTimestamp =
( getOption( ADMIN_INSTALL_TIMESTAMP_OPTION_NAME ) as number ) || 0;
const allowTrackingOption =
getOption( ALLOW_TRACKING_OPTION_NAME ) || 'no';
@ -60,10 +51,6 @@ export const ProductMVPCESFooter: React.FC = () => {
! hasFinishedResolution( 'getOption', [
PRODUCT_MVP_CES_ACTION_OPTION_NAME,
] ) ||
adminInstallTimestamp === null ||
! hasFinishedResolution( 'getOption', [
ADMIN_INSTALL_TIMESTAMP_OPTION_NAME,
] ) ||
! hasFinishedResolution( 'getOption', [
ALLOW_TRACKING_OPTION_NAME,
] );
@ -71,14 +58,38 @@ export const ProductMVPCESFooter: React.FC = () => {
return {
cesShownForActions: shownForActions,
allowTracking: allowTrackingOption === 'yes',
storeAgeInWeeks: getStoreAgeInWeeks( adminInstallTimestamp ),
cesAction: action,
resolving,
};
} );
const shareFeedback = () => {
setShowFeedbackModal( true );
showCesModal(
{
action: cesAction,
label: __(
"How's your experience with the product editor?",
'woocommerce'
),
firstQuestion: __(
'The product editing screen is easy to use',
'woocommerce'
),
secondQuestion: __(
"The product editing screen's functionality meets my needs",
'woocommerce'
),
onsubmitLabel: __(
"Thanks for the feedback. We'll put it to good use!",
'woocommerce'
),
},
{},
{
type: 'snackbar',
icon: <span>🌟</span>,
}
);
updateOptions( {
[ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [
cesAction,
@ -93,32 +104,6 @@ export const ProductMVPCESFooter: React.FC = () => {
} );
};
const recordScore = (
score: number,
secondScore: number,
comments: string
) => {
recordEvent( 'ces_feedback', {
action: cesAction,
score,
score_second_question: secondScore ?? null,
score_combined: score + ( secondScore ?? 0 ),
comments: comments || '',
store_age: storeAgeInWeeks,
} );
createSuccessNotice(
__(
"Thanks for the feedback. We'll put it to good use!",
'woocommerce'
),
{
type: 'snackbar',
icon: <span>🌟</span>,
}
);
setShowFeedbackModal( false );
};
const showCESFooter =
! isLoading && allowTracking && cesAction && cesAction !== 'hide';
@ -158,24 +143,6 @@ export const ProductMVPCESFooter: React.FC = () => {
</div>
</WooFooterItem>
) }
{ showFeedbackModal && (
<CustomerFeedbackModal
title={ __(
"How's your experience with the product editor?",
'woocommerce'
) }
firstQuestion={ __(
'The product editing screen is easy to use',
'woocommerce'
) }
secondQuestion={ __(
"The product editing screen's functionality meets my needs",
'woocommerce'
) }
recordScoreCallback={ recordScore }
onCloseModal={ () => setShowFeedbackModal( false ) }
/>
) }
</>
);
};

View File

@ -37,6 +37,7 @@ import { Header } from '../header';
import { Footer } from './footer';
import Notices from './notices';
import TransientNotices from './transient-notices';
import { CustomerEffortScoreModalContainer } from '../customer-effort-score-tracks';
import { getAdminSetting } from '~/utils/admin-settings';
import '~/activity-panel';
import '~/mobile-banner';
@ -248,6 +249,7 @@ class _Layout extends Component {
</Suspense>
) }
<Footer />
<CustomerEffortScoreModalContainer />
</div>
<PluginArea scope="woocommerce-admin" />
{ window.wcAdminFeatures.navigation && (

View File

@ -3,6 +3,7 @@
*/
import { __ } from '@wordpress/i18n';
import { DropdownMenu, MenuItem } from '@wordpress/components';
import { useDispatch } from '@wordpress/data';
import { getAdminLink } from '@woocommerce/settings';
import { moreVertical } from '@wordpress/icons';
import { Product } from '@woocommerce/data';
@ -14,10 +15,12 @@ import { useFormContext } from '@woocommerce/components';
import { ClassicEditorIcon } from './images/classic-editor-icon';
import { FeedbackIcon } from './images/feedback-icon';
import { WooHeaderItem } from '~/header/utils';
import { STORE_KEY as CES_STORE_KEY } from '~/customer-effort-score-tracks/data/constants';
import './product-more-menu.scss';
export const ProductMoreMenu = () => {
const { values } = useFormContext< Product >();
const { showCesModal } = useDispatch( CES_STORE_KEY );
const classEditorUrl = values.id
? getAdminLink( `post.php?post=${ values.id }&action=edit` )
@ -36,6 +39,28 @@ export const ProductMoreMenu = () => {
<MenuItem
onClick={ () => {
// @todo This should open the CES modal.
showCesModal(
{
action: 'new_product',
label: __(
"How's your experience with the product editor?",
'woocommerce'
),
firstQuestion: __(
'The product editing screen is easy to use',
'woocommerce'
),
secondQuestion: __(
"The product editing screen's functionality meets my needs",
'woocommerce'
),
},
{},
{
type: 'snackbar',
icon: <span>🌟</span>,
}
);
onClose();
} }
icon={ <FeedbackIcon /> }

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add CES feedback functionality to the share feedback button within the Product MVP.