From 9a361dde6c76090fc21f8ade7877226130d66815 Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Mon, 20 Sep 2021 17:17:22 -0400 Subject: [PATCH] Add Task API (https://github.com/woocommerce/woocommerce-admin/pull/7665) * Add Task model API * Add task dismiss and snooze methods * Add tests * Fix snooze time check --- .../src/Features/OnboardingTasks/Task.php | 296 ++++++++++++++++++ .../src/Features/OnboardingTasks/TaskList.php | 11 +- .../tests/features/onboarding-tasks/task.php | 246 +++++++++++++++ 3 files changed, 551 insertions(+), 2 deletions(-) create mode 100644 plugins/woocommerce-admin/src/Features/OnboardingTasks/Task.php create mode 100644 plugins/woocommerce-admin/tests/features/onboarding-tasks/task.php diff --git a/plugins/woocommerce-admin/src/Features/OnboardingTasks/Task.php b/plugins/woocommerce-admin/src/Features/OnboardingTasks/Task.php new file mode 100644 index 00000000000..9cc7e50d096 --- /dev/null +++ b/plugins/woocommerce-admin/src/Features/OnboardingTasks/Task.php @@ -0,0 +1,296 @@ + DAY_IN_SECONDS * 1000, + 'hour' => HOUR_IN_SECONDS * 1000, + 'week' => WEEK_IN_SECONDS * 1000, + ); + + /** + * Constructor + * + * @param array $data Task list data. + */ + public function __construct( $data = array() ) { + $defaults = array( + 'id' => null, + 'title' => '', + 'content' => '', + 'action_label' => __( "Let's go", 'woocommerce-admin' ), + 'action_url' => null, + 'is_complete' => false, + 'can_view' => true, + 'time' => null, + 'is_dismissable' => false, + 'is_snoozeable' => false, + 'snoozed_until' => null, + ); + + $data = wp_parse_args( $data, $defaults ); + + $this->id = (string) $data['id']; + $this->title = (string) $data['title']; + $this->content = (string) $data['content']; + $this->action_label = (string) $data['action_label']; + $this->action_url = (string) $data['action_url']; + $this->is_complete = (bool) $data['is_complete']; + $this->can_view = (bool) $data['can_view']; + $this->time = (string) $data['time']; + $this->is_dismissable = (bool) $data['is_dismissable']; + $this->is_snoozeable = (bool) $data['is_snoozeable']; + + $snoozed_tasks = get_option( self::SNOOZED_OPTION, array() ); + if ( isset( $snoozed_tasks[ $this->id ] ) ) { + $this->snoozed_until = $snoozed_tasks[ $this->id ]; + } + } + + /** + * Bool for task dismissal. + * + * @return bool + */ + public function is_dismissed() { + if ( ! $this->is_dismissable ) { + return false; + } + + $dismissed = get_option( self::DISMISSED_OPTION, array() ); + + return in_array( $this->id, $dismissed, true ); + } + + /** + * Dismiss the task. + * + * @return bool + */ + public function dismiss() { + if ( ! $this->is_dismissable ) { + return false; + } + + $dismissed = get_option( self::DISMISSED_OPTION, array() ); + $dismissed[] = $this->id; + $update = update_option( self::DISMISSED_OPTION, array_unique( $dismissed ) ); + + if ( $update ) { + wc_admin_record_tracks_event( 'tasklist_dismiss_task', array( 'task_name' => $this->id ) ); + } + + return $update; + } + + /** + * Undo task dismissal. + * + * @return bool + */ + public function undo_dismiss() { + $dismissed = get_option( self::DISMISSED_OPTION, array() ); + $dismissed = array_diff( $dismissed, array( $this->id ) ); + $update = update_option( self::DISMISSED_OPTION, $dismissed ); + + if ( $update ) { + wc_admin_record_tracks_event( 'tasklist_undo_dismiss_task', array( 'task_name' => $this->id ) ); + } + + return $update; + } + + /** + * Bool for task snoozed. + * + * @return bool + */ + public function is_snoozed() { + if ( ! $this->is_snoozeable ) { + return false; + } + + $snoozed = get_option( self::SNOOZED_OPTION, array() ); + + return isset( $snoozed[ $this->id ] ) && $snoozed[ $this->id ] > ( time() * 1000 ); + } + + /** + * Snooze the task. + * + * @param string $duration Duration to snooze. day|hour|week. + * @return bool + */ + public function snooze( $duration = 'day' ) { + if ( ! $this->is_snoozeable ) { + return false; + } + + $snoozed = get_option( self::SNOOZED_OPTION, array() ); + $snoozed_until = $this->duration_to_ms[ $duration ] + ( time() * 1000 ); + $snoozed[ $this->id ] = $snoozed_until; + $update = update_option( self::SNOOZED_OPTION, $snoozed ); + + if ( $update ) { + if ( $update ) { + wc_admin_record_tracks_event( 'tasklist_remindmelater_task', array( 'task_name' => $this->id ) ); + $this->snoozed_until = $snoozed_until; + } + } + + return $update; + } + + /** + * Undo task snooze. + * + * @return bool + */ + public function undo_snooze() { + $snoozed = get_option( self::SNOOZED_OPTION, array() ); + unset( $snoozed[ $this->id ] ); + $update = update_option( self::SNOOZED_OPTION, $snoozed ); + + if ( $update ) { + wc_admin_record_tracks_event( 'tasklist_undo_remindmelater_task', array( 'task_name' => $this->id ) ); + } + + return $update; + } + + /** + * Bool for task visibility. + * + * @return bool + */ + public function is_visible() { + return $this->can_view && ! $this->is_snoozed() && ! $this->is_dismissed(); + } + + /** + * Get the task as JSON. + * + * @return array + */ + public function get_json() { + return array( + 'id' => $this->id, + 'title' => $this->title, + 'content' => $this->content, + 'actionLabel' => $this->action_label, + 'actionUrl' => $this->action_url, + 'isComplete' => $this->is_complete, + 'isVisible' => $this->is_visible(), + 'time' => $this->time, + 'isDismissed' => $this->is_dismissed(), + 'isDismissable' => $this->is_dismissable, + 'isSnoozed' => $this->is_snoozed(), + 'isSnoozeable' => $this->is_snoozeable, + 'snoozedUntil' => $this->snoozed_until, + ); + } + +} diff --git a/plugins/woocommerce-admin/src/Features/OnboardingTasks/TaskList.php b/plugins/woocommerce-admin/src/Features/OnboardingTasks/TaskList.php index f0039498559..0efe9657ad1 100644 --- a/plugins/woocommerce-admin/src/Features/OnboardingTasks/TaskList.php +++ b/plugins/woocommerce-admin/src/Features/OnboardingTasks/TaskList.php @@ -5,6 +5,8 @@ namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks; +use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task; + /** * Task List class. */ @@ -107,7 +109,7 @@ class TaskList { * @param array $args Task properties. */ public function add_task( $args ) { - $this->tasks[] = $args; + $this->tasks[] = new Task( $args ); } /** @@ -121,7 +123,12 @@ class TaskList { 'title' => $this->title, 'isHidden' => $this->is_hidden(), 'isComplete' => $this->is_complete(), - 'tasks' => $this->tasks, + 'tasks' => array_map( + function( $task ) { + return $task->get_json(); + }, + $this->tasks + ), ); } diff --git a/plugins/woocommerce-admin/tests/features/onboarding-tasks/task.php b/plugins/woocommerce-admin/tests/features/onboarding-tasks/task.php new file mode 100644 index 00000000000..12d0aed382c --- /dev/null +++ b/plugins/woocommerce-admin/tests/features/onboarding-tasks/task.php @@ -0,0 +1,246 @@ + 'wc-unit-test-task', + ) + ); + + $this->assertEquals( true, $task->is_visible() ); + } + + /** + * Tests that a task is not visible when not capable of being viewed. + */ + public function test_capability_not_visible() { + $task = new Task( + array( + 'id' => 'wc-unit-test-task', + 'can_view' => false, + ) + ); + + $this->assertEquals( false, $task->is_visible() ); + } + + /** + * Tests that a task can be dismissed. + */ + public function test_dismiss() { + $task = new Task( + array( + 'id' => 'wc-unit-test-task', + 'is_dismissable' => true, + ) + ); + + $update = $task->dismiss(); + $dismissed = get_option( Task::DISMISSED_OPTION, array() ); + $this->assertEquals( true, $update ); + $this->assertContains( $task->id, $dismissed ); + } + + /** + * Tests that a dismissal can be undone. + */ + public function test_undo_dismiss() { + $task = new Task( + array( + 'id' => 'wc-unit-test-task', + 'is_dismissable' => true, + ) + ); + + $task->dismiss(); + $task->undo_dismiss(); + $dismissed = get_option( Task::DISMISSED_OPTION, array() ); + $this->assertNotContains( $task->id, $dismissed ); + } + + /** + * Tests that a non-dismissable task cannot be dismissed. + */ + public function test_not_dismissable() { + $task = new Task( + array( + 'id' => 'wc-unit-test-non-dismissable-task', + 'is_dismissable' => false, + ) + ); + + $update = $task->dismiss(); + $dismissed = get_option( Task::DISMISSED_OPTION, array() ); + $this->assertEquals( false, $update ); + $this->assertNotContains( $task->id, $dismissed ); + } + + /** + * Tests that a dismissed task is not visible. + */ + public function test_dismissed_visibility() { + $task = new Task( + array( + 'id' => 'wc-unit-test-task', + 'is_dismissable' => true, + ) + ); + + $task->dismiss(); + $this->assertEquals( false, $task->is_visible() ); + } + + /** + * Tests that a task can be snoozed. + */ + public function test_snooze() { + $task = new Task( + array( + 'id' => 'wc-unit-test-snoozeable-task', + 'is_snoozeable' => true, + ) + ); + + $update = $task->snooze(); + $snoozed = get_option( Task::SNOOZED_OPTION, array() ); + $this->assertEquals( true, $update ); + $this->assertArrayHasKey( $task->id, $snoozed ); + } + + /** + * Tests that a task can be unsnoozed. + */ + public function test_undo_snooze() { + $task = new Task( + array( + 'id' => 'wc-unit-test-snoozeable-task', + 'is_snoozeable' => true, + ) + ); + + $task->snooze(); + $task->undo_snooze(); + $snoozed = get_option( Task::SNOOZED_OPTION, array() ); + $this->assertArrayNotHasKey( $task->id, $snoozed ); + } + + /** + * Tests that a task is not visible when snoozed. + */ + public function test_snoozed_visibility() { + $task = new Task( + array( + 'id' => 'wc-unit-test-snoozeable-task', + 'is_snoozeable' => true, + ) + ); + + $task->snooze(); + $this->assertEquals( false, $task->is_visible() ); + } + + /** + * Tests that a task's snooze time is automatically added. + */ + public function test_snoozed_until() { + $time = time() * 1000; + $snoozed = get_option( Task::SNOOZED_OPTION, array() ); + $snoozed['wc-unit-test-task'] = $time; + update_option( Task::SNOOZED_OPTION, $snoozed ); + + $task = new Task( + array( + 'id' => 'wc-unit-test-task', + 'is_snoozeable' => true, + ) + ); + + $this->assertEquals( $time, $task->snoozed_until ); + + } + + /** + * Tests that a non snoozeable task cannot be snoozed. + */ + public function test_not_snoozeable() { + $task = new Task( + array( + 'id' => 'wc-unit-test-snoozeable-task', + 'is_snoozeable' => false, + ) + ); + + $task->snooze(); + $this->assertEquals( false, $task->is_snoozed() ); + } + + /** + * Tests that a task is no longer consider snoozed after the time has passed. + */ + public function test_snooze_time() { + $task = new Task( + array( + 'id' => 'wc-unit-test-snoozeable-task', + 'is_snoozeable' => true, + ) + ); + + $time = time() * 1000 - 1; + $snoozed = get_option( Task::SNOOZED_OPTION, array() ); + $snoozed['wc-unit-test-snoozeable-task'] = $time; + update_option( Task::SNOOZED_OPTION, $snoozed ); + + $this->assertEquals( false, $task->is_snoozed() ); + } + + + /** + * Tests that a task's properties are returned as JSON. + */ + public function test_json() { + $task = new Task( + array( + 'id' => 'wc-unit-test-task', + ) + ); + + $json = $task->get_json(); + + $this->assertArrayHasKey( 'id', $json ); + $this->assertArrayHasKey( 'title', $json ); + $this->assertArrayHasKey( 'content', $json ); + $this->assertArrayHasKey( 'actionLabel', $json ); + $this->assertArrayHasKey( 'actionUrl', $json ); + $this->assertArrayHasKey( 'isComplete', $json ); + $this->assertArrayHasKey( 'isVisible', $json ); + $this->assertArrayHasKey( 'time', $json ); + $this->assertArrayHasKey( 'isDismissed', $json ); + $this->assertArrayHasKey( 'isDismissable', $json ); + $this->assertArrayHasKey( 'isSnoozed', $json ); + $this->assertArrayHasKey( 'isSnoozeable', $json ); + $this->assertArrayHasKey( 'snoozedUntil', $json ); + } + +} +