Home Screen - Small refactor to tasks extensibility (https://github.com/woocommerce/woocommerce-admin/pull/5833)

* Added unregister extended task

This commit adds a method to unregister extended task when the plugin is deactivated

* Refactored task lists handling

This commit refactors the task lists handling

* Added default task type

This commit adds a default task type

* Fixed method comment

* Moved method to group objects to lib

This commit moves method to group objects to `lib`

* Refactored getUngroupedTasks method

Co-authored-by: Fernando Marichal <contacto@fernandomarichal.com>
This commit is contained in:
Fernando 2020-12-10 23:29:45 -03:00 committed by GitHub
parent 484e945460
commit dc02c47499
9 changed files with 220 additions and 83 deletions

View File

@ -0,0 +1,28 @@
/**
* Returns an object with items grouped by the sent key.
*
* @param {Array} array array of objects.
* @param {string} key the object prop that will be used to group elements.
* @param {string} defaultKey if the key is not found in the object, it will use this value.
* @return {Object} Object that contains the grouped elements.
*/
export const groupListOfObjectsBy = (
array,
key,
defaultKey = 'undefined'
) => {
if ( array && Array.isArray( array ) && array.length ) {
if ( ! key ) {
return array;
}
return array.reduce( ( result, currentValue ) => {
if ( ! currentValue[ key ] ) {
currentValue[ key ] = defaultKey;
}
( result[ currentValue[ key ] ] =
result[ currentValue[ key ] ] || [] ).push( currentValue );
return result;
}, {} );
}
return {};
};

View File

@ -0,0 +1,81 @@
/**
* Internal dependencies
*/
import { groupListOfObjectsBy } from '../index.js';
describe( 'groupListOfObjectsBy', () => {
const objectList = [
{
id: '1',
name: 'Object name 1',
type: 'type1',
},
{
id: '2',
name: 'Object name 2',
type: 'type1',
},
{
id: '3',
name: 'Object name 3',
type: 'type1',
},
{
id: '4',
name: 'Object name 4',
type: 'type2',
},
];
it( 'handles different params', () => {
// Using these params should return an empty object.
const emptyObject = groupListOfObjectsBy();
const otherEmptyObject = groupListOfObjectsBy( [] );
const anotherEmptyObject = groupListOfObjectsBy( 'not an array' );
expect( emptyObject ).toMatchObject( {} );
expect( otherEmptyObject ).toMatchObject( {} );
expect( anotherEmptyObject ).toMatchObject( {} );
// Not sending a key to use for grouping the elements will return the sent list
const ungroupedList = groupListOfObjectsBy( objectList );
expect( ungroupedList.length ).toBe( 4 );
} );
it( 'groups objects by type', () => {
const { type1, type2 } = groupListOfObjectsBy( objectList, 'type' );
expect( type1.length ).toBe( 3 );
expect( type2.length ).toBe( 1 );
} );
it( 'groups objects without type', () => {
const objectWithoutType = {
id: '5',
name: 'Object name 5',
};
objectList.push( objectWithoutType );
const { type1, type2 } = groupListOfObjectsBy(
objectList,
'type',
'type2'
);
expect( type1.length ).toBe( 3 );
expect( type2.length ).toBe( 2 );
} );
it( 'groups objects with a new type', () => {
const objectWithNewType = {
id: '6',
name: 'Object name 4',
type: 'type3',
};
objectList.push( objectWithNewType );
const { type1, type2, type3 } = groupListOfObjectsBy(
objectList,
'type',
'type2'
);
expect( type1.length ).toBe( 3 );
expect( type2.length ).toBe( 2 );
expect( type3.length ).toBe( 1 );
} );
} );

View File

@ -60,14 +60,6 @@ export class TaskDashboard extends Component {
} );
}
groupBy( array, key ) {
return array.reduce( ( result, currentValue ) => {
( result[ currentValue[ key ] ] =
result[ currentValue[ key ] ] || [] ).push( currentValue );
return result;
}, {} );
}
toggleCartModal() {
const { isCartModalOpen } = this.state;
@ -90,34 +82,29 @@ export class TaskDashboard extends Component {
} = this.props;
const { isCartModalOpen } = this.state;
const allTasks = this.getAllTasks();
const { extension: extensionTasks, setup: setupTasks } = this.groupBy(
allTasks,
'type'
);
const { extension: extensionTasks, setup: setupTasks } = allTasks;
return (
<>
{ setupTasks && ! isSetupTaskListHidden && (
<TaskList
allTasks={ allTasks }
dismissedTasks={ dismissedTasks }
isTaskListComplete={ isTaskListComplete }
isExtended={ false }
query={ query }
specificTasks={ setupTasks }
tasks={ allTasks }
trackedCompletedTasks={ trackedCompletedTasks }
/>
) }
{ extensionTasks && ! isExtendedTaskListHidden && (
<TaskList
allTasks={ allTasks }
dismissedTasks={ dismissedTasks }
isExtendedTaskListComplete={
isExtendedTaskListComplete
}
isExtended={ true }
query={ query }
specificTasks={ extensionTasks }
tasks={ allTasks }
trackedCompletedTasks={ trackedCompletedTasks }
/>
) }

View File

@ -54,6 +54,20 @@ export class TaskList extends Component {
this.possiblyTrackCompletedTasks();
}
getUngroupedTasks() {
const { tasks: groupedTasks } = this.props;
return Object.values( groupedTasks ).flat();
}
getSpecificTasks() {
const { isExtended, tasks: groupedTasks } = this.props;
const { extension, setup } = groupedTasks;
if ( isExtended ) {
return extension;
}
return setup;
}
possiblyCompleteTaskList() {
const {
isExtended,
@ -61,8 +75,8 @@ export class TaskList extends Component {
isExtendedTaskListComplete,
updateOptions,
} = this.props;
const isSetupTaskListInComplete = ! isExtended && ! isTaskListComplete;
const isExtendedTaskListInComplete =
const isSetupTaskListIncomplete = ! isExtended && ! isTaskListComplete;
const isExtendedTaskListIncomplete =
isExtended && ! isExtendedTaskListComplete;
const taskListToComplete = isExtended
? { woocommerce_extended_task_list_complete: 'yes' }
@ -73,7 +87,7 @@ export class TaskList extends Component {
if (
! this.getIncompleteTasks().length &&
( isSetupTaskListInComplete || isExtendedTaskListInComplete )
( isSetupTaskListIncomplete || isExtendedTaskListIncomplete )
) {
updateOptions( {
...taskListToComplete,
@ -88,8 +102,8 @@ export class TaskList extends Component {
}
getIncompleteTasks() {
const { dismissedTasks, specificTasks } = this.props;
return specificTasks.filter(
const { dismissedTasks } = this.props;
return this.getSpecificTasks().filter(
( task ) =>
task.visible &&
! task.completed &&
@ -169,8 +183,9 @@ export class TaskList extends Component {
}
getVisibleTasks( type ) {
const { allTasks, specificTasks, dismissedTasks } = this.props;
const tasks = type === 'all' ? allTasks : specificTasks;
const { dismissedTasks } = this.props;
const tasks =
type === 'all' ? this.getUngroupedTasks() : this.getSpecificTasks();
return tasks.filter(
( task ) => task.visible && ! dismissedTasks.includes( task.key )
@ -235,9 +250,11 @@ export class TaskList extends Component {
}
getCurrentTask() {
const { specificTasks, query } = this.props;
const { query } = this.props;
const { task } = query;
const currentTask = specificTasks.find( ( s ) => s.key === task );
const currentTask = this.getSpecificTasks().find(
( s ) => s.key === task
);
if ( ! currentTask ) {
return null;

View File

@ -22,6 +22,7 @@ import Shipping from './tasks/shipping';
import Tax from './tasks/tax';
import Payments from './tasks/payments';
import { installActivateAndConnectWcpay } from './tasks/payments/methods';
import { groupListOfObjectsBy } from '../lib/lists';
export function recordTaskViewEvent(
taskName,
@ -247,10 +248,9 @@ export function getAllTasks( {
type: 'setup',
},
];
return applyFilters(
'woocommerce_admin_onboarding_task_list',
tasks,
query
return groupListOfObjectsBy(
applyFilters( 'woocommerce_admin_onboarding_task_list', tasks, query ),
'type',
'extension'
);
}

View File

@ -17,49 +17,52 @@ jest.mock( '../tasks' );
describe( 'TaskDashboard and TaskList', () => {
afterEach( () => jest.clearAllMocks() );
const tasks = [
{
key: 'optional',
title: 'This task is optional',
container: null,
completed: false,
visible: true,
time: '1 minute',
isDismissable: true,
type: 'setup',
},
{
key: 'required',
title: 'This task is required',
container: null,
completed: false,
visible: true,
time: '1 minute',
isDismissable: false,
type: 'setup',
},
{
key: 'completed',
title: 'This task is completed',
container: null,
completed: true,
visible: true,
time: '1 minute',
isDismissable: true,
type: 'setup',
},
{
key: 'extension',
title: 'This task is an extension',
container: null,
completed: false,
visible: true,
time: '1 minute',
isDismissable: true,
type: 'extension',
},
];
const tasks = {
setup: [
{
key: 'optional',
title: 'This task is optional',
container: null,
completed: false,
visible: true,
time: '1 minute',
isDismissable: true,
type: 'setup',
},
{
key: 'required',
title: 'This task is required',
container: null,
completed: false,
visible: true,
time: '1 minute',
isDismissable: false,
type: 'setup',
},
{
key: 'completed',
title: 'This task is completed',
container: null,
completed: true,
visible: true,
time: '1 minute',
isDismissable: true,
type: 'setup',
},
],
extension: [
{
key: 'extension',
title: 'This task is an extension',
container: null,
completed: false,
visible: true,
time: '1 minute',
isDismissable: true,
type: 'extension',
},
],
};
const shorterTasksList = [
{
key: 'completed-1',
@ -141,8 +144,7 @@ describe( 'TaskDashboard and TaskList', () => {
query={ {} }
trackedCompletedTasks={ shorterTasksList }
updateOptions={ updateOptions }
allTasks={ shorterTasksList }
specificTasks={ shorterTasksList }
tasks={ { setup: shorterTasksList } }
/>
);
@ -167,8 +169,7 @@ describe( 'TaskDashboard and TaskList', () => {
query={ {} }
trackedCompletedTasks={ shorterTasksList }
updateOptions={ updateOptions }
allTasks={ shorterTasksList }
specificTasks={ shorterTasksList }
tasks={ { setup: shorterTasksList } }
/>
);
} );
@ -191,8 +192,7 @@ describe( 'TaskDashboard and TaskList', () => {
query={ {} }
trackedCompletedTasks={ shorterTasksList }
updateOptions={ updateOptions }
allTasks={ shorterTasksList }
specificTasks={ shorterTasksList }
tasks={ { extension: shorterTasksList } }
/>
);
} );
@ -208,14 +208,13 @@ describe( 'TaskDashboard and TaskList', () => {
act( () => {
render(
<TaskList
allTasks={ tasks }
tasks={ tasks }
dismissedTasks={ [] }
isTaskListComplete={ true }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ shorterTasksList }
updateOptions={ updateOptions }
specificTasks={ shorterTasksList }
/>
);
} );

View File

@ -87,7 +87,6 @@ addFilter(
time: __( '2 minutes', 'woocommerce-admin' ),
isDismissable: true,
onDismiss: () => console.log( "The task was dismissed" ),
type: 'extension'
},
];
}

View File

@ -8,7 +8,7 @@
use Automattic\WooCommerce\Admin\Features\Onboarding;
/**
* Register the JS.
* Register the task list item and the JS.
*/
function add_task_register_script() {
@ -41,3 +41,12 @@ function add_task_register_script() {
do_action( 'add_woocommerce_extended_task_list_item', 'woocommerce_admin_add_task_example_name' );
}
add_action( 'admin_enqueue_scripts', 'add_task_register_script' );
/**
* Unregister task list item.
*/
function pluginprefix_deactivate() {
do_action( 'remove_woocommerce_extended_task_list_item', 'woocommerce_admin_add_task_example_name' );
}
register_deactivation_hook( __FILE__, 'pluginprefix_deactivate' );

View File

@ -47,6 +47,7 @@ class OnboardingTasks {
add_action( 'add_option_woocommerce_task_list_tracked_completed_tasks', array( $this, 'track_task_completion' ), 10, 2 );
add_action( 'update_option_woocommerce_task_list_tracked_completed_tasks', array( $this, 'track_task_completion' ), 10, 2 );
add_action( 'add_woocommerce_extended_task_list_item', array( $this, 'add_extended_task_list_item' ), 10, 2 );
add_action( 'remove_woocommerce_extended_task_list_item', array( $this, 'remove_extended_task_list_item' ), 10, 2 );
if ( ! is_admin() ) {
return;
@ -394,6 +395,22 @@ class OnboardingTasks {
}
}
/**
* Removes an item from the extended task list.
*
* @param mixed $task_name Task name to remove.
*/
public static function remove_extended_task_list_item( $task_name ) {
if ( $task_name ) {
$extended_tasks_list_items = get_option( 'woocommerce_extended_task_list_items', array() );
if ( in_array( $task_name, $extended_tasks_list_items, true ) ) {
array_push( $extended_tasks_list_items, $task_name );
$tasks_list_items = array_diff( $extended_tasks_list_items, array( $task_name ) );
update_option( 'woocommerce_extended_task_list_items', $tasks_list_items );
}
}
}
/**
* Records an event for individual task completion.
*