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 >(
|
||||
null
|
||||
);
|
||||
const { actionTask } = useDispatch( ONBOARDING_STORE_NAME );
|
||||
const { installAndActivatePlugins } = useDispatch( PLUGINS_STORE_NAME );
|
||||
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
|
||||
const {
|
||||
activePlugins,
|
||||
freeExtensions,
|
||||
installedPlugins,
|
||||
isResolving,
|
||||
trackedCompletedActions,
|
||||
} = useSelect( ( select: WCDataSelector ) => {
|
||||
const { getActivePlugins, getInstalledPlugins } = select(
|
||||
PLUGINS_STORE_NAME
|
||||
|
@ -123,26 +122,11 @@ const Marketing: React.FC< MarketingProps > = ( { onComplete } ) => {
|
|||
ONBOARDING_STORE_NAME
|
||||
);
|
||||
|
||||
const {
|
||||
getOption,
|
||||
hasFinishedResolution: optionFinishedResolution,
|
||||
} = select( OPTIONS_STORE_NAME );
|
||||
|
||||
const completedActions =
|
||||
getOption( 'woocommerce_task_list_tracked_completed_actions' ) ||
|
||||
EMPTY_ARRAY;
|
||||
|
||||
return {
|
||||
activePlugins: getActivePlugins(),
|
||||
freeExtensions: getFreeExtensions(),
|
||||
installedPlugins: getInstalledPlugins(),
|
||||
isResolving: ! (
|
||||
hasFinishedResolution( 'getFreeExtensions' ) &&
|
||||
optionFinishedResolution( 'getOption', [
|
||||
'woocommerce_task_list_tracked_completed_actions',
|
||||
] )
|
||||
),
|
||||
trackedCompletedActions: completedActions,
|
||||
isResolving: ! hasFinishedResolution( 'getFreeExtensions' ),
|
||||
};
|
||||
} );
|
||||
|
||||
|
@ -158,6 +142,7 @@ const Marketing: React.FC< MarketingProps > = ( { onComplete } ) => {
|
|||
|
||||
const installAndActivate = ( slug: string ) => {
|
||||
setCurrentPlugin( slug );
|
||||
actionTask( 'marketing' );
|
||||
installAndActivatePlugins( [ slug ] )
|
||||
.then( ( response: { errors: Record< string, string > } ) => {
|
||||
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 );
|
||||
setCurrentPlugin( null );
|
||||
onComplete();
|
||||
} )
|
||||
.catch( ( response: { errors: Record< string, string > } ) => {
|
||||
createNoticesFromResponse( response );
|
||||
|
|
|
@ -26,6 +26,9 @@ const TYPES = {
|
|||
HIDE_TASK_LIST_SUCCESS: 'HIDE_TASK_LIST_SUCCESS',
|
||||
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;
|
||||
|
|
|
@ -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 ) {
|
||||
yield setIsRequesting( 'updateProfileItems', true );
|
||||
yield setError( 'updateProfileItems', null );
|
||||
|
@ -347,3 +369,21 @@ export function* hideTaskList( id ) {
|
|||
export function* optimisticallyCompleteTask( 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,
|
||||
} ),
|
||||
};
|
||||
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:
|
||||
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(
|
||||
$this->namespace,
|
||||
'/' . $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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* Name of the actioned option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ACTIONED_OPTION = 'woocommerce_task_list_tracked_completed_actions';
|
||||
|
||||
/**
|
||||
* Duration to milisecond mapping.
|
||||
*
|
||||
|
@ -297,6 +304,7 @@ class Task {
|
|||
'canView' => $this->can_view,
|
||||
'time' => $this->time,
|
||||
'level' => $this->level,
|
||||
'isActioned' => $this->is_actioned(),
|
||||
'isDismissed' => $this->is_dismissed(),
|
||||
'isDismissable' => $this->is_dismissable,
|
||||
'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;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Automattic\WooCommerce\Admin\Features\RemoteFreeExtensions\Init as RemoteFreeExtensions;
|
||||
|
||||
/**
|
||||
|
@ -23,23 +24,37 @@ class Marketing {
|
|||
'woocommerce-admin'
|
||||
),
|
||||
'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' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the marketing bundles.
|
||||
* Get the marketing plugins.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_bundles() {
|
||||
return RemoteFreeExtensions::get_extensions(
|
||||
public static function get_plugins() {
|
||||
$bundles = RemoteFreeExtensions::get_extensions(
|
||||
array(
|
||||
'reach',
|
||||
'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
|
||||
*/
|
||||
public static function has_installed_extensions() {
|
||||
$bundles = self::get_bundles();
|
||||
$plugins = self::get_plugins();
|
||||
$remaining = array();
|
||||
$installed = array();
|
||||
|
||||
return array_reduce(
|
||||
$bundles,
|
||||
function( $has_installed, $bundle ) {
|
||||
if ( $has_installed ) {
|
||||
foreach ( $plugins as $plugin ) {
|
||||
if ( ! $plugin->is_installed ) {
|
||||
$remaining[] = $plugin;
|
||||
} else {
|
||||
$installed[] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
// All extensions installed.
|
||||
if ( count( $remaining ) === 0 ) {
|
||||
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 false;
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -213,5 +213,21 @@ class WC_Tests_OnboardingTasks_Task extends WC_Unit_Test_Case {
|
|||
$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