Add an endpoint and method for actioning tasks (https://github.com/woocommerce/woocommerce-admin/pull/7746)
* Add checks for actioned task status * Update completion logic for task * Add rest route for actioning tasks * Add action in data store for actioning tasks * Add test for actioning task * Only prune isActioned from task data
This commit is contained in:
parent
5d7661eeb9
commit
93b42ad9ef
|
@ -107,14 +107,13 @@ const Marketing: React.FC< MarketingProps > = ( { onComplete } ) => {
|
||||||
const [ currentPlugin, setCurrentPlugin ] = useState< string | null >(
|
const [ currentPlugin, setCurrentPlugin ] = useState< string | null >(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
const { actionTask } = useDispatch( ONBOARDING_STORE_NAME );
|
||||||
const { installAndActivatePlugins } = useDispatch( PLUGINS_STORE_NAME );
|
const { installAndActivatePlugins } = useDispatch( PLUGINS_STORE_NAME );
|
||||||
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
|
|
||||||
const {
|
const {
|
||||||
activePlugins,
|
activePlugins,
|
||||||
freeExtensions,
|
freeExtensions,
|
||||||
installedPlugins,
|
installedPlugins,
|
||||||
isResolving,
|
isResolving,
|
||||||
trackedCompletedActions,
|
|
||||||
} = useSelect( ( select: WCDataSelector ) => {
|
} = useSelect( ( select: WCDataSelector ) => {
|
||||||
const { getActivePlugins, getInstalledPlugins } = select(
|
const { getActivePlugins, getInstalledPlugins } = select(
|
||||||
PLUGINS_STORE_NAME
|
PLUGINS_STORE_NAME
|
||||||
|
@ -123,26 +122,11 @@ const Marketing: React.FC< MarketingProps > = ( { onComplete } ) => {
|
||||||
ONBOARDING_STORE_NAME
|
ONBOARDING_STORE_NAME
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
|
||||||
getOption,
|
|
||||||
hasFinishedResolution: optionFinishedResolution,
|
|
||||||
} = select( OPTIONS_STORE_NAME );
|
|
||||||
|
|
||||||
const completedActions =
|
|
||||||
getOption( 'woocommerce_task_list_tracked_completed_actions' ) ||
|
|
||||||
EMPTY_ARRAY;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activePlugins: getActivePlugins(),
|
activePlugins: getActivePlugins(),
|
||||||
freeExtensions: getFreeExtensions(),
|
freeExtensions: getFreeExtensions(),
|
||||||
installedPlugins: getInstalledPlugins(),
|
installedPlugins: getInstalledPlugins(),
|
||||||
isResolving: ! (
|
isResolving: ! hasFinishedResolution( 'getFreeExtensions' ),
|
||||||
hasFinishedResolution( 'getFreeExtensions' ) &&
|
|
||||||
optionFinishedResolution( 'getOption', [
|
|
||||||
'woocommerce_task_list_tracked_completed_actions',
|
|
||||||
] )
|
|
||||||
),
|
|
||||||
trackedCompletedActions: completedActions,
|
|
||||||
};
|
};
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -158,6 +142,7 @@ const Marketing: React.FC< MarketingProps > = ( { onComplete } ) => {
|
||||||
|
|
||||||
const installAndActivate = ( slug: string ) => {
|
const installAndActivate = ( slug: string ) => {
|
||||||
setCurrentPlugin( slug );
|
setCurrentPlugin( slug );
|
||||||
|
actionTask( 'marketing' );
|
||||||
installAndActivatePlugins( [ slug ] )
|
installAndActivatePlugins( [ slug ] )
|
||||||
.then( ( response: { errors: Record< string, string > } ) => {
|
.then( ( response: { errors: Record< string, string > } ) => {
|
||||||
recordEvent( 'tasklist_marketing_install', {
|
recordEvent( 'tasklist_marketing_install', {
|
||||||
|
@ -167,18 +152,9 @@ const Marketing: React.FC< MarketingProps > = ( { onComplete } ) => {
|
||||||
),
|
),
|
||||||
} );
|
} );
|
||||||
|
|
||||||
if ( ! trackedCompletedActions.includes( 'marketing' ) ) {
|
|
||||||
updateOptions( {
|
|
||||||
woocommerce_task_list_tracked_completed_actions: [
|
|
||||||
...trackedCompletedActions,
|
|
||||||
'marketing',
|
|
||||||
],
|
|
||||||
} );
|
|
||||||
onComplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
createNoticesFromResponse( response );
|
createNoticesFromResponse( response );
|
||||||
setCurrentPlugin( null );
|
setCurrentPlugin( null );
|
||||||
|
onComplete();
|
||||||
} )
|
} )
|
||||||
.catch( ( response: { errors: Record< string, string > } ) => {
|
.catch( ( response: { errors: Record< string, string > } ) => {
|
||||||
createNoticesFromResponse( response );
|
createNoticesFromResponse( response );
|
||||||
|
|
|
@ -26,6 +26,9 @@ const TYPES = {
|
||||||
HIDE_TASK_LIST_SUCCESS: 'HIDE_TASK_LIST_SUCCESS',
|
HIDE_TASK_LIST_SUCCESS: 'HIDE_TASK_LIST_SUCCESS',
|
||||||
OPTIMISTICALLY_COMPLETE_TASK_REQUEST:
|
OPTIMISTICALLY_COMPLETE_TASK_REQUEST:
|
||||||
'OPTIMISTICALLY_COMPLETE_TASK_REQUEST',
|
'OPTIMISTICALLY_COMPLETE_TASK_REQUEST',
|
||||||
|
ACTION_TASK_ERROR: 'ACTION_TASK_ERROR',
|
||||||
|
ACTION_TASK_REQUEST: 'ACTION_TASK_REQUEST',
|
||||||
|
ACTION_TASK_SUCCESS: 'ACTION_TASK_SUCCESS',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TYPES;
|
export default TYPES;
|
||||||
|
|
|
@ -199,6 +199,28 @@ export function setEmailPrefill( email ) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function actionTaskError( taskId, error ) {
|
||||||
|
return {
|
||||||
|
type: TYPES.ACTION_TASK_ERROR,
|
||||||
|
taskId,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function actionTaskRequest( taskId ) {
|
||||||
|
return {
|
||||||
|
type: TYPES.ACTION_TASK_REQUEST,
|
||||||
|
taskId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function actionTaskSuccess( task ) {
|
||||||
|
return {
|
||||||
|
type: TYPES.ACTION_TASK_SUCCESS,
|
||||||
|
task,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function* updateProfileItems( items ) {
|
export function* updateProfileItems( items ) {
|
||||||
yield setIsRequesting( 'updateProfileItems', true );
|
yield setIsRequesting( 'updateProfileItems', true );
|
||||||
yield setError( 'updateProfileItems', null );
|
yield setError( 'updateProfileItems', null );
|
||||||
|
@ -347,3 +369,21 @@ export function* hideTaskList( id ) {
|
||||||
export function* optimisticallyCompleteTask( id ) {
|
export function* optimisticallyCompleteTask( id ) {
|
||||||
yield optimisticallyCompleteTaskRequest( id );
|
yield optimisticallyCompleteTaskRequest( id );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* actionTask( id ) {
|
||||||
|
yield actionTaskRequest( id );
|
||||||
|
|
||||||
|
try {
|
||||||
|
const task = yield apiFetch( {
|
||||||
|
path: `${ WC_ADMIN_NAMESPACE }/onboarding/tasks/${ id }/action`,
|
||||||
|
method: 'POST',
|
||||||
|
} );
|
||||||
|
|
||||||
|
yield actionTaskSuccess(
|
||||||
|
possiblyPruneTaskData( task, [ 'isActioned' ] )
|
||||||
|
);
|
||||||
|
} catch ( error ) {
|
||||||
|
yield actionTaskError( id, error );
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -317,6 +317,39 @@ const onboarding = (
|
||||||
isComplete: true,
|
isComplete: true,
|
||||||
} ),
|
} ),
|
||||||
};
|
};
|
||||||
|
case TYPES.ACTION_TASK_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
errors: {
|
||||||
|
...state.errors,
|
||||||
|
actionTask: error,
|
||||||
|
},
|
||||||
|
taskLists: getUpdatedTaskLists( state.taskLists, {
|
||||||
|
id: taskId,
|
||||||
|
isActioned: false,
|
||||||
|
} ),
|
||||||
|
};
|
||||||
|
case TYPES.ACTION_TASK_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
requesting: {
|
||||||
|
...state.requesting,
|
||||||
|
actionTask: true,
|
||||||
|
},
|
||||||
|
taskLists: getUpdatedTaskLists( state.taskLists, {
|
||||||
|
id: taskId,
|
||||||
|
isActioned: true,
|
||||||
|
} ),
|
||||||
|
};
|
||||||
|
case TYPES.ACTION_TASK_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
requesting: {
|
||||||
|
...state.requesting,
|
||||||
|
actionTask: false,
|
||||||
|
},
|
||||||
|
taskLists: getUpdatedTaskLists( state.taskLists, task ),
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,6 +209,19 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
register_rest_route(
|
||||||
|
$this->namespace,
|
||||||
|
'/' . $this->rest_base . '/(?P<id>[a-z0-9_\-]+)/action',
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'methods' => \WP_REST_Server::EDITABLE,
|
||||||
|
'callback' => array( $this, 'action_task' ),
|
||||||
|
'permission_callback' => array( $this, 'get_tasks_permission_check' ),
|
||||||
|
),
|
||||||
|
'schema' => array( $this, 'get_public_item_schema' ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
$this->namespace,
|
||||||
'/' . $this->rest_base . '/(?P<id>[a-z0-9_\-]+)/undo_snooze',
|
'/' . $this->rest_base . '/(?P<id>[a-z0-9_\-]+)/undo_snooze',
|
||||||
|
@ -951,4 +964,36 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
|
||||||
return rest_ensure_response( $json );
|
return rest_ensure_response( $json );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action a single task.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Full details about the request.
|
||||||
|
* @return WP_REST_Request|WP_Error
|
||||||
|
*/
|
||||||
|
public function action_task( $request ) {
|
||||||
|
$id = $request->get_param( 'id' );
|
||||||
|
$task = TaskLists::get_task( $id );
|
||||||
|
|
||||||
|
if ( ! $task && $id ) {
|
||||||
|
$task = new Task(
|
||||||
|
array(
|
||||||
|
'id' => $id,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $task ) {
|
||||||
|
return new \WP_Error(
|
||||||
|
'woocommerce_rest_invalid_task',
|
||||||
|
__( 'Sorry, no task with that ID was found.', 'woocommerce-admin' ),
|
||||||
|
array(
|
||||||
|
'status' => 404,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$task->mark_actioned();
|
||||||
|
return rest_ensure_response( $task->get_json() );
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,6 +114,13 @@ class Task {
|
||||||
*/
|
*/
|
||||||
const SNOOZED_OPTION = 'woocommerce_task_list_remind_me_later_tasks';
|
const SNOOZED_OPTION = 'woocommerce_task_list_remind_me_later_tasks';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the actioned option.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const ACTIONED_OPTION = 'woocommerce_task_list_tracked_completed_actions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration to milisecond mapping.
|
* Duration to milisecond mapping.
|
||||||
*
|
*
|
||||||
|
@ -297,6 +304,7 @@ class Task {
|
||||||
'canView' => $this->can_view,
|
'canView' => $this->can_view,
|
||||||
'time' => $this->time,
|
'time' => $this->time,
|
||||||
'level' => $this->level,
|
'level' => $this->level,
|
||||||
|
'isActioned' => $this->is_actioned(),
|
||||||
'isDismissed' => $this->is_dismissed(),
|
'isDismissed' => $this->is_dismissed(),
|
||||||
'isDismissable' => $this->is_dismissable,
|
'isDismissable' => $this->is_dismissable,
|
||||||
'isSnoozed' => $this->is_snoozed(),
|
'isSnoozed' => $this->is_snoozed(),
|
||||||
|
@ -305,4 +313,32 @@ class Task {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a task as actioned. Used to verify an action has taken place in some tasks.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function mark_actioned() {
|
||||||
|
$actioned = get_option( self::ACTIONED_OPTION, array() );
|
||||||
|
|
||||||
|
$actioned[] = $this->id;
|
||||||
|
$update = update_option( self::ACTIONED_OPTION, array_unique( $actioned ) );
|
||||||
|
|
||||||
|
if ( $update ) {
|
||||||
|
wc_admin_record_tracks_event( 'tasklist_actioned_task', array( 'task_name' => $this->id ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $update;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a task has been actioned.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function is_actioned() {
|
||||||
|
$actioned = get_option( self::ACTIONED_OPTION, array() );
|
||||||
|
return in_array( $this->id, $actioned, true );
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\Features\Features;
|
use Automattic\WooCommerce\Admin\Features\Features;
|
||||||
|
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||||
use Automattic\WooCommerce\Admin\Features\RemoteFreeExtensions\Init as RemoteFreeExtensions;
|
use Automattic\WooCommerce\Admin\Features\RemoteFreeExtensions\Init as RemoteFreeExtensions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,23 +24,37 @@ class Marketing {
|
||||||
'woocommerce-admin'
|
'woocommerce-admin'
|
||||||
),
|
),
|
||||||
'is_complete' => self::has_installed_extensions(),
|
'is_complete' => self::has_installed_extensions(),
|
||||||
'can_view' => Features::is_enabled( 'remote-free-extensions' ) && count( self::get_bundles() ) > 0,
|
'can_view' => Features::is_enabled( 'remote-free-extensions' ) && count( self::get_plugins() ) > 0,
|
||||||
'time' => __( '1 minute', 'woocommerce-admin' ),
|
'time' => __( '1 minute', 'woocommerce-admin' ),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the marketing bundles.
|
* Get the marketing plugins.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function get_bundles() {
|
public static function get_plugins() {
|
||||||
return RemoteFreeExtensions::get_extensions(
|
$bundles = RemoteFreeExtensions::get_extensions(
|
||||||
array(
|
array(
|
||||||
'reach',
|
'reach',
|
||||||
'grow',
|
'grow',
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return array_reduce(
|
||||||
|
$bundles,
|
||||||
|
function( $plugins, $bundle ) {
|
||||||
|
$visible = array();
|
||||||
|
foreach ( $bundle['plugins'] as $plugin ) {
|
||||||
|
if ( $plugin->is_visible ) {
|
||||||
|
$visible[] = $plugin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array_merge( $plugins, $visible );
|
||||||
|
},
|
||||||
|
array()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,22 +63,29 @@ class Marketing {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function has_installed_extensions() {
|
public static function has_installed_extensions() {
|
||||||
$bundles = self::get_bundles();
|
$plugins = self::get_plugins();
|
||||||
|
$remaining = array();
|
||||||
|
$installed = array();
|
||||||
|
|
||||||
return array_reduce(
|
foreach ( $plugins as $plugin ) {
|
||||||
$bundles,
|
if ( ! $plugin->is_installed ) {
|
||||||
function( $has_installed, $bundle ) {
|
$remaining[] = $plugin;
|
||||||
if ( $has_installed ) {
|
} else {
|
||||||
|
$installed[] = $plugin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All extensions installed.
|
||||||
|
if ( count( $remaining ) === 0 ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
foreach ( $bundle['plugins'] as $plugin ) {
|
|
||||||
if ( $plugin->is_installed ) {
|
// Make sure the task has been actioned and at least one extension is installed.
|
||||||
|
$task = new Task( array( 'id' => 'marketing' ) );
|
||||||
|
if ( count( $installed ) > 0 && $task->is_actioned() ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,5 +213,21 @@ class WC_Tests_OnboardingTasks_Task extends WC_Unit_Test_Case {
|
||||||
$this->assertArrayHasKey( 'snoozedUntil', $json );
|
$this->assertArrayHasKey( 'snoozedUntil', $json );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that a task can be actioned.
|
||||||
|
*/
|
||||||
|
public function test_action_task() {
|
||||||
|
$task = new Task(
|
||||||
|
array(
|
||||||
|
'id' => 'wc-unit-test-task',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$update = $task->mark_actioned();
|
||||||
|
$actioned = get_option( Task::ACTIONED_OPTION, array() );
|
||||||
|
$this->assertEquals( true, $update );
|
||||||
|
$this->assertContains( $task->id, $actioned );
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue