diff --git a/docker-compose.yml b/docker-compose.yml index be77b29..a6ac892 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -69,7 +69,7 @@ services: profiles: ["wp"] db: - image: mariadb:10.4 + image: mysql:8.0 ports: - 3306:3306 volumes: @@ -84,7 +84,7 @@ services: profiles: ["wp"] phpmyadmin: - image: phpmyadmin/phpmyadmin + image: phpmyadmin:latest ports: - 8080:80 links: diff --git a/package-lock.json b/package-lock.json index a55ed22..6a48740 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36304,6 +36304,8 @@ "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-popover": "^1.0.7", "@tanstack/react-query": "^5.24.1", + "date-fns": "^3.3.1", + "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.51.0", @@ -36332,6 +36334,15 @@ "vite": "^5.0.12", "vite-tsconfig-paths": "^4.2.2" } + }, + "wordpress-plugin/node_modules/date-fns": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz", + "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } } } } diff --git a/wordpress-plugin/package.json b/wordpress-plugin/package.json index 20c5bac..c87bd1b 100644 --- a/wordpress-plugin/package.json +++ b/wordpress-plugin/package.json @@ -26,6 +26,8 @@ "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-popover": "^1.0.7", "@tanstack/react-query": "^5.24.1", + "date-fns": "^3.3.1", + "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.51.0", diff --git a/wordpress-plugin/src/Admin.php b/wordpress-plugin/src/Admin.php index b4571b3..a441c24 100755 --- a/wordpress-plugin/src/Admin.php +++ b/wordpress-plugin/src/Admin.php @@ -154,7 +154,7 @@ function () { if ( ! empty( $manifest ) && is_array( $manifest ) ) { foreach ( $manifest as $key => $value ) { - if ( $value['isEntry'] ) { + if ( array_key_exists( 'isEntry', $value ) && $value['isEntry'] ) { wp_enqueue_script( 'medusawp-' . $key, plugins_url( 'admin/dist/' . $value['file'], MEDUSAWP_PLUGIN_DIR . '/index.php' ), array(), MEDUSAWP_VERSION, true ); wp_localize_script( 'medusawp-' . $key, 'medusawp', $this->get_medusawp_js_data() ); diff --git a/wordpress-plugin/src/DB/SyncProgress.php b/wordpress-plugin/src/DB/SyncProgress.php index d8796d1..640ed55 100644 --- a/wordpress-plugin/src/DB/SyncProgress.php +++ b/wordpress-plugin/src/DB/SyncProgress.php @@ -33,6 +33,7 @@ public static function create_table() { $sql = 'CREATE TABLE ' . static::$table_name . " ( id INT NOT NULL AUTO_INCREMENT, model TEXT NOT NULL, + model_id VARCHAR(191), status MEDIUMTEXT NOT NULL, message TEXT NOT NULL, data MEDIUMTEXT NOT NULL, diff --git a/wordpress-plugin/src/Models/SyncProgress.php b/wordpress-plugin/src/Models/SyncProgress.php index 19d5721..033aae8 100644 --- a/wordpress-plugin/src/Models/SyncProgress.php +++ b/wordpress-plugin/src/Models/SyncProgress.php @@ -16,6 +16,7 @@ class SyncProgress extends Model { public static $columns_format = array( 'id' => '%d', 'model' => '%s', + 'model_id' => '%s', 'status' => '%s', 'message' => '%s', 'data' => '%s', @@ -27,6 +28,7 @@ class SyncProgress extends Model { public static $validation_rules = array( 'model' => 'required|in:product,product-collection,product-variant,region,thumbnail', + 'model_id' => 'required|string', 'status' => 'required|in:syncing,success,error', 'message' => 'required|string', 'data' => 'required|string', @@ -114,28 +116,78 @@ public static function count_all_synced( int $sync_timestamp ) { $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared - "SELECT COUNT(*) FROM $table_name WHERE sync_timestamp = %d", + "SELECT COUNT(DISTINCT model_id) FROM $table_name WHERE sync_timestamp = %d", $sync_timestamp ) ) ); } - public static function get_sync_progress_troubleshoot_messages( int $sync_timestamp ) { + public static function get_troubleshoot_messages( int $sync_timestamp, $options = array() ) { /** * @var \wpdb $wpdb */ global $wpdb; $table_name = static::$table_name; + if ( empty( $options ) ) { + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + return $wpdb->get_results( + $wpdb->prepare( + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + "SELECT * FROM $table_name" + ), + ARRAY_A + ); + } + + $default_options = array( + 'page' => 1, + 'per_page' => get_option( 'posts_per_page', 10 ), + ); + + $options = array_merge( $default_options, $options ); + + $options['page'] = abs( intval( $options['page'] ) ); + $options['per_page'] = abs( intval( $options['per_page'] ) ); + + if ( $options['per_page'] < 1 ) { + $options['per_page'] = $default_options['per_page']; + } + + if ( $options['page'] < 1 ) { + $options['page'] = $default_options['page']; + } + + $offset = ( $options['page'] - 1 ) * $options['per_page']; + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching return $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared - "SELECT * FROM $table_name WHERE sync_timestamp = %d AND status = 'error' ORDER BY started_at DESC;", - array( $sync_timestamp ) + "SELECT * FROM $table_name WHERE sync_timestamp = %d AND status = 'error' ORDER BY started_at DESC LIMIT %d OFFSET %d;", + array( $sync_timestamp, $options['per_page'], $offset ) ), 'ARRAY_A' ); } + + public static function count_troubleshoot_messages( int $sync_timestamp ) { + /** + * @var \wpdb $wpdb + */ + global $wpdb; + $table_name = static::$table_name; + + return intval( + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->get_var( + $wpdb->prepare( + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + "SELECT COUNT(*) FROM $table_name WHERE sync_timestamp = %d AND status = 'error'", + $sync_timestamp + ) + ) + ); + } } diff --git a/wordpress-plugin/src/Routes/Admin/MedusaBulkSync.php b/wordpress-plugin/src/Routes/Admin/MedusaBulkSync.php index f4c3f91..c40cbbe 100644 --- a/wordpress-plugin/src/Routes/Admin/MedusaBulkSync.php +++ b/wordpress-plugin/src/Routes/Admin/MedusaBulkSync.php @@ -44,6 +44,7 @@ private function get_sync_message_schema() { 'ended_at', 'medusa_admin_link', 'model', + 'model_id', ), 'properties' => array( 'id' => array( @@ -54,6 +55,10 @@ private function get_sync_message_schema() { 'type' => 'string', 'description' => __( 'Sync message model.', 'medusawp' ), ), + 'model_id' => array( + 'type' => array( 'string', 'null' ), + 'description' => __( 'Sync message model id.', 'medusawp' ), + ), 'status' => array( 'type' => 'string', 'description' => __( 'Sync message status.', 'medusawp' ), @@ -121,7 +126,7 @@ public function sync_messages_schema() { ); } - private function get_sync_response_object_schema( $with_messages = false ) { + private function get_sync_response_object_schema() { $title = 'MedusaWPSyncResponse'; $required = array( 'started_at', @@ -251,15 +256,6 @@ private function get_sync_response_object_schema( $with_messages = false ) { ), ); - if ( $with_messages ) { - $title = 'MedusaWPSyncResponseWithMessages'; - $required[] = 'messages'; - $properties['messages'] = array( - 'type' => 'array', - 'items' => $this->get_sync_message_schema(), - ); - } - return array( 'title' => $title, 'type' => 'object', @@ -300,6 +296,40 @@ public function sync_progress_response_schema() { ); } + public function sync_progress_messages_response_schema() { + return array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'MedusaWPSyncProgressMessagesResponse', + 'type' => 'object', + 'additionalProperties' => false, + 'required' => array( + 'messages', + 'total', + 'current_page', + 'last_page', + ), + 'properties' => array( + 'messages' => array( + 'type' => 'array', + 'items' => $this->get_sync_message_schema(), + 'description' => __( 'Messages.', 'medusawp' ), + ), + 'total' => array( + 'type' => 'number', + 'description' => __( 'Total number of messages.', 'medusawp' ), + ), + 'current_page' => array( + 'type' => 'number', + 'description' => __( 'Current page.', 'medusawp' ), + ), + 'last_page' => array( + 'type' => 'number', + 'description' => __( 'Last page.', 'medusawp' ), + ), + ), + ); + } + public function register_admin_routes() { register_rest_route( $this->namespace, @@ -369,6 +399,33 @@ public function register_admin_routes() { ), ); + register_rest_route( + $this->namespace, + '/sync-progress/messages', + array( + array( + 'methods' => 'GET', + 'callback' => array( $this, 'get_sync_progress_messages' ), + 'args' => array( + 'page' => array( + 'type' => 'integer', + 'required' => false, + 'default' => 1, + ), + 'per_page' => array( + 'type' => 'integer', + 'required' => false, + 'default' => intval( get_option( 'posts_per_page', 10 ) ), + ), + ), + 'permission_callback' => function () { + return current_user_can( 'manage_options' ); + }, + ), + 'schema' => array( $this, 'sync_progress_messages_response_schema' ), + ), + ); + register_rest_route( $this->namespace, '/remove-synced', @@ -597,13 +654,67 @@ public function get_sync_progress() { 'totals' => $progress['totals'], 'synced' => $synced, 'import_thumbnails' => $progress['type'] === 'bulk_sync_and_import_thumbnails' || $progress['type'] === 'import_thumbnails', - 'messages' => SyncProgress::get_sync_progress_troubleshoot_messages( $sync_timestamp ), 'type' => $progress['type'], ), ) ); } + /** + * Route callback: Get sync progress messages. + * + * @param \WP_REST_Request $req + * @return WP_REST_Response + */ + public function get_sync_progress_messages( $req ) { + $progress = Settings::get_sync_progress(); + $sync_timestamp = $progress['started_at']; + + $params = $req->get_params(); + + /** + * @var int + */ + $page = $params['page']; + + /** + * @var int + */ + $per_page = $params['per_page']; + + $total_records = SyncProgress::count_troubleshoot_messages( $sync_timestamp ); + + $total_pages = ceil( $total_records / $per_page ); + + $options = array( + 'page' => $page, + 'per_page' => $per_page, + ); + + $options['page'] = filter_var( + $page, + FILTER_VALIDATE_INT, + array( + 'options' => array( + 'default' => $page > $total_pages ? $total_pages : 1, + 'min_range' => 1, + 'max_range' => $total_pages, + ), + ) + ); + + $messages = SyncProgress::get_troubleshoot_messages( $sync_timestamp, $options ); + + return new WP_REST_Response( + array( + 'messages' => $messages, + 'total' => $total_records, + 'current_page' => $options['page'], + 'last_page' => $total_pages, + ) + ); + } + /** * Route callback: Remove synced data. * diff --git a/wordpress-plugin/src/ScheduledActions.php b/wordpress-plugin/src/ScheduledActions.php index 39676e0..77a2851 100644 --- a/wordpress-plugin/src/ScheduledActions.php +++ b/wordpress-plugin/src/ScheduledActions.php @@ -63,6 +63,7 @@ public static function import_product_thumbnail( string $product_id, ?int $sync_ $processing_item_id = Models\SyncProgress::save( array( 'model' => 'thumbnail', + 'model_id' => $product_id, 'message' => 'Syncing thumbnail of ' . $product_id . ' product...', 'status' => 'syncing', 'data' => $product ? wp_json_encode( $product ) : array( 'id' => $product_id ), diff --git a/wordpress-plugin/src/Utils.php b/wordpress-plugin/src/Utils.php index 79f1133..e798386 100644 --- a/wordpress-plugin/src/Utils.php +++ b/wordpress-plugin/src/Utils.php @@ -219,6 +219,7 @@ public static function save_sync_progress( string $model, array $data, ?int $syn return Models\SyncProgress::save( array( 'model' => $model, + 'model_id' => $data['id'], 'message' => 'Syncing ' . $data['id'] . '...', 'status' => 'syncing', 'data' => wp_json_encode( $data ),