Move LYS coming soon initialization to core profiler flow (#46708)

Co-authored-by: Ilyas Foo <foo.ilyas@gmail.com>
This commit is contained in:
RJ 2024-04-22 18:49:59 +12:00 committed by GitHub
parent 007a4238c3
commit fd52f18f6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 208 additions and 109 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Move LYS coming soon initialization to core profiler flow

View File

@ -37,6 +37,9 @@ const TYPES = {
KEEP_COMPLETED_TASKS_REQUEST: 'KEEP_COMPLETED_TASKS_REQUEST',
KEEP_COMPLETED_TASKS_SUCCESS: 'KEEP_COMPLETED_TASKS_SUCCESS',
SET_JETPACK_AUTH_URL: 'SET_JETPACK_AUTH_URL',
CORE_PROFILER_COMPLETED_REQUEST: 'CORE_PROFILER_COMPLETED_REQUEST',
CORE_PROFILER_COMPLETED_SUCCESS: 'CORE_PROFILER_COMPLETED_SUCCESS',
CORE_PROFILER_COMPLETED_ERROR: 'CORE_PROFILER_COMPLETED_ERROR',
} as const;
export default TYPES;

View File

@ -502,6 +502,41 @@ export function setJetpackAuthUrl(
};
}
export function coreProfilerCompletedError( error: unknown ) {
return {
type: TYPES.CORE_PROFILER_COMPLETED_ERROR,
error,
};
}
export function coreProfilerCompletedRequest() {
return {
type: TYPES.CORE_PROFILER_COMPLETED_REQUEST,
};
}
export function coreProfilerCompletedSuccess() {
return {
type: TYPES.CORE_PROFILER_COMPLETED_SUCCESS,
};
}
export function* coreProfilerCompleted() {
yield coreProfilerCompletedRequest();
try {
yield apiFetch( {
path: `${ WC_ADMIN_NAMESPACE }/launch-your-store/initialize-coming-soon`,
method: 'POST',
} );
} catch ( error ) {
yield coreProfilerCompletedError( error );
throw error;
} finally {
yield coreProfilerCompletedSuccess();
}
}
export type Action = ReturnType<
| typeof getFreeExtensionsError
| typeof getFreeExtensionsSuccess
@ -539,4 +574,7 @@ export type Action = ReturnType<
| typeof getProductTypesError
| typeof getProductTypesSuccess
| typeof setJetpackAuthUrl
| typeof coreProfilerCompletedRequest
| typeof coreProfilerCompletedSuccess
| typeof coreProfilerCompletedError
>;

View File

@ -437,6 +437,34 @@ const reducer: Reducer< OnboardingState, Action > = (
[ action.redirectUrl ]: action.results,
},
};
case TYPES.CORE_PROFILER_COMPLETED_REQUEST:
return {
...state,
requesting: {
...state.requesting,
coreProfilerCompleted: true,
},
};
case TYPES.CORE_PROFILER_COMPLETED_SUCCESS:
return {
...state,
requesting: {
...state.requesting,
coreProfilerCompleted: false,
},
};
case TYPES.CORE_PROFILER_COMPLETED_ERROR:
return {
...state,
errors: {
...state.errors,
coreProfilerCompleted: action.error,
},
requesting: {
...state.requesting,
coreProfilerCompleted: false,
},
};
default:
return state;
}

View File

@ -12,6 +12,7 @@ import {
AnyEventObject,
BaseActionObject,
Sender,
raise,
} from 'xstate';
import { useMachine, useSelector } from '@xstate/react';
import { useEffect, useMemo, useState } from '@wordpress/element';
@ -371,7 +372,12 @@ const handleGeolocation = assign( {
},
} );
const redirectToWooHome = () => {
const redirectToWooHome = raise( 'REDIRECT_TO_WOO_HOME' );
const exitToWooHome = async () => {
if ( window.wcAdminFeatures[ 'launch-your-store' ] ) {
await dispatch( ONBOARDING_STORE_NAME ).coreProfilerCompleted();
}
window.location.href = getNewPath( {}, '/', {} );
};
@ -699,6 +705,7 @@ const coreProfilerMachineServices = {
browserPopstateHandler,
updateBusinessInfo,
updateTrackingOption,
exitToWooHome,
};
export const coreProfilerStateMachineDefinition = createMachine( {
id: 'coreProfiler',
@ -711,6 +718,9 @@ export const coreProfilerStateMachineDefinition = createMachine( {
EXTERNAL_URL_UPDATE: {
target: 'navigate',
},
REDIRECT_TO_WOO_HOME: {
target: 'redirectingToWooHome',
},
},
context: {
// these are safe default values if for some reason the steps fail to complete correctly
@ -1397,15 +1407,23 @@ export const coreProfilerStateMachineDefinition = createMachine( {
},
sendToJetpackAuthPage: {
invoke: {
src: async () =>
await resolveSelect(
src: async () => {
if (
window.wcAdminFeatures[ 'launch-your-store' ]
) {
await dispatch(
ONBOARDING_STORE_NAME
).coreProfilerCompleted();
}
return await resolveSelect(
ONBOARDING_STORE_NAME
).getJetpackAuthUrl( {
redirectUrl: getAdminLink(
'admin.php?page=wc-admin'
),
from: 'woocommerce-core-profiler',
} ),
} );
},
onDone: {
actions: actions.choose( [
{
@ -1523,6 +1541,11 @@ export const coreProfilerStateMachineDefinition = createMachine( {
},
},
settingUpStore: {},
redirectingToWooHome: {
invoke: {
src: 'exitToWooHome',
},
},
},
} );

View File

@ -138,6 +138,7 @@ describe( 'All states in CoreProfilerMachine should be reachable', () => {
test( `${ path.description }`, async () => {
const rendered = render(
<CoreProfilerController
// @ts-expect-error -- types are wrong
actionOverrides={ actionOverrides }
servicesOverrides={ servicesOverrides }
/>

View File

@ -58,6 +58,7 @@ declare global {
'woo-mobile-welcome': boolean;
'shipping-smart-defaults': boolean;
'shipping-setting-tour': boolean;
'launch-your-store': boolean;
};
wp: {
updates?: {

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Move LYS coming soon initialization to core profiler flow

View File

@ -256,8 +256,6 @@ final class WooCommerce {
add_action( 'woocommerce_updated', array( $this, 'add_woocommerce_inbox_variant' ) );
add_action( 'woocommerce_installed', array( $this, 'add_woocommerce_remote_variant' ) );
add_action( 'woocommerce_updated', array( $this, 'add_woocommerce_remote_variant' ) );
add_action( 'woocommerce_newly_installed', array( $this, 'add_lys_default_values' ) );
add_action( 'woocommerce_updated', array( $this, 'add_lys_default_values' ) );
// These classes set up hooks on instantiation.
$container = wc_get_container();
@ -315,35 +313,6 @@ final class WooCommerce {
}
}
/**
* Set default option values for launch your store task.
*/
public function add_lys_default_values() {
if ( ! Features::is_enabled( 'launch-your-store' ) ) {
return;
}
$is_new_install = current_action() === 'woocommerce_newly_installed';
$coming_soon = $is_new_install ? 'yes' : 'no';
$store_pages_only = WCAdminHelper::is_site_fresh() ? 'no' : 'yes';
$private_link = 'no';
$share_key = wp_generate_password( 32, false );
if ( false === get_option( 'woocommerce_coming_soon', false ) ) {
update_option( 'woocommerce_coming_soon', $coming_soon );
}
if ( false === get_option( 'woocommerce_store_pages_only', false ) ) {
update_option( 'woocommerce_store_pages_only', $store_pages_only );
}
if ( false === get_option( 'woocommerce_private_link', false ) ) {
update_option( 'woocommerce_private_link', $private_link );
}
if ( false === get_option( 'woocommerce_share_key', false ) ) {
update_option( 'woocommerce_share_key', $share_key );
}
}
/**
* Ensures fatal errors are logged so they can be picked up in the status report.
*

View File

@ -104,6 +104,10 @@ class Init {
$product_form_controllers[] = 'Automattic\WooCommerce\Admin\API\ProductForm';
}
if ( Features::is_enabled( 'launch-your-store' ) ) {
$controllers[] = 'Automattic\WooCommerce\Admin\API\LaunchYourStore';
}
if ( Features::is_enabled( 'analytics' ) ) {
$analytics_controllers = array(
'Automattic\WooCommerce\Admin\API\Customers',

View File

@ -0,0 +1,98 @@
<?php
/**
* REST API Launch Your Store Controller
*
* Handles requests to /launch-your-store/*
*/
namespace Automattic\WooCommerce\Admin\API;
use Automattic\WooCommerce\Admin\WCAdminHelper;
defined( 'ABSPATH' ) || exit;
/**
* Launch Your Store controller.
*
* @internal
*/
class LaunchYourStore {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-admin';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'launch-your-store';
/**
* Register routes.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/initialize-coming-soon',
array(
array(
'methods' => 'POST',
'callback' => array( $this, 'initialize_coming_soon' ),
'permission_callback' => array( $this, 'must_be_shop_manager_or_admin' ),
),
)
);
}
/**
* User must be either shop_manager or administrator.
*
* @return bool
*/
public function must_be_shop_manager_or_admin() {
// phpcs:ignore
if ( ! current_user_can( 'manage_woocommerce' ) && ! current_user_can( 'administrator' ) ) {
return false;
}
return true;
}
/**
* Initializes options for coming soon. Does not override if options exist.
*
* @return bool|void
*/
public function initialize_coming_soon() {
$current_user_id = get_current_user_id();
// Abort if we don't have a user id for some reason.
if ( ! $current_user_id ) {
return;
}
$coming_soon = 'yes';
$store_pages_only = WCAdminHelper::is_site_fresh() ? 'no' : 'yes';
$private_link = 'no';
$share_key = wp_generate_password( 32, false );
add_option( 'woocommerce_coming_soon', $coming_soon );
add_option( 'woocommerce_store_pages_only', $store_pages_only );
add_option( 'woocommerce_private_link', $private_link );
add_option( 'woocommerce_share_key', $share_key );
wc_admin_record_tracks_event(
'launch_your_store_initialize_coming_soon',
array(
'coming_soon' => $coming_soon,
'store_pages_only' => $store_pages_only,
'private_link' => $private_link,
)
);
return true;
}
}

View File

@ -27,78 +27,4 @@ class WooCommerce_Test extends \WC_Unit_Test_Case {
$this->assertTrue( $property->isPublic() );
$this->assertInstanceOf( WC_API::class, $property->getValue( WC() ) );
}
/**
* Tear down.
*/
public function tearDown(): void {
parent::tearDown();
delete_option( 'woocommerce_coming_soon' );
delete_option( 'woocommerce_store_pages_only' );
delete_option( 'woocommerce_private_link' );
delete_option( 'woocommerce_share_key' );
}
/**
* Test for add_lys_default_values method on fresh installation.
*/
public function test_add_lys_default_values_on_fresh_installation() {
update_option( 'fresh_site', '1' );
$this->set_current_action( 'woocommerce_newly_installed' );
( WooCommerce::instance() )->add_lys_default_values();
$this->assertEquals( 'yes', get_option( 'woocommerce_coming_soon' ) );
$this->assertEquals( 'no', get_option( 'woocommerce_store_pages_only' ) );
$this->assertEquals( 'no', get_option( 'woocommerce_private_link' ) );
$this->assertNotEmpty( get_option( 'woocommerce_share_key' ) );
$this->assertMatchesRegularExpression( '/^[a-zA-Z0-9]{32}$/', get_option( 'woocommerce_share_key' ) );
}
/**
* Test for add_lys_default_values method on WooCommerce update.
*/
public function test_add_lys_default_values_on_woocommerce_update() {
update_option( 'fresh_site', '0' );
$this->set_current_action( 'woocommerce_updated' );
( WooCommerce::instance() )->add_lys_default_values();
$this->assertEquals( 'no', get_option( 'woocommerce_coming_soon' ) );
$this->assertEquals( 'yes', get_option( 'woocommerce_store_pages_only' ) );
$this->assertEquals( 'no', get_option( 'woocommerce_private_link' ) );
$this->assertNotEmpty( get_option( 'woocommerce_share_key' ) );
$this->assertMatchesRegularExpression( '/^[a-zA-Z0-9]{32}$/', get_option( 'woocommerce_share_key' ) );
}
/**
* Test for add_lys_default_values method when options are already set.
*
*/
public function test_add_lys_default_values_when_options_are_already_set() {
update_option( 'fresh_site', '0' );
update_option( 'woocommerce_coming_soon', 'yes' );
update_option( 'woocommerce_store_pages_only', 'no' );
update_option( 'woocommerce_private_link', 'yes' );
update_option( 'woocommerce_share_key', 'test' );
$this->set_current_action( 'woocommerce_updated' );
( WooCommerce::instance() )->add_lys_default_values();
$this->assertEquals( 'yes', get_option( 'woocommerce_coming_soon' ) );
$this->assertEquals( 'no', get_option( 'woocommerce_store_pages_only' ) );
$this->assertEquals( 'yes', get_option( 'woocommerce_private_link' ) );
$this->assertEquals( 'test', get_option( 'woocommerce_share_key' ) );
}
/**
* Helper method to set the current action for testing.
*
* @param string $action The action to set.
*/
private function set_current_action( $action ) {
global $wp_current_filter;
$wp_current_filter[] = $action; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
}
}