Add Jetpack connection to plugin benefits step (https://github.com/woocommerce/woocommerce-admin/pull/4374)

* Allow updateActivePlugins to add new plugins instead of replace

* Add autoConnect prop to Jetpack Connect component

* Add connect component to plugin benefits screen

* Add onError and missing prop types for Connect

* Update redirect URL after Jetpack connection

* Add tests for added active plugins

* Skip install if plugin error exists

* Update active and installed plugins to use replace flag

* Update tests to handle replace flag

* Refactor plugin install flow pending state
This commit is contained in:
Joshua T Flowers 2020-05-19 03:47:25 +03:00 committed by GitHub
parent 35616d5f22
commit 65145bf92c
7 changed files with 173 additions and 46 deletions

View File

@ -21,20 +21,44 @@ class Connect extends Component {
props.setIsPending( true );
}
componentDidMount() {
const { autoConnect, jetpackConnectUrl } = this.props;
if ( autoConnect && jetpackConnectUrl ) {
this.connectJetpack();
}
}
componentDidUpdate( prevProps ) {
const { createNotice, error, isRequesting, setIsPending } = this.props;
const {
autoConnect,
createNotice,
error,
isRequesting,
jetpackConnectUrl,
onError,
setIsPending,
} = this.props;
if ( prevProps.isRequesting && ! isRequesting ) {
setIsPending( false );
}
if ( error && error !== prevProps.error ) {
if ( onError ) {
onError();
}
createNotice( 'error', error );
}
if ( autoConnect && jetpackConnectUrl ) {
this.connectJetpack();
}
}
async connectJetpack() {
const { jetpackConnectUrl, onConnect } = this.props;
if ( onConnect ) {
onConnect();
}
@ -42,7 +66,17 @@ class Connect extends Component {
}
render() {
const { hasErrors, isRequesting, onSkip, skipText } = this.props;
const {
autoConnect,
hasErrors,
isRequesting,
onSkip,
skipText,
} = this.props;
if ( autoConnect ) {
return null;
}
return (
<Fragment>
@ -73,6 +107,10 @@ class Connect extends Component {
}
Connect.propTypes = {
/**
* If connection should happen automatically, or requires user confirmation.
*/
autoConnect: PropTypes.bool,
/**
* Method to create a displayed notice.
*/
@ -93,6 +131,14 @@ Connect.propTypes = {
* Generated Jetpack connection URL.
*/
jetpackConnectUrl: PropTypes.string,
/**
* Called before the redirect to Jetpack.
*/
onConnect: PropTypes.func,
/**
* Called when the plugin has an error retrieving the jetpackConnectUrl.
*/
onError: PropTypes.func,
/**
* Called when the plugin connection is skipped.
*/
@ -112,6 +158,7 @@ Connect.propTypes = {
};
Connect.defaultProps = {
autoConnect: false,
setIsPending: () => {},
};

View File

@ -12,11 +12,13 @@ import { filter } from 'lodash';
* WooCommerce dependencies
*/
import { Card, H, Plugins } from '@woocommerce/components';
import { getAdminLink } from '@woocommerce/wc-admin-settings';
import { PLUGINS_STORE_NAME } from '@woocommerce/data';
/**
* Internal dependencies
*/
import Connect from 'dashboard/components/connect';
import Logo from './logo';
import ManagementIcon from './images/management';
import SalesTaxIcon from './images/sales_tax';
@ -30,8 +32,9 @@ class Benefits extends Component {
constructor( props ) {
super( props );
this.state = {
isConnecting: false,
isInstalling: false,
isPending: false,
isActioned: false,
};
this.isJetpackActive = props.activePlugins.includes( 'jetpack' );
@ -55,19 +58,27 @@ class Benefits extends Component {
}
componentDidUpdate( prevProps, prevState ) {
const { goToNextStep, isRequesting } = this.props;
const { isInstalling, isPending } = this.state;
const { goToNextStep } = this.props;
const { isActioned } = this.state;
// No longer pending or updating profile items, go to next step.
if (
isPending &&
! isRequesting &&
! isInstalling &&
( prevProps.isRequesting || prevState.isInstalling )
isActioned &&
! this.isPending() &&
( prevProps.isRequesting ||
prevState.isConnecting ||
prevState.isInstalling )
) {
goToNextStep();
}
}
isPending() {
const { isActioned, isConnecting, isInstalling } = this.state;
const { isRequesting } = this.props;
return isActioned && ( isConnecting || isInstalling || isRequesting );
}
async skipPluginInstall() {
const {
createNotice,
@ -75,10 +86,9 @@ class Benefits extends Component {
updateProfileItems,
} = this.props;
this.setState( { isPending: true } );
const plugins = this.isJetpackActive ? 'skipped-wcs' : 'skipped';
await updateProfileItems( { plugins } );
this.setState( { isActioned: true } );
if ( isProfileItemsError ) {
createNotice(
@ -99,10 +109,7 @@ class Benefits extends Component {
async startPluginInstall() {
const { updateProfileItems, updateOptions } = this.props;
this.setState( {
isInstalling: true,
isPending: true,
} );
this.setState( { isActioned: true, isInstalling: true } );
await updateOptions( {
woocommerce_setup_jetpack_opted_in: true,
@ -191,7 +198,8 @@ class Benefits extends Component {
}
render() {
const { isInstalling, isPending } = this.state;
const { isConnecting, isInstalling } = this.state;
const { isJetpackConnected, isRequesting } = this.props;
const pluginNamesString = this.pluginsToInstall
.map( ( pluginSlug ) => pluginNames[ pluginSlug ] )
@ -212,8 +220,10 @@ class Benefits extends Component {
<div className="woocommerce-profile-wizard__card-actions">
<Button
isPrimary
isBusy={ isPending && isInstalling }
disabled={ isPending }
isBusy={
this.isPending() && ( isInstalling || isConnecting )
}
disabled={ this.isPending() }
onClick={ this.startPluginInstall }
className="woocommerce-profile-wizard__continue"
>
@ -221,8 +231,10 @@ class Benefits extends Component {
</Button>
<Button
isDefault
isBusy={ isPending && ! isInstalling }
disabled={ isPending }
isBusy={
this.isPending() && ! isInstalling && ! isConnecting
}
disabled={ this.isPending() }
className="woocommerce-profile-wizard__skip"
onClick={ this.skipPluginInstall }
>
@ -233,14 +245,37 @@ class Benefits extends Component {
<Plugins
autoInstall
onComplete={ () =>
this.setState( { isInstalling: false } )
this.setState( {
isInstalling: false,
isConnecting: ! isJetpackConnected,
} )
}
onError={ () =>
this.setState( { isInstalling: false } )
this.setState( {
isInstalling: false,
} )
}
pluginSlugs={ this.pluginsToInstall }
/>
) }
{ /* Make sure we're finished requesting since this will auto redirect us. */ }
{ isConnecting && ! isJetpackConnected && ! isRequesting && (
<Connect
autoConnect
onConnect={ () => {
recordEvent(
'storeprofiler_jetpack_connect_redirect'
);
} }
onError={ () =>
this.setState( { isConnecting: false } )
}
redirectUrl={ getAdminLink(
'admin.php?page=wc-admin&reset_profiler=0'
) }
/>
) }
</div>
<p className="woocommerce-profile-wizard__benefits-install-notice">
@ -271,7 +306,9 @@ export default compose(
isGetProfileItemsRequesting,
} = select( 'wc-api' );
const { getActivePlugins } = select( PLUGINS_STORE_NAME );
const { getActivePlugins, isJetpackConnected } = select(
PLUGINS_STORE_NAME
);
const isProfileItemsError = Boolean( getProfileItemsError() );
const activePlugins = getActivePlugins();
@ -281,6 +318,7 @@ export default compose(
activePlugins,
isProfileItemsError,
profileItems,
isJetpackConnected: isJetpackConnected(),
isRequesting: isGetProfileItemsRequesting(),
};
} ),

View File

@ -12,18 +12,19 @@ import TYPES from './action-types';
import { WC_ADMIN_NAMESPACE } from '../constants';
import { pluginNames } from './constants';
export function updateActivePlugins( active ) {
export function updateActivePlugins( active, replace = false ) {
return {
type: TYPES.UPDATE_ACTIVE_PLUGINS,
active,
replace,
};
}
export function updateInstalledPlugins( installed, added ) {
export function updateInstalledPlugins( installed, replace = false ) {
return {
type: TYPES.UPDATE_INSTALLED_PLUGINS,
installed,
added,
replace,
};
}

View File

@ -21,20 +21,20 @@ const plugins = (
type,
active,
installed,
added,
selector,
isRequesting,
error,
jetpackConnection,
redirectUrl,
jetpackConnectUrl,
replace,
}
) => {
switch ( type ) {
case TYPES.UPDATE_ACTIVE_PLUGINS:
state = {
...state,
active,
active: replace ? active : concat( state.active, active ),
requesting: {
...state.requesting,
getActivePlugins: false,
@ -50,7 +50,9 @@ const plugins = (
case TYPES.UPDATE_INSTALLED_PLUGINS:
state = {
...state,
installed: added ? concat( state.installed, added ) : installed,
installed: replace
? installed
: concat( state.installed, installed ),
requesting: {
...state.requesting,
getInstalledPlugins: false,

View File

@ -27,7 +27,7 @@ export function* getActivePlugins() {
method: 'GET',
} );
yield updateActivePlugins( results.plugins );
yield updateActivePlugins( results.plugins, true );
} catch ( error ) {
yield setError( 'getActivePlugins', error );
}
@ -43,7 +43,7 @@ export function* getInstalledPlugins() {
method: 'GET',
} );
yield updateInstalledPlugins( results );
yield updateInstalledPlugins( results, true );
} catch ( error ) {
yield setError( 'getInstalledPlugins', error );
}

View File

@ -19,11 +19,17 @@ describe( 'plugins reducer', () => {
expect( state ).not.toBe( defaultState );
} );
it( 'should handle UPDATE_ACTIVE_PLUGINS', () => {
const state = reducer( defaultState, {
type: TYPES.UPDATE_ACTIVE_PLUGINS,
active: [ 'jetpack' ],
} );
it( 'should handle UPDATE_ACTIVE_PLUGINS with replace', () => {
const state = reducer(
{
active: [ 'plugins', 'to', 'overwrite' ],
},
{
type: TYPES.UPDATE_ACTIVE_PLUGINS,
active: [ 'jetpack' ],
replace: true,
}
);
/* eslint-disable dot-notation */
@ -35,11 +41,42 @@ describe( 'plugins reducer', () => {
expect( state.active[ 0 ] ).toBe( 'jetpack' );
} );
it( 'should handle UPDATE_INSTALLED_PLUGINS', () => {
const state = reducer( defaultState, {
type: TYPES.UPDATE_INSTALLED_PLUGINS,
installed: [ 'jetpack' ],
} );
it( 'should handle UPDATE_ACTIVE_PLUGINS with active plugins', () => {
const state = reducer(
{
active: [ 'jetpack' ],
installed: [ 'jetpack' ],
requesting: {},
errors: {},
},
{
type: TYPES.UPDATE_ACTIVE_PLUGINS,
installed: null,
active: [ 'woocommerce-services' ],
}
);
/* eslint-disable dot-notation */
expect( state.requesting[ 'getActivePlugins' ] ).toBe( false );
expect( state.errors[ 'getActivePlugins' ] ).toBe( false );
/* eslint-enable dot-notation */
expect( state.active ).toHaveLength( 2 );
expect( state.active[ 1 ] ).toBe( 'woocommerce-services' );
} );
it( 'should handle UPDATE_INSTALLED_PLUGINS with replace', () => {
const state = reducer(
{
active: [ 'plugins', 'to', 'overwrite' ],
},
{
type: TYPES.UPDATE_INSTALLED_PLUGINS,
installed: [ 'jetpack' ],
replace: true,
}
);
/* eslint-disable dot-notation */
@ -51,7 +88,7 @@ describe( 'plugins reducer', () => {
expect( state.installed[ 0 ] ).toBe( 'jetpack' );
} );
it( 'should handle UPDATE_INSTALLED_PLUGINS with added plugins', () => {
it( 'should handle UPDATE_INSTALLED_PLUGINS with installed plugins', () => {
const state = reducer(
{
active: [ 'jetpack' ],
@ -61,8 +98,7 @@ describe( 'plugins reducer', () => {
},
{
type: TYPES.UPDATE_INSTALLED_PLUGINS,
installed: null,
added: [ 'woocommerce-services' ],
installed: [ 'woocommerce-services' ],
}
);

View File

@ -34,8 +34,11 @@ export const withPluginsHydration = ( data ) => ( OriginalComponent ) => {
startResolution( 'getActivePlugins', [] );
startResolution( 'getInstalledPlugins', [] );
startResolution( 'isJetpackConnected', [] );
updateActivePlugins( dataRef.current.activePlugins );
updateInstalledPlugins( dataRef.current.installedPlugins );
updateActivePlugins( dataRef.current.activePlugins, true );
updateInstalledPlugins(
dataRef.current.installedPlugins,
true
);
updateIsJetpackConnected(
dataRef.current.jetpackStatus &&
dataRef.current.jetpackStatus.isActive